深度剖析 LangChain Agent:从核心流式模式到生产级最佳实践
前言(本文为个人学习笔记与实践总结,如有错误或疏漏,欢迎指正交流-第二次书写博客记录)
AI时代!强烈建议初学者不要光看,一定要动手敲一遍,你不敲不知道,一敲吓一跳,原来自己傻傻的啥都不懂!一敲代码,问题就会像是龙卷风一样袭来!!!所以这是个非常重要的习惯!把代码实现一遍你会踩过数以万计之前不曾注意的坑!进而学习到更多的知识!加油!💪本人完整的代码笔记在文章末尾。
本文是一份生产环境视角下的深度笔记 。我们将聚焦于 LangGraph Agent 的核心机制,特别是
stream_mode的多种流式模式,并深入探讨模型控制、中间件、结构化输出、消息系统架构以及 Anthropic 的提示词缓存等高级特性。
阅读建议 :本文假设你已了解 LangChain 的基础概念。如果这是你第一次接触 Agent,建议先阅读官方/中文文档的基础教程:langchain-doc.cn/v1/python/l...
本章内容是按照官方中文文档基础教程的顺序来学习。
内容按依照如下链接内容:
langchain-doc.cn/v1/python/l...
即官方文档-智能体章节的部分内容;欢迎阅读官方文档。
1. 核心流式模式详解
LangGraph Agent 提供了多种流式模式,让你能根据不同场景(展示思考过程、实现打字机效果、自定义进度条)灵活选择。
1.1 stream_mode="updates":按节点流式传输
模式说明 :该模式在每个图节点 (如 Agent 的推理步骤、工具调用节点)完成后发出一个事件。它非常适合向用户展示 Agent 的"思考过程",例如"正在查询天气..."、"正在计算..."。
核心特点:
- 触发时机:每个节点执行完毕后
- 返回内容:该节点的完整输出(如 AI 的思考结果、工具的执行结果)
- 典型用途:展示 Agent 的执行步骤,让用户了解当前进度
基础示例:创建 Agent 并使用 updates 模式
python
from langchain.agents import create_agent
from langchain.tools import tool
from langchain_openai import ChatOpenAI
# 定义一个简单的天气查询工具
@tool
def get_weather(city: str) -> str:
"""查询指定城市的天气"""
return f"{city}:晴天,24°C"
# 创建 Agent
agent = create_agent(
model="deepseek-chat", # 也可替换为 "openai:gpt-4o"
tools=[get_weather],
system_prompt="你是一个天气助手",
)
# 准备用户输入
user_input = {"messages": [{"role": "user", "content": "北京的天气如何"}]}
# 使用 updates 模式流式执行
for chunk in agent.stream(user_input, stream_mode="updates"):
print(f"收到节点输出: {chunk}")
输出示例(分三次输出):
python
# 第1个 chunk:agent 节点决定调用工具
{
"agent": {
"messages": [
AIMessage(
content="",
tool_calls=[{"name": "get_weather", "args": {"city": "北京"}, "id": "call_001"}]
)
]
}
}
# 第2个 chunk:tools 节点执行工具并返回结果
{
"tools": {
"messages": [
ToolMessage(
content="北京:晴天,24°C",
name="get_weather",
tool_call_id="call_001"
)
]
}
}
# 第3个 chunk:agent 节点根据工具结果生成最终回答
{
"agent": {
"messages": [
AIMessage(content="北京今天天气晴朗,温度24°C")
]
}
}
实际开发中的典型处理模式
python
for chunk in agent.stream(user_input, stream_mode="updates"):
for node_name, node_output in chunk.items():
print(f"节点 [{node_name}] 执行完成")
# 获取该节点输出的最新消息
last_msg = node_output["messages"][-1] # -1 取最后一个元素
# 判断消息类型并做出不同响应
if hasattr(last_msg, "tool_calls") and last_msg.tool_calls:
# Agent 正在调用工具
print(f"🔧 工具调用: {last_msg.tool_calls}")
elif last_msg.content:
# Agent 输出了文本内容
print(f"💬 内容: {last_msg.content}")
1.2 create_agent() 核心参数深度解析
理解了基础的流式模式后,我们来深入剖析 create_agent() 的核心参数。这些参数是构建生产级 Agent 的关键。
1.2.1 返回对象
create_agent() 返回一个可调用的智能体对象 ,它实际上是 Runnable 接口的实现。这个对象有以下主要方法:
agent.invoke(input_dict)- 同步调用,返回完整结果agent.stream(input_dict, stream_mode=...)- 流式调用,返回生成器agent.batch(input_list)- 批量调用,处理多个输入
输入格式 input_dict:
python
{
"messages": [
{"role": "user", "content": "问题内容"},
# 可选:可以传入多轮对话历史
{"role": "assistant", "content": "上一轮的回答"},
{"role": "user", "content": "追问的问题"}
]
}
1.2.2 model:不仅仅是"选哪个模型"
为什么重要 :模型是 Agent 的"大脑",但生产环境中你需要的不只是模型名称,而是对模型行为的精确控制。
python
# ❌ 不推荐生产环境:无法控制模型行为
agent_bad = create_agent(
model="openai:gpt-4o", # 内部使用默认 temperature=0.7, timeout=60
tools=[]
)
# ✅ 生产环境推荐:精确控制模型实例
from langchain_openai import ChatOpenAI
model = ChatOpenAI(
model="gpt-4o",
temperature=0.1, # 降低随机性,提高确定性输出
max_tokens=2048, # 控制输出长度,管理成本
timeout=30, # 防止单次调用阻塞整个 Agent
max_retries=2, # 网络抖动时的自动重试
model_kwargs={"seed": 42} # 某些提供商支持确定性输出
)
agent_good = create_agent(
model=model, # 传入的是实例,内部直接使用,不再重新初始化
tools=[]
)
执行机制 :
agent.invoke()内部调用model.invoke(),若模型调用失败,会触发max_retries=2的重试逻辑。重试仍失败后,异常向上抛出,可被middleware捕获处理。
1.2.3 system_prompt:从静态文本到动态上下文
为什么重要:系统提示是控制 Agent 行为的核心手段。生产环境中,系统提示通常需要根据用户身份、对话阶段动态变化。
python
from langchain.messages import SystemMessage
# 场景:构建一个支持提示词缓存的 Agent(Anthropic 特有)
# 将经常重复使用的长文本(如公司政策)标记为缓存,可降低 90% 成本
cached_system_prompt = SystemMessage(
content=[
{
"type": "text",
"text": "你是某电商平台的客服助手。以下是完整的退换货政策:\n" +
"1. 7天无理由退货,运费由买家承担\n" +
"2. 质量问题15天内换货,运费由卖家承担\n" +
"3. 保修期内免费维修,需提供购买凭证\n" +
"(共3000字政策全文)"
},
{
"type": "text",
"text": "请基于上述政策回答用户问题。",
"cache_control": {"type": "ephemeral"} # 标记这段内容可缓存
}
]
)
agent_with_cache = create_agent(
model="anthropic:claude-sonnet-4-5", # 仅 Anthropic 模型支持缓存
tools=[],
system_prompt=cached_system_prompt
)
执行效果:
- 第1次调用:完整发送系统提示,Claude 返回时告知哪些内容被缓存
- 第2次调用(相同会话上下文):只发送缓存引用,成本降低约 90%
1.2.4 middleware:Agent 的"中间件层"
内部机制 :中间件在 Agent 执行的各个阶段(模型调用前、工具调用前、模型调用后)插入钩子。多个中间件按列表顺序执行,可以修改 request 或 state。
模式1:动态模型选择(成本优化)
python
from langchain.agents.middleware import wrap_model_call, ModelRequest, ModelResponse
from langchain_openai import ChatOpenAI
basic_model = ChatOpenAI(model="gpt-4o-mini", temperature=0.1)
advanced_model = ChatOpenAI(model="gpt-4o", temperature=0.1)
@wrap_model_call
def dynamic_model_selector(request: ModelRequest, handler) -> ModelResponse:
"""
根据对话复杂度动态选择模型
执行流程:
1. 拦截模型调用请求
2. 分析当前 state 中的消息数量和工具调用次数
3. 决定使用哪个模型
4. 将修改后的请求传给 handler
"""
# 假设的复杂度判断逻辑
# if is_complex(request.state):
# model = advanced_model
# else:
# model = basic_model
# return handler(request.override(model=model))
return handler(request) # 占位,实际使用时需实现逻辑
agent_dynamic = create_agent(
model=basic_model, # 默认模型
tools=[],
middleware=[dynamic_model_selector]
)
模式2:工具错误处理(系统健壮性)
python
from langchain.agents.middleware import wrap_tool_call
from langchain.messages import ToolMessage
@wrap_tool_call
def graceful_tool_error_handler(request, handler):
"""
拦截工具执行,将异常转换为友好的 ToolMessage
为什么重要:默认情况下,工具异常会导致 Agent 执行中断
通过中间件,可以让 Agent 继续执行,并向 LLM 提供错误信息
"""
try:
return handler(request)
except Exception as e:
# 返回友好的错误消息,而非抛出异常
return ToolMessage(
content=f"工具 '{request.tool_name}' 执行失败:{str(e)}",
tool_call_id=request.tool_call_id
)
agent_resilient = create_agent(
model=model,
tools=[], # 这些工具可能因网络/权限失败
middleware=[graceful_tool_error_handler]
)
数据流转:
- Agent 调用
database_query_tool(sql="SELECT * FROM users") - 数据库连接超时 → 抛出
ConnectionError - 中间件捕获异常 → 返回
ToolMessage("工具执行失败:连接超时...") - LLM 收到这个
ToolMessage后,可以重试、告知用户或使用其他工具替代 - Agent 不会崩溃,继续执行
模式3:动态系统提示(上下文感知)
python
from langchain.agents.middleware import dynamic_prompt
@dynamic_prompt
def context_aware_system_prompt(request: ModelRequest) -> str:
"""
根据用户上下文动态生成系统提示
执行时机:每次模型调用前
数据来源:invoke() 时传入的 context 参数
"""
# user_role = request.context.get("user_role", "guest")
# if user_role == "admin":
# return "你是管理员助手,可以执行敏感操作。"
# return "你是普通用户助手,只能查询公开信息。"
return "你是通用助手。" # 占位
1.2.5 response_format:结构化输出
为什么重要 :当 Agent 作为上游系统的一部分时,你需要的是可解析的数据,而不是自然语言。例如,一个数据分析 Agent 需要返回 JSON 给前端图表组件。
python
from pydantic import BaseModel
from langchain.agents import create_agent
from langchain.agents.structured_output import ToolStrategy, ProviderStrategy
# 定义输出结构
class OrderInfo(BaseModel):
order_id: str
status: str # pending, shipped, delivered, cancelled
estimated_delivery: str | None
items: list[str]
# 方式1:ToolStrategy(适用于所有支持工具调用的模型)
agent_tool = create_agent(
model="gpt-4o-mini",
response_format=ToolStrategy(OrderInfo)
)
result = agent_tool.invoke({
"messages": [{"role": "user", "content": "帮我查订单 ORD-12345 的状态"}]
})
# result["structured_response"] 是 OrderInfo 实例
print(result["structured_response"].order_id) # "ORD-12345"
print(result["structured_response"].status) # "shipped"
# 方式2:ProviderStrategy(仅支持原生结构化输出的提供商)
agent_provider = create_agent(
model="openai:gpt-4o", # 必须支持原生 structured output
response_format=ProviderStrategy(OrderInfo)
)
ToolStrategy内部机制 :create_agent()内部创建一个名为structured_output_response的工具,强制模型在最终回答时调用它,工具调用参数即为你定义的结构。
2. agent.stream() 返回对象深度剖析
2.1 stream_mode="updates" 完整解析
返回对象结构:
python
# chunk 的类型是 dict,格式为:
{
"node_name_1": { # 节点名称,如 "agent"、"tools"
"messages": [ # 该节点输出的消息列表
AIMessage(...), # 可能是 AI 的思考/回答
# 或
ToolMessage(...), # 工具执行结果
]
}
}
消息类型说明:
| 消息类型 | 说明 | 关键属性 |
|---|---|---|
AIMessage |
AI 的响应消息 | .content(文本内容) .tool_calls(工具调用列表) |
ToolMessage |
工具执行结果 | .content(工具返回结果) .name(工具名称) |
HumanMessage |
用户输入 | .content(用户问题) |
SystemMessage |
系统消息 | .content(系统提示) |
2.2 stream_mode="messages":令牌级流式(打字机效果)
该模式在 LLM 生成每一个令牌(token) 时都发出事件,是实现"打字机效果"的基础。
python
user_input = {"messages": [{"role": "user", "content": "北京今天天气如何?"}]}
for token, metadata in agent.stream(user_input, stream_mode="messages"):
node_name = metadata.get("langgraph_node", "unknown")
# 处理不同类型的 content_blocks
for content_block in token.content_blocks:
block_type = content_block.get("type")
if block_type == "text":
# 普通文本令牌,直接输出(实现打字机效果)
print(content_block.get("text", ""), end="", flush=True)
elif block_type == "tool_call_chunk":
# 工具调用的流式片段,通常不直接展示给用户
tool_name = content_block.get("name")
args_chunk = content_block.get("args", "")
if tool_name:
print(f"\n[调用工具: {tool_name}]", end="")
if args_chunk:
print(args_chunk, end="")
返回值结构拆解:
每次迭代返回一个元组 (token, metadata):
| 元素 | 类型 | 关键属性 | 说明 |
|---|---|---|---|
token |
AIMessageChunk |
.content_blocks |
文本片段或工具调用片段列表 |
.tool_call_chunks |
工具调用的流式片段 | ||
metadata |
dict |
["langgraph_node"] |
产生该 token 的节点名称("model"/"tools") |
["langgraph_step"] |
执行步骤序号 | ||
["thread_id"] |
会话线程 ID(如果配置了) |
完整的数据流转示例:
python
# 阶段1:工具调用开始(第一个 token)
(
AIMessageChunk(
content="",
tool_call_chunks=[{"name": "get_weather", "args": "", "id": "call_abc123", "index": 0}]
),
{"langgraph_node": "model", "langgraph_step": 1, "thread_id": "session_456"}
)
# 阶段2:工具参数逐步输出(多个 token)
# token 2: '{' → token 3: '"city"' → token 4: ':"北京"' → token 5: '}'
# 阶段3:工具执行节点返回结果
(
ToolMessageChunk(
content="北京:晴天,24°C",
tool_call_id="call_abc123",
name="get_weather"
),
{"langgraph_node": "tools", "langgraph_step": 2, "thread_id": "session_456"}
)
# 阶段4:最终回答文本逐步输出(多个 token)
(
AIMessageChunk(content="北京今天天气晴朗,温度24°C", tool_call_chunks=[]),
{"langgraph_node": "model", "langgraph_step": 3, "thread_id": "session_456"}
)
2.3 stream_mode="custom":自定义进度更新
这是最灵活的模式。你可以在工具函数内部,通过 get_stream_writer() 获取一个写入器,主动向客户端发送任意状态信息。
python
from langgraph.config import get_stream_writer
import time
@tool
def get_weather_with_progress(city: str) -> str:
"""带进度提示的天气查询工具"""
writer = get_stream_writer()
# 发送自定义进度消息
writer(f"⏳ 正在解析城市: {city}...")
time.sleep(0.5)
writer(f"🌐 正在连接天气服务...")
time.sleep(0.5)
writer(f"✅ 成功获取{city}天气数据")
return f"{city}当前天气晴朗,温度24°C"
# 创建使用该工具的 Agent
agent_custom = create_agent(
model="openai:gpt-4o",
tools=[get_weather_with_progress],
system_prompt="你是一个天气助手。"
)
# 使用 custom 模式流式执行
user_input = {"messages": [{"role": "user", "content": "上海今天天气如何?"}]}
for custom_event in agent_custom.stream(user_input, stream_mode="custom"):
# custom_event 就是在工具中 writer() 传入的内容
print(f"[自定义进度] {custom_event}")
输出示例:
ini
[自定义进度] ⏳ 正在解析城市: 上海...
[自定义进度] 🌐 正在连接天气服务...
[自定义进度] ✅ 成功获取上海天气数据
2.4 四种流式模式对比
| 模式 | 每次迭代返回 | 触发时机 | 典型用途 |
|---|---|---|---|
stream_mode="updates" |
dict(节点输出) |
每个节点完成后 | 展示 Agent 执行步骤 |
stream_mode="messages" |
(token, metadata) 元组 |
每个 token 生成时 | 实现打字机效果 |
stream_mode="custom" |
任意值(你传入的) | 调用 writer() 时 |
自定义进度、状态更新 |
stream_mode="values" |
dict(完整状态) |
每次状态变更后 | 调试、保存快照 |
3. 补充知识:消息系统三层架构
LangChain 的消息系统是一个精心设计的三层架构,为开发者提供了统一的接口,同时兼容不同 LLM 提供商的差异。
ini
┌─────────────────────────────────────────────────────────────────┐
│ 第1层:消息类型(Role 层) │
│ 定义了消息的发送者身份,决定消息在对话中的语义角色 │
├─────────────────────────────────────────────────────────────────┤
│ • SystemMessage → role="system" → 设定 AI 行为边界 │
│ • HumanMessage → role="user" → 用户输入 │
│ • AIMessage → role="assistant" → AI 回复 │
│ • ToolMessage → role="tool" → 工具执行结果 │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 第2层:消息结构(Message 层) │
│ 定义消息的组成部分,LangChain 统一不同提供商的差异 │
├─────────────────────────────────────────────────────────────────┤
│ class BaseMessage: │
│ content: str | List[dict] # 第3层决定格式 │
│ additional_kwargs: dict # 提供商特有字段 │
│ response_metadata: dict # 响应元数据 │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 第3层:内容格式(Content Block 层) │
│ 定义 content 字段的内部结构,支持多模态和扩展元数据 │
├─────────────────────────────────────────────────────────────────┤
│ content = [ │
│ {"type": "text", "text": "..."}, # 文本块 │
│ {"type": "image", "url": "..."}, # 图片块 │
│ {"type": "tool_use", ...}, # 工具调用块 │
│ ] │
└─────────────────────────────────────────────────────────────────┘
完整演示
python
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
from langchain_openai import ChatOpenAI
# 步骤1:在 LangChain 中创建消息
system_msg = SystemMessage(
content=[
{"type": "text", "text": "你是一个图像分析助手"},
{"type": "image", "url": "https://example.com/photo.png"}
]
)
human_msg = HumanMessage(content="这张图片里有什么?")
ai_msg = AIMessage(
content="让我分析一下这张图片",
tool_calls=[{"name": "analyze_image", "args": {"image_url": "..."}, "id": "call_123"}]
)
# 步骤2:查看内部存储结构
print(f"SystemMessage.type: {system_msg.type}") # 'system'
print(f"HumanMessage.type: {human_msg.type}") # 'human'
print(f"AIMessage.type: {ai_msg.type}") # 'ai'
场景化使用指南
python
# 场景1:纯文本对话(99% 的日常使用)
system_msg = SystemMessage(content="你是一个助手")
human_msg = HumanMessage(content="你好")
# 场景2:需要多模态输入(图片、音频、文档)
system_msg_multimodal = SystemMessage(
content=[
{"type": "text", "text": "你是文档 OCR 专家"},
{"type": "image", "url": "document_page1.png"}
]
)
# 场景3:需要缓存控制(高频调用,长系统提示)- Anthropic 特有
system_msg_cached = SystemMessage(
content=[
{"type": "text", "text": "3000字系统指令...",
"cache_control": {"type": "ephemeral"}},
{"type": "text", "text": "当前用户消息"}
]
)
4. 补充知识:cache_control 缓存机制深度解析
4.1 为什么需要提示词缓存?
在你的生产 Agent 中,每次调用都传输相同的 3000 字系统提示,会导致:
- 高延迟:每次都要处理 3000 个 token
- 高成本:输入 token 按量计费
- 资源浪费:LLM 重复处理相同内容
一句话总结:缓存让 LLM 记住重复出现的提示前缀,后续调用只传输变化的部分,大幅降低延迟和成本。
4.2 缓存机制流程图
json
┌─────────────────────────────────────────────────────────────────┐
│ 第一次调用(缓存写入) │
├─────────────────────────────────────────────────────────────────┤
│ 请求内容: │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 系统提示(3000字)+ cache_control 标记 │ │
│ │ 用户消息:"分析这份合同" │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Claude API: │ │
│ │ 1. 处理完整提示(耗时) │ │
│ │ 2. 将"系统提示"写入缓存(断点位置 = 标记处) │ │
│ │ 3. 返回响应 + 缓存元数据 │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ 响应中的缓存指标: │
│ {"cache_creation_input_tokens": 3000, ← 写入缓存的 token 数 │
│ "cache_read_input_tokens": 0, ← 命中缓存的 token 数 │
│ "input_tokens": 3020} │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 第二次调用(缓存命中) │
├─────────────────────────────────────────────────────────────────┤
│ 请求内容(相同会话上下文): │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 系统提示(3000字)+ cache_control 标记 ← 命中缓存! │ │
│ │ 用户消息:"换个角度分析..."(只有这 20 字是新内容) │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Claude API: │ │
│ │ 1. 检测到缓存命中 │ │
│ │ 2. 直接从缓存读取系统提示(不重新计算) │ │
│ │ 3. 只处理新的用户消息 │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ 响应中的缓存指标: │
│ {"cache_creation_input_tokens": 0, ← 本次未写入新缓存 │
│ "cache_read_input_tokens": 3000, ← 命中缓存的 token 数 │
│ "input_tokens": 20} ← 实际处理的 token 数 │
└─────────────────────────────────────────────────────────────────┘
4.3 什么是 cache_control?
一句话解释 :cache_control 是一个提示标记,告诉 Anthropic 的 API:"从这里开始往后的内容,请帮我缓存起来,下次调用时别再重新计算了。"
注意:它不是缓存"这条消息",而是缓存从消息开头到这个标记处为止的所有内容。
4.4 使用方式:多个缓存断点(推荐)
python
content = [
# 块1:系统角色(短文本,不缓存也行)
{"type": "text", "text": "你是数据分析助手"},
# 块2:工具定义(静态,建议缓存)
{"type": "text",
"text": "工具1: query_database - 查询数据库\n工具2: analyze_data - 分析数据",
"cache_control": {"type": "ephemeral"}},
# 块3:系统规则(静态,建议缓存)
{"type": "text",
"text": "规则: 1. 优先使用缓存数据\n2. 遵循数据安全规范\n3. 记录所有查询操作",
"cache_control": {"type": "ephemeral"}},
# 块4:用户消息(动态,不设缓存)
{"type": "text", "text": "用户输入"}
]
4.5 成本对比
| 场景 | 输入 Token | 费用(按 $3/M 输入计) |
|---|---|---|
| 无缓存:每次传 3000 字系统提示 + 20 字用户消息 | 3020 | $0.00906 |
| 有缓存:仅传 20 字用户消息 | 20 | $0.00006 |
| 每次调用节省 | - | $0.009(99% 成本降低) |
对于每天 10 万次调用的生产系统:
- 无缓存:$906/天
- 有缓存:$6/天
- 年节省:约 $328,500
5. 补充知识:消息简写格式 ("role", "content")
这个格式是 LangChain 提供的便捷消息创建方式。它让你用元组列表快速构建对话历史,而无需手动实例化各种 Message 类。
适用场景:
- 从数据库加载对话历史(存储为 JSON)
- 从 API 接收用户消息(格式可能是
{role, content}) - 快速测试不同的对话流程
对比演示
python
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
# 冗长的方式
messages_verbose = [
SystemMessage(content="你是一个边塞诗人。"),
HumanMessage(content="写一首唐诗。"),
AIMessage(content="锄禾日当午,汗滴禾下土..."),
HumanMessage(content="按照你上一个回复的格式,再写一首唐诗。")
]
# 简洁的方式(一行搞定!)
messages_concise = [
("system", "你是一个边塞诗人。"),
("human", "写一首唐诗。"),
("ai", "锄禾日当午,汗滴禾下土..."),
("human", "按照你上一个回复的格式,再写一首唐诗。")
]
数据流转过程
python
# 你的输入:简写元组
input_messages = [
("system", "你是一个边塞诗人。"),
("human", "写一首唐诗。"),
("ai", "锄禾日当午..."),
("human", "按照你上一个回复的格式,再写一首唐诗。")
]
# ↓ LangChain 内部转换 ↓ 消息对象
# [
# SystemMessage(content="你是一个边塞诗人。"),
# HumanMessage(content="写一首唐诗。"),
# AIMessage(content="锄禾日当午..."),
# HumanMessage(content="按照你上一个回复的格式,再写一首唐诗。")
# ]
# ↓ 发送给 LLM API(OpenAI 格式)↓ 字典格式
# [
# {"role": "system", "content": "你是一个边塞诗人。"},
# {"role": "user", "content": "写一首唐诗。"},
# {"role": "assistant", "content": "锄禾日当午..."},
# {"role": "user", "content": "按照你上一个回复的格式,再写一首唐诗。"}
# ]
总结:关键 Takeaways
-
选择合适的流式模式:
updates→ 展示 Agent 执行步骤messages→ 实现打字机效果custom→ 自定义进度更新
-
生产环境务必精确控制模型参数 :
temperature、max_retries、timeout等 -
利用 middleware 实现横切关注点:动态模型选择、统一错误处理、上下文感知提示
-
结构化输出让 Agent 可组合 :使用
ToolStrategy或ProviderStrategy -
消息系统三层架构:理解 Role → Message → Content Block 的层次关系
-
提示词缓存(Anthropic)可降低 99% 成本:对于长系统提示的高频场景至关重要
-
简写格式提升代码可读性 :
("role", "content")元组列表
本文为个人学习笔记与实践总结,如有错误或疏漏,欢迎指正交流。
如果觉得本文对你有帮助,欢迎点赞、收藏、转发!
本人该章节的完整学习笔记与实践总结如下:(个人笔记,如有错误或疏漏,欢迎指正交流。)
ini
#核心流式模式详解
# 1. stream_mode="updates":按节点流式传输
# 该模式在每个 图节点(如一个 Agent 的推理步骤、一个工具调用)完成后发出一个事件。
# 它适合向用户展示 Agent 的"思考过程",例如"正在查询天气..."、"正在计算..."。
from langchain.agents import create_agent
from langchain.tools import tool
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
@tool
def get_weather(city:str)->str:
return f"{city}weather"
agent = create_agent(
model="deepseek-chat",
tools=[get_weather],
system_prompt="你是一个人天气助手",
)
# 使用 updates 模式流式执行
# for chunk in agent.stream(user_input, stream_mode="updates"):
# print(f"收到节点输出: {chunk}")
#1.1 create_agent()讲解
"""
返回的对象:
create_agent()返回一个可调用的智能体对象,它实际上是一个Runnable接口的实现。这个对象有以下主要方法:
agent.invoke(input_dict) - 同步调用
agent.stream(input_dict, stream_mode=...) - 流式调用
agent.batch(input_list) - 批量调用
"""
#input_dict:输入字典,必须是特定格式
"""
{
"messages":
[
{"role": "user", "content": "问题内容"}
]
}
"""
#1.11model:不仅仅是"选哪个模型"
#为什么重要:模型是 Agent 的"大脑",但生产环境中你需要的不只是模型名称,而是对模型行为的精确控制。
# ❌ 不推荐生产环境:无法控制模型行为
agent2 = create_agent(
model="openai:gpt-4o", # 内部使用默认 temperature=0.7, timeout=60
tools=[]
)
# ✅ 生产环境推荐:精确控制
model = ChatOpenAI(
model="gpt-4o",
temperature=0.1, # 降低随机性,提高确定性
max_tokens=2048, # 控制输出长度,管理成本
timeout=30, # 防止单次调用阻塞整个 Agent
max_retries=2, # 网络抖动时的自动重试
model_kwargs={"seed": 42} # 某些提供商支持确定性输出
)
agent3 = create_agent(
model=model, # 传入的是实例,内部直接使用,不再重新初始化
tools=[]
)
# 执行时:agent.invoke() 内部调用 model.invoke()
# 如果模型调用失败,会触发 max_retries=2 的重试逻辑
# 重试仍失败后,异常向上抛出,可被 middleware 捕获处理
#1.12 system_prompt:从静态文本到动态上下文
#为什么重要:系统提示是控制 Agent 行为的核心手段。生产环境中,系统提示通常需要根据用户身份、对话阶段动态变化。
from langchain.agents import create_agent
from langchain.messages import SystemMessage
# 场景:构建一个支持提示词缓存的 Agent(Anthropic 特有)
# 将经常重复使用的长文本(如公司政策)标记为缓存,降低 90% 成本
cached_system_prompt = SystemMessage(
content=[
{
"type": "text",
"text": "你是某电商平台的客服助手。以下是完整的退换货政策:\n" +
"1. 7天无理由退货...\n2. 质量问题15天内换货...\n" +
"(共3000字政策全文)"
},
{
"type": "text",
"text": "请基于上述政策回答用户问题。",
"cache_control": {"type": "ephemeral"} # 标记这段内容可缓存
}
]
)
agent4 = create_agent(
model="anthropic:claude-sonnet-4-5",
tools=[],
system_prompt=cached_system_prompt # 直接传 SystemMessage
)
# 执行效果:
# 第1次调用:完整发送系统提示,Claude 返回时告知哪些内容被缓存
# 第2次调用(相同 thread_id):只发送缓存引用,成本降低约 90%
#1.13 middleware:Agent 的"中间件层"
"""
内部机制:
中间件在 Agent 执行的各个阶段(模型调用前、工具调用前、模型调用后)插入钩子
多个中间件按列表顺序执行,可以修改 request 或 state
LangGraph 内部将中间件包装为图节点,在模型节点和工具节点之间插入
"""
# 三种生产级中间件模式:中间件的简单介绍!
# 模式1:动态模型选择(成本优化)
from langchain.agents import create_agent
from langchain.agents.middleware import wrap_model_call, ModelRequest, ModelResponse
from langchain_openai import ChatOpenAI
# 配置:简单问题用小模型,复杂问题用大模型
basic_model = ChatOpenAI(model="gpt-4o-mini", temperature=0.1)
advanced_model = ChatOpenAI(model="gpt-4o", temperature=0.1)
@wrap_model_call
def dynamic_model_selector(request: ModelRequest, handler) -> ModelResponse:
"""
根据对话复杂度动态选择模型
执行流程:
1. 拦截模型调用请求
2. 分析当前 state 中的消息数量和工具调用次数
3. 决定使用哪个模型
4. 将修改后的请求传给 handler
"""
return handler(request.override(model=model))
agent5 = create_agent(
model=basic_model, # 默认模型
tools=[],
middleware=[dynamic_model_selector]
)
# 执行效果:
# 第1轮:"帮我搜索 Python 教程" → 使用 gpt-4o-mini
# 第5轮(已进行3次工具调用后):"基于搜索结果,帮我分析哪个教程最适合初学者" → 自动切换到 gpt-4o
#模式2:工具错误处理(系统健壮性)
from langchain.agents.middleware import wrap_tool_call
@wrap_tool_call
def graceful_tool_error_handler(request, handler):
"""
拦截工具执行,将异常转换为友好的 ToolMessage
为什么重要:默认情况下,工具异常会导致 Agent 执行中断
通过中间件,可以让 Agent 继续执行,并向 LLM 提供错误信息,让它自己决定下一步
"""
pass
agent6 = create_agent(
model=model,
tools=[], # 这些工具可能因网络/权限失败
middleware=[graceful_tool_error_handler]
)
# 执行效果(数据流转):
# 1. Agent 调用 database_query_tool(sql="SELECT * FROM users")
# 2. 数据库连接超时 → 抛出 ConnectionError
# 3. 中间件捕获异常 → 返回 ToolMessage("工具 'database_query_tool' 执行失败:连接超时...")
# 4. LLM 收到这个 ToolMessage 后,可以:
# - 重试(调整参数)
# - 告知用户暂时无法查询
# - 使用其他工具替代
# 5. Agent 不会崩溃,继续执行
#模式3:动态系统提示(上下文感知)
from langchain.agents.middleware import dynamic_prompt
@dynamic_prompt
def context_aware_system_prompt(request: ModelRequest) -> str:
"""
根据用户上下文动态生成系统提示
执行时机:每次模型调用前
数据来源:invoke() 时传入的 context 参数
"""
pass
#1.14 response_format:结构化输出
# 为什么重要:当 Agent 作为上游系统的一部分时,你需要的是可解析的数据,
# 而不是自然语言。例如,一个数据分析 Agent 需要返回 JSON 给前端图表组件。
# ToolStrategy:create_agent() 内部创建一个名为 structured_output_response 的工具
# 强制模型在最终回答时调用它,工具调用参数即为你定义的结构
#
# ProviderStrategy:直接使用模型的 response_format 参数,
# 模型返回的 AIMessage 的 additional_kwargs 中包含解析后的结构化数据
from pydantic import BaseModel
from langchain.agents import create_agent
from langchain.agents.structured_output import ToolStrategy, ProviderStrategy
# 定义输出结构
class OrderInfo(BaseModel):
order_id: str
status: str
estimated_delivery: str | None
items: list[str]
# 方式1:ToolStrategy(适用于所有模型)
agent_tool = create_agent(
model="gpt-4o-mini", # 任何支持工具调用的模型都可以
#tools=[query_order_tool],
response_format=ToolStrategy(OrderInfo)
)
result = agent_tool.invoke({
"messages": [{"role": "user", "content": "帮我查订单 ORD-12345 的状态"}]
})
# result["structured_response"] 是 OrderInfo 实例
print(result["structured_response"].order_id) # "ORD-12345"
print(result["structured_response"].status) # "shipped"
# 方式2:ProviderStrategy(仅支持原生结构化输出的提供商)
agent_provider = create_agent(
model="openai:gpt-4o", # 必须支持原生 structured output
#tools=[query_order_tool],
response_format=ProviderStrategy(OrderInfo)
)
#1.15:context_schema:依赖注入的类型安全(略)
# 为什么重要:生产环境中,你的工具和中间件通常需要访问数据库连接、
# 用户 ID、配置等外部依赖。context_schema 配合 middleware 提供了类型安全的依赖注入机制
user_input={"messages":[{"role":"user","content":"北京的天气如何"}]}
# 2.11 agent.stream() 的返回对象结构与数据流转stream_mode="updates"
#stream() 返回的是一个生成器,每次迭代产出一个节点执行结果的快照字典,让你能实时追踪 Agent 的每一步执行。
# 模式1:updates(默认)- 只返回节点输出的增量更新
for chunk in agent.stream(user_input, stream_mode="updates"):
print(chunk)
# 模式2:values - 返回每次更新后的完整状态
for chunk in agent.stream(user_input, stream_mode="values"):
print(chunk)
for chunk in agent.stream(user_input,stream_mode="updates"):
for node_name,node_output in chunk.items():
print(f"{node_name} -> {node_output}")
last_msg=node_output["messages"][-1]
#Python 的 -1 索引是取列表最后一个元素的惯用写法,无论列表多长,[-1] 永远返回最新消息.
if hasattr(last_msg, "tool_calls") and last_msg.tool_calls:
print(f"工具调用请求: {last_msg.tool_calls}")
# hasattr(obj, name)检查对象obj是否具有属性name。
elif last_msg.content:
print(f"内容: {last_msg.content}")
"""
agent.stream()返回一个生成器(generator),每次迭代产生一个chunk字典
# chunk示例格式/所具有的元素(stream_mode="updates"时):
{
"agent": # ← node_name 字符串
{
"messages": [
AIMessage(content="思考中..."),# AI消息对象
AIMessage(tool_calls=[...])
# 或带content的消息或 ToolMessage, HumanMessage, SystemMessage等
]
AIMessage:AI的响应消息
可能包含.content(文本内容)
可能包含.tool_calls(工具调用列表)
ToolMessage:工具执行结果消息
包含.content(工具返回结果)
HumanMessage:用户输入消息
包含.content(用户问题)
SystemMessage:系统消息
包含.content(系统提示)
},
"tools": # ← node_name 字符串
{
"messages": [
ToolMessage(content="工具执行结果...")
]
}
}
假设一个完整的 Agent 执行流程,三个 chunk 依次是:
Chunk 1(agent 节点发出工具调用)
{
"agent": { # ← node_name
"messages": [ ←node_output 层:节点输出数据
AIMessage(
content="", # 无文本
tool_calls=[{"name": "get_weather", "args": {"city": "北京"}, "id": "call_001"}]
)
]
}
}
↑ 这对应注释中的 AIMessage(tool_calls=[...])
Chunk 2(tools 节点返回结果)
{
"tools": { # ← node_name
"messages": [ ←node_output 层:节点输出数据
ToolMessage(
content="北京:晴天,24°C",
name="get_weather",
tool_call_id="call_001"
)
]
}
}
↑ 这对应注释中的 ToolMessage(content="工具执行结果...")
Chunk 3(agent 节点输出最终答案)
{
"agent": { # ← node_name
"messages": [ ←node_output 层:节点输出数据
AIMessage(content="北京今天天气晴朗,温度24°C")
]
}
}
↑ 这对应你注释中的 AIMessage(content="思考中...") 或最终答案
"""
# 2.12 agent.stream() 的返回对象结构与数据流转stream_mode="messages"
#当使用 stream_mode="messages" 时,每次迭代返回一个 元组 (token, metadata)。
# 2. stream_mode="messages":按令牌流式传输
# 该模式在 LLM 生成 每一个令牌(token) 时都发出事件。
# 这是实现"打字机效果"的基础,能极大提升交互流畅感。
# 使用 messages 模式流式执行
user_input = {"messages": [{"role": "user", "content": "北京今天天气如何?"}]}
for token, metadata in agent.stream(user_input, stream_mode="messages"):
# token 是消息块,metadata 包含该块所属的节点等信息
node_name = metadata.get("langgraph_node", "unknown")
# 提取内容块(可能是文本或工具调用片段)
for content_block in token.content_blocks:
block_type = content_block.get("type")
if block_type == "text":
# 普通文本令牌,直接输出
print(content_block.get("text", ""), end="", flush=True)
elif block_type == "tool_call_chunk":
# 工具调用的流式片段,通常不直接展示给用户
tool_name = content_block.get("name")
args_chunk = content_block.get("args", "")
if tool_name:
print(f"\n[调用工具: {tool_name}]", end="")
if args_chunk:
print(args_chunk, end="")
#拆解agent.stream(user_input, stream_mode="messages"):的返回值
"""
# 每次迭代返回的是一个 Python 元组,包含两个元素
(token, metadata) = (token_object, metadata_dict)
token 的类型通常是 AIMessageChunk 或 ToolMessageChunk,它们有一个 content_blocks 属性,包含具体的块数据。
元组元素 类型 关键属性 说明
token AIMessageChunk .content_blocks 文本片段或工具调用片段列表
.tool_call_chunks 工具调用的流式片段
.content 累积的文本内容(在最终块中)
metadata dict ["langgraph_node"] 产生该 token 的节点名称("model"/"tools")
["langgraph_step"] 执行步骤序号
["thread_id"] 会话线程 ID(如果配置了)
["checkpoint_id"] 检查点 ID
阶段1:工具调用开始(第一个 token)
# 第1个 token - 工具调用开始标记
(
# token 部分
AIMessageChunk(
content="", #.content_blocks
tool_call_chunks=[ #.tool_call_chunks
{
"name": "get_weather",
"args": "",
"id": "call_abc123",
"index": 0,
"type": "tool_call_chunk"
}
]
),
# metadata 部分
{
"langgraph_node": "model",
"langgraph_step": 1,
"langgraph_triggers": ["branch:router"],
"langgraph_path": ("__pregel_push", 0),
"langgraph_checkpoint_ns": "model:1",
"checkpoint_id": "1ef7d8a2-3b4c-5d6e-7f8a-9b0c1d2e3f4a",
"thread_id": "session_456"
}
)
阶段2:工具参数流式输出(逐步构建)
# 第2个 token - 参数片段: '{'
(
AIMessageChunk(
content="",
tool_call_chunks=[
{
"name": None,
"args": "{",
"id": None,
"index": 0,
"type": "tool_call_chunk"
}
]
),
{
"langgraph_node": "model",
"langgraph_step": 1,
# ... 其他 metadata 同阶段1
}
)
# 第3个 token - 参数片段: '"city"'
(
AIMessageChunk(
content="",
tool_call_chunks=[
{
"name": None,
"args": '"city"',
"id": None,
"index": 0,
"type": "tool_call_chunk"
}
]
),
metadata # 同前
)
# 第4个 token - 参数片段: ':"北京"'
(
AIMessageChunk(
content="",
tool_call_chunks=[
{
"name": None,
"args": ':"北京"',
"id": None,
"index": 0,
"type": "tool_call_chunk"
}
]
),
metadata
)
# 第5个 token - 参数结束: '}'
(
AIMessageChunk(
content="",
tool_call_chunks=[
{
"name": None,
"args": "}",
"id": None,
"index": 0,
"type": "tool_call_chunk"
}
]
),
metadata
)
阶段3:工具执行节点
# 工具节点的输出 token
(
ToolMessageChunk(
content=
{
"type":"text"
"text":"北京:晴天,24°C"
},
tool_call_id="call_abc123",
name="get_weather"
),
{
"langgraph_node": "tools",
"langgraph_step": 2,
"langgraph_triggers": ["branch:model"],
"langgraph_path": ("__pregel_push", 1),
"langgraph_checkpoint_ns": "tools:1",
"checkpoint_id": "1ef7d8a2-3b4c-5d6e-7f8a-9b0c1d2e3f4b",
"thread_id": "session_456"
}
)
阶段4:最终回答文本流式输出
# 第N个 token - 文本片段
(
AIMessageChunk(
content="北京",
tool_call_chunks=[] # 无工具调用
),
{
"langgraph_node": "model",
"langgraph_step": 3,
"langgraph_triggers": ["branch:tools"],
"langgraph_path": ("__pregel_push", 2),
"langgraph_checkpoint_ns": "model:2",
"checkpoint_id": "1ef7d8a2-3b4c-5d6e-7f8a-9b0c1d2e3f4c",
"thread_id": "session_456"
}
)
# 下一个 token
(
AIMessageChunk(
content="今天",
tool_call_chunks=[]
),
metadata # 同前
)
# 再下一个
(
AIMessageChunk(
content="天气",
tool_call_chunks=[]
),
metadata
)
# 继续直到完成
(
AIMessageChunk(
content="晴朗,温度24°C",
tool_call_chunks=[]
),
metadata
)
"""
# 3. stream_mode="custom":自定义进度更新【拓展,不做要求】
# 这是最灵活的模式。你可以在工具函数内部,
# 通过 get_stream_writer() 获取一个写入器,主动向客户端发送任意状态信息。
# custom 模式允许你在工具函数内部,通过 get_stream_writer()
# 主动向外部发送自定义事件。这些事件会以独立流的形式输出,与 updates 或 messages 模式完全隔离。
from langgraph.config import get_stream_writer
@tool
def get_weather_with_progress(city: str) -> str:
"""带进度提示的天气查询工具"""
writer = get_stream_writer()
# 发送自定义进度消息
writer(f"⏳ 正在解析城市: {city}...")
# 模拟 API 调用前的预处理
import time
time.sleep(0.5)
writer(f"🌐 正在连接天气服务...")
time.sleep(0.5)
writer(f"✅ 成功获取{city}天气数据")
return f"{city}当前天气晴朗,温度24°C"
# 创建使用该工具的 Agent
agent_with_custom = create_agent(
model="openai:gpt-4o",
tools=[get_weather_with_progress],
system_prompt="你是一个天气助手。"
)
# 使用 custom 模式流式执行
user_input = {"messages": [{"role": "user", "content": "上海今天天气如何?"}]}
for custom_event in agent_with_custom.stream(user_input, stream_mode="custom"):
# custom_event 就是在工具中 writer() 传入的内容
print(f"[自定义进度] {custom_event}")
"""
模式 每次迭代返回 触发时机 典型用途
stream_mode="updates" dict(节点输出) 每个节点完成后 展示 Agent 步骤
stream_mode="messages" (token, metadata) 元组 每个 token 生成时 实现打字机效果
stream_mode="custom" 任意值(你传入的) 调用 writer() 时 自定义进度、状态
stream_mode="values" dict(完整状态) 每次状态变更后 调试、保存快照
"""
#==========================================================================
#补充:1.12中关于格式 消息系统的三层架构
"""
┌─────────────────────────────────────────────────────────────────┐
│ 第1层:消息类型(Role 层) │
│ 定义了消息的发送者身份,决定消息在对话中的语义角色 │
├─────────────────────────────────────────────────────────────────┤
│ • SystemMessage → role="system" → 设定 AI 行为边界 │
│ • HumanMessage → role="user" → 用户输入 │
│ • AIMessage → role="assistant" → AI 回复 │
│ • ToolMessage → role="tool" → 工具执行结果 │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 第2层:消息结构(Message 层) │
│ 定义消息的组成部分,LangChain 统一不同提供商的差异 │
├─────────────────────────────────────────────────────────────────┤
│ class BaseMessage: │
│ content: str | List[dict] # 第3层决定格式 │
│ additional_kwargs: dict # 提供商特有字段 │
│ response_metadata: dict # 响应元数据 │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 第3层:内容格式(Content Block 层) │
│ 定义 content 字段的内部结构,支持多模态和扩展元数据 │
├─────────────────────────────────────────────────────────────────┤
│ content = [ │
│ {"type": "text", "text": "..."}, # 文本块 │
│ {"type": "image", "url": "..."}, # 图片块 │
│ {"type": "tool_use", ...}, # 工具调用块 │
│ ] │
└─────────────────────────────────────────────────────────────────┘
"""
#完整的演示
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
from langchain_openai import ChatOpenAI
# ========== 步骤1:在 LangChain 中创建消息 ==========
# 系统消息:角色是 "system",内容是多模态列表
system_msg = SystemMessage(
content=[
{"type": "text", "text": "你是一个图像分析助手"},
{"type": "image", "url": "https://example.com/photo.png"}
]
)
# 用户消息:角色是 "user",内容是简单字符串
human_msg = HumanMessage(content="这张图片里有什么?")
# AI 消息:角色是 "assistant",内容 + 工具调用
ai_msg = AIMessage(
content="让我分析一下这张图片",
tool_calls=[{"name": "analyze_image", "args": {"image_url": "..."}, "id": "call_123"}]
)
# ========== 步骤2:查看 LangChain 内部存储结构 ==========
print(f"SystemMessage 类型: {type(system_msg)}") # <class 'SystemMessage'>
print(f"SystemMessage.role: {system_msg.type}") # 'system'
print(f"SystemMessage.content: {system_msg.content}") # [{'type': 'text', ...}, ...]
print(f"HumanMessage.type: {human_msg.type}") # 'human'
print(f"AIMessage.type: {ai_msg.type}") # 'ai'
# ========== 步骤3:发送给 OpenAI 时的序列化 ==========
model = ChatOpenAI(model="gpt-4o")
# LangChain 内部调用 model._convert_messages() 将消息转换为 OpenAI 格式
# 转换结果大致如下:
openai_format_messages = [
{
"role": "system",
"content": [
{"type": "text", "text": "你是一个图像分析助手"},
{"type": "image_url", "image_url": {"url": "https://example.com/photo.png"}}
]
},
{
"role": "user",
"content": "这张图片里有什么?"
},
{
"role": "assistant",
"content": "让我分析一下这张图片",
"tool_calls": [{"id": "call_123", "type": "function", "function": {...}}]
}
]
# ========== 步骤4:发送给 Anthropic 时的序列化(格式不同) ==========
anthropic_format_messages = [
{
"role": "user",
"content": [
{"type": "text", "text": "你是一个图像分析助手"}, # Anthropic 没有 system role
{"type": "image", "source": {"type": "url", "url": "..."}}
]
},
{
"role": "user",
"content": "这张图片里有什么?"
}
]
#场景1:纯文本对话(99% 的日常使用)
system_msg = SystemMessage(content="你是一个助手")
human_msg = HumanMessage(content="你好")
#场景2:需要多模态输入(图片、音频、文档)
# 必须使用内容块列表
system_msg2 = SystemMessage(
content=[
{"type": "text", "text": "你是文档 OCR 专家"},
{"type": "image", "url": "document_page1.png"}
]
)
#场景3:需要缓存控制(高频调用,长系统提示)
# 必须在内容块中设置 cache_control
system_msg3 = SystemMessage(
content=[
{
"type": "text", "text": "3000字系统指令...",
"cache_control": {"type": "ephemeral"}
},
{"type": "text", "text": "当前用户消息"}
]
)
#补充2:cache_control 缓存机制深度解析->提示词缓存
"""
为什么需要提示词缓存?
在你的生产 Agent 中,每次调用都传输相同的 3000 字系统提示,会导致:
高延迟:每次都要处理 3000 个 token
高成本:输入 token 按量计费
资源浪费:LLM 重复处理相同内容
一句话总结:缓存让 LLM 记住重复出现的提示前缀,后续调用只传输变化的部分,大幅降低延迟和成本。
┌─────────────────────────────────────────────────────────────────┐
│ 第一次调用(缓存写入) │
├─────────────────────────────────────────────────────────────────┤
│ 请求内容: │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 系统提示(3000字)+ cache_control 标记 │ │
│ │ 用户消息:"分析这份合同" │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Claude API: │ │
│ │ 1. 处理完整提示(耗时) │ │
│ │ 2. 将"系统提示"写入缓存(断点位置 = 标记处) │ │
│ │ 3. 返回响应 + 缓存元数据 │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ 响应中的缓存指标: │
│ {"cache_creation_input_tokens": 3000, ← 写入缓存的 token 数 │
│ "cache_read_input_tokens": 0, ← 命中缓存的 token 数 │
│ "input_tokens": 3020} │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 第二次调用(缓存命中) │
├─────────────────────────────────────────────────────────────────┤
│ 请求内容(相同 thread_id): │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 系统提示(3000字)+ cache_control 标记 ← 命中缓存! │ │
│ │ 用户消息:"换个角度分析..."(只有这 20 字是新内容) │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Claude API: │ │
│ │ 1. 检测到缓存命中 │ │
│ │ 2. 直接从缓存读取系统提示(不重新计算) │ │
│ │ 3. 只处理新的用户消息 │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ 响应中的缓存指标: │
│ {"cache_creation_input_tokens": 0, ← 本次未写入新缓存 │
│ "cache_read_input_tokens": 3000, ← 命中缓存的 token 数 │
│ "input_tokens": 20} ← 实际处理的 token 数 │
└─────────────────────────────────────────────────────────────────┘
"""
a_dict={"cache_control": {"type": "ephemeral"}}
#ephemeral:adj 短暂的;瞬息的
# 1. cache_control 到底是什么?
# 一句话解释:cache_control 是一个提示标记,告诉 Anthropic 的 API:"从这里开始往后的内容,请帮我缓存起来,下次调用时别再重新计算了。"
# 它不是缓存"这条消息",而是缓存从消息开头到这个标记处为止的所有内容
# 使用方式:多个缓存断点(推荐)
# 生产最佳实践:在静态内容块后都设置缓存
content = [
# 块1:系统角色(短文本,不缓存也行)
{"type": "text", "text": "你是数据分析助手"},
# 块2:工具定义(静态,建议缓存)
{"type": "text", "text": "工具1: query_database...\n工具2: analyze_data...",
"cache_control": {"type": "ephemeral"}},
# 块3:系统规则(静态,建议缓存)
{"type": "text", "text": "规则: 1. 优先使用缓存...\n2. 遵循数据安全规范...",
"cache_control": {"type": "ephemeral"}},
# 块4:用户消息(动态,不设缓存)
{"type": "text", "text": "用户输入"}
]
""":重要性:
场景 输入Token 费用(按 $3/M 输入计)
无缓存:每次传 3000 字系统提示 + 20 字用户消息 3020 $0.00906
有缓存:仅传 20 字用户消息 20 $0.00006
每次调用节省 - $0.009(99% 成本降低)
对于每天 10 万次调用的生产系统:
无缓存:$906/天
有缓存:$6/天
年节省:约 $328,500
"""
#补充3:
#深度解答:消息简写格式 ("role", "content") 的完整解析
#这个格式是 LangChain 提供的便捷消息创建方式。让我详细解释它的机制、作用和与标准格式的关系。
"""
一句话解释:消息简写格式是为了减少代码冗余,让你用元组列表快速构建对话历史,而无需手动实例化 SystemMessage、HumanMessage、AIMessage 类。
在生产场景中,你可能需要:
从数据库加载对话历史(存储为 JSON)
从 API 接收用户消息(格式可能是 {role, content})
快速测试不同的对话流程
手动创建消息对象代码冗长:
"""
from langchain_core.messages import *
# 冗长的方式
messages = [
SystemMessage(content="你是一个边塞诗人。"),
HumanMessage(content="写一首唐诗。"),
AIMessage(content="锄禾日当午..."),
HumanMessage(content="按照你上一个回复的格式,在写一首唐诗。")
]
# 使用简写格式,一行搞定:
# 简洁的方式
messages1 = [
("system", "你是一个边塞诗人。"),
("human", "写一首唐诗。"),
("ai", "锄禾日当午..."),
("human", "按照你上一个回复的格式,在写一首唐诗。")
]
# 数据流转过程:
# 你的输入:简写元组
input_messages = [
("system", "你是一个边塞诗人。"),
("human", "写一首唐诗。"),
("ai", "锄禾日当午..."),
("human", "按照你上一个回复的格式,在写一首唐诗。")
]
# ↓ LangChain 内部转换 ↓:消息对象
converted_messages = [
SystemMessage(content="你是一个边塞诗人。"),
HumanMessage(content="写一首唐诗。"),
AIMessage(content="锄禾日当午..."),
HumanMessage(content="按照你上一个回复的格式,在写一首唐诗。")
]
# ↓ 发送给 LLM API(OpenAI 格式)↓ :字典格式
openai_messages = [
{"role": "system", "content": "你是一个边塞诗人。"},
{"role": "user", "content": "写一首唐诗。"},
{"role": "assistant", "content": "锄禾日当午..."},
{"role": "user", "content": "按照你上一个回复的格式,在写一首唐诗。"}
]
第二次书写博客记录:2026.4.7