背景
对应于langchain Advance usage这部分。其中 Gudrail,HITL(hunman-in-the-loop),可以直接在核心组件中获取到详细知识。Retrieval单独在langchain Rag的笔记中记录
Runtime
核心概述
-
底层依赖:LangChain 中通过
create_agent创建的代理,其运行依赖 LangGraph 提供的Runtime对象。 -
核心组成部分:Runtime 包含三类关键信息
-
-
Context: 静态信息(如用户 ID、DB 连接)。作用:为工具和中间件提供依赖注入,替代硬编码或全局状态,提升灵活性
-
BaseStore 实例:用于存储代理的长期内存数据,支持数据的读写操作
-
流对象: 通过
"custom"流模式,实现信息的流式传输(如工具执行进度、实时更新)
-
-
核心价值:通过依赖注入机制,避免硬编码或全局状态,使工具更易测试、可复用且灵活。
使用
模拟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查询功能封装为一个工具
工作流程
- 控制智能体接收输入,判断需要调用哪个工具(子智能体)。
- 工具智能体根据控制智能体的指令执行特定任务。
- 工具智能体将执行结果返回给控制智能体。
- 控制智能体决定下一步操作(如调用其他工具、返回结果给用户)或结束流程。
# 子代理保持不变
@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模式,智能体可自主切换活跃状态。
工作流程
- 当前活跃的智能体判断自身无法完成任务,需要其他智能体协助。
- 当前智能体将控制权和状态传递给下一个智能体。
- 新智能体成为活跃智能体,直接与用户交互,直至决定再次移交控制权或完成任务。
实现状态
该模式的具体实现方式暂未发布,后续将补充相关内容。
文档明确标注Implementation (Coming soon) ,即该模式的具体实现方式暂未发布,后续将补充相关内容。虽然它还未实现,但是结合langgraph可以去思考看看:
- 如果langchain来实现,具备调用所有agent则需要维护其他agent的功能描述。
- 并且需要具备主动停止的能力,则每个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 点:
- 分层分类:namespace 作为 “文件夹” 实现记忆的宏观分类(如区分用户数据、系统配置、任务历史),key 作为 “文件名” 实现同一分类下的精准定位,避免数据混乱;
- 高效检索:明确的分层结构让搜索和获取操作仅需定位特定 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 判定任务完成:
- 模型调用:向 LLM 传递提示词、可用工具等信息,返回响应结果或工具执行请求
- 工具执行:执行 LLM 指定的工具,将运行结果反馈给模型
上下文的三层核心类型
- 补充定义:
-
-
临时上下文(Transient):LLM 单轮调用可见,修改消息 / 工具不影响状态存储。
-
持久上下文(Persistent):跨轮保存至状态,生命周期钩子、工具写入会永久修改。
-
上下文分为瞬时和持久两种属性,具体包含三个核心层面,共同决定 Agent 的运行表现:
- 模型上下文(瞬时):控制模型调用的输入内容,包括指令、对话历史、工具集、响应格式等,仅对单次调用有效
- 工具上下文(持久):定义工具可访问和生成的数据,涉及状态读写、存储交互等,跨调用长期有效
- 生命周期上下文(持久):管控模型调用与工具执行之间的流程,如摘要生成、安全护栏、日志记录等,变更会永久保存到状态中
上下文数据来源
Agent 在运行过程中,需通过三类数据来源获取信息,为上下文提供基础支撑:
- 运行时上下文(Runtime Context):作为静态配置,属于会话级范围,包含用户 ID、API 密钥、数据库连接、权限设置等固定信息
- 状态(State):相当于短期记忆,仅限当前会话使用,存储当前消息、上传文件、认证状态、工具执行结果等临时数据
- 存储(Store):作为长期记忆,跨会话共享,记录用户偏好、提取的洞察、历史数据等持久化信息
最佳实践
- 从简开始:先使用静态 prompt 和工具,仅在必要时添加动态逻辑。
- 增量测试:每次仅新增一个上下文工程功能,避免复杂度叠加。
- 监控性能:追踪模型调用次数、token 使用量、延迟等指标。
- 用内置中间件:优先使用 LangChain 提供的
SummarizationMiddleware、LLMToolSelectorMiddleware等,减少自定义成本。 - 文档化策略:明确传递的上下文内容及原因,便于维护。
- 区分临时 / 持久:模型上下文修改为临时(单轮),生命周期上下文修改为持久(跨轮)。
各上下文的详细配置与实践
系统提示(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 与外部系统交互:
- 读(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}"
- 写(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工程师的技术发展也都将围绕着提示词工程,我需要将提示词工程的理论与实践会单独作为一块内容去总结。