Agent 智能体开发学习博客(通俗原理 + 详细注释 · AI应用强化版)
Agent 是让大模型从"只会聊天"进化为"能自主执行任务"的关键技术。这篇博客从实际问题出发 ,用生活化类比 建立直觉,通过术语详解 深入概念本质,再用原理剖析 、图解演示 和可运行代码带你一步步理解。重点覆盖 Agent 面试中的高频考点。
一、初级篇:让模型"动起来"
1. 🔥 ReAct 模式:思考-行动-观察循环
问题
传统的 LLM 只能回答静态问题,如何让它能够自主地使用工具、分步完成任务,比如"帮我查一下北京天气,然后写一段周末出行建议"?
生活化类比
ReAct 就像你教一个实习生处理复杂任务:你告诉他"先思考需要什么信息,然后去执行(查资料、打电话),观察结果,再根据结果思考下一步,直到完成"。实习生不断循环这三步,最终交付成果。
术语详解
- ReAct = Reasoning(推理)+ Acting(行动)。它将大模型的思考 和工具调用 交织在一起,形成一个循环:
- 思考(Thought):分析当前状态,决定下一步做什么。
- 行动(Action):执行一个工具调用(如搜索、计算、API 调用)。
- 观察(Observation) :接收工具返回的结果,作为下一次思考的输入。
这个循环一直重复,直到模型决定"任务完成"并输出最终答案。
原理
ReAct 模式的核心是把工具调用和推理过程统一成一个文本序列。每次循环,模型都会在上下文中追加一段"思考:...行动:...观察:...",然后继续生成。这样,模型能利用之前的观察结果,动态调整后续行为,而不是一次性地预测所有步骤。相比固定流程的 Chain,ReAct 更灵活、更抗干扰。
图解演示:ReAct 循环流程
用户问题
│
▼
┌─────────────┐
│ 思考 (Thought) │ ← 分析现状,制定计划
└──────┬──────┘
│ 决定调用工具
▼
┌─────────────┐
│ 行动 (Action) │ ← 执行具体工具(搜索、计算...)
└──────┬──────┘
│ 工具返回结果
▼
┌─────────────┐
│ 观察 (Observation)│ ← 接收结果,更新上下文
└──────┬──────┘
│ 任务未完成,回到思考
▼
(重复)
│ 任务完成
▼
输出最终答案
演示用例:手写一个极简 ReAct Agent(文本解析版)
为了直观理解,我们用一个手动循环来模拟 ReAct,不使用任何 Agent 框架。模型通过解析输出中的特殊标记来决定是否调用工具。
python
# 模拟 ReAct 循环(演示核心思想)
from openai import OpenAI
import json
client = OpenAI()
# 模拟外部工具:一个简单的词典查询
def lookup_word(word: str) -> str:
"""模拟查询词典,返回词义。参数 word: 要查询的词语。"""
dictionary = {
"苹果": "一种水果,通常是红色或绿色。",
"香蕉": "一种长条形黄色水果。",
"天气": "大气状况,如晴、雨、雪等。"
}
return dictionary.get(word, "未找到该词") # 找不到时返回提示
# 工具列表(用文本形式告诉模型可调用的工具)
tools_desc = """
你可以调用以下工具:
- lookup_word(word):查询一个词的含义,返回字符串。
当你需要查词时,输出格式为:
Action: lookup_word
Action Input: 要查的词
当你能直接回答时,输出格式为:
Action: Finish
Final Answer: 最终答案
"""
# ReAct 循环
def react_agent(user_query: str, max_steps: int = 5):
"""
纯文本解析版 ReAct Agent。
参数 user_query: 用户的原始问题。
参数 max_steps: 最大循环步数,防止无限循环(默认5步)。
"""
# 初始消息:系统指令 + 工具描述 + 用户问题
messages = [
{"role": "system", "content": f"你是一个智能助理。{tools_desc}\n请严格遵循格式输出。"},
{"role": "user", "content": user_query}
]
for step in range(max_steps):
# 调用 LLM 生成思考+行动
response = client.chat.completions.create(
model="gpt-3.5-turbo", # 指定使用的对话模型
messages=messages, # 传入当前完整对话历史
temperature=0.0 # 温度为0,确保输出稳定可解析
)
reply = response.choices[0].message.content # 提取模型回复的文本
print(f"--- Step {step+1} ---")
print(f"模型输出:\n{reply}")
# 将模型的输出作为新消息加入对话(模拟上下文扩展)
messages.append({"role": "assistant", "content": reply})
# 简单解析:查找 Action 和 Action Input
if "Action: Finish" in reply:
# 提取最终答案
final_answer = reply.split("Final Answer:")[-1].strip() # 按标记分割并去空格
return final_answer
elif "Action: lookup_word" in reply:
# 提取要查的词
word = reply.split("Action Input:")[-1].strip() # 获取标记后的内容
# 执行工具------这是"观察"步骤
observation = lookup_word(word)
print(f"工具返回: {observation}")
# 将观察结果作为新消息(模拟工具返回)
messages.append({"role": "user", "content": f"Observation: {observation}"})
else:
# 格式不符合预期,强行结束并返回错误提示
return "Agent 输出格式错误,无法继续。"
return "达到最大步数,任务未完成。" # 循环耗尽仍无结果
# 测试
result = react_agent("苹果是什么?")
print("\n最终答案:", result)
输出结果(示例,实际可能略有差异)
--- Step 1 ---
模型输出:
Thought: 用户想知道"苹果"的含义,我需要查词典。
Action: lookup_word
Action Input: 苹果
工具返回: 一种水果,通常是红色或绿色。
--- Step 2 ---
模型输出:
Thought: 我已获得词义,可以回答用户。
Action: Finish
Final Answer: 苹果是一种水果,通常是红色或绿色。
最终答案: 苹果是一种水果,通常是红色或绿色。
⚠️ 生产环境建议 :上面用手写文本解析来演示原理,但实际项目中推荐使用 OpenAI 原生的
tool_calls机制。模型会直接返回结构化的tool_calls数组(包含工具名和 JSON 参数),避免文本解析的不稳定性。在 LangChain 中,可以直接使用create_openai_tools_agent或AgentExecutor封装好的 ReAct Agent。
面试要点 :ReAct 循环的核心是思考-行动-观察的交互模式,面试官常问"ReAct 和普通的 Chain 有什么区别?"------Chain 是固定流程,ReAct 是动态决策。
AI 应用场景:ReAct 是构建能自主使用工具、搜索、计算、操作数据库的 Agent 的基础模式,也是 OpenAI Assistant API、LangChain Agent 等框架的底层逻辑。
2. ⭐ 记忆管理:缓冲记忆、摘要记忆、向量长期记忆
问题
对话越来越长,Agent 需要记住之前的交互内容。但直接把所有历史塞进上下文会导致 token 爆炸。如何高效管理记忆?
生活化类比
- 缓冲记忆 就像你的草稿纸------只记最近几句话,用完就擦。
- 摘要记忆 就像你的课堂笔记------每隔一段时间把前面的内容总结成要点。
- 向量长期记忆 就像你的外部硬盘------把重要信息存到向量数据库,需要时再检索出来。
术语详解
- 缓冲记忆(Buffer Memory):直接保存最近 K 轮对话原文。简单但占空间,适合短对话。
- 摘要记忆(Summary Memory):用 LLM 将长对话逐步总结为一段摘要,只保留摘要。省 token 但可能丢失细节。
- 摘要缓冲记忆(SummaryBufferMemory):结合缓冲和摘要------保留最近 K 轮原文,超过 K 轮的部分自动压缩为摘要。这是生产中最常用的折中方案,兼顾了细节保留和 token 控制。
- 向量长期记忆(Vector Long-term Memory) :将对话片段转为向量存入数据库,需要时用语义检索。通常在每次交互后异步写入 (避免阻塞主流程),检索时将召回的历史片段拼接到上下文开头并标注来源,供 LLM 参考。
原理
记忆管理的本质是在上下文窗口有限的情况下,尽可能保留对后续推理最有用的信息。缓冲记忆利用"最近性"(最近的信息最重要);摘要记忆利用"概括性"(压缩信息);向量记忆利用"相关性"(只提取与当前问题相关的历史)。三种方式常组合使用。
演示用例:使用 LangChain 实现三种记忆
python
# pip install langchain langchain-openai
from langchain.memory import (
ConversationBufferMemory,
ConversationSummaryMemory,
ConversationSummaryBufferMemory # 混合方案:缓冲 + 摘要
)
from langchain_openai import ChatOpenAI
from langchain.chains import ConversationChain
# 创建 LLM 实例,temperature=0.3 让输出较稳定
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.3)
# ---- 1. 缓冲记忆:保留所有对话原文 ----
# return_messages=True 表示以消息对象列表形式存储,方便直接使用
buffer_memory = ConversationBufferMemory(return_messages=True)
chain_buffer = ConversationChain(llm=llm, memory=buffer_memory)
# predict 方法接收用户输入字符串,返回 AI 回复
chain_buffer.predict(input="你好,我是小明。")
chain_buffer.predict(input="我喜欢吃苹果。")
print("缓冲记忆中的消息数量:", len(buffer_memory.chat_memory.messages))
# ---- 2. 摘要记忆:逐步压缩历史为摘要 ----
# 同样 return_messages=True 保持接口一致
summary_memory = ConversationSummaryMemory(llm=llm, return_messages=True)
chain_summary = ConversationChain(llm=llm, memory=summary_memory)
chain_summary.predict(input="你好,我是小明。")
chain_summary.predict(input="我今天去了公园,看到了很多花。")
print("摘要记忆中的摘要内容:", summary_memory.buffer) # buffer 存储摘要文本
# ---- 3. 摘要缓冲记忆(推荐生产使用) ----
# max_token_limit 控制总 token 上限,超过后旧消息被压缩为摘要
hybrid_memory = ConversationSummaryBufferMemory(
llm=llm, # 用于生成摘要的 LLM
max_token_limit=200, # 当历史 token 超过此值时,将旧消息压缩
return_messages=True # 仍以消息形式返回,方便 Chain 使用
)
chain_hybrid = ConversationChain(llm=llm, memory=hybrid_memory)
chain_hybrid.predict(input="你好,我是小明。")
chain_hybrid.predict(input="我最近在学习 Python。")
print("混合记忆中的消息数量:", len(hybrid_memory.chat_memory.messages))
# ---- 4. 向量长期记忆(概念演示) ----
# 实际实现:将对话片段存入向量库(如 ChromaDB)
# 查询时用当前问题检索相关历史,拼接到上下文
# 写入:通常异步执行,不阻塞主流程
# 检索:召回的历史片段拼接到上下文开头,标注 [历史记忆] 来源
输出结果
缓冲记忆中的消息数量: 4 (用户2条+AI2条)
摘要记忆中的摘要内容: The human introduces themselves as Xiao Ming...
混合记忆中的消息数量: 4 (在 token 限制内保留原文)
AI 应用场景:缓冲记忆适合短期对话;摘要记忆适合长对话但可丢失细节;向量长期记忆是构建能"记住用户偏好"的 Agent 的关键技术;SummaryBufferMemory 是大多数生产环境的推荐选择。
二、中级篇:让 Agent 更聪明、更可靠
1. 🔥 多 Agent 协作:角色分工、状态传递
问题
复杂任务(如"调研市场并生成报告")需要多个专业角色协作:一个搜索员、一个分析师、一个作家。如何让多个 Agent 分工合作?
生活化类比
多 Agent 协作就像公司部门会议:每个 Agent 是一个专家(市场部、技术部、财务部),他们各自完成分内工作,然后通过"会议纪要"(状态传递)将结果汇总给下一个部门。
术语详解
- 多 Agent 系统:由多个具有不同角色和目标的 Agent 组成,通过通信协作完成任务。
- AutoGen (微软)和 CrewAI 是两个主流多 Agent 框架。CrewAI 的核心概念是
Agent(角色)+Task(任务)+Crew(协作组)。 - 状态传递 :上一个 Agent 的产出作为下一个 Agent 的输入,通过
context参数实现。 - 委派机制(allow_delegation) :开启后,Agent 可以将自己无法完成的任务委派给协作组中的其他 Agent(如研究员把计算任务委派给计算器 Agent)。顺序执行模式 中每个 Agent 依次执行各自任务;分层模式中有一个管理者 Agent 负责分配和协调。
- 意见冲突处理:如果多个 Agent 给出矛盾的结果,通常由最后一个汇总 Agent 做判断,或引入投票机制。
演示用例:用 CrewAI 构建一个调研+报告的多 Agent 系统
python
# pip install crewai langchain-openai
from crewai import Agent, Task, Crew, Process
# 定义 Agent:研究员
researcher = Agent(
role="研究员", # Agent 的角色名称,用于显示和日志
goal="找到关于{topic}的最新信息", # 角色目标,决定行为方向
backstory="你是一个经验丰富的研究员,擅长挖掘深度内容。", # 角色背景故事,辅助 LLM 理解角色
verbose=True, # 是否打印详细执行日志
allow_delegation=False, # 是否允许将任务委派给其他 Agent(此处关闭)
llm="gpt-3.5-turbo" # 指定该 Agent 使用的语言模型
)
# 定义 Agent:撰稿人
writer = Agent(
role="撰稿人",
goal="根据研究员的发现,撰写一份简洁的报告",
backstory="你是一个专业撰稿人,擅长将复杂信息转化为流畅文章。",
verbose=True, # 同样打印日志
allow_delegation=False, # 不委派
llm="gpt-3.5-turbo"
)
# 定义任务:研究
task_research = Task(
description="搜索并总结关于{topic}的三个关键点。", # 任务描述,告诉 Agent 要做什么
expected_output="一个包含三个要点的列表,每个要点一段说明。", # 期望输出格式,用于指导 Agent
agent=researcher # 指定由哪个 Agent 执行此任务
)
# 定义任务:撰写报告,依赖研究任务的输出
task_write = Task(
description="根据研究员提供的要点,撰写一份简短报告(不超过200字)。",
expected_output="一份结构清晰的报告。",
agent=writer, # 由撰稿人执行
context=[task_research] # 将研究任务的输出作为本任务的上下文(状态传递)
)
# 组建 Crew(协作组)
crew = Crew(
agents=[researcher, writer], # 参与协作的 Agent 列表
tasks=[task_research, task_write], # 需要执行的任务列表
process=Process.sequential, # 执行模式:sequential 顺序执行,先完成第一个再第二个
verbose=True # 打印详细的执行过程
)
# 启动协作,传入输入变量(会替换 {topic})
result = crew.kickoff(inputs={"topic": "2024年人工智能发展趋势"})
print("最终报告:\n", result)
输出结果
[研究员] 搜索相关信息...
[研究员] 找到三个关键点:
1. 多模态模型成为主流...
2. 开源模型性能逼近闭源...
3. AI 代理(Agent)开始商业化应用...
[撰稿人] 根据要点撰写报告...
最终报告: 2024年人工智能发展呈现三大趋势...
AI 应用场景:多 Agent 适合需要多角色协作的复杂流程,如自动生成市场研究报告、代码审查 + 修复、多轮辩论等。顺序模式适合流水线任务,分层模式适合需要动态调度的复杂项目。
2. 🔥 Plan-and-Execute 与反思机制
问题
ReAct 模式中 Agent 一边想一边做,容易"走一步看一步"而忽略全局规划,导致任务完成率低。如何让 Agent 先制定计划再执行,并且能从失败中反思?
生活化类比
- Plan-and-Execute 就像建筑蓝图:先画好整个房子的设计图(Plan),再按图施工(Execute),而不是砌一块砖想一块。
- 反思机制 就像考试后复盘:做完题后检查一遍,发现错误就修正,总结教训下次不再犯。
术语详解
- Plan-and-Execute:将 Agent 的决策分为两阶段------先由规划器(Planner)生成完整步骤列表,再由执行器(Executor)逐步完成。
- ReWOO (Reason Without Observation):Plan-and-Execute 的变体。在规划阶段就将工具调用的占位符嵌入计划(如"第2步:将[搜索结果]整理为表格"),执行时按顺序填入结果,避免每步都等待 LLM 重新思考,大幅减少调用次数。
- 反思(Reflection):Agent 完成一个步骤或整个任务后,自我评估结果的质量。触发条件包括:工具返回空结果、结果不符合预期格式(如要求 JSON 但返回纯文本)、LLM 自我评估分数低于预设阈值。
原理
ReAct 的问题在于决策碎片化:每执行一个工具就要重新调用 LLM 思考下一步,耗时且容易偏离目标。Plan-and-Execute 将"思考全局计划"和"执行具体步骤"解耦:Planner 一次性生成完整计划(如 1.搜索资料 2.分析数据 3.写报告),Executor 按计划顺序执行,中间不需要 LLM 再次规划。反思机制在执行后引入校验循环:如果某步结果不理想,Executor 可以请求 Planner 仅修改失败步骤而非全盘重来。
演示用例:模拟 Plan-and-Execute + 反思的完整流程
python
# 模拟 Plan-and-Execute + 反思机制的完整流程
import json
def plan_and_execute_with_reflection(user_query: str, max_reflections: int = 2):
"""
完整的 Plan-and-Execute + 反思 Agent。
参数 user_query: 用户请求的原始字符串。
参数 max_reflections: 每个步骤最多重试次数,防止无限重试。
"""
# ---- 阶段1:规划 ----
planner_prompt = f"""
你是一个任务规划器。请将以下用户请求分解为步骤列表,以 JSON 数组返回。
每个步骤包含 step(序号)、action(动作描述)、expected(预期结果)。
用户请求:{user_query}
返回格式示例:
[
{{"step": 1, "action": "搜索北京的天气", "expected": "获得温度和天气状况"}},
{{"step": 2, "action": "根据天气写出行建议", "expected": "一段出行建议文本"}}
]
"""
plan_response = client.chat.completions.create(
model="gpt-3.5-turbo", # 使用规划模型
messages=[{"role": "user", "content": planner_prompt}],
temperature=0.0 # 温度为0,保证规划稳定
)
plan_text = plan_response.choices[0].message.content # 获取规划文本
plan_steps = json.loads(plan_text) # 解析 JSON 为步骤列表
print("生成的计划:")
for s in plan_steps:
print(f" 步骤{s['step']}: {s['action']} → 预期: {s['expected']}")
# ---- 阶段2:执行 + 反思 ----
results = [] # 存储每一步的结果
for i, step in enumerate(plan_steps):
print(f"\n执行步骤 {step['step']}: {step['action']}")
# 模拟执行工具(实际会调用搜索、计算等工具)
# 这里用 LLM 模拟工具返回的结果
exec_prompt = f"模拟执行以下动作并返回结果:{step['action']}"
exec_response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": exec_prompt}],
temperature=0.0
)
step_result = exec_response.choices[0].message.content
print(f" 执行结果: {step_result[:100]}...") # 仅显示前100字符
# ---- 反思:检查结果是否符合预期 ----
reflection_prompt = f"""
预期结果: {step['expected']}
实际结果: {step_result}
请判断实际结果是否满足预期。如果满足,回复 "OK"。
如果不满足,回复 "RETRY: <原因>" 并给出修正建议。
"""
reflection_response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": reflection_prompt}],
temperature=0.0
)
reflection = reflection_response.choices[0].message.content
print(f" 反思结果: {reflection}")
# 如果反思建议重试且还有重试次数
retry_count = 0
while "RETRY" in reflection and retry_count < max_reflections:
print(f" → 重试步骤 {step['step']}...")
# 用反思的建议重新执行
retry_response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": f"重新执行: {step['action']}。注意: {reflection}"}],
temperature=0.0
)
step_result = retry_response.choices[0].message.content
retry_count += 1
# 再次反思(简化:重试后默认通过,实际可再次评估)
reflection = "OK"
results.append({"step": step['step'], "result": step_result})
# ---- 阶段3:汇总所有步骤结果生成最终答案 ----
summary_prompt = f"根据以下步骤结果,回答原始问题:{user_query}\n\n" + \
"\n".join([f"步骤{r['step']}: {r['result']}" for r in results])
final_response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": summary_prompt}],
temperature=0.0
)
return final_response.choices[0].message.content
# 测试
result = plan_and_execute_with_reflection("北京今天天气怎么样?适合户外运动吗?")
print("\n最终答案:", result)
提升任务完成率的关键:
- 规划时要明确每个步骤的输入和输出,用 JSON 格式结构化。
- 执行中出错时,不要全盘重来,只修正失败步骤。
- 反思触发条件:空结果、格式不匹配、LLM 自我评估低于阈值。
- ReWOO 适用于步骤间依赖明确的任务,通过在计划中预埋占位符避免频繁等待。
面试考点:面试官常问"ReAct 有什么缺点?如何改进?"------答:ReAct 缺少全局规划,Plan-and-Execute 或 ReWOO 可以弥补;反思机制能显著提高成功率。
3. 🔥 MCP 协议:概念、资源/工具/提示的标准化暴露
问题
不同的 AI 应用各自定义工具接口,开发者需要为每个模型或框架适配一遍。有没有一种标准协议,让所有模型都能以统一方式调用工具和资源?
生活化类比
MCP 就像 USB 接口:以前每种设备有自己独特的接口(打印机用并口,鼠标用 PS/2),现在统一成 USB,任何设备都能即插即用。MCP 就是 AI 应用和工具之间的"USB 标准"。
术语详解
- MCP(Model Context Protocol) :由 Anthropic 提出,定义了 AI 模型与外部工具、资源交互的标准协议。它规定了三类暴露端点:
- 工具(Tools):可执行的函数,如搜索、计算。
- 资源(Resources):可读取的数据源,如文件、数据库记录。
- 提示(Prompts):预设的提示模板。
- MCP Server:实现了 MCP 协议的服务端,负责暴露工具和资源。
- MCP Client:AI 应用(如 Claude Desktop)通过 MCP 客户端连接到 Server,自动发现并调用这些工具。
- MCP 与 Function Calling 的关系 :MCP 是工具标准化的协议 (定义工具如何被发现和调用),Function Calling 是模型端的能力(模型如何"表示"调用意图)。两者配合:MCP Server 提供标准化的工具接口,模型通过 Function Calling 决定何时调用哪个 MCP 工具。
原理
MCP 使用 JSON-RPC 2.0 作为传输协议。Server 启动后注册自己支持的工具。Client 连接后通过 tools/list 获取所有可用工具,通过 tools/call 调用具体工具。传输层支持三种方式:
| 传输方式 | 适用场景 | 特点 |
|---|---|---|
| stdio | 本地进程通信 | 最简单,Server 作为子进程启动 |
| SSE | Web 服务远程调用 | 支持 HTTP,单向推送 |
| Streamable HTTP | 生产环境远程调用 | 双向通信,适合大规模部署 |
JSON-RPC 报文示例:
json
// 请求:列出所有可用工具
{
"jsonrpc": "2.0", // JSON-RPC 版本
"id": 1, // 请求 ID,用于关联响应
"method": "tools/list", // 调用的方法名
"params": {} // 参数,列出工具不需要额外参数
}
// 响应:返回工具列表
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "add", // 工具名称
"description": "返回两个整数的和", // 工具描述
"inputSchema": { // 输入参数的 JSON Schema
"type": "object",
"properties": {
"a": {"type": "integer"},
"b": {"type": "integer"}
},
"required": ["a", "b"]
}
}
]
}
}
// 请求:调用 add 工具
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "add", // 要调用的工具名称
"arguments": {"a": 3, "b": 5} // 工具参数,符合 inputSchema
}
}
// 响应:返回计算结果
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [{"type": "text", "text": "8"}] // 工具返回的内容,数组形式
}
}
演示用例:写一个简单的 MCP Server 并连接客户端
服务端 (server.py):
python
# pip install mcp
from mcp.server.fastmcp import FastMCP
# 初始化 MCP 服务器,命名为 "DemoServer"(用于标识)
mcp = FastMCP("DemoServer")
# 注册一个工具:加法计算器
@mcp.tool() # 装饰器标记该方法为 MCP 工具
def add(a: int, b: int) -> int:
"""返回两个整数的和。参数 a 和 b 是加数。"""
return a + b
# 注册一个资源:模拟读取配置文件
@mcp.resource("config://app") # 资源标识符 URI,客户端通过此 URI 读取
def get_config() -> str:
"""返回应用的配置信息。"""
return '{"theme": "dark", "language": "zh"}'
# 启动服务器,transport="stdio" 表示通过标准输入输出通信(适合本地子进程模式)
if __name__ == "__main__":
mcp.run(transport="stdio") # 可改为 "sse" 或 "streamable-http" 用于远程访问
客户端 (client.py):
python
# 使用 mcp 库连接 Server 并调用工具
import asyncio
from mcp import Client
from mcp.client.stdio import stdio_client, StdioServerParameters
async def main():
# 配置 Server 参数:通过 stdio 启动 server.py 作为子进程
server_params = StdioServerParameters(
command="python", # 启动命令
args=["server.py"] # 命令参数,即要运行的脚本
)
# 建立 stdio 连接,返回读写流
async with stdio_client(server_params) as (read, write):
# 创建 MCP 客户端,传入读写流
async with Client(read, write) as client:
# 列出所有可用工具
tools = await client.list_tools()
print("可用工具:", [t.name for t in tools]) # 打印工具名列表
# 调用 add 工具,传入参数字典
result = await client.call_tool("add", {"a": 3, "b": 5})
# result.content 是工具返回的内容列表,取第一个元素的 text
print("3 + 5 =", result.content[0].text)
asyncio.run(main())
输出结果
可用工具: ['add']
3 + 5 = 8
AI 应用场景:MCP 让 Agent 的工具生态从"封闭花园"走向"标准化市场",未来开发者可以像安装插件一样为 Agent 添加新能力。面试中常问"MCP 和 Function Calling 是什么关系"------前者是工具标准化协议,后者是模型调用工具的机制,两者互补。
4. ⭐ 会话持久化与检查点恢复
问题
Agent 执行一个长任务(如持续几分钟的数据分析)时,如果服务崩溃或用户断开连接,所有中间状态都丢失了,必须从头开始。如何让 Agent 能"断点续传"?
生活化类比
检查点就像游戏存档:玩到一半时保存进度,即使电脑关机,下次还能从存档处继续,不用重打已经通过的关卡。
术语详解
- 会话持久化:将 Agent 运行中的状态(消息历史、中间结果、计划进度)保存到外部存储(如数据库、文件)。
- 检查点(Checkpoint):在关键步骤后保存的状态快照。Agent 可以从最近的检查点恢复执行。
- LangGraph 等框架内置了检查点机制,可以在每个节点(步骤)后自动保存状态。持久化粒度通常是每个节点执行后保存一次,过于频繁(如每个 token)会影响性能。
原理
持久化的核心是状态的序列化和反序列化 。Agent 的完整状态包括:对话历史、当前任务计划、已完成的步骤、工具调用结果等。将这些保存为 JSON 或数据库记录,恢复时重新加载并继续执行下一步。LangGraph 使用 checkpointer 参数,在编译图时传入,框架自动在每一步执行后调用检查点保存。恢复时用相同的 thread_id 即可从上次断点继续。
演示用例:LangGraph + SqliteSaver 检查点恢复
python
# pip install langgraph langgraph-checkpoint-sqlite
from langgraph.checkpoint.sqlite import SqliteSaver # SQLite 持久化检查点
from langgraph.graph import StateGraph, END
from typing import TypedDict, Annotated
import operator
# 定义 Agent 的状态类型,继承 TypedDict 以获得类型提示
class AgentState(TypedDict):
messages: Annotated[list, operator.add] # 消息列表,operator.add 表示追加而非覆盖
step_count: int # 当前步骤计数
# 构建节点函数,每个函数接收当前状态并返回更新后的状态(部分更新)
def step_one(state: AgentState) -> AgentState:
"""模拟第一步:执行某个工具"""
return {
"messages": ["步骤1:已搜索资料"], # 追加消息(operator.add 自动合并)
"step_count": state.get("step_count", 0) + 1 # 步骤计数 +1
}
def step_two(state: AgentState) -> AgentState:
"""模拟第二步:分析结果"""
return {
"messages": ["步骤2:已分析数据"],
"step_count": state["step_count"] + 1
}
def step_three(state: AgentState) -> AgentState:
"""模拟第三步:生成报告"""
return {
"messages": ["步骤3:已生成报告"],
"step_count": state["step_count"] + 1
}
# 构建状态图
builder = StateGraph(AgentState) # 传入状态类型
builder.add_node("step_one", step_one) # 添加节点:名称 + 处理函数
builder.add_node("step_two", step_two)
builder.add_node("step_three", step_three)
builder.set_entry_point("step_one") # 设置入口节点
builder.add_edge("step_one", "step_two") # 添加边:步骤顺序
builder.add_edge("step_two", "step_three")
builder.add_edge("step_three", END) # 最后指向结束
# 使用 SQLite 持久化检查点
with SqliteSaver.from_conn_string("checkpoints.db") as checkpointer:
# 编译图,传入检查点器
graph = builder.compile(checkpointer=checkpointer)
# 配置会话 ID------相同的 thread_id 用于恢复
config = {"configurable": {"thread_id": "user-session-456"}}
# 第一次运行:从空状态开始,执行所有步骤
print("===== 第一次运行 =====")
result = graph.invoke(
{"messages": [], "step_count": 0}, # 初始状态
config # 传入配置(含 thread_id)
)
print(f"完成步骤数: {result['step_count']}")
print(f"消息: {result['messages']}")
# 模拟中断后恢复------用相同的 thread_id 调用 get_state 查看已保存状态
print("\n===== 模拟中断后恢复 =====")
state = graph.get_state(config) # 从数据库加载状态
print(f"当前步骤数: {state.values.get('step_count', 0)}")
print(f"历史消息: {state.values.get('messages', [])}")
# 如果任务未完成,可继续调用 graph.invoke(..., config) 从断点恢复
输出结果
===== 第一次运行 =====
完成步骤数: 3
消息: ['步骤1:已搜索资料', '步骤2:已分析数据', '步骤3:已生成报告']
===== 模拟中断后恢复 =====
当前步骤数: 3
历史消息: ['步骤1:已搜索资料', '步骤2:已分析数据', '步骤3:已生成报告']
AI 应用场景 :在需要长时间运行的 Agent 服务(如自动化数据分析、代码重构)中,检查点恢复是保证可靠性的关键。
thread_id是恢复的钥匙------相同 ID 即可从上次断点继续。
AI 应用场景速查表
| 知识点 | 核心用途 | 典型场景 |
|---|---|---|
| ReAct 模式 | 让 Agent 动态使用工具 | 搜索问答、自动化操作 |
| 缓冲记忆 | 记住最近对话 | 短期上下文感知 |
| 摘要记忆 | 压缩长对话历史 | 客服长对话 |
| 摘要缓冲记忆 | 缓冲+摘要折中 | 大多数生产环境首选 |
| 向量长期记忆 | 持久化个性化记忆 | 用户偏好学习 |
| 多 Agent 协作 | 复杂任务分工 | 调研报告、代码审查 |
| Plan-and-Execute | 提升任务规划能力 | 多步骤数据分析 |
| ReWOO | 减少 LLM 调用次数 | 步骤间依赖明确的任务 |
| 反思机制 | 自我纠错 | 提高任务成功率 |
| MCP 协议 | 工具标准化暴露 | 跨模型工具复用 |
| 会话持久化 | 断点续传 | 长任务可靠性 |
面试模拟题
1. 原理型:什么是 ReAct 模式?它和普通的 Chain 有什么区别?
答案要点:ReAct 将推理和行动交织在一起,通过思考-行动-观察循环动态决策。Chain 是固定的流程,不能根据中间结果调整后续步骤。ReAct 更灵活,但调用次数更多。生产环境推荐使用 Function Calling 实现,避免文本解析的不稳定性。
2. 场景型:你的 Agent 执行一个需要 10 步的任务,但经常在第 6 步出错后全部重来。如何改进?
答案要点:引入检查点机制(如 LangGraph + SqliteSaver),在每步后保存状态,出错后从最近的检查点恢复而非从头开始。同时加入反思机制,让 Agent 分析失败原因并仅修正失败步骤,而非全盘重来。
3. 原理型:Plan-and-Execute 相比 ReAct 有什么优势?ReWOO 又是什么?
答案要点:Plan-and-Execute 先生成全局计划再执行,减少了 LLM 调用次数,任务完成率更高。ReWOO 是其变体,在规划阶段预埋工具结果占位符,避免每步等待 LLM,进一步减少调用。两者都适合步骤清晰、依赖明确的任务。
4. 对比型:缓冲记忆、摘要记忆、摘要缓冲记忆和向量长期记忆各有什么优缺点?如何组合使用?
答案要点:缓冲记忆简单但占空间,适合短期;摘要记忆省 token 但丢失细节;摘要缓冲记忆结合两者,保留最近原文+压缩旧历史,是生产首选;向量记忆保细节且可扩展,但实现复杂。组合方式:短期用缓冲,长期用向量,折中用摘要缓冲。
5. 热点型:什么是 MCP 协议?它和 OpenAI 的 Function Calling 是什么关系?
答案要点:MCP 是模型上下文协议,标准化了 AI 模型与外部工具的交互方式(JSON-RPC),解决了工具接口碎片化问题。Function Calling 是模型端能力,让模型"表示"调用意图;MCP 是工具端标准,让工具能被任何模型发现和调用。两者互补:MCP 提供标准化工具接口,模型通过 Function Calling 决定调用哪个工具。
总结
从 ReAct 的思考-行动循环,到记忆管理、多 Agent 协作,再到 Plan-and-Execute 的全局规划和 MCP 的标准化工具暴露,你已掌握 Agent 开发的核心技术栈。面试中的高频考点------ReAct 的原理与改进、Plan-and-Execute 的规划-执行闭环、MCP 的协议细节------都已覆盖。现在你可以开始构建自己的智能体,并让它可靠地完成复杂任务了。