LangChain框架入门07:AI应用监控神器LangSmith

在上一篇文章中,我们介绍了LCEL表达式Runnable组件,通过LCEL表达式可以很轻松的构建复杂的AI应用,LCEL将多个可运行组件串联起来,在执行LCEL表达式时出现了错误,如何判断是哪个组件出现了错误?又如何获取出现错误的上下文呢?

本文将会详细介绍使用LangChain提供的Callback回调机制,如何对LLM应用以及LCEL执行的各个关键节点进行监控,最后,还会介绍LangChain推出的一款在开发阶段的调试监控神器LangSmith,只需要简单的配置就可以和LangChain构建的应用进行无缝集成,自动监控整个项目的生命周期,将监控的结果自动上传到LangSmith平台,使项目监控变得非常简单。

文中所有示例代码:https://github.com/wzycoding/langchain-study

一、什么是LangSmith

LangSmith 是一个由LangChain推出,用于构建LLM应用的监控平台,如下图所示,LangSmith的功能主要包括以下三个方面:

可观测性:LangSmith可以进行数据的分析和追踪,并提供了仪表盘、告警等功能。

可评估:可以对应用运行情况进行评估,分析其性能表现。

提示词工程:支持对提示词进行管理,并且提供提示词版本控制功能

LangSmith由LangChain提供,无需本地部署,LangSmith地址如下:

https://smith.langchain.com/

登录后界面

二、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_starton_llm_endon_chain_starton_chain_end,在聊天模型开始执行和结束执行进行了信息输出,在on_chain_starton_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的核心模块和高级用法,敬请期待。