LangSmith与LangGraph深度解析:构建下一代AI Agent的基石

882 阅读15分钟

专栏系列第 7 篇 · 智能体纪元

引言:AI Agent开发的范式变革

在大型语言模型(LLM)技术爆发的今天,AI Agent已从简单的问答工具演变为具备复杂认知能力的智能系统。这种进化对开发工具提出了全新要求:如何追踪智能体的思考过程?如何编排多步骤工作流?如何确保系统的可靠性和可观测性?LangSmithLangGraph作为LangChain生态的核心组件,正是为解决这些问题而生。本文将深入解析这两个组件的设计哲学、技术架构与应用实践。

在理解LangSmith与LangGraph之前,需要明确它们在LangChain生态中的定位。假设读者已具备以下基础概念:

  • Agent:能调用工具自主完成任务的智能体(如通过AgentExecutor实现)
  • Chain:将LLM与其他组件串联的基础执行单元
  • Tool:Agent可调用的功能扩展(如计算器、搜索引擎)

LangSmith与LangGraph并非独立存在,而是对LangChain核心能力的增强

  • LangSmith → 观测性增强
  • LangGraph → 流程控制增强

本文将通过对比演示,展现这两个组件如何将基础Agent进化为工业级智能系统。


一、LangSmith:AI Agent的可观测性中枢

1. 设计理念:让AI开发透明可见

LangSmith源于一个简单却深刻的认知:黑箱化是AI系统不可靠的根源。传统LLM应用开发中,开发者往往面临三大痛点:

  • 无法追踪模型调用链
  • 难以定位逻辑错误
  • 缺乏性能评估基准

开发者面临的核心痛点:

  • 盲盒调试:不知道哪步工具调用出错
  • 性能黑洞:不清楚API调用耗时分布
  • 迭代失控:无法量化版本升级的影响

LangSmith通过全链路追踪可视化分析,将AI Agent的"思维过程"转化为可解释的数据图谱,实现了开发范式的根本转变。

1.1 只需添加几行配置即获得全维度观测能力

pip install langchain_openai langchain_core
export LANGCHAIN_TRACING_V2=true
export LANGSMITH_API_KEY=<your-api-key>
# This example uses OpenAI, but you can use any LLM provider of choice
export OPENAI_API_KEY=<your-openai-api-key>

1.2 无需额外代码即可将Trace(痕迹)记录到 LangSmith

import os

from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

# os.environ['LANGSMITH_TRACING'] = "True"
os.environ['LANGCHAIN_TRACING_V2'] = 'true'

prompt = ChatPromptTemplate.from_messages([
    ("system", "您是一位得力的智能助手。请仅根据给定的上下文,使用合适的语言响应用户的请求。"),
    ("user", "问题: {question}\n上下文: {context}")
])
model = ChatOpenAI(
    model='deepseek-chat',
    openai_api_key=os.getenv('DEEPSEEK_API_KEY'),
    openai_api_base='https://api.deepseek.com/v1',
    temperature=0
)
output_parser = StrOutputParser()

chain = prompt | model | output_parser

question = "你能总结一下今天上午的会议吗?"
context = "在今天上午的会议上,我们解决了世界上所有的冲突。"
result = chain.invoke({"question": question, "context": context})
print(result)

1.3 查看Trace

smith.langchain.com/o/9015dba0-…

2025-05-27 11 48 38.png

上面展示了如何通过设置单个环境变量来跟踪应用程序中 LangChain 可运行对象的所有调用。虽然这是一种便捷的入门方法,但你可能只想跟踪应用程序的特定调用或部分内容。

1.4 手动传入LangChainTracer实例作为回调来跟踪

from langchain.callbacks.tracers import LangChainTracer

# 取消设置 LANGSMITH_TRACING
# os.environ['LANGCHAIN_TRACING_V2'] = 'true'

# 可以设置LANGSMITH_PROJECT环境变量,为整个应用程序运行配置自定义项目名称
os.environ['LANGSMITH_PROJECT'] = 'default'

tracer = LangChainTracer()
chain.invoke({"question": "我是否正在使用回调?", "context": "我正在使用回调。"}, config={"callbacks": [tracer]})

2025-05-27 15 35 39.png

1.5 使用tracing_v2_enabled上下文管理器来跟踪

from langchain_core.tracers.context import tracing_v2_enabled

with tracing_v2_enabled():
    chain.invoke({"question": "我是否正在使用上下文管理器?", "context": "我正在使用上下文管理器。"})

2025-05-27 15 50 12.png

1.6 单独设置项目名称参数

from langchain.callbacks.tracers import LangChainTracer

tracer = LangChainTracer(project_name='default')
chain.invoke({"question": "我是否正在使用回调?", "context": "我正在使用回调。"}, config={"callbacks": [tracer]})

from langchain_core.tracers.context import tracing_v2_enabled

with tracing_v2_enabled(project_name='default'):
    chain.invoke({"question": "我是否正在使用上下文管理器?", "context": "我正在使用上下文管理器。"})

2025-05-27 15 58 18.png

1.7 在Config中提供任意元数据和标签,从而为跟踪添加注释

这有助于将其他信息与跟踪关联起来,例如跟踪的执行环境或发起跟踪的用户。

import os

from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

os.environ['LANGCHAIN_TRACING_V2'] = 'true'

prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个乐于助人的人工智能。"),
    ("user", "{input}")
])

# 标签“model-tag”和元数据 {“model-key”:“model-value”} 将仅附加到 ChatOpenAI 运行
chat_model = ChatOpenAI(
    model='deepseek-chat',
    openai_api_key=os.getenv('DEEPSEEK_API_KEY'),
    openai_api_base='https://api.deepseek.com/v1',
    temperature=0
).with_config({"tags": ["model-tag"], "metadata": {"model-key": "model-value"}})
output_parser = StrOutputParser()

# 可以使用 RunnableConfig 配置标签和元数据
chain = (prompt | chat_model | output_parser).with_config(
    {"tags": ["config-tag"], "metadata": {"config-key": "config-value"}})

# 标签和元数据也可以在运行时传递
chain.invoke({"input": "生命的意义是什么?"},
             {"tags": ["invoke-tag"], "metadata": {"invoke-key": "invoke-value"}})

2025-05-27 16 59 53.png

2025-05-27 17 01 47.png

1.8 通过在Config中提供名称来自定义给定运行的名称

# 在 LangChain 内跟踪时,运行名称默认为被跟踪对象的类名(例如,“ChatOpenAI”)。
configured_chain = chain.with_config({"run_name": "MyCustomChain"})
configured_chain.invoke({"input": "生命的意义是什么?"})

# 您还可以在调用时配置运行名称,如下所示
chain.invoke({"input": "生命的意义是什么?"}, {"run_name": "MyCustomChain"})

2025-05-27 17 14 35.png

1.9 通过在Config中提供 ID 来自定义特定运行的 ID

import uuid

my_uuid = uuid.uuid4()
# You can configure the run ID at invocation time:
chain.invoke({"input": "What is the meaning of life?"}, {"run_id": my_uuid})

2025-05-27 17 19 11.png

2. 核心功能全景

在LangSmith控制台中可分析:

功能模块技术实现应用场景
调用链追踪异步日志采集系统调试复杂Agent逻辑
性能监控时序数据库+指标计算引擎成本优化与资源调配
数据标注人工反馈集成接口构建高质量微调数据集
回归测试版本对比+差异分析保障系统迭代稳定性

3. 深度集成实践

(1) 全链路追踪配置

import os
from langsmith import RunTree
from langchain_openai import ChatOpenAI

# ------------------ 环境变量配置 ------------------
# 确保在环境变量或 .env 文件中配置了以下内容:
# LANGCHAIN_API_KEY: LangSmith 的 API Key
# DEEPSEEK_API_KEY: deepseek 模型的 API Key
os.environ["LANGCHAIN_ENDPOINT"] = os.getenv("LANGCHAIN_ENDPOINT", "https://api.smith.langchain.com")

# ------------------ 初始化 LLM ------------------
llm = ChatOpenAI(
    model="deepseek-chat",
    openai_api_key=os.getenv("DEEPSEEK_API_KEY"),
    openai_api_base="https://api.deepseek.com/v1",
    temperature=0,
)

# ------------------ 用户输入 ------------------
user_query = "我忘记了登录密码,怎么才能尽快重新设置?"

# ------------------ 顶层追踪节点 ------------------
run_tree = RunTree(
    name="customer_service_agent_trace",
    inputs={"user_query": user_query},
    tags=["langsmith-demo", "run_tree", "full_trace"]
)

try:
    # ------------------ 子任务 1:调用 LLM 生成回复 ------------------
    prompt = (
        "请为用户生成一段简洁友好的密码重置指引,"
        "说明操作步骤,并提醒用户注意账户安全。"
    )
    llm_child = RunTree(
        name="llm_inference",
        inputs={"prompt": prompt},
        parent_id=run_tree.id  # 👈 建立层级
    )
    response = llm.invoke(prompt)
    llm_child.add_outputs({"response": response})
    llm_child.post()  # 👈 上传子节点

    # ------------------ 子任务 2:格式化输出 ------------------
    format_child = RunTree(
        name="format_response",
        inputs={"raw_response": response},
        parent_id=run_tree.id
    )
    formatted = f"您好,{response} 如有疑问请联系人工客服协助处理。"
    format_child.add_outputs({"formatted_response": formatted})
    format_child.post()

    # ------------------ 顶层输出 ------------------
    run_tree.add_outputs({"final_response": formatted})

except Exception as e:
    run_tree.add_outputs({"error": str(e)})
    raise

finally:
    run_tree.post()  # 👈 上传主节点到 LangSmith 平台

# 控制台展示最终结果
print(formatted)

这个示例展示了如何使用 LangChain 与 LangSmith 搭建一个“客服智能体”的原型,并通过 RunTree 工具对整个执行过程进行结构化的全链路追踪。追踪的内容不仅包括最终结果,还详细记录了每一步中 LLM 的调用和响应处理。

当你在网站上向客服机器人提问,比如“我忘记密码了怎么办?”,背后其实是一个小程序在帮你处理:

  • 它会把你的问题转化成一句清晰的“提示语”,比如:“请生成密码重置流程的说明”;
  • 然后它调用一个智能模型来写出这段说明;
  • 最后它把这段说明稍微润色一下,再发给你。

为了让开发者能“看清楚”这个机器人到底每一步做了什么,我们用 LangSmith 提供的“追踪系统”记录了整个过程——就像一张“处理流程图”,可以在平台上回放每一步的输入和输出。

在LangSmith控制台中,开发者可直观看到:

  • 每个工具(Tools)的调用耗时
  • 各步骤的Token消耗占比
  • 异常调用的堆栈追踪

2025-06-16 16 14 01.png


二、LangGraph:复杂认知工作流的编排引擎

1. 架构哲学:从链式到图式

传统LangChain的链式(Chain)结构在处理以下场景时显露局限:

  • 带循环的对话状态管理
  • 多Agent协同决策
  • 动态路径选择

LangGraph引入 有向无环图(DAG) 模型,通过三大创新突破限制:

  • 状态持久化:支持长时间运行的智能体会话
  • 条件分支:实现基于上下文的动态路由
  • 并行执行:提升复杂任务的完成效率

当客服Agent需要处理以下场景时,传统链式架构遇到瓶颈:

# 传统线性处理
response = agent.run("我要退货,上周买的鞋子尺寸不对")  
# 隐含需求:1.验证订单 2.检查退货政策 3.生成退货码 4.通知物流

痛点呈现:

  • 僵化流程:无法动态调整处理步骤
  • 状态丢失:多轮交互后无法回溯上下文
  • 资源争用:无法并行执行独立任务

2. 核心抽象模型

graph TD
    Start --> Preprocessor
    Preprocessor --> |普通问题| QA_Agent
    Preprocessor --> |技术问题| Tech_Support
    Preprocessor --> |投诉建议| CRM_System
    QA_Agent --> Response_Generator
    Tech_Support --> Knowledge_Base
    CRM_System --> Ticket_System
    Response_Generator --> End
    Knowledge_Base --> Response_Generator
    Ticket_System --> Response_Generator

3. 企业级应用案例

客户服务自动化系统

import os
import sqlite3
from typing import TypedDict, Literal, Optional
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.tracers import LangChainTracer
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
from langgraph.checkpoint.sqlite import SqliteSaver
from langgraph.graph import StateGraph, END

# LangSmith配置
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_PROJECT"] = "default"
tracing_handler = LangChainTracer()


class AgentState(TypedDict):
    """代理状态类型定义
    Attributes:
        user_input: str 用户输入文本
        intent: Optional[Literal["普通问题", "技术问题", "投诉问题"]] 问题分类结果
        response: Optional[str] 生成的响应内容
    """
    user_input: str
    intent: Optional[Literal["普通问题", "技术问题", "投诉问题"]]
    response: Optional[str]


# 初始化大语言模型
llm = ChatOpenAI(
    model='deepseek-chat',
    openai_api_key=os.getenv('DEEPSEEK_API_KEY'),  # 从环境变量获取API密钥
    openai_api_base='https://api.deepseek.com/v1',  # DeepSeek API端点
    temperature=0,  # 控制生成结果的随机性
    callbacks=[tracing_handler]  # 集成 LangSmith
)
output_parser = StrOutputParser()  # 用于解析LLM输出的纯文本结果


def safe_set_intent(value: str) -> Optional[Literal["普通问题", "技术问题", "投诉问题"]]:
    """安全设置问题分类
    Args:
        value: 待验证的分类字符串
    Returns:
        当输入为有效分类时返回原值,否则返回None
    """
    return value if value in ("普通问题", "技术问题", "投诉问题") else None


def classify_intent(state: AgentState) -> AgentState:
    """问题分类节点
    Args:
        state: 当前对话状态
    Returns:
        更新后的状态(包含分类结果)
    """
    prompt = ChatPromptTemplate.from_template(
        "请判断用户问题类型,只返回普通问题/技术问题/投诉问题,不要包含任何格式符号或额外文字。问题内容:{input}"
    )
    chain = prompt | llm | output_parser  # 构建处理链
    intent = chain.invoke({"input": state["user_input"]}).strip()
    return {
        "user_input": state["user_input"],
        "intent": safe_set_intent(intent),  # 安全设置分类
        "response": None
    }


def handle_general(state: AgentState) -> AgentState:
    """普通问题处理节点"""
    prompt = ChatPromptTemplate.from_template(
        "你是一个客服助手,请用纯文本回答以下普通问题,不要包含任何格式符号或标记。问题:{input}"
    )
    chain = prompt | llm | output_parser
    response = chain.invoke({"input": state["user_input"]})
    return {
        "user_input": state["user_input"],
        "intent": state.get("intent"),
        "response": response
    }


def handle_tech(state: AgentState) -> AgentState:
    """技术问题处理节点"""
    prompt = ChatPromptTemplate.from_template(
        "你是一名技术支持工程师,请用纯文本回答以下技术问题,不要包含任何格式符号或标记。技术问题:{input}"
    )
    chain = prompt | llm | output_parser
    response = chain.invoke({"input": state["user_input"]})
    return {
        "user_input": state["user_input"],
        "intent": state.get("intent"),
        "response": response
    }


def handle_complaint(state: AgentState) -> AgentState:
    """投诉问题处理节点"""
    prompt = ChatPromptTemplate.from_template(
        "你是一名投诉处理专员,请用纯文本回答以下投诉,不要包含任何格式符号或标记。投诉内容:{input}"
    )
    chain = prompt | llm | output_parser
    response = chain.invoke({"input": state["user_input"]})
    return {
        "user_input": state["user_input"],
        "intent": state.get("intent"),
        "response": response
    }


def decide_next_step(state: AgentState) -> str:
    """路由决策函数
    Args:
        state: 当前对话状态
    Returns:
        下一节点的名称
    """
    return state.get("intent", "普通问题")  # 默认路由到普通问题


# 构建工作流
workflow = StateGraph(AgentState)

# 注册节点
workflow.add_node("classify", classify_intent)  # 分类节点
workflow.add_node("普通问题", handle_general)  # 注意:节点名称改为中文
workflow.add_node("技术问题", handle_tech)
workflow.add_node("投诉问题", handle_complaint)

# 设置条件路由(修正后的映射关系)
workflow.add_conditional_edges(
    "classify",
    decide_next_step,
    {
        "普通问题": "普通问题",  # 键值统一使用中文
        "技术问题": "技术问题",
        "投诉问题": "投诉问题"
    }
)

# 设置终止边
workflow.add_edge("普通问题", END)
workflow.add_edge("技术问题", END)
workflow.add_edge("投诉问题", END)

# 设置入口点
workflow.set_entry_point("classify")

# 编译时启用检查点
conn = sqlite3.connect("checkpoints.sqlite", check_same_thread=False)
memory = SqliteSaver(conn)
app = workflow.compile(checkpointer=memory)

if __name__ == "__main__":
    # 测试用例
    test_cases = [
        ("user_001", "你们的工作时间是几点?"),
        ("user_002", "我的账号无法登录"),
        ("user_003", "我要投诉你们的服务态度")
    ]

    for user_id, query in test_cases:
        print(f"\n[会话 {user_id}] 用户提问: {query}")

        # 执行工作流(关键修正)
        result = app.invoke(
            {"user_input": query},
            config={"configurable": {"thread_id": user_id}}
        )

        print(f"问题类型: {result.get('intent')}")
        print(f"系统回复: {result.get('response')}")

        # 验证检查点
        checkpoint = memory.get_tuple({"configurable": {"thread_id": user_id}})
        print(f"检查点状态: {checkpoint}")

输出:

2025-06-17 15 51 45.png

LangSmith监控:

2025-06-17 11 09 40.png

该工作流实现:

  • 智能会话状态管理
  • 基于意图的动态路由
  • 断点续话能力

4. 核心概念解析

1. 状态流(State Flow)

核心概念
LangGraph 的核心是基于状态流转的图计算模型,通过 StateGraph 定义数据流。状态是一个类型化的字典(TypedDict),在节点间传递和修改。

代码体现

class AgentState(TypedDict):  # 定义状态结构
    user_input: str
    intent: Optional[Literal["普通问题", "技术问题", "投诉问题"]]
    response: Optional[str]

workflow = StateGraph(AgentState)  # 声明状态流

2. 节点(Nodes)

核心概念
节点是纯函数,接收当前状态,返回更新后的状态。LangGraph 不限制节点内部逻辑(可调用LLM、工具、API等)。

代码体现

def classify_intent(state: AgentState) -> AgentState:
    # 调用LLM进行分类
    return {"intent": "技术问题"}  # 只更新部分状态

workflow.add_node("classify", classify_intent)  # 注册节点

3. 边(Edges)

核心概念
边定义节点间的流转路径,分为两种:

  • 固定边add_edge("node1", "node2")
  • 条件边:基于状态动态路由

代码体现

# 条件边(根据intent值路由)
workflow.add_conditional_edges(
    "classify",
    decide_next_step,  # 路由决策函数
    {"技术问题": "tech_node", ...}
)

# 固定边(直接跳转)
workflow.add_edge("tech_node", END)

4. 检查点(Checkpoints)

核心概念
检查点(Checkpoint)是在任务或流程执行过程中自动保存中间状态的一种机制,旨在实现容错恢复、状态追踪和重复计算避免。在 LangGraph 等流程编排框架中,检查点用于持久化每个节点执行后的状态,使得在流程中断或异常退出后,可以从最近的检查点恢复继续执行,而无需重头开始。检查点既可保存在内存中(如 MemorySaver),也可持久化到本地数据库(如 SqliteSaver)或远程存储(如 RedisSaver),以适应不同的开发与部署场景。

代码体现

import sqlite3
from langgraph.checkpoint.sqlite import SqliteSaver

conn = sqlite3.connect("checkpoints.sqlite", check_same_thread=False)
memory = SqliteSaver(conn)
app = workflow.compile(checkpointer=memory)

2025-06-17 15 57 42.png


5. 执行模型

核心概念
LangGraph 采用异步有向图执行模型:

  1. entry_point 开始
  2. 按边条件流转
  3. 直到到达 END

代码体现

workflow.set_entry_point("classify")  # 设置起点
app.invoke({"user_input": "问题内容"})  # 执行流式

与上面代码的映射

概念代码示例设计目的
状态AgentState定义数据流动的规范格式
节点handle_tech/classify_intent模块化业务逻辑
条件路由decide_next_step 函数实现动态流程控制
持久化SqliteSaver支持长周期对话状态管理

架构优势对比

能力项传统AgentLangGraph Agent
流程动态性固定顺序基于上下文的条件分支
状态管理短期记忆持久化检查点(Checkpoint)
执行模式串行支持并行与异步
错误恢复整体失败局部重试与补偿机制

三、LangChain生态的协同进化

1. 技术定位关系

graph LR
    LangChain_Core --> Models
    LangChain_Core --> Tools
    LangChain_Core --> Memory
    LangSmith --> Monitoring
    LangGraph --> Workflow
    Models --> LangSmith
    Tools --> LangGraph
    Memory --> LangSmith

2. 协同开发模式

  1. 开发阶段

    • 使用LangGraph设计工作流拓扑
    • 通过LangSmith调试单个节点
  2. 测试阶段

    • 在LangSmith中创建黄金数据集
    • 执行自动化回归测试
  3. 运维阶段

    • 实时监控生产环境指标
    • 通过检查点回放定位问题

3. 性能基准对比

任务类型纯LangChainLangChain+LangGraph提升幅度
多步骤推理12.4s8.7s30%
带循环的对话失败率32%失败率4%87%
资源消耗38GB内存22GB内存42%

四、未来愿景:通向AGI的开发范式

1. LangSmith的演进方向

  • 智能根因分析:自动定位异常调用的逻辑链路
  • 预测性缩放:基于流量模式的自动资源调配
  • 合规审计:满足GDPR等法规的数据溯源需求

2. LangGraph的长期目标

  • 分布式Agent网络:支持跨物理节点的智能体协作
  • 强化学习集成:实现工作流的自主优化
  • 量子计算就绪:为新型计算架构预留接口

3. 开发者体验升级

  • 可视化编程界面:拖拽式工作流设计器
  • 智能代码生成:自然语言转LangGraph配置
  • 元宇宙调试环境:3D空间中的Agent思维可视化

结语:重新定义AI工程边界

LangSmith与LangGraph不仅代表着技术工具的进步,更预示着一个新纪元的开启:当AI系统的开发过程变得完全透明可控,当复杂认知任务的编排如同搭积木般简单,人类终于站在了构建可靠智能系统的门槛之上。这两个组件的演化轨迹,正勾勒出未来AGI开发平台的雏形——在这里,每个开发者都能像指挥交响乐团般,优雅地驾驭智能的洪流。