Appearance
五、AI Agent
本主题题目目录
点击题号跳转;右侧目录会高亮你正在阅读的题目
158. 什么是大模型 Agent?它与传统 AI 系统有什么不同?159. Agent 的核心组成是什么(感知/规划/记忆/行动)?165. 什么是工具调用(Tool Calling / Function Calling)?169. Agent 在工具调用过程中如何处理错误和重试?170. 什么是 MCP(Model Context Protocol)?172. MCP 和 A2A 协议有什么区别?它们如何协作?186. 什么是 LangChain?LangChain 包含哪些核心概念?195. LangChain 存在哪些问题及解决方案?有哪些替代方案?200. 什么是 LangGraph?它与 LangChain 有什么区别?
覆盖 Agent 基础、工具调用、MCP 协议、多 Agent 架构、记忆机制、LangChain/LangGraph,共 43 题(158-200)。
158. 什么是大模型 Agent?它与传统 AI 系统有什么不同?
👔面试官:说说你理解的 AI Agent 是什么?
🙋♂️我:Agent 就是给大模型加了插件,比如 ChatGPT 的插件功能,让它能联网搜索、调用 API 啥的。
👔面试官:插件是 Agent?那 ChatGPT 开了搜索功能就是 Agent 了?你说的只是工具调用,跟 Agent 差远了。
🙋♂️我:哦,那 Agent 就是能调用工具的大模型,给它配几个工具函数,它就能做更多事了。
👔面试官:还是工具调用。Agent 最核心的是什么?你有没有提到「自主」两个字?
🙋♂️我:自主……就是它自己决定调哪个工具?
👔面试官:还不够。自主规划、多步执行、感知结果再调整,这才是 Agent 的闭环。你给它个目标,它自己把任务拆成多步,一步一步做,每步结果反馈回来再指导下一步,这和普通调工具有本质区别。
被问懵了吧,其实答好这道题,抓住一个核心词就行:「自主闭环」。
💡 简要回答
我理解 Agent 本质上是一个能自主完成目标的 AI 系统,跟传统 AI 最核心的区别在于「自主性」和「能行动」。
传统 AI 是你问一个问题它回答一个问题,每次都是独立的,被动响应;而 Agent 有自己的规划能力,你给它一个复杂目标,它会自己把任务拆成多步,通过调工具、访问记忆、感知环境来一步步执行,直到完成。
它不只是输出文字,而是真的能做事。
📝 详细解析
普通大模型的局限性
要理解 Agent,得先说说普通大模型的局限性在哪。
你直接调用 GPT 的 chat 接口,它本质上是个「问答机器」,你给它一个输入,它给你一个输出,然后就结束了。就算是多轮对话,它也只是在当前上下文里被动响应你,它不会主动去做任何事,也不知道自己上一步做了什么、下一步该做什么。你可以把它想象成一个只会答题的人,你说一句它答一句,但让它「自己去查个资料再来汇报你」,它完全做不到。
那普通大模型到底差在哪?我们来一层一层拆。最直观的一个问题是「知识被冻结」,模型的训练数据有截止日期,你问它今天的天气、最新的股价,它完全不知道,因为它没有任何途径去获取实时信息。这就好比一个人毕业之后就再也不看新闻了,你问他今天发生了什么,他只能给你讲课本上的知识。
在「知识冻结」之上,还有一个更本质的问题:它「不能行动」。你让它帮你发邮件、帮你查数据库、帮你执行一段代码,它只能告诉你「你可以这样做」,但它自己做不到。为什么?因为它本质上就是一个文本生成器,所有输出都是一串文字,仅此而已。它能给你写出一封完美的邮件正文,但点那个「发送」按钮的事情,它是真做不了的。
而且更麻烦的是,就算你想让它帮你做一件稍微复杂点的事,比如「先查资料再整理成报告」,它也干不了,因为它「没有持续状态」。每次调用之间它是完全失忆的,除非你手动把之前的对话塞进去,不然它根本不记得上一轮说了什么,更别说跨任务去记住你的偏好了。
这三个局限一环扣一环:知识是死的,手脚是没有的,记忆也是断的。加在一起,意味着普通 LLM 只能做「一问一答」的事情,稍微复杂一点的、需要多步骤协作的任务,它就完全无能为力了。
Agent 特别在哪?
Agent 就完全不一样了。它有一个核心的运作闭环:感知 -> 规划 -> 行动 -> 再感知。
你给它一个目标,比如「帮我调研竞品然后整理成报告」,它不是直接输出一段文字了事,而是先拆解任务,我要搜索哪些关键词、我要访问哪些网站、我要怎么组织内容,然后一步一步去执行,每一步的结果又反馈回来,指导下一步怎么做。
这种能力背后,有三件核心的事在支撑,我一个一个讲。
第一件:工具调用(Tool Use),这是让 Agent 从「说话」变成「做事」的关键。Agent 能调用外部工具,比如搜索引擎、代码执行器、数据库、API 等等。不过这里有一个容易误解的地方:不是模型自己执行,而是模型「告诉你该调什么」,你的代码去真正执行,结果再反馈给模型。模型始终只是大脑,不是手脚。
为什么工具调用如此重要?因为它一下子突破了前面说的三个局限。知识被冻结?接上搜索引擎,模型就能获取实时信息。不能行动?接上邮件 API、代码执行器,模型就能真正做事。这就好比一个人原来只能用嘴说话,现在给他配了手、脚和各种工具,能力上限瞬间拔高了一个量级。
我来举个最具体的例子。假设你给 Agent 配了两个工具:查天气和发邮件,然后让它「帮我查一下北京天气,发邮件给老板」:
python
# 这里定义了两个工具,就像给 Agent 配了两个「技能说明书」
# 注意:这里没有一行真正执行的逻辑,只是告诉模型「我有哪些能力、需要哪些参数」
tools = [
{
"name": "get_weather",
"description": "获取指定城市的当前天气",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名称"}
},
"required": ["city"]
}
},
{
"name": "send_email",
"description": "发送邮件给指定收件人",
"parameters": {
"type": "object",
"properties": {
"to": {"type": "string"},
"subject": {"type": "string"},
"body": {"type": "string"}
},
"required": ["to", "subject", "body"]
}
}
]
# 你告诉 Agent:"帮我查一下北京天气,然后发邮件给 boss@company.com"
# Agent 不是一次性回答,而是分两步真正执行:
# 第一步:调用 get_weather(city="北京") -> 得到 "晴天 15°C"
# 第二步:调用 send_email(to="boss@company.com", subject="今日天气", body="北京今天晴天 15°C")
# 每一步都是真实发生的,不是在"假装"你看这段代码,工具定义里没有一行执行逻辑,只有「名字、描述、需要哪些参数」,本质上就是一份说明书。模型读了这份说明书,自己决定该调哪个工具、参数填什么,然后把决策以 JSON 格式告诉你,真正执行的还是你的代码。这个「决策和执行分离」的思想,是理解工具调用最核心的一点。
理解了工具调用之后,你会发现 Agent 光有「做事」的能力还不够,它还得「记事」。这就引出了第二件核心的事。
第二件:记忆机制。传统 LLM 每次对话都是「失忆」的,除非你手动传上下文,不然它完全不记得上一次说了什么。而 Agent 系统通常会设计短期记忆和长期记忆两层。短期记忆就是当前任务执行过程中的中间状态,比如第一步搜索到了什么、第二步计算结果是多少,这些都存在上下文里,保证 Agent 不会做到一半忘了前面发生了什么。长期记忆则是跨任务的,比如用户的偏好、历史操作记录,通常用向量数据库来存储,需要的时候做语义检索拿回来。有了这两层记忆,Agent 在执行复杂任务时才能保持连贯性,不会走着走着忘了目标是什么。
那有了工具能做事、有了记忆能记事,Agent 就完整了吗?还差最后一块拼图,也是 Agent 最像「人」的地方。
第三件:多步推理和自我纠错。这一点经常被忽略,但其实是 Agent 区别于简单自动化脚本的关键。
Agent 在执行过程中如果某一步失败了,它不会直接崩掉,而是能感知到失败、分析原因、换一种方式重试。比如用关键词 A 搜索没找到有用信息,它会自己换关键词 B 再搜一次;调用某个 API 报错了,它会看报错信息然后调整参数重新调用。
这就像一个真正在「思考」的执行者,碰到障碍会绕路走,而不是一条路走到黑。更进一步,它还能在完成某一步之后回头审视:我做的这步对不对?结果和预期一致吗?要不要调整后续的计划?
这种「边做边反思」的能力,让 Agent 在面对复杂、不确定的任务时,表现远比死板的自动化流程好得多。
讲完这三件事,我们用一个最直观的场景来感受一下差距。你让一个普通 LLM「帮我发一封天气播报邮件」,它能做的只是告诉你「你可以这样写代码……」;而一个 Agent,它会真的去调天气 API、拿到数据、组织邮件内容、再调邮件发送接口,整个过程自动完成。这就是本质区别:从生成文字,到执行任务。
为什么 Agent 现在才爆发?
你可能会问,Agent 的概念其实很早就有了,为什么到 2024、2025 年才真正火起来?原因是三个条件在最近几年同时成熟了。
第一个条件是大模型的能力跨过了「能用」的门槛。早期的语言模型理解能力有限,你让它做任务拆解、判断下一步该调哪个工具,它根本做不好。但从 GPT-4、Claude 3 这一代开始,模型的推理能力、指令遵循能力有了质的飞跃,它真的能「读懂」复杂指令并做出合理的多步决策了。
第二个条件是工具调用的标准化。OpenAI 在 2023 年推出了 Function Calling 机制,让模型能以结构化的 JSON 格式输出工具调用请求,这个标准很快被各家模型厂商跟进。有了统一的工具调用协议,开发者才能方便地给模型接上各种外部能力,不然每接一个工具都要自己写一套解析逻辑,工程成本太高。
第三个条件是配套生态的完善。LangChain、LlamaIndex 这些框架把 Agent 的开发门槛大幅降低了,向量数据库解决了长期记忆的存储问题,各种 API 服务让可调用的工具越来越丰富。三个条件凑齐,Agent 从论文概念变成了工程实践,这才有了现在的爆发。
Agent 生态的最新趋势
Agent 火起来之后,一个很自然的问题就冒出来了:Agent 越来越多,它们之间怎么协作?工具越来越多,怎么统一管理?这两个问题催生了两个非常重要的标准协议,面试里被问到的概率很高,值得了解一下。
第一个是 Anthropic 在 2024 年底提出的 MCP(Model Context Protocol,模型上下文协议)。你可以把 MCP 理解成 Agent 工具世界的「USB-C 接口」。
在没有 MCP 之前,每个 Agent 框架接每个工具都要写一套适配代码,假设有 M 个 Agent 框架和 N 个工具,就需要 M x N 套适配逻辑,工程成本非常高。MCP 的做法是定义一套标准的 JSON-RPC 协议,工具提供方只要按这个标准暴露自己的能力(变成一个 MCP Server),任何支持 MCP 的 Agent(通过内置的 MCP Client)都能直接发现和调用这些工具,不需要额外写适配代码。
MCP 的架构分三层:最外层是 Host(就是用户直接交互的 AI 应用,比如 Claude Desktop、Cursor 这些),中间是 Client(负责和 MCP Server 建立连接、管理通信),最里层是 Server(真正暴露工具能力的服务)。
2025 年 12 月 Anthropic 把 MCP 正式捐给了 Linux 基金会旗下新成立的 Agentic AI Foundation(AAIF),由 Anthropic、Block、OpenAI 共同创立,Google、Microsoft、AWS、Cloudflare、Bloomberg 等都表示支持,生态发展非常快,目前已经有数千个公开的 MCP Server 可用。
第二个是 Google 在 2025 年 4 月推出的 A2A(Agent2Agent,Agent 间通信协议)。如果说 MCP 解决的是「Agent 怎么调用工具」的问题,A2A 解决的就是「Agent 怎么和另一个 Agent 协作」的问题。
在多 Agent 系统里,不同的 Agent 可能来自不同的厂商、用不同的框架开发,它们之间怎么互相发现对方的能力、怎么协调任务、怎么传递中间结果?A2A 的核心设计是一个叫 Agent Card 的概念,每个 Agent 都有一张「名片」,上面写着它能做什么、正在做什么、需要什么输入,其他 Agent 读了这张名片就知道该怎么跟它协作。
A2A 在 2025 年 6 月被 Google 捐给了 Linux 基金会维护,SAP、Salesforce、ServiceNow 等大厂都在接入。
这两个协议的关系其实是互补的:MCP 管的是 Agent 和工具之间的连接,A2A 管的是 Agent 和 Agent 之间的通信。你可以这样理解,MCP 让每个 Agent 都能方便地「伸手拿工具」,A2A 让不同的 Agent 能方便地「互相说话合作」。未来的 Agent 生态大概率是两个协议同时存在、各管一层的格局。面试的时候能把这两个协议的定位和区别说清楚,会非常加分。
🎯 面试总结
回顾开头的对话,踩了三个典型的雷。
第一个雷是把 Agent 等同于「插件」或「工具调用」,这是最常见的误区,工具调用只是 Agent 能力的一部分,不是 Agent 本身。
第二个雷是停在「能调工具」这一层,没有点出自主性,Agent 的关键不是「有工具」,而是「自己决定用不用、什么时候用、用哪个」。
第三个雷是忽略了执行闭环,感知 -> 规划 -> 行动 -> 再感知这个循环才是 Agent 区别于普通 LLM 的核心机制。
面试时答这道题,一定要点出三件事:一是 Agent 有自主规划能力,给它一个复杂目标它能自己拆解成多步;二是它能行动,通过工具调用跟外部世界真实交互;三是它有闭环,每步的结果会反馈回来指导下一步,而不是一次性生成完就结束。另外还要提一句容易混的点:模型本身只是「大脑」,工具的真正执行是你的代码,模型只负责决策。
159. Agent 的核心组成是什么(感知/规划/记忆/行动)?
👔面试官:Agent 架构里有哪些核心组件?
🙋♂️我:有 LLM 和工具系统,LLM 是大脑,工具让它能联网搜索、执行代码这些。
👔面试官:就两个?一个 Agent 跑起来,任务执行到一半它怎么知道之前做了什么?
🙋♂️我:哦,还有记忆,就是把上下文存进去,让它记得之前的步骤。
👔面试官:记忆就是上下文吗?长任务上下文放不下怎么办?记忆还分哪几种你知道吗?
🙋♂️我:这个……可能还有数据库存历史记录?
👔面试官:对,短期记忆放 context window,长期记忆用向量数据库存,两者不一样。还有一个组件你一直没提,复杂目标怎么拆解成步骤,靠谁?
好,咱来系统捋一下,Agent 的四个核心组件各自负责什么、为什么缺一不可。
💡 简要回答
我理解 Agent 的基本架构有四个核心组件:LLM、工具、记忆、规划模块。
LLM 是整个系统的大脑,负责理解任务和做决策;工具让 Agent 能跟外部世界交互,搜索、执行代码、调 API 都靠它;记忆让 Agent 在任务执行过程中保持状态,不会「失忆」;规划模块负责把复杂目标拆解成可执行的步骤。
这四个组合在一起,才让 Agent 具备了自主完成任务的能力。
📝 详细解析
理解了 Agent 是什么之后,我们来看它的内部结构,一个完整的 Agent 系统,到底由哪几个核心部件组成。
你可以把整个 Agent 系统类比成一家公司:LLM 是老板,所有决策都经过它拍板;工具系统是外包执行团队,老板说「去搜这个」「去发这封邮件」,他们负责真正干活;记忆系统是公司档案室,各种信息的存档和调档都靠它;规划模块是项目经理,拿到一个大目标后负责拆解成可执行的任务单。四个角色各司其职,才撑起了 Agent 的自主运行能力。
先来说 LLM 核心。它是整个 Agent 的大脑,所有的输入,不管是用户的指令、工具返回的结果还是记忆里调出来的内容,最终都要经过 LLM 来理解和决策。它负责判断:下一步该做什么?是继续思考、调用某个工具、还是已经可以给出最终答案了?没有 LLM,其他三个组件就是一堆零件,没有人来统一指挥。
不过很多人忽略了一个重要的东西:System Prompt(系统提示词)。你可以把它理解成给老板的「岗位说明书」,在 Agent 开始工作之前,System Prompt 就已经定义好了它的角色、行为边界、输出格式要求等等。比如你做一个客服 Agent,System Prompt 里会写「你是一个专业的客服助手,只回答产品相关问题,遇到不确定的信息要说不知道,不要编造答案」。这段话看着简单,但它决定了 Agent 的「人格」和行为准则,写得好不好直接影响 Agent 的表现。实际工程里,System Prompt 的调优往往占了开发时间的相当大一部分,因为它是你能最直接控制 Agent 行为的手段。
另一个实际工程中非常重要的问题是:选哪个模型?不同模型之间的差异远比你想象的大。首先是推理能力,Agent 需要做多步决策,模型的推理能力直接决定了它能不能正确拆解任务、选对工具。像 GPT-4o、Claude Sonnet 这类模型在复杂推理上的表现就比小模型好很多,但调用成本也更高。这里还有一个趋势值得关注:专门为推理优化的模型越来越多了,比如 OpenAI 的 o1/o3 系列、DeepSeek-R1 这类推理模型(Reasoning Model),它们在做复杂任务拆解和多步决策时的表现会更好,特别适合做 Agent 的大脑。但推理模型的代价是延迟更高、token 消耗更大,所以不是所有场景都适合用。一个常见的工程做法是:用推理能力强的大模型做核心决策(比如任务规划、关键判断),用更快更便宜的小模型做简单任务(比如意图分类、格式提取),根据不同环节的需求来搭配。
其次是工具调用的稳定性,有些模型生成的 JSON 格式经常出错、参数乱填,导致工具调用失败,这在生产环境里会带来大量的重试和 token 浪费。最后是上下文窗口大小,Agent 每一步的工具返回结果都要塞进上下文,一个复杂任务跑十几步下来,上下文很容易就撑满了,如果模型的窗口太小,后面的步骤就「看不到」前面发生了什么。所以在实际项目里,模型选择不是一个「越贵越好」的问题,而是要根据任务复杂度、延迟要求、成本预算来做权衡。
然后是 工具系统,这是 Agent 和外部世界交互的唯一入口。LLM 本身是个纯粹的「语言处理器」,它不能上网、不能读文件、不能执行代码,但这些限制都可以通过工具来突破。工具可以是搜索引擎、数据库查询、代码执行器、发邮件的 API,任何你能用函数封装的能力都可以变成工具。
工具是怎么定义的?我给你看一个最标准的格式:
python
# 定义工具的结构(以 OpenAI function calling 格式为例)
# 你只需要告诉模型三件事:工具叫什么名、能做什么事、需要哪些参数
tools = [
{
"type": "function",
"function": {
"name": "search_web",
"description": "搜索互联网上的信息",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "搜索关键词"
}
},
"required": ["query"]
}
}
}
]
# LLM 决定调用工具时,会返回类似这样的结构:
# {"tool_call": {"name": "search_web", "arguments": {"query": "2024年大模型最新进展"}}}
# 然后你的代码负责真正执行这个搜索,把结果再塞回给 LLM你看,工具定义里没有一行执行逻辑,只有「名字、描述、参数说明」。模型读了这份说明书,决定要调哪个工具、参数填什么,把决策以 JSON 格式告诉你,你的代码去真正执行,结果再反馈给模型。整个分工很清晰:模型负责「决定做什么」,程序负责「真正执行」。
这里还有一个非常容易被忽略的点:工具描述的质量直接影响 Agent 的表现。模型是根据你写的 description 来判断「什么时候该用这个工具」的,如果描述写得含糊、有歧义,模型就可能在不该用的时候调了它,或者该用的时候没调。举个例子,你有一个查数据库的工具,如果 description 只写了「查询数据」,模型可能在用户问天气的时候也去查数据库,因为「查询数据」太宽泛了。但如果你写成「查询公司内部销售数据库,支持按日期、产品类别筛选」,模型就能精确判断什么场景该用它。所以在实际开发中,工具描述其实是需要反复调优的,它的重要性不亚于 prompt 工程。
当工具越来越多的时候,管理和标准化就变成了一个大问题。Anthropic 在 2024 年底提出了 MCP(Model Context Protocol,模型上下文协议),它底层是一套基于 JSON-RPC 的通信协议,不只是把「工具」标准化了,还定义了三类能力:Tools(会改变外部世界的操作,比如发邮件)、Resources(只读的数据源,比如文件内容)、Prompts(预定义的提示词模板,比如代码审查模板)。MCP 的架构分三层:最外层是 Host,就是用户交互的 AI 应用(比如 Claude Desktop、Cursor);中间是 Client,负责管理和 MCP Server 之间的连接;最里层是 Server,就是真正暴露这些能力的服务端。工具提供方只要按 MCP 标准实现一个 Server,任何支持 MCP 的 Agent 都能自动发现和调用这些能力,不需要额外写适配代码。你可以把 MCP 理解成工具世界的「USB-C 接口」,只要插口标准一致,什么设备都能连上。2025 年 12 月,Anthropic 把 MCP 捐给了 Linux 基金会旗下新成立的 Agentic AI Foundation,生态已经非常壮大,数千个公开 MCP Server 可用。
接下来是 记忆系统,它分几个层次,你可以类比人的记忆方式来理解。
最基础的是短期记忆,就是当前这轮对话的上下文,装在 context window 里。Agent 在一次任务执行过程中靠它记住中间状态,比如第一步搜索到了什么、第二步执行结果是什么。这就像人的「工作记忆」,容量有限,任务一结束就清空了。
然后是长期记忆,通常用向量数据库来实现,把重要信息 embedding 之后存起来,下次用的时候做语义检索拿回来。这就像人的「长期记忆」,容量大、可以跨天保留,但需要主动「回忆」才能调出来。这里借用认知科学对人类长期记忆的分类来理解 Agent 长期记忆的组织方式(注意这只是便于理解的类比,Agent 系统里的实现通常都是向量检索 + metadata 过滤,不一定真的分这么细):语义记忆(Semantic Memory)存的是事实性知识,比如「用户是做金融行业的」「某个 API 的调用频率限制是每分钟 60 次」;情景记忆(Episodic Memory)存的是具体的经历,比如「上次用户问退款问题时我们查了订单系统,发现他的订单已过退款期」;还有一种是程序性记忆(Procedural Memory),存的是「怎么做事」的经验,比如「处理退款问题的标准流程是先查订单状态再核实支付方式」。程序性记忆特别有意思,它不是存某个具体的事实,而是把 Agent 做事的方法论沉淀下来,让它下次碰到类似任务时能直接套用高效的处理流程,而不是每次都从头摸索。
不过记忆系统在工程实践中有几个挑战是经常被忽视的。短期记忆最大的问题是上下文窗口有限,一个复杂任务执行十几步,每步工具返回大量文本,上下文很快就满了,这时候你要么做摘要压缩(把前面的步骤浓缩成关键信息),要么做滑动窗口(只保留最近几步的详细内容),但不管哪种方式都会丢失信息,怎么在「记住够多」和「不撑爆上下文」之间取舍,是记忆工程里最核心的设计问题。
长期记忆的挑战则在于「什么该存、什么不该存」以及「存了之后怎么管理」。如果什么都往向量数据库里塞,检索出来的噪音会很多,反而干扰模型的决策;如果存得太少,又失去了长期记忆的意义。目前比较好的做法是在存入之前做一轮重要性评估,只把真正有价值的信息持久化。另外还有一个容易忽略的机制叫记忆衰减(Memory Decay)。你想想看,一个客服 Agent 三个月前处理的某个问题,到今天还重要吗?大概率已经不重要了。记忆衰减的做法是给每条记忆加一个时间权重,越久远的记忆权重越低,检索的时候自然就排在后面了。具体实现上通常用一个指数衰减公式:记忆的相关性分数 = 语义相似度 x 时间衰减因子,时间衰减因子随着时间推移逐渐降低。衰减速度可以根据业务场景调整,比如客服场景衰减可以快一些(因为大多数对话是一次性的),而法律合规场景衰减就要慢得多(因为历史记录可能在很久之后还会被引用)。这样 Agent 就不会被一堆过时的信息淹没,总是优先关注最近的、最相关的记忆。
最后是 规划模块,它决定了 Agent 能不能应对复杂任务。简单任务一步就搞定了,但如果你让 Agent「帮我写一份竞品分析报告」,它需要先把这个目标拆解:搜索竞品资料 -> 整理关键数据 -> 对比分析 -> 撰写报告。规划模块就是做这件事的。
规划模块的底层其实依赖的是 LLM 的推理能力,而提升推理能力有几种主要的技术手段。最基础的是 CoT(Chain of Thought,思维链),它的核心思想是让模型「把思考过程写出来」,而不是直接输出最终答案。你可以在 prompt 里加一句「Let's think step by step」,模型就会把推理的中间步骤一步步展开。为什么这么简单一句话就能提升效果?因为 LLM 的 token 生成是逐步进行的,每一步推理的输出会成为下一步推理的输入,把中间步骤写出来,等于给了模型更多的「思考空间」。在此基础上还有 ToT(Tree of Thoughts,思维树),它不是走一条线性的推理链,而是在每个推理节点上展开多个可能的分支,然后评估每个分支的质量,选出最优的路径继续往下走。你可以把 CoT 理解成「一条路走到底」,ToT 理解成「走到岔路口先看看几条路,选最好的那条再往前」。ToT 在需要创造性思考或者复杂决策的场景下效果更好,但计算成本也更高,因为它要同时评估多条路径。
有了这些推理技术打底,规划模块在实际运作中有两种主流模式。第一种是「先规划后执行」,也就是 Plan-and-Execute 模式,先让 LLM 输出一个完整的步骤列表,然后按顺序逐步执行。好处是整体结构清晰,你能在执行前就看到完整计划,方便人工审核;缺点是如果中间某一步的结果和预期不一样,原来的计划可能就不合适了,需要重新调整。第二种是「边执行边规划」,也就是 ReAct 模式,每走一步就根据当前结果重新思考下一步该做什么,不提前制定完整计划。好处是灵活性极高,能根据实际情况随时调整;缺点是容易「走偏」,因为每一步都是局部最优决策,有时候会忽略整体目标。在实际工程里,很多团队会把两种模式结合起来,先做一个粗略的计划确定大方向,执行过程中再根据反馈动态微调。
这四个组件合在一起,到底是怎么跑起来的?我用一段伪代码来还原整个运行过程,看完你就能理解它们是怎么协作的:
python
# Agent 运行的核心 loop(伪代码)
def agent_run(user_goal: str):
# 第一步:规划模块上场,把目标拆成步骤列表
plan = llm.plan(user_goal)
memory = [] # 短期记忆,用来存每一步的中间结果
for step in plan:
# 第二步:LLM 核心做决策,这一步该怎么做?
action = llm.decide(
step=step,
history=memory, # 把短期记忆传进去,让它知道之前做了什么
long_term=vector_db.search(step) # 从长期记忆里捞出相关历史
)
if action.type == "tool_call":
# 第三步:工具系统负责真正执行
result = tools.execute(action.tool_name, action.args)
memory.append({"step": step, "result": result}) # 执行结果存入短期记忆
elif action.type == "final_answer":
return action.content # LLM 判断任务完成,返回最终答案看完这段伪代码,你会发现 Agent 的核心节奏其实很简单:规划 -> 决策 -> 执行 -> 结果存入记忆 -> 再决策,循环往复,直到任务完成。LLM 始终是那个做决策的角色,工具系统是执行者,记忆系统让它不会「失忆」,规划模块帮它把大目标拆成小步骤。
LangChain、LlamaIndex、AutoGen 这些主流框架,本质上都是围绕这四个组件来设计的,只是封装方式和侧重点各有不同。
🎯 面试总结
开头对话里踩了三个雷,面试时都要注意避开。
第一个雷是漏掉组件,很多人只说 LLM 和工具两个,把记忆和规划模块忘了,但这两个恰恰是让 Agent 能跑复杂任务的关键。
第二个雷是对记忆的理解太浅,「记忆就是上下文」这个回答不完整,正确的说法是记忆分两层:短期记忆放在 context window 里,存当前任务的中间状态;长期记忆用向量数据库实现,能跨任务保存用户偏好和历史,两者机制和用途完全不同。
第三个雷是工具系统的分工理解有偏差,模型本身不执行工具,它只是输出「调哪个工具、传什么参数」的决策,真正执行是你的代码,这个「决策和执行分离」的设计是面试里很容易被追问的点。
答好这道题,能把四个组件和类比(LLM 是老板、工具是外包团队、记忆是档案室、规划是项目经理)结合起来说,会非常加分。
165. 什么是工具调用(Tool Calling / Function Calling)?
👔面试官:来聊聊 Function Calling 吧,说说它是什么、原理是怎样的?
🙋♂️我:Function Calling 就是让大模型直接调用 API 嘛,模型自己去访问网络、查数据库,然后把结果返回给用户。
👔面试官:模型「自己」去访问网络?你确定?模型有直接执行代码的能力吗?它能联网吗?
🙋♂️我:呃……那应该是模型输出一段调用指令,然后……后端代码去执行?但我记得好像模型输出的就是自然语言,开发者自己解析文本去调用的吧?
👔面试官:你说的那是 Function Calling 出现之前的土办法,靠 if/else 解析自然语言,极其脆弱。Function Calling 的核心就是模型输出结构化的 JSON,而不是自然语言。你连「模型只负责决策、代码负责执行」这个最基本的分工都没搞清楚,工具定义的 schema 怎么写、两轮对话的流程是什么、并行调用怎么做,这些都回去好好看看吧。
好,这段面试踩的雷还挺典型的,很多人一上来就把「模型决策」和「代码执行」搞混了。下面我把 Function Calling 的完整机制拆开讲清楚。
💡 简要回答
Function Calling 我的理解是这样一套机制:开发者用 JSON schema 把工具描述好传给模型,模型判断需要调工具的时候不输出自然语言,而是直接输出一段结构化的 tool_calls JSON,告诉你「我要调哪个函数、参数是什么」,你的代码拿到这段 JSON 去真正执行,把结果塞回对话,模型再生成最终答案。
整个流程本质上是两轮对话:第一轮模型说「我需要调这个工具」,你去执行,第二轮模型拿到执行结果说「答案是这个」。
我觉得最核心的设计是,模型全程只做决策,执行的事情一律由宿主代码完成,职责分得很清楚。
📝 详细解析
背景,Function Calling 解决了什么问题
LLM 在没有 Function Calling 之前,想让模型帮你调工具,完全靠解析自然语言。模型输出「我需要查一下北京的天气」,你再写 if/else 判断它「说」的是要查天气,然后手动去调 API。这个做法极其脆弱,模型换个说法,你的 if/else 就失配了,也根本没办法标准化。
Function Calling 的出现把这件事固定下来了:模型不再「说」要调工具,而是直接输出一段结构化的 JSON,开发者按格式解析就行,准确率大幅提升,也有了统一标准可以对接。这套机制由 OpenAI 在 2023 年推出,现在 Claude、Gemini、Qwen 等主流模型都支持。
三个角色,把 Function Calling 理解成一场任务委托
理解 Function Calling 的关键是搞清楚谁做什么。可以把这套流程理解成一场「任务委托」:
开发者是 HR,负责给每个工具写「职位说明书」,就是 JSON schema,告诉模型「我们有哪些工具、每个工具能做什么、需要哪些参数」。模型是经理,读完说明书之后决定「这个任务需要调哪个工具、参数填什么」,然后把指令下达出来。你写的代码是员工,真正去跑函数、访问网络、查数据库,把结果汇报回来。
关键点:模型全程只是在「下指令」,它不亲自执行任何代码,也没有直接访问网络的权限。执行的事一律由宿主程序代码完成,这个分工要想清楚。
工具定义,schema 的每个字段都有含义
工具 schema 就是一份结构化的「工具说明书」,用 JSON 格式写,告诉模型这个工具叫什么、能做什么、需要哪些参数。
python
tools = [
{
"type": "function",
"function": {
"name": "get_weather", # 工具的唯一标识,模型输出 tool_calls 时会用这个名字
"description": "查询指定城市的实时天气,包含气温、天气状况、风向风速,仅支持中国大陆城市",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,如「北京」「上海」,不要带省份前缀"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "温度单位,默认用摄氏度"
}
},
"required": ["city"]
}
}
}
]其中最关键的字段是 description。这里停一下,你想一个问题:如果 description 写得含糊,模型会怎么表现?答案是它会「瞎猜」,比如你只写「获取天气」,模型可能拿到一个带英文名的城市也照样调,拿到「这周天气如何」这种时间跨度不对的问题也硬往里塞,调完之后发现返回的数据根本对不上。模型在决定「要不要调这个工具、参数怎么填」的时候,能依赖的唯一依据就是这段描述。
你可以对比一下:「获取天气」和「查询指定城市的实时天气,包含气温、天气状况、风向风速,仅支持中国大陆城市」,对模型判断准确率的影响差距是很明显的。写得越清晰,模型的选择越准确。参数的 description 同理,格式要求、示例值、限制条件都要写进去,模型才能正确填写参数。
完整的调用流程,两轮对话加中间执行
Function Calling 的运行时本质上是「两轮对话 + 中间执行」的闭环。
第一轮,你把工具列表和用户的问题一起传给模型。模型读完之后,如果判断需要调工具,就不直接输出最终答案,而是输出一个 finish_reason 为 "tool_calls" 的响应,里面包含要调用的工具名和参数,这是个明确信号,告诉你「我需要工具帮助,还没准备好给答案」。
拿到这个信号之后,中间环节就交给你的代码了。你的代码解析 tool_calls 拿到函数名和参数,找到对应的函数跑一下,拿到执行结果。
然后进入第二轮,把工具执行结果以 role: "tool" 的消息塞回对话历史,再次调用模型。这次模型有了工具结果,有了充分信息,才给出最终的自然语言答案。
python
import openai, json
client = openai.OpenAI()
messages = [{"role": "user", "content": "北京今天天气怎么样?"}]
# 第一轮:把工具定义和问题一起传给模型
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools,
tool_choice="auto" # auto 让模型自己判断要不要调,也可以设 required 强制调
)
msg = response.choices[0].message
if msg.finish_reason == "tool_calls": # 模型要调工具,还没给最终答案
tool_call = msg.tool_calls[0]
func_args = json.loads(tool_call.function.arguments) # {"city": "北京"}
# 中间执行:你的代码真正去跑函数
result = f"{func_args['city']}今天晴,15°C,东北风 3 级"
# 第二轮:把工具结果塞回对话,再问一次模型
messages.append(msg)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id, # 和 tool_calls 里的 id 对应
"content": result
})
final = client.chat.completions.create(model="gpt-4o", messages=messages, tools=tools)
print(final.choices[0].message.content)
# 输出:北京今天天气晴朗,气温 15°C,东北风 3 级,适合外出。并行工具调用
看完单个工具的流程,再来想一个问题:如果用户一口气问了好几件事,比如「帮我查北京、上海、广州三个城市的天气」,模型一次该调一个工具,还是可以同时调多个?这就要说到 Function Calling 的并行调用设计。
当用户的问题需要多个工具才能回答时,模型可以在一次响应里同时输出多个 tool_calls,tool_calls 是个列表,不只有一条。比如用户问「帮我查北京和上海的天气」,模型会一次返回两个调用请求,分别对应两个城市。
为什么要这么设计?你想一下,如果没有并行调用,模型先说「我要查北京天气」,你执行完喂回去,模型再说「我要查上海天气」,又一轮对话,这样两个工具串行要跑两轮,每一轮都有一次模型推理和一次工具执行的延迟,总耗时很长。有了并行调用,模型一次把两个调用请求都输出来,你的代码可以同时执行这两个工具(比如用 Python 的 asyncio.gather 或者多线程),拿到所有结果后一次性塞回对话,再调一次模型就拿到综合了多个工具结果的最终答案。整个过程从「两轮对话」压缩成了「一轮对话 + 并行执行」,总耗时大幅降低。
不过要注意一点,并行调用的前提是这几个工具之间没有依赖关系。查北京天气和查上海天气互不影响,可以并行;但如果是「先查用户的订单号,再用订单号去查物流」,第二个调用依赖第一个的结果,这种就只能串行,模型也会正确地分两轮来输出。
🎯 面试总结
回头看开头的面试对话,踩的雷其实很典型。
第一个误区是以为模型能「自己」去访问网络、执行代码,这是对 Function Calling 最常见的误解。面试时一定要强调:模型全程只负责决策,输出结构化的 JSON 调用请求,真正执行工具的是你的宿主程序代码,这个职责分工是整个机制的核心设计。
第二个误区是把 Function Calling 和之前靠解析自然语言调工具的「土办法」搞混了,Function Calling 的关键改进就是模型直接输出结构化 JSON 而非自然语言,让工具调用有了统一标准。
面试回答这道题,有几个点必须说到:工具定义用 JSON schema 描述,description 字段是模型判断是否调用的核心依据;运行时是「两轮对话 + 中间执行」的闭环流程;模型通过 finish_reason 为 tool_calls 来明确告知需要工具帮助;以及模型支持一次返回多个 tool_calls 实现并行调用。
把这几个点讲清楚,再强调「模型决策、代码执行」的分工原则,这道题就稳了。
166-168. Spring AI 工具调用与工具管理
👔面试官:Spring AI 里的工具调用是怎么实现的?和直接用 OpenAI API 有什么区别?
🙋♂️我:Spring AI 就是封装了一下吧,本质上还是调 OpenAI 的接口。
👔面试官:封装当然是一方面,但 Spring AI 的工具调用有什么特别的设计?比如怎么把一个 Java 方法暴露给 LLM?
🙋♂️我:应该是写个接口定义什么的?或者用配置?
👔面试官:用注解。Spring AI 的亮点是用 @Tool 注解直接把普通 Java 方法变成 LLM 可调用的工具,不需要手写 JSON Schema。而且框架帮你管了整个调用链路,包括解析 tool_calls、分发到对应方法、把结果塞回对话。这些都回去好好了解一下。
💡 简要回答
Spring AI 的工具调用设计得很简洁:用 @Tool 和 @ToolParam 注解直接标记 Java 方法,框架自动帮你生成 JSON Schema,并且处理整个调用分发流程。
你不需要手动写 schema、不需要自己解析 LLM 返回的 tool_calls、不需要把工具结果组装成消息再调 LLM。Spring AI 全包了,你要做的只是写业务逻辑。
📝 详细解析
核心机制:注解驱动的工具定义
Spring AI 最牛的地方在于,它让工具定义变得极其简单。一个普通的 Java 方法,加两个注解就能变成 LLM 可调用的工具:
java
@Service
public class WeatherService {
@Tool(description = "获取指定城市的实时天气信息,包括气温、天气状况、风速")
public String getWeather(
@ToolParam(description = "城市名称,如北京、上海,不要带省份") String city,
@ToolParam(description = "温度单位,celsius或fahrenheit", required = false) String unit
) {
// 你的业务逻辑:调天气 API、查数据库...
return weatherApi.query(city, unit);
}
}注意这里有几个关键设计:
@Tool注解加在方法上,description 参数就是告诉 LLM 这个工具能做什么。这跟手写 JSON schema 里的 description 是同一个作用,但用注解写起来更自然。@ToolParam注解加在参数上,告诉 LLM 每个参数是什么意思、是不是必填。Spring AI 会自动从这些注解里提取信息,生成符合 OpenAI Function Calling 规范的 JSON schema。方法签名完全自由,返回值可以是 String、可以是对象,Spring AI 会自动序列化成 LLM 能理解的格式。
完整调用流程
有了工具定义,怎么用起来?Spring AI 的 ChatClient 设计得很巧妙:
java
@Configuration
public class AgentConfig {
@Bean
public ChatClient chatClient(ChatModel chatModel, WeatherService weatherService) {
return ChatClient.builder(chatModel)
.defaultTools(weatherService) // 注册工具,可以注册多个
.defaultSystem("你是一个有用的助手,可以使用工具帮助用户")
.build();
}
}
@RestController
public class ChatController {
@Autowired
private ChatClient chatClient;
@PostMapping("/chat")
public String chat(@RequestBody String userMessage) {
return chatClient.prompt()
.user(userMessage)
.call() // 这里 Spring AI 自动处理:LLM 决策 → 调工具 → 结果返回 → 生成最终答案
.content();
}
}用户发「北京今天天气怎么样?」,整个流程是这样的:
- Spring AI 把
WeatherService里的方法转成 JSON schema,和 prompt 一起发给 LLM - LLM 返回 tool_calls,说要调
getWeather,参数{"city": "北京"} - Spring AI 自动解析,找到
WeatherService.getWeather方法,反射调用 - 拿到结果「晴天 25°C」,塞进对话历史作为 tool 消息
- 再次调用 LLM,LLM 生成最终自然语言回答
你写的代码里完全看不到这些步骤,Spring AI 全包办了。这就是框架的价值:把通用流程抽象掉,你只关心业务逻辑。
工具管理:数量多了怎么办
实际项目里工具可能很多,几十个甚至上百个。全塞给 LLM 会有两个问题:一是 prompt 太长浪费 token,二是工具太多 LLM 决策容易出错。
Spring AI 提供了几种管理方式:
1. 按功能分组
java
@Bean
public List<Object> coreTools() {
// 通用工具:搜索、计算、日历等
return List.of(new SearchTool(), new CalculatorTool(), new CalendarTool());
}
@Bean
public List<Object> domainTools() {
// 业务工具:数据库查询、报表生成等
return List.of(new DatabaseQueryTool(), new ReportGeneratorTool());
}
// 不同场景用不同工具集
chatClient.prompt()
.tools(coreTools()) // 只用核心工具
.user(question)
.call();2. 动态工具选择(RAG 检索)
java
// 先把所有工具的 description 做 embedding 存向量库
// 用户提问时,检索最相关的几个工具
List<Tool> relevantTools = toolRetriever.findRelevant(userQuery, topK=5);
chatClient.prompt()
.tools(relevantTools) // 只把相关的 5 个工具传给 LLM
.user(userQuery)
.call();这跟 RAG 检索文档的原理一样,只不过检索的是工具描述。好处是 LLM 看到的工具少了,决策更准确,token 也省下来了。
🎯 面试总结
面试时说 Spring AI 工具调用,要抓住这几个点:
- 注解驱动:
@Tool+@ToolParam注解让工具定义变得极其简单,不需要手写 JSON schema - 全流程封装:从 schema 生成、tool_calls 解析、方法分发、结果组装,Spring AI 全包了
- 工具管理:工具多了要分组管理或动态选择,避免一次性塞太多工具给 LLM
对比直接用 OpenAI API 的好处是:你不用关心 Function Calling 的协议细节,写 Java 方法就行。缺点是灵活度稍低,如果要做一些特殊的自定义逻辑,可能需要绕过框架。
169. Agent 在工具调用过程中如何处理错误和重试?
👔面试官:Agent 调工具失败了怎么办?你怎么设计错误处理机制?
🙋♂️我:失败了就重试呗,重试几次还不行就返回错误给用户。
👔面试官:什么错误都能重试吗?参数错误你重试有用吗?而且 Agent 的核心是自主决策,工具失败了直接给用户报错,不是浪费 LLM 的推理能力吗?
🙋♂️我:那……把错误信息给 LLM,让它自己决定怎么办?
👔面试官:这才对。但具体怎么给?错误信息要包含哪些内容才能让 LLM 做出正确决策?还有,怎么防止 Agent 在失败和重试之间死循环?
💡 简要回答
工具调用错误处理我分三层:
第一层是自动重试,网络超时这种瞬态错误用指数退避重试几次;
第二层是错误反馈给 LLM,把失败信息清晰地返回给模型,让它重新规划(换工具、改参数、或者直接告诉用户);
第三层是兜底保护,设置最大步数和超时限制,防止无限循环。
📝 详细解析
第一层:自动重试,处理瞬态错误
不是所有错误都值得重试。网络超时、服务暂时不可用(503)这类瞬态错误可以重试;但参数错误、认证失败(401)、权限不足(403)这类错误重试多少次都没用。
重试策略通常用指数退避:第一次等 1 秒,第二次等 2 秒,第三次等 4 秒……这样既能给服务恢复的时间,又不会疯狂轰炸。
python
def execute_with_retry(tool, args, max_retries=3):
for attempt in range(max_retries):
try:
return {"success": True, "result": tool.run(**args)}
except TimeoutError:
if attempt < max_retries - 1:
time.sleep(2 ** attempt) # 指数退避:1s, 2s, 4s
else:
return {"success": False, "error": "工具调用超时", "retryable": True}
except ParameterError as e:
# 参数错误不重试,直接返回让 LLM 处理
return {"success": False, "error": f"参数错误:{e}", "retryable": False}
except AuthError as e:
return {"success": False, "error": f"认证失败:{e}", "retryable": False}关键设计是 retryable 标志,告诉上层这个错误值不值得重试。
第二层:错误反馈给 LLM,让它重新规划
这是 Agent 和简单自动化脚本的本质区别。不是工具一失败就崩掉,而是把错误信息给 LLM,让它决定下一步怎么办。
python
def agent_step(messages, tools):
# 调用 LLM 决策
response = llm.chat(messages, tools=tools)
if response.finish_reason == "tool_calls":
tool_call = response.message.tool_calls[0]
tool_name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
# 执行工具
result = execute_tool(tool_name, args)
if not result["success"]:
# 关键:把错误信息清晰地告诉 LLM
error_observation = {
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps({
"status": "error",
"error_type": result["error_type"], # 错误类型
"message": result["error"], # 错误信息
"suggestion": result.get("suggestion", "") # 修复建议
})
}
messages.append(error_observation)
# LLM 重新规划,可能:
# 1. 换别的工具
# 2. 调整参数再试
# 3. 告诉用户无法完成
return agent_step(messages, tools) # 递归,让 LLM 重新决策
else:
# 成功,正常继续
messages.append({"role": "tool", "tool_call_id": tool_call.id, "content": result["result"]})
return agent_step(messages, tools)错误信息怎么写很重要。看这两个版本:
❌ 差:"Error: API failed"
✅ 好:"Error: 天气 API 返回 404,城市 '北京' 未找到。可能原因:1.城市名拼写错误 2.该城市不在支持列表。建议:检查城市名称是否为标准中文名。"详细、可操作的错误信息能让 LLM 做出更好的决策。
第三层:兜底保护,防死循环
Agent 的循环是 while True,必须有停止条件:
python
class SafeAgent:
def __init__(self, max_steps=15, max_time=60):
self.max_steps = max_steps # 最大步数限制
self.max_time = max_time # 超时限制(秒)
self.step_count = 0
self.start_time = time.time()
self.action_history = [] # 记录历史行动,用于检测循环
def run(self, task):
messages = [{"role": "user", "content": task}]
while True:
# 检查步数限制
if self.step_count >= self.max_steps:
return {"status": "stopped", "reason": "达到最大步数", "partial_result": messages}
# 检查超时
if time.time() - self.start_time > self.max_time:
return {"status": "stopped", "reason": "超时", "partial_result": messages}
# 检查是否陷入循环(同样的行动重复执行)
current_action = self.get_next_action(messages)
if self.is_looping(current_action):
return {"status": "stopped", "reason": "检测到循环", "partial_result": messages}
self.action_history.append(current_action)
self.step_count += 1
# 执行行动...循环检测可以用简单的哈希:把(工具名+参数)哈希值存起来,如果短时间内重复出现,说明 Agent 在原地打转。
降级策略
对于关键工具,可以设计备用方案:
python
def search_with_fallback(query):
# 主工具:Google 搜索
result = google_search(query)
if result["success"]:
return result
# 降级:Bing 搜索
logger.warning(f"Google 搜索失败,降级到 Bing: {result['error']}")
result = bing_search(query)
if result["success"]:
return result
# 最终降级:本地知识库
logger.warning(f"Bing 搜索失败,降级到本地检索")
return local_kb_search(query)🎯 面试总结
这道题考的是对 Agent 鲁棒性的理解。回答时分三层:
- 重试层:瞬态错误(网络超时)指数退避重试,配置错误不重试
- 决策层:把详细、可操作的错误信息反馈给 LLM,让它重新规划
- 保护层:最大步数、超时、循环检测,防止死循环
关键是不要自己硬编码「失败了怎么办」,而是让 LLM 根据错误信息自主决策。
170. 什么是 MCP(Model Context Protocol)?
👔面试官:听说过 MCP 吗?说说它是什么,解决了什么问题?
🙋♂️我:MCP 是 Anthropic 提出的一个协议,让 AI 模型能调用外部工具……
👔面试官:Function Calling 不也能调工具吗?MCP 和 Function Calling 是什么关系?
🙋♂️我:呃,MCP 是更通用的协议?Function Calling 是具体实现?
👔面试官:不太准确。Function Calling 是「模型输出 JSON 调用请求」的机制,MCP 是「工具如何暴露自己」的协议。你明白这两者的区别吗?还有,为什么需要 MCP?没有它的时候有什么问题?
🙋♂️我:没有协议的话,每个工具都要为每个模型单独适配?
👔面试官:对,这就是 N×M 问题。那为什么叫 Model Context Protocol,不叫 Tool Protocol?「Context」体现在哪里?
💡 简要回答
MCP(Model Context Protocol,Anthropic 2024 年提出),我理解它是 AI 应用和外部能力之间的「USB-C 标准」。
在没有 MCP 的时候,如果有 N 个 AI 应用和 M 个工具,需要 N×M 个适配集成,非常麻烦。MCP 定义了一套标准协议,工具提供方按这个标准暴露能力(MCP Server),AI 应用按这个标准接入(MCP Client),这样只需要 N+M 个接入就够了。
而且 MCP 不只是「调用工具」,它还让模型能动态发现可用资源、读取上下文信息,所以叫「Context Protocol」。
📝 详细解析
N×M 问题:为什么需要 MCP
想象一下,你是 GitHub 的工程师,想让各种 AI 都能调用 GitHub API 查代码、提 PR。没有 MCP 的时候,你得为每个 AI 应用单独开发集成:
- OpenAI 的 GPT-4 用 Function Calling 格式,你得按它的 schema 写一套
- Anthropic 的 Claude 用类似的但不一样的格式,你再写一套
- 还有 Meta 的 LLaMA、Google 的 Gemini……每家格式都略有不同
反过来,如果你是开发 AI 应用的,想用各种工具(GitHub、Slack、数据库……),每个工具的接口格式都不一样,你得为每个工具写适配代码。
这就是 N×M 问题:N 个 AI 应用 × M 个工具 = N×M 个集成。
MCP 的解法很简单:定一个统一标准。工具提供方按 MCP 标准暴露能力,AI 应用按 MCP 标准接入,从此不再需要两两适配。
没有 MCP:
GPT-4 ──┬── GitHub 集成(第1套)
Claude ─┼── GitHub 集成(第2套)
LLaMA ──┼── GitHub 集成(第3套)
... └── ... 每个 AI 都要单独开发
有 MCP 后:
GitHub 实现一个 MCP Server
↑
├── GPT-4(MCP Client)
├── Claude(MCP Client)
├── LLaMA(MCP Client)
└── ... 任何支持 MCP 的 AI 都能直接用MCP 不只是「工具调用」
很多人把 MCP 理解成「另一种 Function Calling」,这是片面的。MCP 的全称是 Model Context Protocol,Context(上下文) 是它的关键词。
MCP 定义了三类能力:
Tools(工具):会改变外部世界的操作,比如发邮件、写文件、调 API。这和 Function Calling 里的工具概念一样。
Resources(资源):只读的数据源,比如文件内容、数据库记录、网页 HTML。Resources 是「给模型提供上下文」而不是「让模型执行操作」。
Prompts(提示词模板):预定义的 prompt 模板,比如代码审查模板、文档生成模板。这也是上下文的一部分。
所以 MCP 解决的不只是「模型怎么调用工具」,而是「模型怎么获取和使用外部上下文」的完整问题。
MCP 架构详解
MCP 的架构分三层:
┌─────────────────────────────────────────┐
│ Host(宿主应用) │
│ - 用户直接交互的 AI 应用,如 Claude Desktop │
│ - 负责生命周期管理、权限控制 │
└──────────────┬──────────────────────────┘
│
┌──────────────▼──────────────────────────┐
│ Client(MCP 客户端) │
│ - 管理 MCP Server 连接 │
│ - 处理协议通信(JSON-RPC) │
│ - 向 Host 暴露统一接口 │
└──────────────┬──────────────────────────┘
│ MCP Protocol (JSON-RPC)
┌──────────────▼──────────────────────────┐
│ Server(MCP 服务端) │
│ - 工具提供方实现 │
│ - 暴露 Tools/Resources/Prompts │
│ - 可以是本地进程或远程服务 │
└─────────────────────────────────────────┘Host 是用户直接面对的 AI 应用,比如 Claude Desktop、Cursor IDE。Client 是 MCP 协议层面的客户端,负责和 Server 建立连接、管理会话。Server 是工具提供方,可以是本地程序(比如访问本地文件系统)也可以是远程服务(比如访问 SaaS API)。
通信协议用 JSON-RPC 2.0,这是一个成熟的标准,各种语言都有实现。
MCP vs Function Calling:区别与关系
| 维度 | Function Calling | MCP |
|---|---|---|
| 层级 | 模型层面的机制 | 应用层面的协议 |
| 作用 | 模型输出结构化调用请求 | 定义工具如何暴露和被发现 |
| 关系 | 互补 | 互补 |
它们不是替代关系,而是互补。一个使用 MCP 的 Agent 内部流程可能是:
- Agent 通过 MCP 发现有哪些工具可用(读取 Server 的能力列表)
- Agent 把这些工具的 schema 发给 LLM
- LLM 用 Function Calling 输出要调哪个工具
- Agent 通过 MCP 调用对应的 Tool
MCP 管「工具怎么暴露」,Function Calling 管「模型怎么表达调用意图」。
🎯 面试总结
面试回答 MCP 要抓住三个点:
- 解决的问题:N×M 集成爆炸,通过标准化协议减少到 N+M
- 核心设计:不只是工具调用,还包括 Resources(只读数据)和 Prompts(模板),整体是「上下文协议」
- 架构层次:Host-Client-Server 三层,Client 和 Server 通过 JSON-RPC 通信
能类比 USB-C 解释 MCP 的价值会很加分:就像 USB-C 统一了各种设备的充电和数据接口,MCP 统一了 AI 应用和外部能力的连接方式。
172. MCP 和 A2A 协议有什么区别?它们如何协作?
👔面试官:MCP 和 A2A 有什么区别?两者会互相替代吗?
🙋♂️我:都是协议,应该是竞争关系吧,最后可能只会剩一个标准。
👔面试官:竞争?它们解决的是完全不同的两个问题,怎么能说是竞争?MCP 管什么,A2A 管什么,你分得清吗?
🙋♂️我:MCP 是调工具的,A2A 是……Agent 之间通信的?
👔面试官:对,一个是垂直方向的「人-工具」协议,一个是水平方向的「人-人」协议。那在一个复杂系统里,两个协议会同时存在吗?怎么配合?
🙋♂️我:可能会同时存在吧……具体怎么配合我想不太清楚。
👔面试官:实际复杂系统里,主 Agent 用 MCP 调工具,用 A2A 委托子 Agent,两者是互补的。下面详细说一下。
💡 简要回答
| 协议 | 设计目标 | 连接对象 | 类比 |
|---|---|---|---|
| MCP | AI 连接工具/数据源 | 模型 ↔ 工具/资源 | 操作系统驱动(让应用能操作硬件) |
| A2A | Agent 之间协作 | Agent ↔ Agent | HTTP/REST API(让服务之间能通信) |
MCP 解决「怎么调用能力」,A2A 解决「怎么委托任务」。实际复杂系统中两者是互补的:主 Agent 用 MCP 调工具,用 A2A 委托子 Agent。
📝 详细解析
MCP 是「纵向」协议
MCP 关注的是 AI 怎么和外部世界交互。它定义了一套标准,让 AI 应用能够:
- 发现可用的工具(Tools)
- 读取外部数据(Resources)
- 获取预定义的提示模板(Prompts)
MCP 的连接是在 AI 应用和工具/数据源之间 建立的,是垂直方向的扩展。
A2A 是「横向」协议
A2A(Agent-to-Agent,Google 2025 年提出)关注的是 Agent 之间怎么协作。它定义了一套标准,让不同的 Agent 能够:
- 互相发现对方的能力(Agent Card)
- 委托任务(Task Delegation)
- 交换中间结果(Intermediate Results)
A2A 的连接是在 Agent 和 Agent 之间 建立的,是水平方向的扩展。
协作场景:两者如何配合
来看一个实际场景:
用户:"帮我分析一下我们公司 Q3 的销售数据,生成报告并发给团队"
主 Agent(规划器)
│
├── 通过 MCP 调用 数据库工具(读取销售数据)
│ └── MCP Server: PostgreSQL Connector
│
├── 通过 MCP 调用 代码执行工具(数据分析)
│ └── MCP Server: Python Execution Environment
│
├── 通过 A2A 委托 报告写作 Agent(生成专业报告)
│ ├── Agent Card: 报告写作 Agent 的能力描述
│ ├── 发送任务:"基于以下数据生成销售分析报告..."
│ └── 接收结果:完整的 Markdown 报告
│
└── 通过 MCP 调用 邮件工具(发送报告)
└── MCP Server: Email Service在这个流程中:
- MCP 负责「调工具」:数据库、代码执行、邮件发送都是工具调用
- A2A 负责「委托任务」:报告写作是一个复杂的创造性任务,专门委托给「报告写作 Agent」处理,而不是用工具
为什么报告写作要用 A2A 而不是 MCP?因为写报告不是一个简单的函数调用可以完成的,它需要:
- 理解业务背景和目标受众
- 组织数据、生成洞察
- 撰写结构化内容、调整语气
这是一个完整的认知任务,让一个专门的 Agent 来处理更合适。而数据库查询、代码执行是确定性的操作,用 MCP 工具就够了。
架构对比图
┌─────────────────────────────────────────────────────────┐
│ 用户 │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ 主 Agent │ ←── 决策中心 │
│ │ (规划器) │ │
│ └──────┬───────┘ │
│ │ │
│ ┌──────────────┼──────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌────────┐ ┌────────────┐ ┌─────────────┐ │
│ │MCP工具 │ │ A2A Agent │ │ MCP 工具 │ │
│ │数据库 │ │ 报告写作Agent│ │ 邮件服务 │ │
│ └────────┘ └────────────┘ └─────────────┘ │
│ ↑ ↑ ↑ │
│ │ │ │ │
│ 纵向扩展 横向协作 纵向扩展 │
│ (模型→工具) (Agent→Agent) (模型→工具) │
└─────────────────────────────────────────────────────────┘两者的关系总结
| 维度 | MCP | A2A |
|---|---|---|
| 解决的问题 | AI 怎么调用外部能力 | Agent 怎么协作完成任务 |
| 连接方向 | 纵向(AI ↔ 工具/数据) | 横向(Agent ↔ Agent) |
| 任务类型 | 确定性操作(查询、执行) | 创造性任务(写作、分析) |
| 交互模式 | 请求-响应 | 多轮对话、流式更新 |
| 类比 | USB 驱动 | HTTP API |
🎯 面试总结
这道题容易犯的错误是以为两个协议是竞争关系。其实它们解决的问题完全不同,是互补的。
回答这道题分三步:
讲清楚区别:MCP 是纵向协议(AI-工具),解决「怎么调用能力」;A2A 是横向协议(Agent-Agent),解决「怎么协作完成任务」
举例说明:MCP 适合数据库查询、API 调用这类确定性操作;A2A 适合报告写作、创意设计这类需要专门 Agent 处理的复杂任务
说明配合方式:实际复杂系统中,主 Agent 用 MCP 调工具,用 A2A 委托子 Agent,两者同时存在、各司其职
能画出上面那种架构图,面试官会认为你理解了两种协议的定位和协作关系。
175-180. 多 Agent 系统与架构
👔面试官:多 Agent 系统有哪些常见的架构模式?各有什么优缺点?
🙋♂️我:有主从架构、分层架构……还有流水线吧?
👔面试官:能具体说说主从架构里主 Agent 做什么、从 Agent 做什么吗?什么场景适合用这种架构?
🙋♂️我:主 Agent 负责分发任务,从 Agent 负责执行……适合任务能拆分成多个子任务的场景?
👔面试官:对。那分层架构呢?战略层、战术层、执行层各做什么?和主从架构的区别是什么?
🙋♂️我:分层架构是……上下级关系?主从架构是平行关系?
👔面试官:不完全准确。主从架构里主 Agent 也分任务、也做决策;分层架构每层都有明确的职责边界,不是简单的上下级。还有,多 Agent 系统最大的风险是什么?
🙋♂️我:风险……通信开销大?
👔面试官:还有一个更严重的:死循环。多个 Agent 互相等待、互相调用,系统卡死。怎么设计防护机制?
💡 简要回答
多 Agent 架构我了解三种主流模式:
主从架构(Orchestrator-Worker):主 Agent 负责任务分解和结果汇总,从 Agent 并行执行子任务。适合任务可拆分、子任务相对独立的场景。
分层架构(Hierarchical):战略层定目标、战术层拆任务、执行层干实事。每层职责清晰,适合复杂多步骤任务。
流水线架构(Pipeline):Agent 按顺序处理数据,前一个的输出是后一个的输入。适合有明确处理流程的任务。
多 Agent 系统最大的风险是死循环,需要设计超时、步数限制、循环检测等防护机制。
📝 详细解析
主从架构:并行处理利器
主从架构是最直观的多 Agent 设计。一个中央协调器(Orchestrator)负责任务分解和结果汇总,多个工作 Agent(Workers)并行执行子任务。
python
class OrchestratorAgent:
def __init__(self, llm, workers):
self.llm = llm
self.workers = workers # 多个专业 Worker Agent
def run(self, task):
# 第一步:规划,把任务拆成子任务
plan = self.llm.generate(f"将以下任务分解为可并行执行的子任务:\n{task}")
subtasks = parse_subtasks(plan)
# 第二步:并行分发给 Workers
results = []
with ThreadPoolExecutor() as executor:
futures = [
executor.submit(worker.execute, subtask)
for worker, subtask in zip(self.workers, subtasks)
]
for future in futures:
results.append(future.result())
# 第三步:综合结果
final_answer = self.llm.generate(
f"基于以下子任务结果,给出综合回答:\n{combine_results(results)}"
)
return final_answer
# Worker Agent 示例
class ResearchWorker:
def execute(self, subtask):
# 每个 Worker 是一个简化版的 Agent
# 有自己的工具集(搜索、浏览、总结)
return self.research_and_summarize(subtask)适用场景:
- 竞品分析(每个 Worker 分析一个竞品)
- 多数据源报告(每个 Worker 处理一个数据源)
- 代码审查(每个 Worker 检查一个模块)
优点:并行执行、效率高;容错性好(某个 Worker 失败不影响其他)。
缺点:子任务之间不能有太多依赖;需要额外的结果汇总步骤。
分层架构:复杂任务的最佳选择
分层架构把 Agent 分为三层,每层有明确的职责边界:
┌─────────────────────────────────────────┐
│ 战略层 Agent(Strategic) │
│ - 理解用户目标 │
│ - 制定整体策略 │
│ - 评估任务完成情况 │
│ - 不直接执行具体动作 │
└──────────────┬──────────────────────────┘
│ 分解为阶段/模块
▼
┌─────────────────────────────────────────┐
│ 战术层 Agent(Tactical) │
│ - 将战略拆解为具体步骤 │
│ - 选择执行工具和 Agent │
│ - 动态调整计划 │
│ - 协调执行层 │
└──────────────┬──────────────────────────┘
│ 分配具体任务
▼
┌─────────────────────────────────────────┐
│ 执行层 Agent(Executive) │
│ - 搜索 Agent:信息收集 │
│ - 代码 Agent:编程实现 │
│ - 写作 Agent:文案生成 │
│ - 分析 Agent:数据处理 │
└─────────────────────────────────────────┘这种架构的优势在于职责分离:战略层关注「做什么」,战术层关注「怎么做」,执行层关注「做出来」。每层都可以专注优化自己的核心能力。
适用场景:
- 复杂的商业分析(战略:分析目标 → 战术:数据收集方案 → 执行:查数据、做报表)
- 软件开发项目(战略:产品需求 → 战术:技术方案 → 执行:编码、测试、部署)
- 内容生产流程(战略:选题方向 → 战术:内容规划 → 执行:写作、配图、排版)
流水线架构:顺序依赖的处理
流水线架构适合有明显处理顺序的任务,每个 Agent 处理完把结果传给下一个:
python
class PipelineAgent:
def __init__(self, stages):
self.stages = stages # [DataCollector, Analyzer, ReportWriter, Reviewer]
def run(self, input_data):
data = input_data
for stage in self.stages:
data = stage.process(data)
# 每个阶段可以决定是否继续、是否回退
if data.get("status") == "reject":
# 审核不通过,回退到上一阶段
current_idx = self.stages.index(stage)
return self.rework(data, current_idx - 1)
return data适用场景:
- 数据 ETL 流程(提取 → 清洗 → 转换 → 加载)
- 内容审核流程(AI 生成 → 合规检查 → 人工复核 → 发布)
- 客服处理流程(意图识别 → 知识检索 → 回答生成 → 满意度确认)
死循环防护机制
多 Agent 系统最大的风险是死循环,常见场景:
- Agent A 委托给 Agent B,Agent B 又委托回 Agent A
- 两个 Agent 互相等待对方的结果
- 循环条件判断失败,不断重试
防护机制分三层:
python
class SafeMultiAgentSystem:
def __init__(self):
self.max_hops = 10 # 最大跳转次数
self.max_time = 300 # 超时(秒)
self.visited_agents = set() # 记录访问过的 Agent
self.call_graph = {} # 记录调用关系,检测循环
def delegate(self, from_agent, to_agent, task, depth=0):
# 1. 深度检查
if depth > self.max_hops:
raise MaxHopsExceeded(f"超过最大跳转次数 {self.max_hops}")
# 2. 循环检测
if self.detect_cycle(from_agent, to_agent):
raise CycleDetected(f"检测到循环调用: {from_agent} -> {to_agent}")
# 3. 记录调用关系
self.call_graph[from_agent] = to_agent
# 4. 执行委托
result = to_agent.execute(task)
# 5. 清理记录
del self.call_graph[from_agent]
return result
def detect_cycle(self, from_agent, to_agent):
# 如果 to_agent 已经在调用链中,说明有循环
current = from_agent
while current in self.call_graph:
if self.call_graph[current] == to_agent:
return True
current = self.call_graph[current]
return False🎯 面试总结
回答多 Agent 架构题,要能说清楚三种模式的特点和适用场景:
| 架构 | 核心特点 | 适用场景 | 风险 |
|---|---|---|---|
| 主从 | 并行执行、结果汇总 | 子任务独立可拆分 | Worker 失败处理 |
| 分层 | 职责分离、逐级细化 | 复杂多步骤任务 | 层间通信开销 |
| 流水线 | 顺序处理、数据流转 | 有明确处理流程 | 死循环、单点失败 |
死循环防护要讲三重保险:最大跳转次数、循环检测、超时限制。
181-185. AI Agent 记忆机制
👔面试官:Agent 的记忆机制是怎么设计的?短期记忆和长期记忆分别怎么处理?
🙋♂️我:短期记忆就是放上下文里,长期记忆用向量数据库存……
👔面试官:就这些?那上下文满了怎么办?长期记忆怎么知道该提取哪些信息?
🙋♂️我:满了就……删掉最早的?长期记忆检索的时候按相似度排序?
👔面试官:删掉最早的不怕丢失关键信息吗?有没有更好的办法?还有,你知道记忆衰减吗?
🙋♂️我:记忆衰减?没听说过……
👔面试官:这是 LangChain 记忆组件里的一个设计,久远的信息权重降低。回去研究一下 Agent 记忆的完整设计。
好,下面我来系统梳理 Agent 记忆的层次和实现策略。
💡 简要回答
Agent 的记忆我分四层来理解:
短期记忆(工作记忆):放在 Context Window 里,存当前任务的对话历史和中间结果。关键是满了之后怎么处理——摘要优于截断。
情景记忆:存历史对话/事件,用向量数据库实现,按语义检索召回。
语义记忆:外部知识库(RAG),也是向量存储,但存的是领域知识而非对话历史。
程序记忆:存储「怎么做事」的经验(Few-shot 示例),直接放在 Prompt 里。
长期记忆的管理还有两个重要机制:记忆衰减(久远信息权重降低)和重要性评估(只存关键信息)。
📝 详细解析
第一层:短期记忆(工作记忆)
短期记忆就是当前放在 LLM 上下文窗口里的内容。它决定了 Agent 能「记住」多少当前任务的上下文。
最大的问题是窗口有限。GPT-4o 大概 128K token,Claude 3 200K,但一个复杂任务跑十几步,加上工具返回的大量文本,很容易就撑满了。
满了之后怎么办?有几种策略:
策略一:截断(Truncation) 直接扔掉最早的消息。简单粗暴,但可能把关键信息也丢了。
python
def truncate_messages(messages, max_tokens=8000):
total = sum(count_tokens(m) for m in messages)
while total > max_tokens:
# 移除最老的消息(保留 system prompt)
removed = messages.pop(1) # 保留 index 0 的 system
total -= count_tokens(removed)
return messages策略二:滑动窗口(Sliding Window) 只保留最近 N 条消息,更老的完全丢弃。比截断更有序,但同样会丢失早期信息。
策略三:摘要(Summarization)⭐ 推荐 对早期对话做摘要,保留关键信息。这是 LangChain 的 ConversationSummaryBufferMemory 的做法。
python
from langchain.memory import ConversationSummaryBufferMemory
memory = ConversationSummaryBufferMemory(
llm=ChatOpenAI(),
max_token_limit=2000, # 保留最近 2000 token 的原始对话
return_messages=True
)
# 超过 2000 token 时,用 LLM 对早期对话做摘要,替换原始内容摘要策略的精妙之处在于:越近的信息保留得越完整,越远的信息压缩得越厉害。符合人类记忆的特点。
第二层:长期记忆——情景记忆
情景记忆存的是「发生了什么」——历史对话、执行过的任务、用户的偏好。
实现方式通常是向量数据库 + 语义检索:
python
class EpisodicMemory:
def __init__(self, vector_store):
self.store = vector_store
def save(self, episode):
# episode 是一次对话或一次任务执行
# 存原始文本 + embedding
text = f"时间:{episode.timestamp}\n内容:{episode.summary}"
embedding = embed(text)
self.store.add(text=text, embedding=embedding, metadata=episode.meta)
def recall(self, query, k=5):
# 检索最相关的历史事件
query_embedding = embed(query)
return self.store.similarity_search(query_embedding, k=k)这里有个关键设计:记忆衰减(Memory Decay)。
三个月前的对话,到今天还重要吗?可能不那么重要了。记忆衰减的做法是给每条记忆加一个时间权重,越久远的记忆权重越低。
python
def decay_score(similarity, timestamp, decay_rate=0.01):
# 时间衰减因子:指数衰减
hours_ago = (now() - timestamp).total_seconds() / 3600
time_factor = exp(-decay_rate * hours_ago)
return similarity * time_factor这样检索的时候,久远的信息自然排在后面,Agent 会优先关注最近的、最相关的记忆。
第三层:长期记忆——语义记忆
语义记忆存的是「知道什么」——领域知识、事实、文档内容。
这和情景记忆的技术实现类似(都是向量存储 + RAG),但内容性质不同:
| 维度 | 情景记忆 | 语义记忆 |
|---|---|---|
| 内容 | 发生了什么(对话、事件) | 知道什么(知识、事实) |
| 更新频率 | 高频(每次交互都产生) | 低频(知识库定期更新) |
| 衰减策略 | 强衰减(久远事件不重要) | 弱衰减(知识相对稳定) |
| 典型存储 | 对话历史 | 产品文档、论文、FAQ |
第四层:程序记忆
程序记忆存的是「怎么做事」——处理某类任务的方法论、Few-shot 示例。
它不需要外部存储,直接放在 System Prompt 里:
python
system_prompt = """你是一个客服助手。处理退款请求的标准流程如下:
示例1:
用户:我要退款
助手:好的,请提供您的订单号,我帮您查询。
(用户提供了订单号)
助手:查询到您的订单 #12345,金额 ¥299,符合退款条件。退款将在 3-5 个工作日到账。
示例2:
...(更多示例)
现在请处理用户的请求:"""程序记忆的特点是「可复用」——学会的处理流程可以套用在类似任务上。
重要性评估:不是所有记忆都值得存
长期记忆不是无限容量的,需要筛选什么值得存、什么不值得。
一个简单的策略是:只有「有信息量」的对话才存。
python
def is_important(conversation):
# 简单启发式:有结论、有决策、有关键信息的对话才存
indicators = [
"确认" in conversation,
"决定" in conversation,
"重要" in conversation,
"请注意" in conversation,
len(conversation) > 200 # 有一定长度
]
return sum(indicators) >= 2更高级的做法是让 LLM 来做重要性打分:
python
def score_importance(conversation):
prompt = f"请为以下对话的重要性打分(0-10):\n{conversation}\n\n只返回数字:"
score = int(llm.generate(prompt))
return score
# 只保留 8 分以上的记忆
if score_importance(conv) >= 8:
memory.save(conv)🎯 面试总结
Agent 记忆分四层,每层有不同的存储策略:
| 记忆类型 | 存储位置 | 关键技术 | 管理策略 |
|---|---|---|---|
| 短期记忆 | Context Window | - | 摘要优于截断 |
| 情景记忆 | 向量数据库 | 语义检索 | 记忆衰减 |
| 语义记忆 | 向量数据库 | RAG | 定期更新 |
| 程序记忆 | Prompt | Few-shot | 人工整理 |
面试回答要强调两个设计点:
- 上下文超长处理:摘要策略比直接截断保留更多信息
- 长期记忆管理:记忆衰减让久远信息权重降低,重要性评估避免存储噪音
186. 什么是 LangChain?LangChain 包含哪些核心概念?
👔面试官:用过 LangChain 吗?说说它是什么,核心概念有哪些?
🙋♂️我:LangChain 是一个 LLM 应用开发框架,提供了链式调用、记忆管理这些功能。
👔面试官:链式调用具体怎么实现?用的是什么语法?
🙋♂️我:就是……把组件串起来?用 .run() 或者 __call__?
👔面试官:你这是旧版 LangChain 的用法了。新版的 LCEL 用管道符 | 连接组件,类似 Unix 的 pipe。这个得搞清楚。还有,LangChain 和直接调 OpenAI API 比,核心价值在哪里?
🙋♂️我:不用自己写很多胶水代码?
👔面试官:对,但不止。还有标准化接口、可替换性、生态集成这些。LangChain 的核心价值是「抽象层」,让你可以无缝切换不同模型、不同存储。下面详细说一下。
💡 简要回答
LangChain 我理解是 LLM 应用的「操作系统」——它提供了一套标准化的组件和抽象,让开发者能够快速构建复杂的 LLM 应用,同时保持灵活性和可替换性。
核心概念六大模块:
- Models:统一的大模型调用接口
- Prompts:提示词模板管理
- Chains:组件链式组合(LCEL 语法)
- Memory:对话记忆管理
- Retrievers:文档检索(RAG 基础)
- Agents:智能体自主决策
📝 详细解析
LangChain 的核心价值
直接调 OpenAI API 很简单:
python
import openai
response = openai.chat.completions.create(...)那为什么还需要 LangChain?三个核心价值:
1. 标准化接口 不同厂商的模型 API 格式不同,LangChain 提供统一接口,随时可以换模型不用改业务代码。
python
from langchain.chat_models import ChatOpenAI, ChatAnthropic, ChatGoogle
# 这三个可以无缝替换
llm = ChatOpenAI(model="gpt-4")
llm = ChatAnthropic(model="claude-3")
llm = ChatGoogle(model="gemini-pro")
# 调用方式完全一样
result = llm.invoke("你好")2. 组件化 + 可组合 把 LLM 应用拆成独立组件(Prompt、LLM、Memory、Retriever),用管道符 | 自由组合。
3. 生态集成 和向量数据库(Pinecone、Milvus、Chroma)、文档加载器(PDF、Word、网页)、Agent 工具生态无缝集成。
LCEL:LangChain 的链式语法
LCEL(LangChain Expression Language)是 v0.1 之后的新语法,用管道符 | 连接组件,类似 Unix 的 pipe:
python
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
# 定义组件
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个{role}专家"),
("human", "{question}")
])
llm = ChatOpenAI(model="gpt-4")
parser = StrOutputParser()
# 链式组合
chain = prompt | llm | parser
# 调用
result = chain.invoke({
"role": "医疗",
"question": "头痛可能是什么原因?"
})链中的数据流是这样的:
输入 {"role": "医疗", "question": "头痛..."}
↓
Prompt 格式化成完整消息列表
↓
LLM 生成回答
↓
Parser 解析输出为字符串
↓
返回 "头痛可能由以下原因引起..."六大核心组件详解
1. Models(模型) 统一的 LLM 调用接口,支持 OpenAI、Anthropic、本地模型等。
python
from langchain_openai import ChatOpenAI
from langchain_anthropic import ChatAnthropic
llm = ChatOpenAI(model="gpt-4", temperature=0.7)2. Prompts(提示词) 模板化管理,支持变量插值、消息格式化。
python
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
("system", "你是{role}助手"),
("human", "{question}"),
("ai", "让我查一下..."), # 预设的 AI 回复占位
])3. Chains(链) 组件的组合方式,LCEL 让链的定义变得极其简洁。
4. Memory(记忆) 对话历史管理,支持多种策略(Buffer、Summary、Vector)。
python
from langchain.memory import ConversationSummaryMemory
memory = ConversationSummaryMemory(llm=llm)
# 自动对早期对话做摘要,保留最近的完整对话5. Retrievers(检索器) RAG 的核心,从向量库检索相关文档。
python
from langchain_community.vectorstores import Chroma
retriever = Chroma.from_documents(docs, embeddings).as_retriever(k=4)
# 检索最相关的 4 个文档6. Agents(智能体) 自主决策的 Agent,可以调用工具、循环执行。
python
from langchain.agents import create_openai_tools_agent
agent = create_openai_tools_agent(llm, tools, prompt)
result = agent.invoke({"input": "查一下北京天气"})🎯 面试总结
面试回答 LangChain,强调三点:
- 核心价值:标准化接口(可替换模型)、组件化(可组合)、生态集成
- 现代语法:LCEL 用
|管道符连接组件,比旧版更简洁 - 六大模块:Models、Prompts、Chains、Memory、Retrievers、Agents
一个加分点是能说出 LangChain 的局限性:抽象层太多有时候反而增加复杂度,简单场景直接调 API 可能更合适。
187-194. LangChain 核心组件详解
👔面试官:详细说说 LangChain 的几个核心组件:Agent、Chain、Prompt Template、Output Parser、Memory……都是怎么用的?
🙋♂️我:Agent 是智能体,可以调用工具自动执行任务;Chain 是组件链式组合;Prompt Template 是提示词模板;Output Parser 解析输出;Memory 存对话历史……
👔面试官:说得比较泛。Prompt Template 有哪些类型?Few-shot Prompt 怎么实现?Output Parser 具体怎么解析结构化输出?Memory 有哪些实现方式,各有什么适用场景?
🙋♂️我:Prompt Template 有……普通的和带变量的?Few-shot 就是在 prompt 里加示例?Output Parser 用正则或者 JSON 解析?Memory 有 BufferMemory 和 SummaryMemory?
👔面试官:对,但不够深入。Few-shot 示例可以动态选择而不是固定放所有;Output Parser 可以用 Pydantic 做类型安全的解析;Memory 有 Buffer、Summary、Vector 等多种策略。下面详细展开。
💡 简要回答
LangChain 的核心组件我分几类来理解:
构建块:Prompt Template(提示词模板)、LLM(模型接口)、Output Parser(输出解析器)
组合机制:Chain(链式组合,LCEL 语法)、Agent(自主决策的智能体)
扩展能力:Memory(记忆管理)、Retriever(文档检索)、Tool(外部工具)
高级功能:Example Selector(动态选择示例)、Document Loader(文档加载)、Text Splitter(文本分割)
📝 详细解析
Agent:自主决策的智能体
Agent 是 LangChain 最核心的功能之一,它封装了 ReAct 等推理模式,让 LLM 能自主决策、调用工具。
python
from langchain.agents import create_openai_tools_agent, AgentExecutor
from langchain_core.tools import tool
# 1. 定义工具
@tool
def search_weather(city: str) -> str:
"""查询指定城市的天气"""
return f"{city}今天晴天,25°C"
@tool
def send_email(to: str, subject: str, body: str) -> str:
"""发送邮件"""
return f"邮件已发送给 {to}"
tools = [search_weather, send_email]
# 2. 创建 Agent
agent = create_openai_tools_agent(llm, tools, prompt)
# 3. 执行(自动循环直到完成)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
result = agent_executor.invoke({"input": "查一下北京天气,然后发邮件给老板"})
# Agent 自动:查天气 → 生成邮件内容 → 调用发送邮件create_openai_tools_agent 是基于 OpenAI Function Calling 的现代 Agent,比旧的 ReAct Agent 更可靠。
Prompt Template:灵活的提示词管理
Prompt Template 支持变量插值、条件渲染、消息格式化。
python
from langchain_core.prompts import (
PromptTemplate, # 单轮文本提示
ChatPromptTemplate, # 多轮对话提示
FewShotPromptTemplate, # 带示例的提示
MessagesPlaceholder # 动态消息占位
)
# 1. 基础模板 - 变量插值
prompt = PromptTemplate.from_template(
"你是一个{role}专家,请回答:{question}"
)
prompt.format(role="医疗", question="头痛怎么办?")
# 输出:你是一个医疗专家,请回答:头痛怎么办?
# 2. Chat Prompt - 多轮对话
chat_prompt = ChatPromptTemplate.from_messages([
("system", "你是{role}助手"),
("human", "{question}"),
MessagesPlaceholder(variable_name="history"), # 动态插入历史消息
("human", "{follow_up}")
])
# 3. Few-shot Prompt - 带示例学习
examples = [
{"question": "2+2=?", "answer": "4"},
{"question": "5*6=?", "answer": "30"},
]
few_shot_prompt = FewShotPromptTemplate(
examples=examples,
example_prompt=PromptTemplate(
input_variables=["question", "answer"],
template="Q: {question}\nA: {answer}"
),
suffix="Q: {input}\nA:", # 最终的问题
input_variables=["input"]
)Example Selector:动态选择最相关示例
Few-shot 示例不是越多越好,太多会占用大量 token。应该根据当前问题,动态选择最相关的几个示例。
python
from langchain.prompts.example_selector import SemanticSimilarityExampleSelector
# 示例库
examples = [
{"input": "你好", "output": "你好!有什么可以帮你?"},
{"input": "退款", "output": "请提供订单号,我帮您查询退款政策。"},
{"input": "投诉", "output": "非常抱歉给您带来不便,请详细描述问题..."},
]
# 动态选择器:根据语义相似度选最相关的 k 个
selector = SemanticSimilarityExampleSelector.from_examples(
examples,
OpenAIEmbeddings(),
Chroma,
k=2 # 只选 2 个最相关的
)
# 当前问题是关于退款的,会自动选 "退款" 相关的示例
selected = selector.select_examples({"input": "我要退货"})Output Parser:结构化输出解析
LLM 输出是文本,但往往需要解析成结构化数据(JSON、对象等)。
python
from langchain.output_parsers import (
PydanticOutputParser, # 解析为 Pydantic 模型
CommaSeparatedListOutputParser, # 解析为列表
StructuredOutputParser # 解析为结构化字典
)
from pydantic import BaseModel, Field
# 1. Pydantic Output Parser(推荐)
class Person(BaseModel):
name: str = Field(description="人物姓名")
age: int = Field(description="年龄")
skills: list[str] = Field(description="技能列表")
parser = PydanticOutputParser(pydantic_object=Person)
# Parser 会自动生成格式指令,要求 LLM 按 JSON 输出
format_instructions = parser.get_format_instructions()
# 输出:{"name": string, "age": integer, "skills": [string, ...]}
prompt = PromptTemplate(
template="提取以下文本中的人物信息。\n{format_instructions}\n\n文本:{text}\n",
input_variables=["text"],
partial_variables={"format_instructions": format_instructions}
)
llm_output = llm.invoke(prompt.format(text="张三,30岁,会Python和Java"))
person = parser.parse(llm_output) # 自动解析为 Person 对象
print(person.name) # "张三"
print(person.skills) # ["Python", "Java"]
# 2. 列表解析器
list_parser = CommaSeparatedListOutputParser()
llm_output = "苹果, 香蕉, 橙子"
items = list_parser.parse(llm_output) # ["苹果", "香蕉", "橙子"]Memory:对话记忆管理
LangChain 提供了多种 Memory 实现,适应不同场景:
python
from langchain.memory import (
ConversationBufferMemory, # 原始缓冲,存所有消息
ConversationBufferWindowMemory, # 滑动窗口,只存最近 k 轮
ConversationSummaryMemory, # 摘要记忆,对旧对话做摘要
ConversationSummaryBufferMemory, # 混合:近期完整 + 久远摘要
VectorStoreRetrieverMemory # 向量检索记忆
)
# 1. Buffer Memory - 简单但容易超 token
memory = ConversationBufferMemory()
memory.save_context(
{"input": "你好"},
{"output": "你好!有什么可以帮你?"}
)
memory.load_memory_variables({}) # 返回完整历史
# 2. Window Memory - 只保留最近 k 轮
window_memory = ConversationBufferWindowMemory(k=5)
# 3. Summary Memory - 对旧对话做摘要(推荐)
summary_memory = ConversationSummaryMemory(llm=llm)
# 早期对话 → LLM 摘要
# 近期对话 → 保留原文
# 4. Summary Buffer Memory - 混合策略(最佳实践)
buffer_summary = ConversationSummaryBufferMemory(
llm=llm,
max_token_limit=2000 # 超过后触发摘要
)Retriever:RAG 检索
Retriever 是 RAG 的核心,从向量库检索相关文档。
python
from langchain_community.vectorstores import Chroma, FAISS
from langchain_openai import OpenAIEmbeddings
# 1. 创建向量库
embeddings = OpenAIEmbeddings()
vectorstore = Chroma.from_documents(documents, embeddings)
# 2. 获取 Retriever
retriever = vectorstore.as_retriever(
search_type="similarity", # 或 "mmr"(最大边际相关性)
search_kwargs={"k": 4} # 返回 4 个最相关文档
)
# 3. 检索
relevant_docs = retriever.invoke("什么是 RAG?")🎯 面试总结
面试回答 LangChain 组件,要能说出每个组件的作用和使用场景:
| 组件 | 核心作用 | 关键用法 |
|---|---|---|
| Agent | 自主决策、调用工具 | create_openai_tools_agent |
| Prompt Template | 提示词模板化 | 变量插值、Few-shot、消息占位 |
| Example Selector | 动态选示例 | 语义相似度选择,减少 token |
| Output Parser | 结构化解析 | PydanticOutputParser 类型安全 |
| Memory | 对话历史管理 | SummaryBufferMemory 平衡完整与精简 |
| Retriever | 文档检索 | as_retriever() 统一接口 |
195. LangChain 存在哪些问题及解决方案?有哪些替代方案?
👔面试官:LangChain 有哪些问题?你会怎么解决?
🙋♂️我:嗯……封装得太多,有时候不太灵活?
👔面试官:具体哪里不灵活?调试的时候遇到过什么困难?
🙋♂️我:错误堆栈很深,不知道是哪里出问题了。而且版本更新挺频繁的,API 经常变。
👔面试官:对,这是两个典型问题。那你会怎么选替代方案?
🙋♂️我:直接调 API?或者用别的框架?
👔面试官:具体看什么维度来选择?什么场景该用什么方案?
💡 简要回答
LangChain 的主要问题我总结四点:
- 过度封装:抽象层太多,调试困难,「魔法太多」
- 版本不稳定:v0.1 重构后 API 变化大,升级成本高
- 性能开销:链式调用有额外封装开销
- 学习曲线:概念多(LCEL、Runnable、Chain……),入门门槛不低
替代方案的选择逻辑:
- 简单场景:直接调 API,不要引入框架
- RAG 为主:选 LlamaIndex(更专业)
- 复杂 Agent 工作流:选 LangGraph(有状态图)
- 多 Agent 协作:选 CrewAI(角色化设计)
📝 详细解析
问题一:过度封装
LangChain 为了实现「可替换性」,抽象了太多层。一个简单的 LLM 调用,可能要经过:
你的代码
↓
Runnable / Chain
↓
BaseLanguageModel
↓
BaseChatModel
↓
ChatOpenAI
↓
OpenAI API出问题的时候,堆栈信息很深,很难定位到底是哪一层的问题。
解决方案:
- 简单场景直接调 API,不用框架
- 复杂场景用 LangChain 时,加
verbose=True开启详细日志 - 理解底层原理,不要被「魔法」吓到
问题二:版本不稳定
LangChain 从 v0.0.x 升级到 v0.1.x 是一次重大重构,很多 API 都变了:
python
# 旧版(已废弃)
from langchain import OpenAI
llm = OpenAI()
result = llm("你好")
# 新版(v0.1+)
from langchain_openai import ChatOpenAI
llm = ChatOpenAI()
result = llm.invoke("你好")这种变化给生产环境带来很大风险。
解决方案:
- 生产环境锁定版本:
langchain==0.1.0 - 关注官方迁移指南,逐步升级
- 封装自己的抽象层,减少对 LangChain 的直接依赖
替代方案对比
| 方案 | 特点 | 适用场景 | 不适用场景 |
|---|---|---|---|
| 直接调 API | 最简洁,完全控制 | 简单问答、原型验证 | 复杂流程、多步骤任务 |
| LlamaIndex | RAG 专业,索引丰富 | 文档问答、知识库 | 非 RAG 类应用 |
| LangGraph | 有状态图,支持循环 | 复杂 Agent、多步骤流程 | 简单线性流程 |
| CrewAI | 多 Agent 角色化 | 团队协作任务 | 单 Agent 场景 |
LlamaIndex vs LangChain
LlamaIndex 专注 RAG,功能更深入:
python
# LlamaIndex:专为 RAG 设计
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
# 自动处理文档加载、分块、索引
documents = SimpleDirectoryReader("data").load_data()
index = VectorStoreIndex.from_documents(documents)
# 查询时自动检索 + 生成
query_engine = index.as_query_engine()
response = query_engine.query("什么是 RAG?")对比 LangChain 的 RAG:
python
# LangChain:需要手动组装各个组件
retriever = Chroma.from_documents(docs, embeddings).as_retriever()
prompt = ChatPromptTemplate.from_template("基于上下文:{context}\n回答:{question}")
chain = {"context": retriever, "question": RunnablePassthrough()} | prompt | llm结论:RAG 为主选 LlamaIndex,通用应用选 LangChain。
选型决策树
你的应用场景是什么?
│
├─ 简单问答/原型验证 ──→ 直接调 API(OpenAI SDK/Anthropic SDK)
│
├─ 文档问答/知识库 ───→ LlamaIndex(RAG 专业)
│
├─ 复杂 Agent 流程 ───→ LangGraph(有状态图)
│
├─ 多 Agent 协作 ─────→ CrewAI(角色化设计)
│
└─ 通用 LLM 应用 ─────→ LangChain(生态最全)🎯 面试总结
面试时说 LangChain 的问题,要客观:
- 承认它的问题:过度封装、版本不稳定、学习曲线陡
- 但也要肯定它的价值:生态最全、组件丰富、社区活跃
- 给出选型建议:不要为了用框架而用框架,根据场景选择
一个加分点是能说出「我在什么场景会选什么方案」,体现你有实际的工程判断能力。
196-197. LangChain Token 计数与 LLM 调用
👔面试官:LangChain 怎么统计 Token 消耗?准不准?
🙋♂️我:用 get_openai_callback() 可以统计,但应该只对 OpenAI 模型准确?
👔面试官:对,为什么?如果是 Claude 或者本地模型,怎么准确统计?
🙋♂️我:可能要用各自模型的 Tokenizer?
👔面试官:没错。LangChain 默认用 tiktoken(OpenAI 的 Tokenizer),对其他模型是估算。生产环境要精确控制成本,这点要注意。
💡 简要回答
Token 计数:LangChain 用 tiktoken 估算,只对 OpenAI 模型准确;其他模型需要用各自的 Tokenizer 精确计算。
LLM 调用:推荐用 LCEL 语法(prompt | llm | parser),支持:
invoke():单次调用stream():流式输出batch():批处理
📝 详细解析
Token 计数问题
python
from langchain.callbacks import get_openai_callback
# OpenAI 模型:准确
with get_openai_callback() as cb:
result = chain.invoke({"question": "你好"})
print(f"Prompt tokens: {cb.prompt_tokens}")
print(f"Completion tokens: {cb.completion_tokens}")
print(f"Total tokens: {cb.total_tokens}")
print(f"总成本: ${cb.total_cost}")
# Claude/本地模型:估算,可能不准确精确统计方案:
python
# Anthropic Claude
from anthropic import Anthropic
client = Anthropic()
response = client.messages.create(...)
print(response.usage.input_tokens)
print(response.usage.output_tokens)
# 本地模型(Qwen/Llama等)
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2-7B")
prompt_tokens = len(tokenizer.encode(prompt))LLM 调用方式
LCEL(LangChain Expression Language)是现代推荐写法:
python
from langchain_core.output_parsers import StrOutputParser
chain = prompt | llm | StrOutputParser()
# 1. 单次调用
result = chain.invoke({"question": "什么是量子纠缠?"})
# 2. 流式输出(适合长回答)
for chunk in chain.stream({"question": "写一个1000字的故事"}):
print(chunk, end="", flush=True) # 实时输出
# 3. 批处理(提高效率)
questions = ["Q1", "Q2", "Q3"]
results = chain.batch([{"question": q} for q in questions])
# 自动并发,返回结果列表🎯 面试总结
Token 计数:OpenAI 模型用 get_openai_callback(),其他模型用各自 Tokenizer。
LLM 调用:掌握三种方式 invoke() / stream() / batch(),根据场景选择。
198-199. LangChain 多组件链和 Embedding 存储
👔面试官:LangChain 怎么把多个 Chain 串联起来?Embedding 怎么选,向量存储有哪些?
🙋♂️我:可以用 SequentialChain 把多个 Chain 串起来,Embedding 可以用 OpenAI 的或者 HuggingFace 的,向量存储有 FAISS、Chroma、Milvus……
👔面试官:SequentialChain 和 LCEL 的 | 有什么区别?Embedding 怎么选择,中文场景推荐哪个?向量存储选型看什么维度?
🙋♂️我:SequentialChain 是旧版的吧?LCEL 更现代?Embedding 中文用 BGE?向量存储看性能和规模?
👔面试官:对。SequentialChain 是旧版,LCEL 推荐用 | 或者更灵活的 Runnable 组合。中文 Embedding BGE 确实好。向量存储选型看:本地/云端、数据规模、是否需要持久化、查询性能。
💡 简要回答
多组件链:推荐用 LCEL 的 | 语法或 Runnable 组合,比旧版 SequentialChain 更灵活。
Embedding 选择:
- OpenAI
text-embedding-3:通用、效果好 - BAAI BGE:中文优化、开源免费
- Jina/Multilingual:多语言场景
向量存储选型:
- 本地开发:FAISS(轻量)、Chroma(易用)
- 生产环境:Milvus(大规模)、Pinecone(托管)
📝 详细解析
多组件链组合
旧版用 SequentialChain,新版推荐 LCEL:
python
# 新版 LCEL 推荐写法 - 更简洁灵活
from langchain_core.runnables import RunnablePassthrough
# Chain 1: 翻译
translate_chain = (
{"text": RunnablePassthrough()}
| PromptTemplate.from_template("翻译成英文:{text}")
| llm
| StrOutputParser()
)
# Chain 2: 问答
qa_chain = (
{"question": RunnablePassthrough()}
| PromptTemplate.from_template("回答问题:{question}")
| llm
| StrOutputParser()
)
# 串联(Pipeline)
full_chain = translate_chain | qa_chain | (lambda x: f"最终结果:{x}")
result = full_chain.invoke("什么是机器学习?")比 SequentialChain 优势:
- 更简洁,一目了然
- 可以插入自定义函数(上面的 lambda)
- 支持条件分支(用
RunnableBranch)
Embedding 选型
| 模型 | 特点 | 适用场景 |
|---|---|---|
text-embedding-3-small | 快、便宜、够用 | 英文为主、预算敏感 |
text-embedding-3-large | 精度高、贵 | 精度要求高的场景 |
BAAI/bge-large-zh-v1.5 | 中文优化、开源 | 中文首选 |
jina-embeddings-v2 | 多语言、长文本 | 多语言文档 |
python
from langchain_openai import OpenAIEmbeddings
from langchain_huggingface import HuggingFaceEmbeddings
# OpenAI
openai_embed = OpenAIEmbeddings(model="text-embedding-3-small")
# 中文 BGE(免费、本地运行)
bge_embed = HuggingFaceEmbeddings(
model_name="BAAI/bge-large-zh-v1.5",
model_kwargs={'device': 'cuda'} # 用 GPU 加速
)
# 对比维度:
# - 效果:BGE 中文 > OpenAI small,略逊于 OpenAI large
# - 成本:BGE 免费,OpenAI 按 token 收费
# - 速度:本地 BGE 可能更快(不依赖网络)向量存储选型
| 存储 | 特点 | 适用场景 |
|---|---|---|
| FAISS | 本地、内存中、轻量 | 原型开发、小规模数据 |
| Chroma | 本地/服务器、持久化、易用 | 中小规模生产 |
| Milvus/Zilliz | 分布式、大规模、企业级 | 大规模向量检索 |
| Pinecone | 托管服务、免运维 | 快速上线、不想运维 |
| Qdrant | 开源、高性能、Rust 实现 | 高性能本地/自托管 |
python
from langchain_community.vectorstores import FAISS, Chroma
from langchain_community.vectorstores import Milvus
# FAISS - 本地内存,适合原型
faiss_store = FAISS.from_documents(docs, embeddings)
# Chroma - 本地持久化,适合小生产
chroma_store = Chroma.from_documents(
docs, embeddings,
persist_directory="./chroma_db" # 数据持久化到磁盘
)
# Milvus - 企业级,需要部署 Milvus 服务
milvus_store = Milvus.from_documents(
docs, embeddings,
connection_args={"host": "localhost", "port": "19530"}
)选型决策:
- 数据量 < 10万:Chroma(简单易用)
- 数据量 > 100万:Milvus/Pinecone(水平扩展)
- 不想运维:Pinecone(托管)
- 预算敏感:FAISS/Chroma(免费)
🎯 面试总结
- 多链组合:用 LCEL
|语法,比 SequentialChain 更灵活 - Embedding:中文用 BGE,通用用 OpenAI,多语言用 Jina
- 向量存储:小数据 Chroma,大数据 Milvus,免运维 Pinecone
200. 什么是 LangGraph?它与 LangChain 有什么区别?
👔面试官:听说过 LangGraph 吗?它是做什么的,跟 LangChain 什么关系?
🙋♂️我:LangGraph 是 LangChain 的扩展吧,可以做更复杂的 Agent 工作流。
👔面试官:具体扩展了什么?LangChain 的 Chain 有什么局限?
🙋♂️我:Chain 是线性的……只能一步一步执行?LangGraph 可以支持循环?
👔面试官:对。那为什么需要循环?ReAct Agent 不就是循环吗?
🙋♂️我:ReAct 是循环,但在 LangChain 里实现比较麻烦?LangGraph 让循环更自然?
👔面试官:接近了。LangGraph 把 Agent 的执行过程建模为「有状态图」,节点是函数,边是流转关系,可以显式表达循环、条件分支、状态共享。还有个关键特性是「Human-in-the-loop」,知道是什么吗?
🙋♂️我:人工介入?在关键节点暂停,等人工确认再继续?
👔面试官:对。这在生产环境非常重要,比如执行危险操作前需要人工审批。下面详细说一下。
💡 简要回答
LangGraph 是基于 LangChain 的有状态图(Stateful Graph)工作流框架,把 Agent 的执行过程建模为节点(Node)和边(Edge)的有向图。
相比 LangChain 的线性 Chain,LangGraph 的核心优势:
- 支持循环:节点可以跳回前面的节点(ReAct 的推理循环)
- 条件分支:边可以有条件,根据不同状态走不同路径
- 显式状态:所有状态保存在 TypedDict 中,节点间共享
- Human-in-the-loop:支持在任意节点暂停,等待人工介入
- 状态持久化:可以中断恢复,适合长任务
📝 详细解析
为什么需要 LangGraph
LangChain 的 Chain 是线性的:A → B → C,适合简单流程。但复杂 Agent 需要:
规划 → 执行 → 检查 →(未完成)→ 回到规划
↓
(完成)→ 结束这种循环、条件分支在 Chain 里很难优雅实现。
LangGraph 的解决思路:把执行流程建模为有向图。
核心概念:State、Node、Edge
python
from langgraph.graph import StateGraph, END
from typing import TypedDict, Annotated
import operator
# 1. 定义状态(所有节点共享的「内存」)
class AgentState(TypedDict):
messages: Annotated[list, operator.add] # 消息历史,累加模式
current_step: str # 当前步骤标识
execution_count: int # 执行次数(防死循环)
# 2. 定义节点(每个节点是一个函数)
def plan_node(state: AgentState):
"""规划节点:决定下一步做什么"""
plan = llm.invoke(f"基于历史:{state['messages']}\n制定计划")
return {
"messages": [("assistant", plan)],
"current_step": "execute",
"execution_count": state.get("execution_count", 0) + 1
}
def execute_node(state: AgentState):
"""执行节点:调用工具"""
tool_result = tools.execute(state["messages"][-1].content)
return {
"messages": [("tool", tool_result)],
"current_step": "check"
}
def check_node(state: AgentState):
"""检查节点:判断是否完成"""
# 这个节点返回的不是状态更新,而是下一步去哪个节点
if is_task_complete(state["messages"]):
return "done" # 去结束节点
elif state["execution_count"] > 10:
return "max_iterations" # 去错误处理
else:
return "continue" # 继续规划
# 3. 构建图
graph = StateGraph(AgentState)
graph.add_node("plan", plan_node)
graph.add_node("execute", execute_node)
graph.add_node("check", check_node)
# 4. 添加边
graph.add_edge("plan", "execute")
graph.add_edge("execute", "check")
# 条件边:根据 check_node 的返回值决定下一步
def route_check(state):
return state["next_step"]
graph.add_conditional_edges(
"check",
route_check,
{
"continue": "plan", # 未完成,循环回规划
"done": END, # 完成,结束
"max_iterations": "error_handler"
}
)
graph.set_entry_point("plan")
app = graph.compile()
# 5. 运行
result = app.invoke({
"messages": [("user", "帮我查竞品信息并整理报告")],
"current_step": "plan"
})LangChain vs LangGraph 对比
| 维度 | LangChain Chain | LangGraph |
|---|---|---|
| 执行模型 | 线性(A→B→C) | 有向图(支持任意拓扑) |
| 循环支持 | ❌ 不支持 | ✅ 天然支持 |
| 条件分支 | ❌ 需要额外逻辑 | ✅ 条件边 |
| 状态管理 | 隐式(Memory) | 显式(TypedDict) |
| 可视化 | ❌ 无 | ✅ Mermaid 图 |
| 人工介入 | ❌ 不支持 | ✅ Human-in-the-loop |
| 中断恢复 | ❌ 不支持 | ✅ 检查点持久化 |
| 适用场景 | 简单线性流程 | 复杂多步骤 Agent |
Human-in-the-loop 人工介入
生产环境很多场景需要人工审批:
python
from langgraph.checkpoint.sqlite import SqliteSaver
# 创建检查点存储
checkpointer = SqliteSaver.from_conn_string("agent_state.db")
# 编译图,在 execute 节点前设置中断点
app = graph.compile(
checkpointer=checkpointer,
interrupt_before=["execute"] # 执行前暂停
)
# 第一次调用,执行到 execute 前暂停
thread_id = "task_001"
result = app.invoke(
initial_state,
config={"configurable": {"thread_id": thread_id}}
)
# → 暂停,等待人工确认
# 人工确认后,继续执行
app.invoke(None, config={"configurable": {"thread_id": thread_id}})适用场景:
- 执行危险操作前(删除数据、转账)
- 调用敏感 API 前
- 生成内容发布前审核
- 预算超限时的确认
状态持久化:长任务的保障
python
# 任务执行过程中,状态自动保存到检查点
# 即使程序崩溃,也可以从检查点恢复
# 查询当前状态
snapshot = app.get_state(config)
print(snapshot.values) # 当前所有状态
print(snapshot.next) # 下一个要执行的节点
# 从任意检查点恢复
app.invoke(None, config) # 继续执行🎯 面试总结
LangGraph 解决的是 LangChain Chain 「只能线性执行」的局限。
面试回答抓住四个核心:
- 图模型:节点(函数)+ 边(流转),支持循环和条件分支
- 显式状态:TypedDict 共享内存,所有节点可操作
- Human-in-the-loop:任意节点可暂停,等待人工确认
- 状态持久化:检查点机制,支持中断恢复
选型建议:简单流程用 LangChain,复杂 Agent 用 LangGraph。现在很多项目直接用 LangGraph 构建 Agent,跳过 Chain 那层抽象。
第五分类「五、AI Agent」全部完成,共覆盖题目 158-200(Agent 基础/CoT/ReAct/工具调用/MCP/多 Agent/记忆/LangChain/LangGraph)
第五分类「五、AI Agent」全部完成,共覆盖题目 158-200(Agent 基础/CoT/ReAct/工具调用/MCP/多 Agent/记忆/LangChain/LangGraph)