LangChain + LangGraph Agent 完全指南:流式传输、结构化输出、提示词缓存


深度剖析 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 执行的各个阶段(模型调用前、工具调用前、模型调用后)插入钩子。多个中间件按列表顺序执行,可以修改 requeststate

模式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]
)

数据流转

  1. Agent 调用 database_query_tool(sql="SELECT * FROM users")
  2. 数据库连接超时 → 抛出 ConnectionError
  3. 中间件捕获异常 → 返回 ToolMessage("工具执行失败:连接超时...")
  4. LLM 收到这个 ToolMessage 后,可以重试、告知用户或使用其他工具替代
  5. 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

  1. 选择合适的流式模式

    • updates → 展示 Agent 执行步骤
    • messages → 实现打字机效果
    • custom → 自定义进度更新
  2. 生产环境务必精确控制模型参数temperaturemax_retriestimeout

  3. 利用 middleware 实现横切关注点:动态模型选择、统一错误处理、上下文感知提示

  4. 结构化输出让 Agent 可组合 :使用 ToolStrategyProviderStrategy

  5. 消息系统三层架构:理解 Role → Message → Content Block 的层次关系

  6. 提示词缓存(Anthropic)可降低 99% 成本:对于长系统提示的高频场景至关重要

  7. 简写格式提升代码可读性("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

相关推荐
wuhen_n21 小时前
LangChain Agents 实战:构建智能文件管理助手
前端·javascript·人工智能·langchain·ai编程
wAEWQ6Ib71 天前
性能干翻235B,单卡私有化部署OpenClaw
langchain
wAEWQ6Ib71 天前
[拆解LangChain执行引擎]支持自然语言查询的长期存储
数据库·oracle·langchain
Ofm1z1Q9R1 天前
python-langchain框架(3-5-pdf文件load_and_split()加载 )
python·langchain·pdf
一如既往の2 天前
LangChain 是什么
langchain
new Object ~2 天前
langchain 的向量存储
langchain
Timer@2 天前
LangChain 教程 04|Agent 详解:让 AI 学会“自己干活“
javascript·人工智能·langchain
YuanDaima20482 天前
基于 LangChain 1.0 的检索增强生成(RAG)实战
人工智能·笔记·python·langchain·个人开发·langgraph
qq_5470261792 天前
LangChain 工具调用(Tool Calling)
java·大数据·langchain