【可能是全网最丝滑的LangChain教程】二十二、LangChain进阶之Callbacks(完结篇)

这是LangChain进阶教程的最后一篇,Let’s get it!!!

01 Callback介绍

在LangChain中,Callback 是一种非常重要的机制,它允许用户监听和处理在执行链式任务 (Chain) 过程中的各种事件。这包括但不限于开始执行、结束执行、异常处理等。Callback 可以帮助开发者追踪执行过程、调试问题、监控性能指标或者进行日志记录等。

基本概念

  • Callback 是一个可以接收事件通知的对象。
  • Callback 可以在不同的执行阶段被触发,例如在语言模型 (LLM) 开始运行、结束运行、生成文本等时刻。
  • Callback 可以是同步或异步的。

核心组件

  • BaseCallbackHandler: 所有回调处理器的基础类。
  • CallbackManager: 负责管理多个 CallbackHandler 实例,控制它们的触发顺序和方式。(已标记废弃,不建议使用这个

常用回调处理器

  • StreamingStdOutCallbackHandler: 将流式输出直接打印到标准输出。
  • FileCallbackHandler: 将回调信息写入文件。
  • CustomCallbackHandler: 用户可以自定义自己的回调处理器。

如何使用 Callbacks

  • 通常通过 CallbackManager 来添加和管理 CallbackHandler 实例。(已标记废弃,不建议使用这个
  • 当创建 Chain 或者其他组件时,可以通过 callbacks 参数传入一个 CallbackManager 实例。

02 基础使用

这里以 StdOutCallbackHandler 为例:

from langchain_core.callbacks import StdOutCallbackHandler
from langchain.chains import LLMChain
from langchain_core.prompts import PromptTemplate

handler = StdOutCallbackHandler()
prompt = PromptTemplate.from_template("1 + {number} = ")

# 初始化chain的时候设置callback
chain = LLMChain(llm=llm_model, prompt=prompt, callbacks=[handler])
chain.invoke({"number":2})

# 或者直接设置verbose=True,内部依然调用StdOutCallbackHandler
# chain = LLMChain(llm=llm_model, prompt=prompt, verbose=True)
# chain.invoke({"number":2})


# 或者在调用时设置
# chain = LLMChain(llm=llm_model, prompt=prompt)
# chain.invoke({"number":2}, {"callbacks":[handler]})


# 或者通过config参数设置
# config = {
#     'callbacks' : [handler]
# }
# chain = LLMChain(llm=llm_model, prompt=prompt)
# chain.invoke({"number":2}, config=config)


# 4种方式打印回调如下:
"""
> Entering new LLMChain chain...
Prompt after formatting:
1 + 2 = 

> Finished chain.
"""

上面代码中,我们使用的是 invoke 来执行 chain 的调用,如果我们使用 ainvoke,则建议使用 AsyncCallbackHandler 来实现回调。

Custom callback handlers

要创建自定义回调处理程序,我们需要确定我们希望回调处理程序处理的事件,以及我们希望回调处理程序在触发事件时执行的操作。然后,我们需要做的就是将回调处理程序附加到对象上,作为构造器回调或请求回调。

示例代码如下:

import asyncio
from typing import Any, Dict, List, Optional

from langchain.callbacks.base import AsyncCallbackHandler, BaseCallbackHandler
from langchain_core.messages import HumanMessage
from langchain_core.outputs import LLMResult


class MyCustomSyncHandler(BaseCallbackHandler):
    def on_llm_new_token(self, token: str, **kwargs) -> None:
        print(f"Sync handler being called in a `thread_pool_executor`: token: {token}")


class MyCustomAsyncHandler(AsyncCallbackHandler):
    """Async callback handler that can be used to handle callbacks from langchain."""

    async def on_llm_start(
        self, serialized: Dict[str, Any], prompts: List[str], **kwargs: Any
    ) -> None:
        """Run when chain starts running."""
        print("zzzz....")
        await asyncio.sleep(0.3)
        class_name = serialized["name"]
        print("Hi! I just woke up. Your llm is starting")

    async def on_llm_end(self, response: LLMResult, **kwargs: Any) -> None:
        """Run when chain ends running."""
        print("zzzz....")
        await asyncio.sleep(0.3)
        print("Hi! I just woke up. Your llm is ending")


# To enable streaming, we pass in `streaming=True` to the ChatModel constructor
# Additionally, we pass in a list with our custom handler
chat_model = ChatTongyi(dashscope_api_key='sk-da184735f2454123ab213cea8d39e9ce',streaming=True,callbacks=[MyCustomSyncHandler(), MyCustomAsyncHandler()],)


await chat_model.agenerate([[HumanMessage(content="Tell me a joke")]])

File logging

from langchain_core.callbacks import FileCallbackHandler, StdOutCallbackHandler
from langchain_core.prompts import PromptTemplate
from loguru import logger

logfile = "output.log"

logger.add(logfile, colorize=True, enqueue=True)
handler_1 = FileCallbackHandler(logfile)
handler_2 = StdOutCallbackHandler()

prompt = PromptTemplate.from_template("1 + {number} = ")

# this chain will both print to stdout (because verbose=True) and write to 'output.log'
# if verbose=False, the FileCallbackHandler will still write to 'output.log'
chain = prompt | llm_model

response = chain.invoke({"number": 2}, {"callbacks": [handler_1, handler_2]})
logger.info(response)

我们的日志文件打开后是乱码的,需要用第三方工具做一次转换

在这里插入图片描述

转换代码如下:

from ansi2html import Ansi2HTMLConverter
from IPython.display import HTML, display

with open("output.log", "r") as f:
    content = f.read()

conv = Ansi2HTMLConverter()
html = conv.convert(content, full=True)

display(HTML(html))

用 display 查看,最终效果如下(线上可用):

在这里插入图片描述

Tags

我们可以通过向 call()/run()/apply() 方法传递 tags 参数来向回调添加标签。这对于过滤日志很有用,例如,如果我们想记录对特定 LLMChain 发出的所有请求,可以添加一个标签,然后按该标签过滤您的日志。

示例代码如下:

from langchain.callbacks.base import AsyncCallbackHandler, BaseCallbackHandler
from typing import Any, Dict, List

from langchain.chains.llm import LLMChain
from langchain_community.llms.tongyi import Tongyi
from langchain_core.callbacks import BaseCallbackHandler
from langchain_core.prompts import PromptTemplate

class MyCustomHandler(BaseCallbackHandler):
    def on_llm_start(
        self, serialized: Dict[str, Any], prompts: List[str], **kwargs: Any
    ) -> Any:
        print(f"自定义回调,on_llm_start")
        
    def on_llm_end(
        self, serialized: Dict[str, Any], **kwargs: Any
    ) -> Any:
        print(f"自定义回调,on_llm_end")
        
    def on_llm_new_token(
        self, token: str, **kwargs: Any
    ) -> Any:
        print(f"自定义回调,on_llm_new_token")
    
    def on_chain_start(
        self,
        serialized: Dict[str, Any],
        inputs: Dict[str, Any],
        **kwargs: Any,
    ) -> Any:
        print(f"自定义回调,on_chain_start, {kwargs}")
        
    def on_chain_end(
        self,
        outputs: Dict[str, Any],
        **kwargs: Any,
    ) -> Any:
        print(f"自定义回调,on_chain_end, {kwargs}")
        
handler = MyCustomHandler()
prompt = PromptTemplate.from_template("1 + {number} = ")

chain = LLMChain(llm=llm_model, prompt=prompt,verbose=False, callbacks=[handler],tags=['custom-------'])
chain.invoke({"number":2})

"""
自定义回调,on_chain_start, {'run_id': UUID('91086ade-0121-4565-946e-8fadbb870f27'), 'parent_run_id': None, 'tags': ['custom-------'], 'metadata': {}, 'name': 'LLMChain'}
自定义回调,on_chain_end, {'run_id': UUID('91086ade-0121-4565-946e-8fadbb870f27'), 'parent_run_id': None, 'tags': ['custom-------']}
"""

Token counting

如果我们正在使用openai的相关模型,我们就能使用这个令牌计数的功能。

示例代码如下:

import asyncio

from langchain_community.callbacks import get_openai_callback
from langchain_openai import OpenAI

llm = OpenAI(temperature=0)
with get_openai_callback() as cb:
    llm.invoke("What is the square root of 4?")

total_tokens = cb.total_tokens
assert total_tokens > 0

with get_openai_callback() as cb:
    llm.invoke("What is the square root of 4?")
    llm.invoke("What is the square root of 4?")

assert cb.total_tokens == total_tokens * 2

# You can kick off concurrent runs from within the context manager
with get_openai_callback() as cb:
    await asyncio.gather(
        *[llm.agenerate(["What is the square root of 4?"]) for _ in range(3)]
    )

assert cb.total_tokens == total_tokens * 3

# The context manager is concurrency safe
task = asyncio.create_task(llm.agenerate(["What is the square root of 4?"]))
with get_openai_callback() as cb:
    await llm.agenerate(["What is the square root of 4?"])

await task
assert cb.total_tokens == total_tokens

03 总结与回顾

从2024年3月3日第一篇 LangChain 相关文章的发布,到现在 LangChain 最后一篇文章的发表,期间跨度已近半年,总共输出22篇文章。

从初级使用教程(7篇),到进阶理解教程(15篇),我们从知道 LangChain 到使用 LangChain 再到理解 LangChain,一步一步循序渐进的走来。

最初写这些文章的目的是为了“武装自己”,但是在看到各大平台上的读者留言后,渐渐的想把这些文章写好写透,因为我能从这些留言上看到“以前自己的影子”。

这生活已经很难了,难能可贵的是您能在百忙之中抽空阅读这些文章。如果能给到您一点小小的帮助,也是我非常喜闻乐见的😁

WX搜索公众号关注
在这里插入图片描述

以上内容依据官方文档编写,官方地址:https://python.langchain.com/docs/modules/callbacks

这真的是“进阶系列”的最后一篇文章了哦,我们下个系列再见。Love & Peace~

在这里插入图片描述

  • 14
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值