LangChain v1 Agent 开发实战:从框架演进到上下文与记忆
大语言模型最初更像一个只负责生成文本的接口:输入提示词,模型返回答案。随着应用需求从"回答问题"走向"完成任务",模型还需要查询外部信息、调用业务接口、保存对话状态,并根据执行结果调整下一步行动。Agent 正是在这一背景下成为 LLM 应用的重要形态。
LangChain v1 对 Agent 开发方式进行了明显收敛:开发者使用统一的 create_agent 入口组织模型、工具和提示词,底层则由 LangGraph 提供状态管理、持久化、流式输出和人工审批等能力。这种分层让简单应用保持简洁,也为复杂应用保留了扩展空间。
本文将从 LangChain 的演进逻辑出发,说明 Agent 的运行原理、Agent 与 Graph 的选型方式,并通过两个示例展示如何逐步构建具备上下文、结构化输出和记忆能力的 Agent。
从"链式调用"到 Agent 应用
LangChain 最早提供的核心能力可以概括为两部分:对不同大语言模型的统一抽象,以及将多个步骤串联起来的 Chain。以 RAG 为例,系统通常先检索相关资料,再将检索结果交给模型生成答案。这类任务的执行路径由开发者预先确定,适合流程稳定、输入输出边界清晰的场景。
随后,ReAct 思想被引入通用 Agent。模型不再只是流程中的一个处理节点,而是能够决定调用哪个工具、传入什么参数,以及是否需要根据工具结果继续行动。模型提供商陆续推出原生函数调用能力后,工具调用也从解析模型生成的 JSON,逐渐转向更可靠的结构化协议。
当 Agent 开始进入生产环境,仅有工具调用还不够。开发者还需要解决运行轨迹追踪、效果评估、状态持久化、流式响应、人工审批和故障恢复等问题。LangGraph 因此承担起底层编排职责,而 LangChain 则继续提供更易用的高层开发接口。
LangChain v1 的整体方向可以概括为三个关键词:
- 统一 :使用
create_agent作为构建 Agent 的标准入口。 - 精简:核心命名空间集中保留模型、消息、工具和 Agent 等基础模块。
- 可扩展:通过 LangGraph 和中间件支持持久化、流式输出、状态管理与上下文工程。
对于仍依赖旧版 Chain、检索器、索引接口或 Hub 模块的项目,可以使用 langchain-classic 作为兼容方案;新项目则更适合围绕 v1 的 Agent 接口组织代码。
LangChain v1 带来了什么
统一的 Agent 构建入口
create_agent 将模型、工具、系统提示词、上下文、响应格式和记忆组件汇集到一个入口。它的底层运行逻辑并不神秘:
text
接收用户消息
↓
调用模型进行判断
↓
模型是否请求调用工具?
├─ 是:执行工具 → 将结果加入消息 → 再次调用模型
└─ 否:返回最终回答
这个循环为简单 Agent 提供了足够清晰的心智模型。开发者负责提供能力边界,模型负责在边界内做决策。
基于 LangGraph 的运行能力
通过 LangGraph,create_agent 创建的 Agent 可以获得多项工程能力:
- 使用检查点保存对话状态,使会话能够持续进行;
- 流式返回模型生成内容、工具调用和执行进度;
- 在转账、删除、发送消息等敏感操作前暂停,等待人工确认;
- 回到已有状态并从新的输入继续执行,用于调试或探索其他路径。
这意味着开发者可以先用高层 API 快速完成业务,再按需要逐步引入更细粒度的控制,而不必一开始就手写完整状态图。
跨模型的标准消息内容
不同模型提供商可能返回文本、推理内容、工具调用、引用或多模态数据。v1 使用 content_blocks 提供统一访问方式,让上层业务尽量少依赖某一家模型的原始响应结构。
python
response = model.invoke("What's the capital of France?")
for block in response.content_blocks:
if block["type"] == "reasoning":
print("Reasoning:", block["reasoning"])
elif block["type"] == "text":
print("Answer:", block["text"])
elif block["type"] == "tool_call":
print("Tool:", block["name"], block["args"])
统一内容格式的价值不只在于代码更短,更重要的是减少模型切换和多模型协同时的适配成本。
Agent 的核心:推理、行动与反馈
一个可执行任务的 Agent 通常由三部分组成:
text
Agent = LLM 推理引擎 + Tools 执行能力 + 自主决策循环
LLM 理解用户意图并决定下一步,Tool 负责读取数据或改变外部系统,控制循环则把工具返回结果重新交给模型,直到任务完成或达到迭代上限。
这一过程常用 ReAct 来描述,即 Reasoning、Acting 和 Observation 的循环。以"上海明天天气如何"为例,Agent 的内部工作过程可以抽象为:
- 判断问题需要实时天气数据,现有上下文不足以直接回答。
- 调用天气工具,并传入城市和日期参数。
- 读取工具返回的天气情况。
- 判断信息是否足够;如果足够,则组织最终答案。
- 如果工具报告参数错误,则分析原因、修正参数并重新调用。
与固定流程相比,ReAct 的优势在于它能够根据反馈调整行动。不过,自主性也会带来不确定性,因此生产系统仍然需要明确的工具描述、参数校验、迭代上限、超时策略和敏感操作审批。
Agent 与 Graph 应该怎么选
Agent 和 Graph 不是互相替代的两条路线,它们解决的是不同问题。
| 对比维度 | Agent | Graph |
|---|---|---|
| 核心特征 | 自主决策 | 确定流程 |
| 执行路径 | 运行时由模型动态选择 | 开发时预先定义 |
| 典型入口 | 对话、自然语言任务 | 按钮、API、定时任务 |
| 适合场景 | 开放式问答、多步骤问题解决 | 转录、审核、生成报表等固定流程 |
| 主要风险 | 行为不确定、成本难预测 | 灵活性有限、分支需要显式维护 |
可以用三个问题完成初步选型:
- 任务路径完全确定吗?如果确定,优先使用 Graph。
- 任务需要多轮对话和动态决策吗?如果需要,使用 Agent。
- 整体目标开放,但其中包含必须稳定执行的子流程吗?使用 Agent 与 Graph 的组合。
例如,"生成上周销售周报"可以由 Agent 理解用户意图和时间范围,再调用一个固定的 Graph。Graph 内部依次完成拉取数据、分析指标、生成图表和排版输出。这样既保留了自然语言交互的灵活性,又保证关键业务流程具有可预测性。
两者通常有两种组合方式:把 Agent 作为 Graph 中的决策节点,或者把完整 Graph 封装成 Tool 交给 Agent 调用。实践中,后者很适合复用已经稳定运行的业务流程。
构建一个最小可用 Agent
先安装核心包和对应的模型集成:
bash
pip install -U langchain langgraph langchain-openai
同时按照模型提供商的要求配置 API Key。下面使用一个模拟天气工具展示最小结构:
python
from langchain.agents import create_agent
from langchain.tools import tool
@tool
def get_weather_for_location(city: str) -> str:
"""获取指定城市的天气信息。"""
return f"{city}今天晴朗。"
agent = create_agent(
model="gpt-5-mini",
tools=[get_weather_for_location],
system_prompt="你是一位乐于助人的天气助手。",
)
response = agent.invoke(
{
"messages": [
{"role": "user", "content": "北京的天气如何?"}
]
}
)
print(response["messages"][-1].content)
这个示例虽然很小,却包含了 Agent 的三个基本构件:
model是决策核心;tools定义可以执行的外部能力;system_prompt规定角色、目标和行为边界。
运行时,模型先判断是否需要工具。如果需要,它会生成结构化工具调用;框架执行工具并把结果写回消息列表;模型读取结果后再生成最终回答。整个过程中,Agent 状态会维护完整的消息轨迹。
需要注意的是,示例工具返回的是模拟数据。真实项目应在工具内部调用可靠的数据源,并补充超时、异常处理和返回值校验。
加入上下文、结构化输出与记忆
真实应用往往还需要回答三个问题:当前用户是谁、输出应该遵循什么格式、系统应该记住哪些信息。下面在基础示例上加入这些能力。
python
from dataclasses import dataclass
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain.tools import ToolRuntime, tool
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.store.memory import InMemoryStore
SYSTEM_PROMPT = """你是一位天气助手。
你可以使用以下工具:
- get_weather_for_location:查询指定城市的天气
- get_user_location:根据当前用户获取所在城市
当用户询问天气却没有说明地点时,先调用 get_user_location,
再调用 get_weather_for_location。不要猜测用户位置。
"""
@dataclass
class Context:
user_id: str
@dataclass
class WeatherResponse:
answer: str
weather_conditions: str | None = None
@tool
def get_weather_for_location(city: str) -> str:
"""获取指定城市的天气信息。"""
return f"{city}今天晴朗。"
@tool
def get_user_location(runtime: ToolRuntime[Context]) -> str:
"""根据当前用户 ID 获取所在城市。"""
user_id = runtime.context.user_id
store = runtime.store
profile = store.get(("users",), user_id)
if profile is None:
default_city = "北京" if user_id == "1" else "上海"
store.put(("users",), user_id, {"city": default_city})
return default_city
return profile.value["city"]
model = init_chat_model("gpt-5-mini", temperature=0)
checkpointer = InMemorySaver()
store = InMemoryStore()
agent = create_agent(
model=model,
name="weather_agent",
system_prompt=SYSTEM_PROMPT,
tools=[get_user_location, get_weather_for_location],
context_schema=Context,
response_format=WeatherResponse,
checkpointer=checkpointer,
store=store,
)
config = {"configurable": {"thread_id": "conversation-001"}}
response = agent.invoke(
{
"messages": [
{"role": "user", "content": "外面的天气怎么样?"}
]
},
config=config,
context=Context(user_id="1"),
)
print(response["structured_response"])
这段代码中有几个容易混淆但非常重要的概念。
运行时上下文
Context 保存本次调用需要使用、但不应该混入自然语言消息的数据,例如用户 ID、租户 ID、权限信息或请求来源。工具通过 ToolRuntime[Context] 读取这些信息,不需要让模型生成或转述它们。
这种设计比把用户 ID 拼进提示词更可靠,也更容易进行权限控制。
结构化输出
WeatherResponse 规定最终结果必须包含 answer,并允许附带 weather_conditions。业务代码可以直接读取字段,而不必再次解析自然语言。
结构化输出适合 API 返回、前端卡片渲染、数据入库以及后续自动化流程。字段设计应只包含业务真正需要的数据,避免为了"看起来完整"而堆积大量可选字段。
短期记忆
checkpointer 按 thread_id 保存同一段对话的状态。继续使用相同的 thread_id 调用 Agent,系统就能恢复之前的消息,并理解"刚才那个城市""继续查询明天"等依赖会话历史的表达。
短期记忆的边界是会话。不同对话应使用不同的 thread_id,否则互不相关的消息可能被错误地串联起来。
长期记忆
store 保存跨会话复用的用户信息。示例按 user_id 存储城市,新的对话即使使用不同 thread_id,仍然可以读取该用户的资料。
长期记忆不应等同于"保存所有对话"。更合理的做法是只沉淀稳定且有业务价值的信息,例如用户偏好、常用地点或已确认的配置,并为更新、过期和删除设计明确规则。
从示例走向生产
一个能运行的 Agent 与一个可靠的 Agent 之间,通常还隔着若干工程约束。
工具要小而清晰
工具名称、参数类型和文档字符串都会影响模型的选择。一个工具最好只完成一种明确操作。对于复杂的确定性流程,可以先封装为 Graph,再作为单个 Tool 暴露给 Agent。
提示词要描述边界
系统提示词不仅要告诉模型"做什么",还要说明"何时调用哪个工具""缺少信息时怎么办"以及"哪些事情不能自行假设"。规则应具体、可验证,避免只有角色描述而没有行为约束。
记忆要按用途分层
会话消息放入检查点,跨会话资料放入 Store,临时请求信息放入 Context。三类数据用途不同,混在一起会增加上下文长度,也容易造成隐私和状态污染问题。
固定流程不要强行交给模型
身份校验、扣款、审批、归档等关键业务步骤通常需要确定性。让 Agent 负责理解意图和选择能力,让 Graph 或普通代码负责稳定执行,是更容易测试和审计的架构。
必须设置失败边界
工具调用可能超时,参数可能无效,模型也可能重复尝试。生产环境应设置最大迭代次数、超时时间、错误重试策略和降级方案。对有外部影响的操作,还应加入人工确认或明确授权。
总结
LangChain v1 将 Agent 开发收敛到更清晰的分层结构:create_agent 提供简洁入口,模型负责推理与决策,工具提供执行能力,LangGraph 承担状态和运行时编排。开发者可以从一个只有模型、工具和提示词的最小 Agent 开始,再逐步加入上下文、结构化输出、短期记忆与长期记忆。
真正重要的不是把所有流程都改造成 Agent,而是划清自主决策与确定执行的边界:开放式任务交给 Agent,固定步骤交给 Graph,复杂应用则让两者协同。沿着这一思路构建,既能发挥大语言模型的灵活性,也能保留业务系统需要的可靠性与可维护性。