LangChain 1.0 进阶实战:从核心组件到高级使用

52 阅读27分钟

背景

对应于langchain Advance usage这部分。其中 Gudrail,HITL(hunman-in-the-loop),可以直接在核心组件中获取到详细知识。Retrieval单独在langchain Rag的笔记中记录

Runtime

核心概述

  1. 底层依赖:LangChain 中通过 create_agent 创建的代理,其运行依赖 LangGraph 提供的 Runtime 对象。

  2. 核心组成部分:Runtime 包含三类关键信息

    • Context: 静态信息(如用户 ID、DB 连接)。作用:为工具和中间件提供依赖注入,替代硬编码或全局状态,提升灵活性

    • BaseStore 实例:用于存储代理的长期内存数据,支持数据的读写操作

    • 流对象: 通过 "custom" 流模式,实现信息的流式传输(如工具执行进度、实时更新)

  3. 核心价值:通过依赖注入机制,避免硬编码或全局状态,使工具更易测试、可复用且灵活。

使用

模拟RAG中的组合检索方式,将静态参数,长期记忆,流对象输出进行结合

@dataclass
class CustomContext:
    """
    自定义上下文
    """
    db_user_password: str
    db_user_name: str
    db_url: str
    user_id: str


def rag_query(query: str, runtime: ToolRuntime[CustomContext]):
    """
    模拟 组合检索
    """
    runtime.stream_writer(f'用户的问题是:{query}')

    result = []
    print(f"数据库连接建立:{runtime.context.db_url},得到数据库上下文")
    db_content = "2025年美国总统是特朗普"
    result.append(db_content)

    if runtime.store:
        if memory := runtime.store.get(("users",), runtime.context.user_id):
            content = memory.value["chat"]
            result.append(content)

    return result


store = InMemoryStore()
store.put(("users",), "1", {"chat": "2023年美国总统是拜登"})
agent = create_agent(
    model=base_model,
    tools=[rag_query],
    context_schema=CustomContext,
    store=store
)

for steam_model, event in agent.stream(
        {"messages": [{"role": "user", "content": "今年是2025年,今年的美国总统是谁?"}]},
        context={"user_id": "1", "db_url": "mysql://root:123456@127.0.0.1:3306/test", "db_user_name": "root",
                 "db_user_password": "123456"},
        stream_mode=["custom", "updates"],
):
    print(f"stream_mode: {steam_model}")
    print(f"content: {event}")

# 输出
# stream_mode: updates
# content: {'model': {'messages': [AIMessage(content='', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 27, 'prompt_tokens': 265, 'total_tokens': 292, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}}, 'model_provider': 'openai', 'model_name': 'qwen3-max', 'system_fingerprint': None, 'id': 'chatcmpl-83fcc779-4c1f-45dc-b960-34883aa01781', 'finish_reason': 'tool_calls', 'logprobs': None}, id='lc_run--59f89e1d-1da4-4816-b3ec-18f2aec6d61d-0', tool_calls=[{'name': 'rag_query', 'args': {'query': '2025年美国总统是谁'}, 'id': 'call_64e4f0fca2ac469b8cc3c216', 'type': 'tool_call'}], usage_metadata={'input_tokens': 265, 'output_tokens': 27, 'total_tokens': 292, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}})]}}
# 数据库连接建立:mysql://root:123456@127.0.0.1:3306/test,得到数据库上下文
# stream_mode: custom
# content: 用户的问题是:2025年美国总统是谁
# stream_mode: updates
# content: {'tools': {'messages': [ToolMessage(content='["2025年美国总统是特朗普", "2023年美国总统是拜登"]', name='rag_query', id='01b7e1a9-4615-4e84-853e-85c1676b0dcd', tool_call_id='call_64e4f0fca2ac469b8cc3c216')]}}
# stream_mode: updates
# content: {'model': {'messages': [AIMessage(content='根据检索结果,2025年美国总统是特朗普。', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 13, 'prompt_tokens': 326, 'total_tokens': 339, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 128}}, 'model_provider': 'openai', 'model_name': 'qwen3-max', 'system_fingerprint': None, 'id': 'chatcmpl-019c97b8-4856-4ea6-95ed-769f22715ad1', 'finish_reason': 'stop', 'logprobs': None}, id='lc_run--5a03bc88-acf6-4815-98c5-b2c887338174-0', usage_metadata={'input_tokens': 326, 'output_tokens': 13, 'total_tokens': 339, 'input_token_details': {'cache_read': 128}, 'output_token_details': {}})]}}

Model Context Protocol (MCP)

Model Context Protocol (MCP) 是一种开放协议,用于标准化应用程序向 LLM 提供工具和上下文的方式,LangChain 智能体可通过 * langchain-mcp-adapters* 库使用 MCP 服务器上定义的工具;其支持 stdio、Streamable HTTP、Server-Sent Events (SSE) 三种传输类型,可通过 MultiServerMCPClient 调用多服务器工具(如本地数学工具、远程天气工具),也可借助 mcp 库创建自定义 MCP 服务器,同时支持无状态工具调用和通过 ClientSession 实现的有状态工具调用。

安装包

  • LangChain agent 使用 MCP 服务器的工具
pip install langchain - mcp - adapters
  • 创建自定义 MCP 服务器,定义专属工具
pip install mcp

传输类型

Stdio——客户端启动服务器作为子进程,通过标准输入 / 输出通信。适合本地工具、简单搭建的场景。

Streamable HTTP——服务器作为独立进程运行处理 HTTP 请求。需远程连接、支持多个客户端的场景。

Server-Sent Events (SSE)——Streamable HTTP 的变体,优化实时流通信。需实时流式数据交互的场景。

stdio自定义MCP

mcp = FastMCP("Math")


@mcp.tool()
def add(a: float, b: float) -> float:
    """
    两个数相加
    :param a: 左边的值
    :param b: 右边的值
    :return: a+b的和
    """
    return a + b


@mcp.tool()
def multiply(a: float, b: float) -> float:
    """
    两个数相乘
    :param a:
    :param b:
    :param a: 左边的值
    :param b: 右边的值
    :return: a * b的值
    """
    return a * b


mcp.run(transport="stdio")

传输类型使用的是stdio,将本地工具转化成MCP使用

调用本地MCP和DashScope平台上的MCP

async def get_mcp_tools():
    """
    初始化MCP客户端和Agent
    """
    print("初始化MCP客户端...")

    # 定义MCP client
    client = MultiServerMCPClient(
        {
            "time-service": {
                "command": "python",
                "args": ["math_server.py"],
                "transport": "stdio",
                "env": {},
            },
            # 百炼墨迹天气服务(HTTP传输)
            "weather-service": {
                "url": "https://dashscope.aliyuncs.com/api/v1/mcps/market-cmapi013828/mcp",
                "transport": "streamable_http",
                "headers": {
                    "Authorization": f"Bearer {os.getenv('MOJI_API_KEY')}"
                }
            }
        }
    )

    # 获取所有MCP服务器的工具
    tools = await client.get_tools()
    print(f"成功加载 {len(tools)} 个工具")

    # 打印工具列表
    for tool in tools:
        print(f"  - {tool.name}: {tool.description}")

    return tools


async def main():
    """主函数"""
    try:
        # 运行综合示例
        tools = await get_mcp_tools()
        agent = create_agent(model=base_model, tools=tools)
        r = await agent.ainvoke(
            {"messages": [{"role": "user", "content": "北京市今天的温度是多少度? 如果再下降50%那温度是多少度?"}]})
        for message in r['messages']:
            message.pretty_print()
    except Exception as e:
        print(f"运行出错: {str(e)}")
        import traceback
        traceback.print_exc()

注意: 的是客户端再调用MCP时必须要使用异步调用。

await client.get_tools()

await agent.ainvoke
== == == == == == == == == == == == == == == == Human
Message == == == == == == == == == == == == == == == == =

北京市今天的温度在是多少度? 如果再下降50 % 那温度是多少度?
== == == == == == == == == == == == == == == == == Ai
Message == == == == == == == == == == == == == == == == ==
Tool
Calls:
天气实况(call_aecf6ad483b74e4d893467ed)
Call
ID: call_aecf6ad483b74e4d893467ed
Args:
cityId: 101010100
token: 50
b53ff8dd7d9fa320d3d3ca32cf8ed1
== == == == == == == == == == == == == == == == = Tool
Message == == == == == == == == == == == == == == == == =
Name: 天气实况

{"code": 10, "msg": "101| cityId is illegal", "rc": {"c": 10, "p": "101| cityId is illegal"}} - 以下是返回参数说明
- 参数名称: code, 参数类型: integer, 参数描述: 无描述, 参数示例: 0
- 参数名称: data, 参数类型: object, 参数描述: 无描述, 参数示例: 无示例
- 参数名称: data.city, 参数类型: object, 参数描述: 无描述, 参数示例: 无示例
- 参数名称: data.city.cityId, 参数类型: integer, 参数描述: 无描述, 参数示例: 284609
- 参数名称: data.city.counname, 参数类型: string, 参数描述: 无描述, 参数示例: 中国
- 参数名称: data.city.name, 参数类型: string, 参数描述: 无描述, 参数示例: 东城区
- 参数名称: data.city.pname, 参数类型: string, 参数描述: 无描述, 参数示例: 北京市
- 参数名称: data.condition, 参数类型: object, 参数描述: 无描述, 参数示例: 无示例
- 参数名称: data.condition.condition, 参数类型: string, 参数描述: 无描述, 参数示例: 晴
- 参数名称: data.condition.conditionId, 参数类型: string, 参数描述: 无描述, 参数示例: 5
- 参数名称: data.condition.humidity, 参数类型: string, 参数描述: 无描述, 参数示例: 42
- 参数名称: data.condition.icon, 参数类型: string, 参数描述: 无描述, 参数示例: 30
- 参数名称: data.condition.pressure, 参数类型: string, 参数描述: 无描述, 参数示例: 999
- 参数名称: data.condition.realFeel, 参数类型: string, 参数描述: 无描述, 参数示例: 18
- 参数名称: data.condition.sunRise, 参数类型: string, 参数描述: 无描述, 参数示例: 2016 - 0
9 - 01
05: 42:00
- 参数名称: data.condition.sunSet, 参数类型: string, 参数描述: 无描述, 参数示例: 2016 - 0
9 - 01
18: 45:00
- 参数名称: data.condition.temp, 参数类型: string, 参数描述: 无描述, 参数示例: 24
- 参数名称: data.condition.tips, 参数类型: string, 参数描述: 无描述, 参数示例: 冷热适宜,感觉很舒适。
- 参数名称: data.condition.updatetime, 参数类型: string, 参数描述: 无描述, 参数示例: 2016 - 0
9 - 01
22: 03:00
- 参数名称: data.condition.uvi, 参数类型: string, 参数描述: 无描述, 参数示例: 0
- 参数名称: data.condition.windDir, 参数类型: string, 参数描述: 无描述, 参数示例: 东北风
- 参数名称: data.condition.windLevel, 参数类型: string, 参数描述: 无描述, 参数示例: 2
- 参数名称: data.condition.windSpeed, 参数类型: string, 参数描述: 无描述, 参数示例: 2.45
- 参数名称: msg, 参数类型: string, 参数描述: 无描述, 参数示例: success
- 参数名称: rc, 参数类型: object, 参数描述: 无描述, 参数示例: 无示例
- 参数名称: rc.c, 参数类型: integer, 参数描述: 无描述, 参数示例: 0
- 参数名称: rc.p, 参数类型: string, 参数描述: 无描述, 参数示例: success
== == == == == == == == == == == == == == == == == Ai
Message == == == == == == == == == == == == == == == == ==
Tool
Calls:
天气实况(call_5758d8e402504dbcb20d89f7)
Call
ID: call_5758d8e402504dbcb20d89f7
Args:
cityId: 284609
token: 50
b53ff8dd7d9fa320d3d3ca32cf8ed1
== == == == == == == == == == == == == == == == = Tool
Message == == == == == == == == == == == == == == == == =
Name: 天气实况

{"code": 0, "data": {
    "city": {"cityId": 284609, "counname": "中国", "ianatimezone": "Asia/Shanghai", "name": "东城区", "pname": "北京市",
             "secondaryname": "北京市", "timezone": "8"},
    "condition": {"condition": "晴", "conditionId": "5", "humidity": "80", "icon": "30", "pressure": "1016",
                  "realFeel": "5", "sunRise": "2025-11-10 06:54:00", "sunSet": "2025-11-10 17:03:00", "temp": "6",
                  "tips": "天气阴冷,穿暖和点吧!", "updatetime": "2025-11-10 23:30:08", "uvi": "1", "vis": "8100",
                  "windDegrees": "45", "windDir": "东北风", "windLevel": "1", "windSpeed": "0.89"}}, "msg": "success",
 "rc": {"c": 0, "p": "success"}} - 以下是返回参数说明
- 参数名称: code, 参数类型: integer, 参数描述: 无描述, 参数示例: 0
- 参数名称: data, 参数类型: object, 参数描述: 无描述, 参数示例: 无示例
- 参数名称: data.city, 参数类型: object, 参数描述: 无描述, 参数示例: 无示例
- 参数名称: data.city.cityId, 参数类型: integer, 参数描述: 无描述, 参数示例: 284609
- 参数名称: data.city.counname, 参数类型: string, 参数描述: 无描述, 参数示例: 中国
- 参数名称: data.city.name, 参数类型: string, 参数描述: 无描述, 参数示例: 东城区
- 参数名称: data.city.pname, 参数类型: string, 参数描述: 无描述, 参数示例: 北京市
- 参数名称: data.condition, 参数类型: object, 参数描述: 无描述, 参数示例: 无示例
- 参数名称: data.condition.condition, 参数类型: string, 参数描述: 无描述, 参数示例: 晴
- 参数名称: data.condition.conditionId, 参数类型: string, 参数描述: 无描述, 参数示例: 5
- 参数名称: data.condition.humidity, 参数类型: string, 参数描述: 无描述, 参数示例: 42
- 参数名称: data.condition.icon, 参数类型: string, 参数描述: 无描述, 参数示例: 30
- 参数名称: data.condition.pressure, 参数类型: string, 参数描述: 无描述, 参数示例: 999
- 参数名称: data.condition.realFeel, 参数类型: string, 参数描述: 无描述, 参数示例: 18
- 参数名称: data.condition.sunRise, 参数类型: string, 参数描述: 无描述, 参数示例: 2016 - 0
9 - 01
05: 42:00
- 参数名称: data.condition.sunSet, 参数类型: string, 参数描述: 无描述, 参数示例: 2016 - 0
9 - 01
18: 45:00
- 参数名称: data.condition.temp, 参数类型: string, 参数描述: 无描述, 参数示例: 24
- 参数名称: data.condition.tips, 参数类型: string, 参数描述: 无描述, 参数示例: 冷热适宜,感觉很舒适。
- 参数名称: data.condition.updatetime, 参数类型: string, 参数描述: 无描述, 参数示例: 2016 - 0
9 - 01
22: 03:00
- 参数名称: data.condition.uvi, 参数类型: string, 参数描述: 无描述, 参数示例: 0
- 参数名称: data.condition.windDir, 参数类型: string, 参数描述: 无描述, 参数示例: 东北风
- 参数名称: data.condition.windLevel, 参数类型: string, 参数描述: 无描述, 参数示例: 2
- 参数名称: data.condition.windSpeed, 参数类型: string, 参数描述: 无描述, 参数示例: 2.45
- 参数名称: msg, 参数类型: string, 参数描述: 无描述, 参数示例: success
- 参数名称: rc, 参数类型: object, 参数描述: 无描述, 参数示例: 无示例
- 参数名称: rc.c, 参数类型: integer, 参数描述: 无描述, 参数示例: 0
- 参数名称: rc.p, 参数类型: string, 参数描述: 无描述, 参数示例: success
== == == == == == == == == == == == == == == == == Ai
Message == == == == == == == == == == == == == == == == ==
Tool
Calls:
multiply(call_d688b39ec46142d5ba173c0f)
Call
ID: call_d688b39ec46142d5ba173c0f
Args:
a: 6
b: 0.5
== == == == == == == == == == == == == == == == = Tool
Message == == == == == == == == == == == == == == == == =
Name: multiply

3.0
== == == == == == == == == == == == == == == == == Ai
Message == == == == == == == == == == == == == == == == ==

根据天气实况数据,北京市今天的温度是6°C。如果温度再下降50 %,那么温度会是3°C。

无状态调用:

MultiServerMCPClient默认模式,每次工具调用会创建新的ClientSession,执行完成后清理会话,不保留上下文。

有状态调用

如果需要保持会话,比如database session 或者 消息队列等等,不希望频繁创建与销毁。通过client.session()创建持久的 ClientSession

from langchain_mcp_adapters.tools import load_mcp_tools

client = MultiServerMCPClient({...})
async with client.session("message_queue") as session:
    tools = await load_mcp_tools(session)

Multi-Agent

Multi-agent(多智能体)系统是将复杂应用拆解为多个专业智能体协同解决问题的架构,适用于单一智能体工具过多、上下文 / 记忆过载或任务需专业化分工的场景;其核心包含Tool Calling(工具调用)Handoffs(移交) 两种模式,前者由控制智能体将其他智能体作为工具调用(集中式控制,工具智能体不直接与用户交互),后者由当前智能体转移控制权给其他智能体(去中心化控制,新智能体可直接与用户交互)。

模型

Tool Calling(工具调用)

一个 “控制智能体” 将其他智能体作为工具调用;工具智能体不直接与用户交互,仅执行任务并返回结果。
类似于langgraph的supervisor模式,只时这里使用的并非图,而是通过工具进行调用。本质上还是使用了langchain agent调用工具,这是现在将整个智能体封装成一个工具。

举个例子:可以将一个RAG查询功能封装为一个工具

工作流程

  1. 控制智能体接收输入,判断需要调用哪个工具(子智能体)。
  2. 工具智能体根据控制智能体的指令执行特定任务。
  3. 工具智能体将执行结果返回给控制智能体。
  4. 控制智能体决定下一步操作(如调用其他工具、返回结果给用户)或结束流程。

# 子代理保持不变
@tool
def get_weather(location: str) -> str:
    """Get the weather at a location."""
    return {"messages": [{"role": "assistant", "content": f"It's sunny in {location}."}], "time": 123}


weather_agent = create_agent(
    model=base_model,
    tools=[get_weather],
)


# 将调用子代理封装为一个工具,并返回子代理的结果
@tool
def get_sub_agent(runtime: ToolRuntime[AgentState]) -> str:
    """Get the weather."""
    result = weather_agent.invoke({"messages": [{"role": "user", "content": runtime.state["messages"][0].content}]})
    return result["messages"][-1].content


main_agent = create_agent(
    model=base_model,
    tools=[get_sub_agent],
)

r = main_agent.invoke(input={"messages": [{"role": "user", "content": "北京的天气如何?"}]})

注意:哪些消息需要传递给子代理,这个在整个多智能体中管理是整个架构的难点

Handoffs(交接)

当前智能体决定将控制权转移给另一个智能体;活跃智能体发生变化,用户可直接与新智能体交互

类似于langgraph的network模式,智能体可自主切换活跃状态。

工作流程

  1. 当前活跃的智能体判断自身无法完成任务,需要其他智能体协助。
  2. 当前智能体将控制权和状态传递给下一个智能体。
  3. 新智能体成为活跃智能体,直接与用户交互,直至决定再次移交控制权或完成任务。

实现状态

该模式的具体实现方式暂未发布,后续将补充相关内容。

文档明确标注Implementation (Coming soon) ,即该模式的具体实现方式暂未发布,后续将补充相关内容。虽然它还未实现,但是结合langgraph可以去思考看看:

  1. 如果langchain来实现,具备调用所有agent则需要维护其他agent的功能描述。
  2. 并且需要具备主动停止的能力,则每个agent都需要设置结束标志性。

可以类似维护一个tools的数组方式维护一个agent列表,其底层其实就是使用langgraph的图的结构。每个agent运行结束后,自行判断是移交给其他agent还是说结束。

自定义智能体上下文

LangChain 提供细粒度控制能力,支持自定义以下 4 个维度的信息传递:

  • 传递给每个智能体的对话或状态片段(即筛选关键信息,避免信息过载)。
  • 为子智能体量身定制的专属提示词(提升子智能体任务适配度)。
  • 是否包含或排除中间推理过程(例如仅传递最终结论,或保留推理逻辑)。
  • 每个智能体的输入 / 输出格式(统一数据交互标准)。

小结

多智能体之前的交互,上下文的管理,输入输出结构化数据都是我们整个架构的难点。目前可能将功能拆分后,每个智能体各自承担相应的功能,由一个总管(supervisor)agent管理是目前多agent领域有落地的可能,而交接网络,或者多层级主管的方式可能需要等待模型,算力,算法等技术的提升来同步推进

长期记忆

LangChain 智能体借助LangGraph 持久化实现长期记忆(Long-term memory) ,需具备 LangGraph 相关知识;其长期记忆以JSON 文档形式存储在存储器(如InMemoryStore,生产环境建议用数据库支持的存储器)中,按自定义命名空间(namespace) 和* 唯一键(key)* 分层组织,支持存储、获取、搜索操作;同时提供了在工具中读取长期记忆(如查询用户信息)和写入长期记忆 (如更新用户信息)的实现方式,需通过create_agent传入存储器和上下文 schema 完成配置。

存储器(store)

“namespace + key” 是长期记忆的核心组织方式,其作用包括 2 点:

  1. 分层分类:namespace 作为 “文件夹” 实现记忆的宏观分类(如区分用户数据、系统配置、任务历史),key 作为 “文件名” 实现同一分类下的精准定位,避免数据混乱;
  2. 高效检索:明确的分层结构让搜索和获取操作仅需定位特定 namespace,减少检索范围,提升效率(如搜索用户 “user_123” 的信息时,仅需在 “users” namespace 下查找,无需遍历所有记忆)。

存储记忆(store.put)

from langgraph.store.memory import InMemoryStore

# 定义存储器
store = InMemoryStore()

# 定义命名空间 user_id + type
user_id = "baqiF2"
type = "advance_content"
name_space = (user_id, type)
# 命名空间的key
key = "long-term-memory"

# 存储数据
store.put(name_space, key, {
    "content": "这是长期记忆的内容",
    "time": 123
})

读取记忆(store.get)

response = store.get(name_space, key)

## 输出
Item(namespace=['baqiF2', 'advance_content'],
     key='long-term-memory',
     value={'content': '这是长期记忆的内容', 'time': 123},
     created_at='2025-11-13T13:42:41.603090+00:00',
     updated_at='2025-11-13T13:42:41.603183+00:00')

搜索记忆

## 搜索 过滤条件为"time": "123"
items = store.search(
    name_space,
    filter={"time": 123},
)
print(items)

# 输出
[Item(namespace=['baqiF2', 'advance_content'],
      key='long-term-memory',
      value={'content': '这是长期记忆的内容', 'time': 123},
      created_at='2025-11-13T13:50:13.701275+00:00',
      updated_at='2025-11-13T13:50:13.701427+00:00', score=None)]

agent中的使用

先从长期记忆中检索需要的信息,然后根据结果返回,或者一起交给agent处理后续的任务

@tool
def get_weather(location: str, runtime: ToolRuntime) -> str:
    """Get the weather at a location."""
    user_id = runtime.context.user_id
    name_space = (user_id, 'advance')
    store = runtime.store

    result = store.search(name_space, filter={"city": location})
    if result:
        print(f"从长期记忆中获取数据:{result}")
        return result[0].value

    print(f"从网络获取数据:{location}")
    messages = {"city": location, "messages": [{"role": "assistant", "content": f"It's sunny in {location}."}]}
    # 写入长期记忆
    store.put(name_space, 'get_weather', messages)
    return {"messages": [{"role": "assistant", "content": f"It's sunny in {location}."}]}

    ###输出
#     从长期记忆中获取数据:[Item(namespace=['baqiF2', 'advance'], key='get_weather', value={'city': '北京', 'messages': [{'role': 'assistant', 'content': "It's sunny in 北京."}]}, created_at='2025-11-13T14:14:54.639386+00:00', updated_at='2025-11-13T14:14:54.639391+00:00', score=None)]
# ================================ Human Message =================================

# 北京的天气如何?
# ================================== Ai Message ==================================
# Tool Calls:
#   get_weather (call_63185c3184144cbe92d11f55)
#  Call ID: call_63185c3184144cbe92d11f55
#   Args:
#     location: 北京
# ================================= Tool Message =================================
# Name: get_weather

# {"city": "北京", "messages": [{"role": "assistant", "content": "It's sunny in 北京."}]}
# ================================== Ai Message ==================================

# 北京的天气晴朗。

小结

长期记忆相对于短期记忆而言,是跨会话的。是可以被保存持久化为文件的。长期记忆中应该需要存储agent中共享的重要数据。如果管理长期会话是怎么把智能体用好的一个核心内容。我们需要把agent中对任务完成起核心作用的内容存起来。比如核心宪章,功能分块等。

上下文工程

背景

在 LLM 应用开发中,Agent 的可靠性是从原型走向生产的关键瓶颈。而上下文工程作为 AI 工程师的核心工作,正是通过在合适的时机、以合适的格式提供正确的信息和工具,让 LLM 精准完成任务的核心技术。上下文工程是单独一块非常大的内容,下面主要总结langchain在上下文中的应用

Agent 失效的原因

Agent 在实际使用中失灵,本质上源于两个核心原因:一是底层 LLM 的能力边界不足,二是未向 LLM 传递 “正确的上下文”。实践表明,后者是导致 Agent 不可靠的首要因素 —— 即便选用高性能模型,缺乏精准的上下文支撑,也会出现行动偏差、任务中断等问题。

Agent 循环与上下文的三层架构

Agent 的核心循环

Agent 的工作流程由两个关键步骤循环构成,直到 LLM 判定任务完成:

  1. 模型调用:向 LLM 传递提示词、可用工具等信息,返回响应结果或工具执行请求
  2. 工具执行:执行 LLM 指定的工具,将运行结果反馈给模型

上下文的三层核心类型

  • 补充定义
    • 临时上下文(Transient):LLM 单轮调用可见,修改消息 / 工具不影响状态存储。

    • 持久上下文(Persistent):跨轮保存至状态,生命周期钩子、工具写入会永久修改。

上下文分为瞬时和持久两种属性,具体包含三个核心层面,共同决定 Agent 的运行表现:

  • 模型上下文(瞬时):控制模型调用的输入内容,包括指令、对话历史、工具集、响应格式等,仅对单次调用有效
  • 工具上下文(持久):定义工具可访问和生成的数据,涉及状态读写、存储交互等,跨调用长期有效
  • 生命周期上下文(持久):管控模型调用与工具执行之间的流程,如摘要生成、安全护栏、日志记录等,变更会永久保存到状态中

上下文数据来源

Agent 在运行过程中,需通过三类数据来源获取信息,为上下文提供基础支撑:

  • 运行时上下文(Runtime Context):作为静态配置,属于会话级范围,包含用户 ID、API 密钥、数据库连接、权限设置等固定信息
  • 状态(State):相当于短期记忆,仅限当前会话使用,存储当前消息、上传文件、认证状态、工具执行结果等临时数据
  • 存储(Store):作为长期记忆,跨会话共享,记录用户偏好、提取的洞察、历史数据等持久化信息

最佳实践

  1. 从简开始:先使用静态 prompt 和工具,仅在必要时添加动态逻辑。
  2. 增量测试:每次仅新增一个上下文工程功能,避免复杂度叠加。
  3. 监控性能:追踪模型调用次数、token 使用量、延迟等指标。
  4. 用内置中间件:优先使用 LangChain 提供的SummarizationMiddlewareLLMToolSelectorMiddleware等,减少自定义成本。
  5. 文档化策略:明确传递的上下文内容及原因,便于维护。
  6. 区分临时 / 持久:模型上下文修改为临时(单轮),生命周期上下文修改为持久(跨轮)。

各上下文的详细配置与实践

系统提示(System Prompt)

  • 作用:定义 LLM 的行为和能力,需根据用户、场景、会话阶段动态调整。
  • 动态配置:结合 State(如消息数量)、Store(用户偏好)、Runtime Context(配置)生成,示例:会话消息数 > 10 时,提示 LLM “保持简洁”。
@dynamic_prompt
def state_aware_prompt(request: ModelRequest) -> str:
    """根据会话状态动态生成系统提示词"""
    message_count = len(request.messages)
    base_prompt = "你是一个高效的智能助手,根据用户需求提供精准响应。"

    # 长对话优化:超过10条消息时提示简洁
    if message_count > 10:
        base_prompt += "\n当前是长对话,请保持回答简洁明了,避免冗余。"

    # 认证状态提示:已认证用户提供个性化服务
    is_authenticated = request.state.get("authenticated", False)
    if is_authenticated:
        base_prompt += "\n用户已认证,可提供私有数据查询和高级功能服务。"

    return base_prompt

消息(Messages)

  • 作用:构成传入 LLM 的对话历史,需确保 LLM 获取关键信息。

  • 配置方式:

    • 临时更新:用wrap_model_call注入信息(如上传文件元数据),不修改状态;

    • 持久更新:用before_model/after_model钩子修改状态,永久更新对话历史。

  • 示例:从 State 读取上传文件,生成 “文件上下文” 并注入消息列表。

@wrap_model_call
def inject_context(request: ModelRequest, handler: Callable[[ModelRequest], ModelResponse]) -> ModelResponse:
    """注入会话相关上下文(如上传文件、用户信息)"""
    # 1. 注入上传文件上下文(从state读取)
    uploaded_files = request.state.get("uploaded_files", [])
    if uploaded_files:
        file_context = "本次会话可访问的文件:\n"
        for file in uploaded_files:
            file_context += f"- {file['name']}{file['type']}):{file['summary']}\n"
        file_context += "回答问题时请参考上述文件内容(如需)。"

        # 注入到消息列表(瞬时更新,不修改state)
        messages = [
            *request.messages,
            {"role": "system", "content": file_context}
        ]
        request = request.override(messages=messages)

    # 2. 注入用户基础信息(从runtime context读取)
    user_info = request.runtime.get("user_info", {})
    if user_info.get("username"):
        user_context = f"当前用户:{user_info['username']},用户偏好:{user_info.get('preference', '默认')}"
        messages = [
            *request.messages,
            {"role": "system", "content": user_context}
        ]
        request = request.override(messages=messages)

    return handler(request)

工具(Tools)

  • ① 定义工具:需明确名称、描述、参数名、参数描述(非元数据,指导 LLM 使用),示例用@tool装饰器定义search_orders 工具(参数:user_id、status、limit,默认 10)。
  • ② 选择工具:动态筛选工具以避免 “上下文过载” 或 “能力不足”,示例:未认证用户仅显示public_前缀工具,会话前 5 轮隐藏 advanced_search工具。
@wrap_model_call
def inject_context(request: ModelRequest, handler: Callable[[ModelRequest], ModelResponse]) -> ModelResponse:
    """注入会话相关上下文(如上传文件、用户信息)"""
    # 1. 注入上传文件上下文(从state读取)
    uploaded_files = request.state.get("uploaded_files", [])
    if uploaded_files:
        file_context = "本次会话可访问的文件:\n"
        for file in uploaded_files:
            file_context += f"- {file['name']}{file['type']}):{file['summary']}\n"
        file_context += "回答问题时请参考上述文件内容(如需)。"

        # 注入到消息列表(瞬时更新,不修改state)
        messages = [
            *request.messages,
            {"role": "system", "content": file_context}
        ]
        request = request.override(messages=messages)

    # 2. 注入用户基础信息(从runtime context读取)
    user_info = request.runtime.get("user_info", {})
    if user_info.get("username"):
        user_context = f"当前用户:{user_info['username']},用户偏好:{user_info.get('preference', '默认')}"
        messages = [
            *request.messages,
            {"role": "system", "content": user_context}
        ]
        request = request.override(messages=messages)

    return handler(request)

模型(Model)

  • 策略:按任务需求动态切换模型(考虑能力、成本、上下文窗口),示例:
    • 消息数 > 20(长会话):用 claude-sonnet-4-5-20250929(大上下文窗口);

    • 10 < 消息数≤20(中会话):用 gpt-4o;

    • 消息数≤10(短会话):用 gpt-4o-mini(高效低成本)。

@wrap_model_call
def dynamic_model_selector(request: ModelRequest, handler: Callable[[ModelRequest], ModelResponse]) -> ModelResponse:
    """根据会话长度动态切换模型"""
    message_count = len(request.messages)

    # 短对话(<10条):使用高效模型(低成本)
    if message_count < 10:
        model = efficient_model
    # 中对话(10-20条):使用标准模型
    elif 10 <= message_count <= 20:
        model = standard_model
    # 长对话(>20条):使用大上下文模型
    else:
        model = large_model

    request = request.override(model=model)
    return handler(request)

响应格式(Response Format)

  • 作用:将 LLM 输出转为结构化数据(适用于下游系统或特定字段提取)。
  • ① 定义格式:用 Pydantic 模型定义 schema(含字段名、类型、描述),示例CustomerSupportTicket (字段:category、priority、summary、customer_sentiment)。
  • ② 选择格式:动态切换,示例:会话前 3 轮用SimpleResponse(仅 answer 字段),3 轮后用DetailedResponse(含 answer、reasoning、confidence(0-1))。
# 定义响应格式Schema
class SimpleResponse(BaseModel):
    """会话初期的简单响应格式"""
    answer: str = Field(description="简洁明了的回答,不超过3句话")


class DetailedResponse(BaseModel):
    """会话后期的详细响应格式"""
    answer: str = Field(description="详细完整的回答")
    reasoning: str = Field(description="回答的推理过程")
    confidence: float = Field(description="回答的置信度(0-1之间)")
    tool_used: list = Field(description="使用的工具列表(无则为空)")


@wrap_model_call
def dynamic_response_formatter(request: ModelRequest,
                               handler: Callable[[ModelRequest], ModelResponse]) -> ModelResponse:
    """根据会话阶段动态切换响应格式"""
    message_count = len(request.messages)

    # 初期对话(<3条):使用简单格式
    if message_count < 3:
        request = request.override(response_format=SimpleResponse)
    # 后期对话:使用详细格式
    else:
        request = request.override(response_format=DetailedResponse)

    return handler(request)

工具上下文(Tool Context):控制工具的读写能力

工具兼具 “读取上下文” 和 “写入上下文” 的能力,支撑 Agent 与外部系统交互:

  1. 读(Reads) :工具从数据源获取信息,示例:check_authentication工具从 State 读取 “authenticated” 字段,判断用户是否认证。
@dataclass
class Context:
    user_id: str
    api_key: str
    db_connection: str


@tool
def fetch_user_data(
        query: str,
        runtime: ToolRuntime[Context]
) -> str:
    """Fetch data using Runtime Context configuration."""
    # Read from Runtime Context: get API key and DB connection
    user_id = runtime.context.user_id
    api_key = runtime.context.api_key
    db_connection = runtime.context.db_connection

    # Use configuration to fetch data
    results = perform_database_query(db_connection, query, api_key)

    return f"Found {len(results)} results for user {user_id}"
  1. 写(Writes) :工具通过Command更新状态,永久保存信息,示例:authenticate_user工具验证密码后,用Command将 State 的 “authenticated” 设为 True/False。
@tool
def authenticate_user(
        password: str,
        runtime: ToolRuntime
) -> Command:
    """Authenticate user and update State."""
    # Perform authentication (simplified)
    if password == "correct":
        # Write to State: mark as authenticated using Command
        return Command(
            update={"authenticated": True},
        )
    else:
        return Command(update={"authenticated": False})

生命周期上下文(Life-cycle Context):控制模型与工具调用间的操作

通过中间件管控 “模型调用与工具执行之间的流程”,典型场景为对话总结


典型应用:SummarizationMiddleware

  • 触发条件:会话 token 超过4000(可配置max_tokens_before_summary=4000)。

  • 执行逻辑:

    • 用独立 LLM 总结旧消息;

    • 在 State 中用总结替换旧消息(永久更新);

    • 保留最近20 条消息(messages_to_keep=20)。

  • 效果:未来轮次仅可见总结,减少 token 消耗。

from langchain.agents import create_agent
from langchain.agents.middleware import SummarizationMiddleware

agent = create_agent(
    model="gpt-4o",
    tools=[...],
    middleware=[
        SummarizationMiddleware(
            model="gpt-4o-mini",
            max_tokens_before_summary=4000,  # Trigger summarization at 4000 tokens
            messages_to_keep=20,  # Keep last 20 messages after summary
        ),
    ],
)

小结

我理解上下文工程应该是最近几年AI工程最有价值的一个方面。前几年我们与模型只是对话式的交流,所以产生了提示词工程,并且在当时的场景只需要提示词工程。但是今年作为Agent元年,除了要定义合适的提示词,工具,模型本身知识以及能力的提升外,AI工程师的技术发展也都将围绕着提示词工程,我需要将提示词工程的理论与实践会单独作为一块内容去总结。