在上一篇文章中,我们介绍了LCEL表达式
和Runnable组件
,通过LCEL表达式可以很轻松的构建复杂的AI应用,LCEL将多个可运行组件串联起来,在执行LCEL表达式时出现了错误,如何判断是哪个组件出现了错误?又如何获取出现错误的上下文呢?
本文将会详细介绍使用LangChain提供的Callback回调机制,如何对LLM应用以及LCEL执行的各个关键节点进行监控,最后,还会介绍LangChain推出的一款在开发阶段的调试监控神器LangSmith,只需要简单的配置就可以和LangChain构建的应用进行无缝集成,自动监控整个项目的生命周期,将监控的结果自动上传到LangSmith平台,使项目监控变得非常简单。
一、什么是LangSmith
LangSmith 是一个由LangChain推出,用于构建LLM应用的监控平台,如下图所示,LangSmith的功能主要包括以下三个方面:
可观测性:LangSmith可以进行数据的分析和追踪,并提供了仪表盘、告警等功能。
可评估:可以对应用运行情况进行评估,分析其性能表现。
提示词工程:支持对提示词进行管理,并且提供提示词版本控制功能
LangSmith由LangChain提供,无需本地部署,LangSmith地址如下:
登录后界面
二、LangSmith使用
首先点击Tracing Projects
系统中默认存在一个项目,点击New Project创建新项目
LangSmith支持LangChain项目和非LangChain项目,并且分别提供了将LangSmith接入到应用的方法,点击Generate API Key,生成API Key
复制保存好API KEY,并修改项目名为langchain-study
,下方的配置也会自动变成对应项目名称
复制上方配置,放到项目中的.env
文件中
properties
# LangSmith配置
LANGSMITH_TRACING="true"
LANGSMITH_ENDPOINT="https://api.smith.langchain.com"
LANGSMITH_API_KEY="lsv2_pt_***************************"
LANGSMITH_PROJECT="langchain-study"
通过一个最简单的示例进行测试:
python
import dotenv
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
# 读取env配置
dotenv.load_dotenv()
# 1.创建提示词模板
prompt = ChatPromptTemplate.from_template("{question}")
# 2.构建GPT-3.5模型
llm = ChatOpenAI(model="gpt-3.5-turbo")
# 3.创建输出解析器
parser = StrOutputParser()
# 4.执行链
chain = prompt | llm | parser
print(chain.invoke({"question": "请以表格的形式返回三国演义实力最强的十个人,并进行简要介绍"}))
执行完成之后,在Tracing Projects页面就可以看到langchain-study
项目被成功创建
点击进入项目,就可以看到刚刚那一次的调用过程,包括输入、输出、发起时间、总耗时等信息
点击All Runs可以查看各个组件的执行过程,包括Prompt生成、LLM响应、输出解析器处理等各环节的详细执行信息
点击任意组件,如ChatPromptTemplate
,就会将组件的输入和输出结果进行展示
下拉选择,选择Raw Input就可以展示原始的输入,输出部分也可以以相同方式查看原始信息
支持 JSON 与 YAML 格式展示,其中 JSON 更便于观察,如下图可以清晰的观察到ChatPromptTemplate
组件的原始输入输出信息
下面我们将程序改成不存在的模型gpt-3.6-turbo,重新执行程序,来模拟出现错误的情况。
python
llm = ChatOpenAI(model="gpt-3.6-turbo")
在LangSmith中,可以看到执行记录和错误原因
也可以详细查看错误详细原因
三、什么是Callback机制
除了使用LangSmith之外,LangChain还提供了一种回调机制,可以在 LLM 应用程序的各种阶段执行特定的钩子方法。通过这些钩子方法,我们可以轻松地进行日志输出、异常监控等任务,Callback
支持以下事件的钩子方法:
Event 事件 | 触发时机 | 关联钩子方法 |
---|---|---|
Chat model start | 聊天模型启动 | on_chat_model_start |
LLM start LLM | LLM模型启动 | on_llm_start |
LLM new token LLM | LLM生成新的 token 时触发,仅在启用流式输出(streaming)模式下生效 | on_llm_new_token |
LLM ends | LLM 或聊天模型完成运行时 | on_llm_end |
LLM errors | LLM 或聊天模型出错 | on_llm_error |
Chain start | 链开始执行(实际上就是每个可运行组件开始执行) | on_chain_start |
Chain end | 链结束执行(实际上就是每个可运行组件结束执行) | on_chain_end |
Chain error | 链执行出错 | on_chain_error |
Tool start | 工具开始执行 | on_tool_start |
Tool end | 工具结束执行 | on_tool_end |
Tool error | 工具执行出错 | on_tool_error |
Agent action | agent开始执行 | on_agent_action |
Agent finish | agent结束执行 | on_agent_finish |
Retriever start | 检索器开始执行 | on_retriever_start |
Retriever end | 检索器结束执行 | on_retriever_end |
Retriever error | 检索器执行出错 | on_retriever_error |
Text | 每次模型输出一段文本时,就会调用这个方法 | on_text |
Retry | 当某个组件(比如 LLM 调用或链)发生失败并触发重试机制时 | on_retry |
四、如何使用Callback机制
首先,使用Callback
机制,需要使用到Callback handler
,即回调处理器,那些各个生命周期的钩子方法,就定义在回调处理器中,回调处理器支持同步和异步,同步回调处理器继承BaseCallbackHandler
类,异步回调处理器继承AsyncCallbackHandler
类。
如下图在PyCharm中,定义类继承BaseCallbackHandler
类,使用ctrl+o快捷键,就会出现这些可以重写的钩子方法。
那么,如何使自定义的CallbackHandler
生效呢?可以在调用可执行组件的invoke()
方法中,除了传递输入参数外,再传递config
配置参数,config
配置参数可以传递各种配置信息,其中,callbacks
属性用来传递回调处理器,callbacks
属性接收一个数组,数组里面包含自定义的CallbackHandler
对象,代码示例如下:
python
from typing import Dict, Any, Optional, List
from uuid import UUID
import dotenv
from langchain_core.callbacks import BaseCallbackHandler
from langchain_core.messages import BaseMessage
from langchain_core.output_parsers import StrOutputParser
from langchain_core.outputs import LLMResult
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
dotenv.load_dotenv()
class CustomCallbackHandler(BaseCallbackHandler):
"""自定义回调处理类"""
def on_chat_model_start(self, serialized: Dict[str, Any], messages: List[List[BaseMessage]], *, run_id: UUID,
parent_run_id: Optional[UUID] = None, tags: Optional[List[str]] = None,
metadata: Optional[Dict[str, Any]] = None, **kwargs: Any) -> Any:
print("======聊天模型结束执行======")
def on_llm_end(self, response: LLMResult, *, run_id: UUID, parent_run_id: Optional[UUID] = None,
**kwargs: Any) -> Any:
print("======聊天模型结束执行======")
def on_chain_start(self, serialized: Dict[str, Any], inputs: Dict[str, Any], *, run_id: UUID,
parent_run_id: Optional[UUID] = None, tags: Optional[List[str]] = None,
metadata: Optional[Dict[str, Any]] = None, **kwargs: Any) -> Any:
print(f"开始执行当前组件{kwargs['name']},run_id: {run_id}, 入参:{inputs}")
def on_chain_end(self, outputs: Dict[str, Any], *, run_id: UUID, parent_run_id: Optional[UUID] = None,
**kwargs: Any) -> Any:
print(f"结束执行当前组件,run_id: {run_id}, 执行结果:{outputs}, {kwargs}")
# 1.创建提示词模板
prompt = ChatPromptTemplate.from_template("{question}")
# 2.构建GPT-3.5模型
llm = ChatOpenAI(model="gpt-3.5-turbo")
# 3.创建输出解析器
parser = StrOutputParser()
# 4.执行链
chain = prompt | llm | parser
chain.invoke({"question": "请输出静夜思的原文"},
{"callbacks": [CustomCallbackHandler()]})
在示例中,创建了一个CustomCallbackHandler
类,继承了BaseCallbackHandler
,分别重写了on_chain_start
、on_llm_end
、on_chain_start
、on_chain_end
,在聊天模型开始执行和结束执行进行了信息输出,在on_chain_start
、on_chain_end
打印了当前链执行的组件名称、运行id、输入参数、输出结果。
执行结果如下,通过输出结果可以清晰地看到每一个组件的输入和输出结果,以及LLM何时开始执行、结束执行,若需监控异常情况,可重写 on_chain_error
方法。
python
开始执行当前组件RunnableSequence,run_id: 6eaf8cba-87d8-4e8f-8ec3-ea67a2b2cc6c, 入参:{'question': '请输出静夜思的原文'}
开始执行当前组件ChatPromptTemplate,run_id: 5f452385-74e3-40bf-a7b7-2dcbb921b801, 入参:{'question': '请输出静夜思的原文'}
结束执行当前组件,run_id: 5f452385-74e3-40bf-a7b7-2dcbb921b801, 执行结果:messages=[HumanMessage(content='请输出静夜思的原文')], {'tags': ['seq:step:1']}
======聊天模型结束执行======
======聊天模型结束执行======
开始执行当前组件StrOutputParser,run_id: 7c1043c0-aa7c-4969-9822-f8464b312922, 入参:content='《静夜思》是唐代诗人李白创作的一首诗,原文如下:\n\n**静夜思**\n\n床前明月光, \n疑是地上霜。 \n举头望明月, \n低头思故乡。' response_metadata={'token_usage': {'completion_tokens': 76, 'prompt_tokens': 17, 'total_tokens': 93, 'completion_tokens_details': None, 'prompt_tokens_details': None}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_b28b39ffa8', 'finish_reason': 'stop', 'logprobs': None} id='run-80f91d8a-c8d0-47c5-8255-806272bdbfcf-0' usage_metadata={'input_tokens': 17, 'output_tokens': 76, 'total_tokens': 93}
结束执行当前组件,run_id: 7c1043c0-aa7c-4969-9822-f8464b312922, 执行结果:《静夜思》是唐代诗人李白创作的一首诗,原文如下:
**静夜思**
床前明月光,
疑是地上霜。
举头望明月,
低头思故乡。, {'tags': ['seq:step:3']}
结束执行当前组件,run_id: 6eaf8cba-87d8-4e8f-8ec3-ea67a2b2cc6c, 执行结果:《静夜思》是唐代诗人李白创作的一首诗,原文如下:
**静夜思**
床前明月光,
疑是地上霜。
举头望明月,
低头思故乡。, {'tags': []}
五、总结
本文介绍了什么是LangSmith,以及如何创建LangSmith应用、无缝集成到LangChain当中,通过LangSmith可以清晰的监控到AI应用每一步的执行过程,包括执行时间、原始输入输出、花费金额、使用token数等详细信息。
随后我们还介绍了LangChain提供的Callback
机制,利用BaseCallbackHandler
提供的钩子方法,可以轻松地监控各个关键执行流程,有读者可能会疑惑:既然有了 LangSmith,为何还需要 Callback
机制?在实际开发中,LangSmith 更适合在开发调试阶段使用,而在生产环境下,出于数据隐私和安全考量,我们通常不会将敏感数据上传到LangSmith平台。这时,Callback
机制就能将执行信息接入到本地或自定义的监控系统,实现同样的可观测性。
相信通过本文你已经掌握如何对AI应用进行监控,后续将继续深入介绍LangChain的核心模块和高级用法,敬请期待。