Python & AI Tutorials Logo
LangChain & LangGraph

2. 代理构建基础要点

在第 1 章中,你完成了第一次 LLM 调用,并看到了最基本的请求-响应流程。现在我们需要理解我们实际在构建什么:代理式(agentic)AI 系统。本章将建立你在全书中会反复使用的基础概念。

在本章结束时,你将理解:

  • 什么让一个 AI 系统变得“代理式”(以及为什么这很重要)
  • 为什么会有 LangChain 和 LangGraph 这样的框架
  • LLM 在底层实际上如何工作(以及为什么这会影响代理设计)
  • LLM 使用的经济学(token、成本与模型选择)
  • 如何为代理系统编写有效的提示词

这一章偏概念——我们会在第 3 章回到动手编码。但这些概念对于理解你在构建代理时将做出的设计决策至关重要。

2.1) 什么是代理式 AI?(聊天机器人 vs 代理)

当大多数人想到“AI 应用”时,他们会想到聊天机器人(chatbot):你问一个问题,AI 回答,对话结束。但代理式 AI从根本上不同。代理不仅仅是回应——它会自主采取行动以实现目标

核心区别

我们从一个清晰的定义开始:

聊天机器人: 一个用生成文本来回应用户输入的系统。交互纯粹是对话式的。聊天机器人除了生成下一条消息之外,没有能力在世界中采取行动。

代理(agent): 一个可以:

  1. 感知其环境(例如:读取订单状态、检查库存)
  2. 决策要采取哪些行动(例如:在“创建工单、发送邮件或升级处理”之间选择)
  3. 行动以执行这些决策(例如:调用 API、更新数据库)
  4. 迭代直到达成目标(例如:验证订单 → 创建工单 → 确认 → 完成)

关键差异在于自主性与行动。聊天机器人是被动反应式的;代理是主动的。

具体示例:客户支持

我们来看看这种区别在实践中是什么样的。

聊天机器人方案:

用户: "我想要退款订单 #12345"
 
聊天机器人: "我理解您想要退款。要处理此事,请:
1. 访问我们的退款门户 example.com/refunds
2. 输入您的订单号
3. 选择一个原因
4. 提交表单
5. 等待 24-48 小时进行人工审核
 
还有什么我可以帮您的吗?"

聊天机器人提供了信息,但不采取任何行动。用户必须完成所有工作。

代理方案:

用户: "我想要退款订单 #12345"
 
代理(内部推理):
1. 用户想要退款订单 #12345
2. 我需要验证该订单是否存在
3. [调用 get_order_details(order_id="12345")]
4. 找到订单,符合退款资格
5. [调用 create_refund_ticket(order_id="12345", reason="customer_request")]
6. 已创建工单: TICKET-789
 
代理: "我已为订单 #12345 创建了退款工单 TICKET-789。
我们的退款团队将在 3-5 个工作日内处理。
你将很快收到一封邮件确认。"

代理采取了行动:它验证了订单、创建了工单,并确认了结果。用户无需手动步骤就解决了问题。

为什么这对开发很重要

理解这个区别会影响你如何设计系统:

聊天机器人开发:

  • 关注回答质量与对话流程
  • 主要关注点:生成有帮助、准确的文本
  • 简单架构:提示词 → LLM → 响应
  • 不需要外部集成

代理开发:

  • 关注决策与动作执行
  • 主要关注点:选择正确动作、处理错误、维护状态
  • 复杂架构:感知 → 推理 → 动作选择 → 执行 → 验证
  • 需要工具集成、错误处理、状态管理

自主性的光谱

并非所有代理都同等自主。它存在一个光谱:

等级 1:辅助式行动

  • 代理建议行动,用户逐一批准
  • 示例:“我可以创建一个退款工单。要继续吗?”
  • 对高风险操作最安全的方式

等级 2:有边界的自主

  • 代理在预定义约束内行动
  • 示例:可以创建工单与发送邮件,但不能处理超过 $500 的退款,或无法直接访问支付系统
  • 生产系统中最常见(效率与安全的平衡)

等级 3:完全自主

  • 代理独立采取行动以实现目标
  • 示例:在无人干预下完成整个退款流程
  • 需要强健的护栏与监控

代理的关键特征

总结一下,一个代理式 AI 系统具备这些核心属性:

  1. 工具使用: 能调用函数、API 与外部服务(没有这一点,它就只是聊天机器人)
  2. 目标导向: 朝着特定结果工作,而不仅仅是回应
  3. 多步骤: 将复杂任务拆解为一系列行动
  4. 自适应: 基于中间结果调整行为
  5. 有状态: 跨多次交互维持上下文

前两项是必需的——没有工具与目标,你就没有代理。其余的是质量因素,区分了好代理与优秀代理。

现在你理解了代理是什么,以及为什么它们强大。

2.2) 为什么是 LangChain 和 LangGraph?

你可能会想:“为什么我需要框架?我不能直接调用 OpenAI API 吗?”我们来探讨为什么会有框架,以及它们解决了什么问题。

代理开发的复杂性

用原始 API 调用构建一个简单的聊天机器人很直接:

python
import openai
 
response = openai.chat.completions.create(
    model="gpt-5-mini",
    messages=[{"role": "user", "content": "你好!"}]
)
print(response.choices[0].message.content)

这对于基础用例没问题。但一旦你想构建一个代理,复杂度会迅速上升:

挑战 1:多步骤工作流 你的代理需要:

  • 从知识库检索相关文档
  • 基于用户意图决定调用哪个工具
  • 执行工具并处理错误
  • 格式化结果并回复用户

每一步都需要谨慎编排、错误处理与状态管理。

挑战 2:供应商抽象 如果你想要:

  • 从 OpenAI 切换到 Anthropic 或 Google?
  • 针对不同任务使用不同模型?
  • 当主模型失败时回退到更便宜的模型?

用原始 API 调用时,你需要为每个供应商重写大量代码。

挑战 3:对话记忆 代理需要记住上下文:

  • 对话中的先前消息
  • 早先查询检索到的文档
  • 工具调用产生的中间结果

手动管理这些状态既容易出错又很繁琐。

挑战 4:工具集成 你的代理需要:

  • 用 schema 定义可用工具
  • 让 LLM 选择要调用的工具
  • 从 LLM 输出中解析工具参数
  • 在校验下安全执行工具
  • 处理工具错误与重试逻辑

这需要大量样板代码,并且在每一步都需要考虑安全问题

挑战 5:复杂路由 真实代理需要条件逻辑:

  • “如果用户询问退款,检索政策文档”
  • “如果用户想退款,创建工单”
  • “如果问题跑题,礼貌拒绝”

用 if-else 来实现很快就会变得难以维护。

LangChain 提供了什么

LangChain 是一个用于构建 LLM 应用的框架。它提供:

1. 模型抽象

为不同 LLM 供应商(OpenAI、Anthropic、Google 等)提供统一接口。

python
from langchain_openai import ChatOpenAI
from langchain_anthropic import ChatAnthropic
 
# 相同接口,不同供应商
openai_llm = ChatOpenAI(model="gpt-5-mini")
anthropic_llm = ChatAnthropic(model="claude-4-5-sonnet")
 
# 二者都使用 .invoke(),并采用相同的消息格式
response = openai_llm.invoke([{"role": "user", "content": "你好"}])

无需重写应用逻辑就能切换供应商。

2. 可组合的链(LCEL)

LangChain Expression Language——一种将组件连接成流水线的语法。

python
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
 
# 使用 | 运算符组合流水线(像 Unix 管道一样)
chain = prompt | llm | output_parser
 
# 一次调用执行整个流水线
result = chain.invoke({"input": "用户问题"})

构建复杂工作流时,不必在步骤之间手动传递数据。我们会在第 6 章学习 LCEL。

3. 对话记忆

用于有状态对话的消息历史管理。

python
from langchain_core.chat_history import InMemoryChatMessageHistory
 
# 消息历史助手
history = InMemoryChatMessageHistory()
history.add_user_message("你好")
history.add_ai_message("你好!")
 
# 需要时取回消息
messages = history.messages
response = llm.invoke(messages)

用辅助类抽象消息存储,而不是手动管理 list。在这个阶段,history 仍然需要显式传给模型。 我们会在第 8 章把它集成进链里。LangGraph(第 15 章+)用内置状态管理让这一点更简单。

4. 工具集成

基于装饰器的系统,用于将 Python 函数暴露给 LLM。

python
from langchain_core.tools import tool
 
@tool
def create_ticket(order_id: str, reason: str) -> str:
    """为订单创建支持工单。"""
    # 在这里实现
    return f"已为 {order_id} 创建工单"
 
# LangChain 处理 schema 生成与 LLM 集成

把任何 Python 函数变成可被支持工具调用的 LLM 或代理发现并调用的工具——无需手写 JSON schema。

5. 文档加载器与向量存储

可直接使用的组件,用于从多种来源加载文档,并将其存为可搜索的嵌入。

python
from langchain_community.document_loaders import TextLoader
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings
 
# 加载文档
docs = TextLoader("support_docs.txt").load()
 
# 创建可搜索索引
embeddings = OpenAIEmbeddings()
vectorstore = Chroma.from_documents(docs, embeddings)
 
# 检索相关文档
results = vectorstore.similarity_search("退款政策")

使用预构建的加载器与向量存储来搭建 RAG 系统,而不是从零实现解析、嵌入与检索。

LangGraph 提供了什么

LangGraph 在 LangChain 之上扩展,以支持复杂代理工作流。它提供:

1. 显式状态管理

用一个统一的强类型 schema 定义所有代理数据,而不是把它散落在各个变量里。

python
from typing import TypedDict, Optional
from langchain_core.messages import BaseMessage
from langchain_core.documents import Document
 
class AgentState(TypedDict):
    messages: list[BaseMessage]
    retrieved_docs: list[Document]
    current_step: Optional[str]
 
# 所有代理数据都在这里——调试时便于在一个地方检查

不必再到处找数据放在哪里、如何在步骤之间流转。状态变更是显式的:节点从 state 读取并返回更新。你的 IDE 会自动补全字段名,类型检查器会在运行前发现错误。

2. 基于图的工作流

通过声明步骤(节点)及其连接(边)来构建工作流,而不是编写编排代码。

python
graph = StateGraph(AgentState)
 
# 定义节点(工作流中的步骤)
graph.add_node("retrieve", retrieve_docs)
graph.add_node("answer", generate_answer)
 
# 定义边(步骤间的转移)
graph.add_edge("retrieve", "answer")

你定义结构——“先运行 retrieve,再运行 answer”——然后 LangGraph 负责执行。无需编写编排代码在步骤之间传递状态。诸如顺序执行或分支等控制流,都在图结构本身中被声明。

3. 条件路由

运行时决定工作流该走哪条路径。

python
from langchain_core.messages import BaseMessage
 
def route_request(state):
    last_message = state["messages"][-1].content
    if "refund" in last_message:
        return "create_ticket"
    else:
        return "answer_question"
 
graph.add_conditional_edges(
    "classify",
    route_request,
    {
        "create_ticket": "create_ticket",
        "answer_question": "answer_question",
    }
)

关键洞见: 路由函数返回的是下一个节点的名称("create_ticket" 或 "answer_question")。

为什么这很重要: 你的决策逻辑与执行逻辑是分离的。想修改路由规则?改一个函数。想看所有可能路径?看图的定义。想调试到底走了哪条路径?检查执行轨迹——无需在层层嵌套的函数调用中排查。

4. 检查点与持久化

每一步之后都会自动为状态做检查点(checkpointing),从而支持崩溃恢复与暂停-恢复工作流。

python
from langgraph.checkpoint.sqlite import SqliteSaver
 
checkpointer = SqliteSaver("agent_state.db")
graph = graph.compile(checkpointer=checkpointer)
 
# 每一步都会自动保存状态
result = graph.invoke(
    input_state,
    config={"configurable": {"thread_id": "user-123"}}
)

在每一步之后,LangGraph 会把当前状态 checkpoint 到持久化存储中。如果进程崩溃,执行可以从与同一个 thread ID 关联的最新 checkpoint 恢复。

当与条件路由或中断结合时,checkpointing 支持暂停并恢复的工作流,例如等待人工批准。

什么时候使用哪个框架

使用 LangChain 的场景:

  • 构建简单链(提示词 → LLM → 解析器)
  • 实现 RAG 系统
  • 处理基于消息的上下文(显式传递聊天历史)
  • 跨 LLM 供应商做抽象

使用 LangGraph 的场景:

  • 构建多步骤代理工作流
  • 实现条件路由逻辑
  • 跨步骤管理复杂状态
  • 需要 checkpointing 与崩溃恢复

2.3) LLM 如何工作(面向代理构建者)

要构建有效的代理,你需要理解 LLM 实际如何工作。这不是关于 transformer 的数学——而是关于塑造你如何设计代理系统的心智模型。

核心机制:token 预测

这里有一个根本洞见:LLM 并不像数据库那样“知道”事实。它们预测下一个 token。

我们用一个具体例子来拆解。

输入: "The capital of France is"

你可能以为发生了什么:

  1. LLM 在它的知识库中“查找”法国首都
  2. LLM “检索”答案:Paris
  3. LLM 返回 "Paris"

实际发生的是:

  1. LLM 将输入转换为 token:["The", "capital", "of", "France", "is"]
  2. LLM 计算所有可能的下一个 token 的概率分布
  3. 最可能的下一个 token:"Paris"(最高概率)
  4. LLM 从分布中采样(通常选择最高概率)
  5. LLM 返回 "Paris"

LLM 并不是“知道”Paris 是首都。它预测在给定输入模式下,"Paris" 是最可能的下一个 token。

(注意:tokenization 与概率是概念性的,并因模型与 tokenizer 而异。)

为什么这对代理很重要

这种 token 预测模型对代理设计有深远影响:

影响 1:LLM 会产生幻觉

因为 LLM 预测 token(而不是检索事实),它们可能生成听起来合理但错误的信息。

python
from langchain_openai import ChatOpenAI
 
llm = ChatOpenAI(model="gpt-4o-mini")
response = llm.invoke("亚特兰蒂斯的首都是什么?")
print(response.content)
 
# 注意:实际输出可能会有所不同。
# 现代模型可能会识别亚特兰蒂斯是虚构的,并拒绝回答。
# 关键点是:
# 如果没有显式的事实核查,LLM 可能生成听起来合理但不正确的信息。

可能的(历史上或不受约束的)输出:

亚特兰蒂斯的首都是 Etheria。根据柏拉图的记载,Etheria 是一座位于该岛最东端的港口城市,其名称源于人们相信它直通天际的观念。

LLM 生成了一个听起来合理的答案,即使亚特兰蒂斯是虚构的。对代理而言,这意味着:

  • 永远不要盲目信任 LLM 输出
  • 用权威来源验证事实
  • 实现护栏

影响 2:上下文就是一切

LLM 只能看到你提供的 token。除非你显式包含上下文,否则它们对之前的对话没有记忆。

python
# 第一次调用
response1 = llm.invoke("我的名字是 Alice")
print(response1.content)  # "很高兴认识你,Alice!"
 
# 第二次调用(独立调用)
response2 = llm.invoke("我叫什么名字?")
print(response2.content)  # "我无法访问你的名字..."

第二次调用没有来自第一次的上下文。对代理而言,这意味着:

  • 你必须管理对话历史
  • 上下文窗口限制很重要
  • 状态管理至关重要

影响 3:提示词是指令,不是查询

因为 LLM 预测 token,你如何表述提示词会显著影响输出质量。

python
# 弱提示词(查询式)
response = llm.invoke("退款政策")
# 输出:"你想了解退款政策的哪些内容?"
 
# 强提示词(指令式)
response = llm.invoke(
    "你是一名客户支持专员。请清晰且简洁地解释我们的退款政策。"
)
# 输出:"我们的退款政策允许在 30 天内退货..."

对代理而言,这意味着:

  • 提示词是你的主要控制机制
  • 提示词工程是核心技能
  • system 消息设定代理行为

影响 4:结构化输出需要引导

LLM 天然会生成自由文本。要获得结构化输出(JSON、特定格式)需要显式指令。

python
# 没有结构化引导
response = llm.invoke("从:'I want a refund for order #12345' 中提取订单 ID")
print(response.content)
# 输出:"订单 ID 是 12345"(纯文本,格式不一致)
 
# 有结构化引导
response = llm.invoke(
    '提取订单 ID,并且只返回一个 JSON 对象,格式为:{"order_id": "..."}\n\n'
    "Text: 'I want a refund for order #12345'"
)
print(response.content)
# 输出:{"order_id": "12345"}(结构化,可解析)

对代理而言,这意味着:

  • 使用 schema 约束输出格式
  • 显式指定输出格式
  • 校验并解析 LLM 响应

自由文本为人类优化。代理需要显式格式化来确保机器可读。

确定性、随机性与现代 LLM

LLM 本质上是概率系统。它们通过预测最可能的下一个 token 来生成文本,而不是执行确定性规则。因此,同样的输入并不总能保证同样的输出。

在较早的模型中,开发者会用诸如 temperature 之类的参数显式控制这种随机性。更低的值会产生更可预测的输出,而更高的值会鼓励变化与创造力。

许多现代面向推理的模型往往不再暴露 temperaturetop_p 之类的参数。相反,它们会在内部管理解码与采样策略,以倾向于更稳定、更结构化的推理。但这并意味着这些模型完全确定性。

即使是这些模型也不保证输出完全一致。输出可能因以下原因而变化:

  • 内部采样:模型可能走不同的推理路径,输出在结构、细节或措辞上有所变化。
  • 模型更新:供应商会持续更新模型且不通知,因此同一提示词在不同时间可能得到不同响应。
  • 安全过滤:内容审核可能导致模型在某些情况下直接回答,但在另一些情况下保留、拒答或改写。
  • 工具策略:在代理系统中,模型可能对同一输入调用不同工具——或不调用工具——从而改变执行路径。

这对代理构建者意味着:

关键关注点不是参数调优——而是可预测性。代理系统应当在设计上假设:除非被显式约束,LLM 输出在措辞、结构,甚至结论上都可能变化。

这会导向一些具体的代理系统设计原则:

  • 永远不要依赖精确措辞来做逻辑决策——控制流应依赖结构化信号(schema、enum、flag),而不是匹配模型输出中的特定短语。
  • 在边界处强制结构——只要 LLM 输出会被代码消费,就要用 schema、校验器或严格格式来约束它,让程序无需“解释”自由文本。
  • 验证一切重要内容——影响金钱、权限或不可逆动作的事实必须用工具或外部系统核查,不能只信任模型。
  • 将 LLM 输出视为提案,而非决策——模型建议怎么做,但系统决定是否、何时以及如何行动。

在现代代理系统中,可靠性来自系统设计,而不是参数调优。任务越关键,模型应拥有的自由度越小——你的代理就越应该强制结构化。

关键要点: 通过 schema、校验与工具集成来构建可靠代理——而不是寄希望于 LLM 输出一致。

在下一节中,我们将探讨基于 token 处理的经济影响。

2.4) token:最基础的资源

token 是 LLM 处理的基本单位。理解 token 至关重要,因为它们定义了代理系统中什么是可能的(约束)以及什么是昂贵的(成本)。

什么是 token?

token 是 LLM 进行推理与生成时所使用的最小文本单位。

根据语言与上下文不同,一个 token 可能代表:

  • 一个词(agent
  • 一个词的一部分(calculation
  • 一个数字或符号(#123
  • 标点或空白

token 不是字符,也不是单词——它们是 tokenizer 创建的、与模型相关的单位。

发送给模型或由模型生成的每一条信息都会以 token 来计量:

  • 系统指令
  • 用户消息
  • 检索到的文档
  • 工具描述
  • 模型输出

token 是 LLM 交互的基础货币

token 作为系统约束

token 不仅是成本——它还是对你的代理在一次请求中能做什么的硬限制

每个 LLM 都有一个上下文窗口:它一次能处理的 token 最大数量。

示例场景: 你的客户支持代理需要:

  • 系统指令:200 token
  • 最近 10 条消息:~2,000 token
  • 3 篇检索到的帮助文章:~1,500 token
  • 生成的响应:~200 token
  • 总计:3,900 token

如果你的模型上下文窗口是 4,000 token,你就用了 97.5% 的容量。再来一条长消息,系统就会停止工作。

(现代模型通常有 128K+ token 的上下文窗口,但原则不变:上下文是有限的,你必须围绕这个限制来设计。)

超过限制会发生什么:

  • 更早的消息被丢弃 → 代理忘记更早的上下文,破坏对话连续性
  • 检索文档被截断 → 关键信息丢失,导致错误答案
  • 请求完全失败 → 系统根本无法响应

你无法花钱买更多空间。 上下文窗口是硬限制——就像试图把 2 升装进 1 升瓶子里。

这就是为什么长时间运行的代理必须主动管理哪些内容留在上下文中,哪些不留。token 管理是核心架构问题,而不是优化细节。

token 直接影响什么

除了直接的上下文窗口限制之外,token 还会影响两个关键设计决策:

1. 记忆策略:完整历史 vs 总结

保留完整对话历史可以保留细节,但 token 使用会持续增长。

一种常见替代是记忆总结

  • 用一个紧凑的总结替代更早的消息
  • 在降低 token 成本的同时保留意图

这种权衡会影响:

  • 成本
  • 准确性
  • 代理的长期一致性

因此,记忆设计也是一个 token 管理问题。

2. RAG 分块大小与检索策略

在检索增强生成(RAG)中,文档会在检索前被切分为 chunk。

  • 大 chunk

    • 更少的检索调用
    • 每次请求 token 成本更高
    • 更多无关上下文
  • 小 chunk

    • token 成本更低
    • 更高精度
    • 可能遗漏关键信息

chunk 大小是一个关键设计决策——它会直接影响成本与答案质量。

token 经济学

理解 token 成本有助于你构建更具成本效益的系统。

典型定价(2026):

模型输入(每 1M token)输出(每 1M token)
GPT-5$1.25$10.00
GPT-5-mini$0.25$2.00
Claude 4.5 Sonnet$3.00$15.00
Gemini 3 Pro$2.00$12.00
Gemini 3 Flash$0.50$3.00

关键洞见: 输出 token 的成本比输入 token 高 4–8 倍,这意味着在生产系统中,不受控的生成往往是最大的成本驱动因素。

快速成本估算:

以 GPT-5-mini 的一次典型请求为例:500 个输入 token 与 50 个输出 token:

  • 输入:(500 / 1,000,000) × $0.25 = $0.000125
  • 输出:(50 / 1,000,000) × $2.00 = $0.0001
  • 合计:~$0.000225 / 次请求

按 10,000 次请求/天:~$67.5/月

实践中的成本优化

策略 1:让模型与任务复杂度匹配

简单任务使用更小、更便宜的模型:

python
from langchain_openai import ChatOpenAI
 
# 复杂推理使用昂贵模型
complex_llm = ChatOpenAI(model="gpt-5")
 
# 简单任务使用便宜模型
simple_llm = ChatOpenAI(model="gpt-5-nano")
 
def get_llm_for_task(task_type):
    if task_type == "complex_reasoning":
        return complex_llm
    else:
        return simple_llm

策略 2:在上下文大小与质量之间做平衡

只包含必要的上下文:

python
# 高效:只包含相关 chunk
relevant_chunks = retrieve_top_k(user_question, k=3)  # ~500 token
prompt = f"上下文:{relevant_chunks}\n\n问题:{user_question}"

重要: 过于激进地减少上下文会损害答案准确性。在省成本与质量之间做平衡。

策略 3:控制输出长度

限制模型生成的数量:

python
# 成本可控
llm = ChatOpenAI(model="gpt-5-mini", max_tokens=100)
response = llm.invoke(messages)
# 最多 100 个输出 token

关键要点: token 管理不仅是为了降低成本——它是为了在硬资源约束下设计可靠、可扩展的代理系统。

在下一节中,我们将探索模型生态,并学习如何为每个任务选择合适的模型。

2.5) 理解模型生态

为你的代理选择合适的 LLM 需要在以下因素之间做权衡:

  • 上下文窗口: 模型能处理多少文本?
  • 成本: 每次请求成本是多少?
  • 延迟: 模型响应有多快?
  • 能力: 模型推理得有多好?

我们来概览 2026 年的模型生态。

主要模型家族

OpenAI GPT 模型

模型上下文窗口输入成本输出成本延迟最适合
GPT-5400K token$1.25 / 1M$10.00 / 1M~2–4s复杂推理,高级代码
GPT-5-mini400K token$0.25 / 1M$2.00 / 1M~1.5–3s通用任务、聊天、总结
GPT-5-nano400K token$0.05 / 1M$0.40 / 1M~1–2s分类、抽取

Anthropic Claude 模型

模型上下文窗口输入成本输出成本延迟最适合
Claude Opus 4.5200K token$5.00 / 1M$25.00 / 1M~2–4s深度推理、分析
Claude Sonnet 4.5200K token$3.00 / 1M$15.00 / 1M~1.5–3s均衡性能、编码

Google Gemini 模型

模型上下文窗口输入成本输出成本延迟最适合
Gemini 3.0 Pro1M token$2.00 / 1M$12.00 / 1M~3–5s超大上下文、研究
Gemini 3.0 Flash1M token$0.50 / 1M$3.00 / 1M~1–2s高吞吐应用

供应商特性

OpenAI:

  • 最适合:通用代理、跨多领域工作流、函数调用
  • 强项:通用推理能力强、开发者工具与生态完善、模型更新频繁

Anthropic:

  • 最适合:安全敏感工作流、结构化且延展的推理
  • 强项:深度分析、方法论式输出、延展思考且对齐性强

Google:

  • 最适合:超大上下文摄取与多模态任务
  • 强项:大规模文档分析、多模态理解、高吞吐处理

让模型与任务匹配

根据任务复杂度、上下文大小与延迟需求来选择:

按任务复杂度

简单任务 → 成本优化模型(GPT-5-nano、GPT-5-mini):

  • 意图分类、情感分析、关键词抽取、简单格式化
  • 适用场景: 成本是首要关注点

中等任务 → 均衡模型(GPT-5-mini):

  • 问答、总结、工具选择
  • 适用场景: 需要在成本与质量之间平衡

复杂任务 → 性能优先模型(GPT-5、Claude Sonnet、Gemini Pro):

  • 多步骤推理、代码生成、细致分析
  • 适用场景: 推理能力更重要,成本可接受

最高复杂度 → 高端模型(Claude Opus):

  • 极其复杂的推理、任务关键型决策
  • 适用场景: 准确性至上,成本次之

按延迟需求

实时(用户感知延迟 ~1s 或更少) → 快速模型(GPT-5-nano、Gemini Flash):

  • 面向用户的聊天
  • 交互式应用

准实时(1-3s) → 大多数模型:

  • 标准代理任务

批处理(>3s) → 能力更强的模型:

  • 后台分析

上下文窗口考量

标准任务: 所有主流模型都支持 200K+ token,足以覆盖大多数代理工作流。

特殊情况:

  • 需要 400K token: GPT-5 系列(整本文档分析、大型代码库)
  • 需要 1M token: Gemini 模型(整本书、超大文档集)

实践建议: 即使上下文窗口很大,选择性检索(RAG)通常也会产生更好的结果。

关键要点

  1. 不存在单一“最佳”模型 → 不同模型擅长不同任务
  2. 让模型匹配任务复杂度 → 不要为简单任务多花钱
  3. 上下文窗口 ≠ 更好 → 使用选择性检索
  4. 延迟影响用户体验 → 交互任务要考虑响应时间

在实践中: 大多数代理会使用多个模型——用便宜模型处理简单任务,用能力更强的模型处理复杂推理。我们会在后续章节实现这一点。

在下一节,我们将学习如何通过有效提示词来控制模型行为。

2.6) 代理系统的提示词基础

提示词是你控制 LLM 行为的主要接口。对代理而言,有效提示词至关重要——它决定你的代理是否能做出正确决策、调用正确工具,并产出可靠输出。

提示词的构成

一个完整提示词包含三个组件:

1. System 消息(角色与约束) 定义代理的人设、能力与边界。

2. 上下文(相关信息) 提供完成任务所需的信息。

3. 指令(具体任务) 告诉代理要做什么,且要具体到位。

我们来看一个实践示例:

python
from langchain_openai import ChatOpenAI
 
llm = ChatOpenAI(model="gpt-5-mini")
 
response = llm.invoke([
    # system 消息:定义角色与约束
    {
        "role": "system",
        "content": """你是 TechCorp 的客户支持专员。
        
你的能力:
- 回答关于退款政策的问题
- 创建支持工单
- 提供故障排查指导
 
你的约束:
- 只回答关于 TechCorp 产品的问题
- 永远不要对退款时效做出承诺
- 始终保持礼貌与专业"""
    },
    
    # user 消息:上下文 + 指令
    {
        "role": "user",
        "content": """上下文:客户于 2026-01-15 购买了 X500 笔记本电脑型号。
今天是 2026-02-20。我们的退款政策允许在 30 天内退货。
 
指令:客户想要退款。我应该告诉他们什么?"""
    }
])
 
print(response.content)

输出:

我理解你想为你的 X500 笔记本电脑申请退款。很遗憾,
由于你的购买日期是 1 月 15 日,而今天是 2 月 20 日,我们已经
超出了 30 天的退货期限。不过,我很乐意创建一个支持工单来探索
其他选项,例如保修服务或换货。
你希望我继续为你创建工单吗?

System 消息:设定代理行为

system 消息是你定义代理个性与能力的地方。这是代理提示词中最重要的部分。

弱 system 消息:

python
system_message = "你是一个有帮助的助手。"

强 system 消息:

python
system_message = """你是 TechCorp 的客户支持专员。
 
角色(ROLE):
你帮助客户处理退款请求、产品问题与技术问题。
 
能力(CAPABILITIES):
- 使用提供的文档回答问题
- 需要时创建支持工单
- 提供分步骤的故障排查
 
约束(CONSTRAINTS):
- 只回答关于 TechCorp 产品的问题
- 如果你不知道,就说明不知道——不要猜测
- 使用文档时始终引用来源
- 永远不要承诺具体时效或结果
 
语气(TONE):
专业、有同理心、以解决问题为导向。
"""

为什么强版本更有效:

  1. 能力明确 → 代理知道它能做什么
  2. 约束清晰 → 代理知道要避免什么
  3. 语气定义 → 个性一致
  4. 指令具体 → 降低歧义

指令清晰度:要具体

LLM 会字面意义地遵循指令。模糊指令会产生不可靠结果。

模糊指令:

python
instruction = "帮助客户处理退款。"

具体指令:

python
instruction = """分析客户请求并判断:
1. 订单是否符合退款资格?(对比购买日期与退款政策)
2. 若符合:解释退款流程
3. 若不符合:解释原因并提供替代方案
 
将你的回复格式化为:
- Eligibility: [YES/NO]
- Reason: [简要解释]
- Next Steps: [客户应该做什么]
"""

上下文管理:提供所需信息

代理需要上下文才能决策,但太多上下文会浪费 token 并让模型困惑。

过度提供上下文(浪费):

python
context = f"""
公司历史:TechCorp 成立于 1995 年...
产品目录:我们销售 500+ 产品,包括...
退款政策:{refund_policy_text}
配送政策:{shipping_policy_text}
保修政策:{warranty_policy_text}
客户历史:{full_customer_history}
"""
# 5000+ token,大多数无关

选择性上下文(高效):

python
context = f"""
相关政策:{refund_policy_text}
订单详情:{order_details}
"""
# 200 token,全部相关

输出格式:让你的响应结构化

对代理而言,你往往需要结构化输出(JSON、特定格式),而不是自由文本。

非结构化输出(难解析):

python
response = llm.invoke([
    {"role": "system", "content": "你是一个支持专员。"},
    {"role": "user", "content": "对于这个退款请求,我们应该创建工单吗?"}
])
print(response.content)
# 输出:"是的,我认为我们应该创建一个工单,因为..."
# 问题:难解析,格式不一致

结构化输出(易解析):

python
response = llm.invoke([
    {"role": "system", "content": """你是一个支持专员。
    
始终以如下 JSON 格式回复:
{
  "action": "CREATE_TICKET" or "ANSWER_QUESTION" or "ESCALATE",
  "reason": "简要说明",
  "response_text": "要对客户说的话"
}"""},
    {"role": "user", "content": "客户想为订单 #12345 退款,购买于 40 天前"}
])
print(response.content)

输出:

json
{
  "action": "CREATE_TICKET",
  "reason": "订单超出 30 天退款窗口,需要人工审核",
  "response_text": "我已创建支持工单来审核你的退款请求。我们的团队会在 24 小时内联系你。"
}

我们会在第 7 章使用 Pydantic schema 来实现健壮的结构化输出。

Few-Shot 示例:展示,而不只是告诉

对于复杂任务,提供示例往往比冗长指令更有效。

Zero-Shot(仅指令):

python
prompt = """从客户消息中提取订单 ID、产品名称与问题。
 
客户消息:"My laptop X500 order #12345 won't turn on"
"""
# 模型可能在格式上吃力

Few-Shot(带示例):

python
prompt = """从客户消息中提取订单 ID、产品名称与问题。
 
示例 1:
输入: "My laptop X500 order #12345 won't turn on"
输出: {"order_id": "12345", "product": "laptop X500", "issue": "won't turn on"}
 
示例 2:
输入: "Order 67890 - phone not charging"
输出: {"order_id": "67890", "product": "phone", "issue": "not charging"}
 
现在从这条消息中提取:
输入: "My tablet order #11111 has a cracked screen"
输出:
"""

模型从示例学习模式,并一致地应用。

面向代理的提示词工程模式

模式 1:思维链(推理)

对于复杂决策,让模型“逐步思考”:

python
prompt = """你需要决定是否创建支持工单。
 
请逐步思考:
1. 客户在请求什么?
2. 这能用现有文档回答吗?
3. 这是否需要人工介入?
4. 我们应该采取什么行动?
 
客户消息:"I want a refund for order #12345 but I lost the receipt"
 
Reasoning:
"""

模型会显式展示推理,使决策更透明、更可靠。

模式 2:受限生成(安全)

限制模型可能输出的范围:

python
prompt = """对客户意图进行分类。必须且只能用以下选项中的一个进行回复:
- REFUND_REQUEST
- PRODUCT_QUESTION
- TECHNICAL_ISSUE
- OFF_TOPIC
 
客户消息:"How do I reset my password?"
 
Classification:
"""

这能防止模型生成意外输出。通过显式约束输出选项:

  • 防止解析错误(总是四个选项之一)
  • 阻止非预期动作(防止执行未定义动作)
  • 简化调试(有限输出空间让问题更易追踪)

模式 3:自我批评(质量)

让模型校验并改进自己的输出:

python
prompt = """生成给客户的回复,然后对其进行批评。
 
客户消息:"I want a refund"
 
第 1 步——生成回复:
[在此填写你的回复]
 
第 2 步——批评:
- 这条回复准确吗?
- 它有帮助吗?
- 它是否符合公司政策?
- 有哪些可以改进的地方?
 
第 3 步——最终回复(吸收批评后):
[改进后的回复写在这里]
"""

这种多步骤方法通常能产出更高质量的输出,因为:

  • 更早捕捉错误:模型在最终输出前复查自己的推理
  • 改善语气与清晰度:自我反思有助于发现不清晰或不恰当的措辞
  • 确保符合政策:批评步骤核验是否遵循指南

常见提示词错误

错误 1:假设模型“知道”一些事

LLM 无法访问实时信息或隐含上下文。务必显式提供所有必要数据。

python
# 不好:假设模型知道当前日期
prompt = "这个订单是否符合退款资格?订单 #12345"
 
# 好:提供所有必要信息
prompt = f"""这个订单是否符合退款资格?
订单 #12345
购买日期 {purchase_date}
今天:{current_date}
政策:30 天内可退货
"""

错误 2:指令含糊

像“handle”“process”“deal with”这样的模糊动词会留下太多解释空间。要明确所需的具体动作。

python
# 不好:"handle" 是什么意思?
prompt = "Handle this refund request"
 
# 好:明确动作
prompt = "判断该退款请求是否符合资格。如果是,创建工单。如果不是,解释原因。"

错误 3:上下文堆叠过载

包含无关信息会浪费 token、增加延迟,并可能让模型困惑。只检索特定任务所需内容。

python
# 不好:10,000 token 的上下文,大多无关
prompt = f"""
{entire_knowledge_base}
 
Question: What's the refund policy?
"""
 
# 好:只检索相关部分
prompt = f"""
{refund_policy_section}
 
Question: What's the refund policy?
"""

错误 4:格式不一致

当输出格式变化时,下游代码就会崩。务必指定你期望的精确格式,尤其是结构化数据。

python
# 不好:有时 JSON,有时纯文本
prompt = "Respond with your decision"
 
# 好:总是指定格式
prompt = '以 JSON 格式回复:{"decision": "...", "reason": "..."}'

关键要点

面向代理的有效提示词需要:

  1. 清晰的 system 消息 → 定义角色、能力、约束
  2. 具体指令 → 明确告诉模型要做什么
  3. 选择性上下文 → 只提供相关信息
  4. 结构化输出 → 显式指定格式
  5. Few-shot 示例 → 展示你想要的模式
  6. 推理提示词 → 要求逐步思考

提示词是一项熟能生巧的技能。在本书中,你会看到这些模式在真实代理系统中的应用。


章节总结

你现在理解了构建代理式 AI 系统的基础概念:

代理式 AI vs 聊天机器人:

  • 代理会自主行动以实现目标
  • 代理会使用工具并做出多步骤决策
  • 代理会维护状态并基于结果进行适配

为什么框架很重要:

  • LangChain 提供模型抽象、链、记忆、工具与 RAG
  • LangGraph 增加状态管理、路由与 checkpointing
  • 框架减少样板代码并支持复杂工作流

LLM 机制:

  • LLM 预测 token,而不是检索事实
  • 上下文是显式的,不是隐式的
  • 提示词是指令,不是查询
  • 结构化输出需要引导
  • 上下文窗口是有限的

token 经济学:

  • 输出 token 的成本是输入 token 的 4–8 倍
  • 模型选择对成本有 10-100 倍影响
  • 选择性上下文能显著降低成本
  • 监控与预算可防止失控花费

模型选择:

  • 让模型匹配任务复杂度
  • 考虑上下文需求与延迟约束
  • 使用多模型架构进行成本优化
  • 从便宜开始,只有在需要时才升级

提示词基础:

  • system 消息定义代理行为
  • 具体指令产生可靠结果
  • 选择性上下文提升质量并降低成本
  • 结构化输出便于解析与校验
  • few-shot 示例能有效教授模式