2026 大厂 AI Agent 高频面试题:10 道真题 + 参考答案 + 源码解读
2026 大厂 AI Agent 高频面试题:10 道真题 + 参考答案 + 源码解读

写在前面:2026 年春招之后,大厂 Agent 岗位的面试风格肉眼可见地变了。字节、阿里、腾讯、美团面试官不再满足于"什么是 Agent"这种概念题,转而追问"你的 Agent 调了三个工具就死循环了,异常处理写在哪?"“MCP 和传统 Function Calling 最大的区别是什么?”——他们不是在面一个会调 API 的人,而是在面一个能搭系统的人。
这篇文章把今年高频出现的考点整理成 10 道真题,每题都给出参考答案和源码级解读,最后留一道开放系统设计题。建议先收藏,再对着镜子自测一遍。
目录
- 01|Agent 和 Chatbot 的本质区别在哪?
- 02|Agentic Loop 是什么?ReAct 的消息格式怎么设计?
- 03|Function Calling 底层到底怎么实现?
- 04|MCP 相比 Function Calling 解决了什么痛点?
- 05|MCP 和 A2A 是什么关系?
- 06|Agent 陷入死循环,怎么从工程上防御?
- 07|LLM 同时调多个工具,顺序、超时、熔断怎么管?
- 08|Agent 的记忆系统到底怎么设计?
- 09|Prompt Injection 在架构层面怎么防御?
- 10|多 Agent 架构:Subagents 和 Agent Teams 怎么选?
- 11|RAG 和 Agent 怎么结合?检索质量差怎么办?
- 12|开放题:设计一个"不崩"的 Agent 平台
- 高频追问速查表
- 写在最后
01|Agent 和 Chatbot 的本质区别在哪?
考点:这是大多数 Agent 岗面试的第一道分水岭。别急着背定义,面试官真正想听的是"自主决策 + 行动"这两个词背后的系统含义。
参考答案:
Chatbot 的工作模型是"用户输入 → LLM 推理 → 输出文本",一句话说完就结束;Agent 的模型是"用户输入 → LLM 推理 → 决定调用哪个工具 → 执行 → 观察结果 → 决定下一步 → 循环直到目标达成"。
所以 Agent 相比 Chatbot 多出来的是四个能力:
| 能力 | 职责 | 对应组件 |
|---|---|---|
| 感知(Perception) | 理解任务意图、解析外部输入 | Prompt / 多模态输入 |
| 规划(Planning) | 把大目标拆成小步骤,决定执行顺序 | Planner、任务队列 |
| 行动(Action) | 调用工具、操作外部系统 | Function Calling / MCP |
| 记忆(Memory) | 跨步骤、跨会话保留状态与知识 | 上下文窗口 / 向量库 / SQLite |
举一个最经典的例子:用户说"帮我查一下明天北京的天气,如果下雨就取消日历里的晨跑"。
- Chatbot 会回答:“您可以打开天气 App 查询北京明日天气,如果降雨概率大于 60%,建议您取消晨跑计划……”
- Agent 会直接做:调天气 API → 明天中雨 → 调日历 API → 找到明早 7:00 的晨跑日程 → 删除 → 回复"已帮您取消,明天下雨记得带伞"。
加分点:主动指出"自主性是双刃剑"。给 Agent 太多自主权,模型可能做出不可预期的危险操作;给太少,就退化成 Chatbot。生产环境的真正挑战是设计安全的自主边界——权限、审批、审计一个都不能少。
02|Agentic Loop 是什么?ReAct 的消息格式怎么设计?
考点:Agentic Loop(智能体循环)几乎是所有 Agent 岗位的必问题。概念好答,但面试官追问到 <tool_call> / <tool_response> 的结构和角色分配时,很多人会卡住。
参考答案:
Agentic Loop 就是 Agent 的"工作流水线":Think(思考)→ Act(行动)→ Observe(观察)→ 循环。用户说"帮我退掉上周五买的书",Agent 先判断需要查订单,调用订单查询工具;发现书已发货不能直接取消,再去查退货政策;确认符合条件后创建退货单。每一步都在循环中推进。
ReAct(Reasoning + Acting)是这个循环最经典的实现,核心是让模型在"推理"和"行动"之间交替。落地时关键是消息格式——它在底层就是一段对话协议:

ReAct 的消息格式在代码层就是如下对话结构:
# 一个最小可跑的 ReAct 循环(伪代码级别)
def react_loop(task: str, tools: dict, llm, max_steps: int = 10):
messages = [
{"role": "system", "content": SYSTEM_PROMPT}, # 声明可用工具和输出格式
{"role": "user", "content": task},
]
for step in range(max_steps):
resp = llm.chat(messages)
# 模型输出两种可能:要么是思考 + 工具调用,要么是最终答案
if resp.is_final_answer:
return resp.answer
# 结构化的工具调用请求,模型只负责"下指令",不负责执行
messages.append({
"role": "assistant",
"content": resp.thought, # 思考过程,可审计
"tool_calls": [{"id": resp.tool_call_id,
"name": resp.tool_name,
"arguments": resp.tool_args}],
})
# 执行器(Executor)真正调用工具,结果以 tool 角色回填
result = tools[resp.tool_name](**resp.tool_args)
messages.append({
"role": "tool",
"tool_call_id": resp.tool_call_id,
"content": json.dumps(result, ensure_ascii=False),
})
raise MaxStepsExceeded(f"超过 {max_steps} 步仍未收敛")
代码解读:
messages是整个循环的"共享内存"——每一步的工具结果都会回填,模型下次推理能看到;- 模型不执行函数,它只输出结构化的
tool_calls,真正执行的是我们的 Executor。这是 Function Calling 的底层铁律; tool_call_id必须一一对应,否则 OpenAl 系模型会报错——这是最容易被忽略的细节;- 循环必须带
max_steps上限,否则模型一旦跑偏,就是无底洞的 token 账单。
加分点:补一句"思考过程(thought)要落日志"。ReAct 最大的价值是透明可审计——每步想了什么、调了什么、结果如何都看得见,这是后续排查问题和做安全审计的基础。
追问:CoT → ReAct → ToT 是什么递进关系?——CoT 解决"推理能力"(只会想),ReAct 解决"推理 + 行动"(边想边做),ToT 解决"多路径探索"(同时尝试多条推理分支选最优)。回答时最好点一句"真实任务需要跟外部世界交互,CoT 只是在文本空间里推演,这就是为什么必须升级到 ReAct"。
03|Function Calling 底层到底怎么实现?
考点:字节、阿里面试高频题。别背概念,讲清楚"LLM 不执行函数、只输出 JSON"这条链路。
参考答案:
Function Calling 的本质是:把工具的描述喂给模型,让模型输出一个结构化的"调用指令",再由你的代码真正执行。核心是四步:
# Step 1:定义工具(把"能力"描述给模型看)
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市指定日期的天气,用于行程决策",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名,如 北京"},
"date": {"type": "string", "description": "日期,格式 YYYY-MM-DD"},
},
"required": ["city", "date"],
},
},
}]
# Step 2:把 tools 与对话一起发给模型
resp = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools, # 工具定义进请求,不进 system prompt
tool_choice="auto", # auto:让模型自己决定要不要调用
)
# Step 3:解析模型输出,得到调用指令
tool_call = resp.choices[0].message.tool_calls[0]
print(tool_call.function.name) # get_weather
print(tool_call.function.arguments) # {"city": "北京", "date": "2026-08-13"}
# Step 4:执行器真正执行,把结果以 tool 角色回传
result = dispatch(tool_call.function.name, json.loads(tool_call.function.arguments))
messages.append({"role": "tool", "tool_call_id": tool_call.id, "content": json.dumps(result)})
代码解读:
parameters用的是 JSON Schema——模型在训练时见过海量 JSON Schema,所以能精确填参;description写得好不好,直接决定模型选对工具的准确率。业界经验:描述要写"何时用 + 怎么用 + 边界",比如"用于行程决策"就比"获取天气"多了一层意图语义;tool_choice="auto"表示模型可自主决定是否调用;"required"则强制它调用——单轮问答用 auto,多步任务流用 required 更稳。
追问 1:同时触发多个 Function Call 怎么处理?——模型可能一次返回多个 tool_calls,无依赖的可以并行执行(Anthropic API 原生支持一次响应返回多个 tool_use 块),有依赖的必须串行,详见第 07 题。
追问 2:Function Call 和"Prompt + 正则解析"有什么区别?——正则解析脆弱且无法泛化,模型一旦换个说法就失配;Function Calling 是模型原生输出的结构化协议,有严格的 schema 校验,是"接口",不是"字符串匹配"。
04|MCP 相比 Function Calling 解决了什么痛点?
考点:2026 年 Agent 面试最硬核的考点,MCP 已经从"加分项"变成"必答题"。传统 Function Calling 有"三大绝症",要能一口气说出来。
参考答案:
传统 Function Calling 的三大绝症:
- 厂商绑定:OpenAI、Claude、通义各自工具定义的语法不同,换个模型,工具层代码重写一遍;
- 静态配置:新增一个工具要改代码、发版、重启,工具和 Agent 强耦合;
- 无执行标准:超时、重试、错误处理全靠开发者自己硬编码,没有统一规范。
MCP(Model Context Protocol)的思路是:把"工具的描述"和"工具的执行"彻底分开。工具定义统一放在 MCP Server 端,Client 通过标准协议动态发现、调用,LLM 只负责决策"调哪个、填什么参数"。
# 用 FastMCP 定义一个工具,模型侧零改动即可发现
from fastmcp import FastMCP
mcp = FastMCP("crm-tools")
@mcp.tool()
def query_order(order_id: str) -> dict:
"""按订单号查询订单状态,用于售后流程判断。
返回:status(success/refunded/shipped)、items、total_amount"""
# 实际业务逻辑:查数据库 → 组装结构化结果
return {"order_id": order_id, "status": "shipped", "items": 2, "total_amount": 199.00}
if __name__ == "__main__":
mcp.run(transport="stdio") # stdio 适合本地;远程用 SSE/Streamable HTTP
代码解读:工具只需要一个装饰器和 docstring——docstring 会被 MCP 自动提取成工具的 description 注入模型上下文。写清楚"何时用 + 返回结构"是基本功。
面试里的深一层答案:MCP 最大的改进,是把工具调用从"一次性编码"变成了"可复用资产"。传统模式下每接一个新工具都是一次新的编码工程;MCP Server 写一次,所有 Agent、所有项目、所有模型都能用,治理集中在一处、审计全量可追溯。用复杂度公式说:Server-First 架构让工具治理复杂度从 O(N×M)(N 个模型 × M 个工具)降到 O(N+M)。
对比表(背下来):
| 维度 | Function Calling | MCP |
|---|---|---|
| 本质 | 模型 ↔ 调用方的协议 | 工具/数据源的标准化协议 |
| 工具接入 | 每个模型各写一遍 | Server 写一次,处处可用 |
| 工具扩展 | 改代码 + 发版 | 运行时动态发现 |
| 执行标准 | 无统一规范 | 统一的超时/错误/生命周期 |
| 治理审计 | 分散在各应用里 | 集中在 Server 端 |
05|MCP 和 A2A 是什么关系?
考点:2026 年高频新题。一句话总结:MCP 是垂直连接(Agent 怎么用工具),A2A 是水平连接(Agent 怎么找 Agent 协作)。
参考答案:
MCP 解决的是"我能用什么"——Agent 到工具、数据源的连接;A2A(Agent-to-Agent Protocol)解决的是"我能和谁合作"——Agent 之间的发现、委托、结果交换。两者是分层协作关系,不是竞争关系:MCP 管 Agent 的手脚,A2A 管 Agent 的社交。

一个完整的企业智能体系统里,Agent 通过 MCP 调用内部系统(CRM、订单库、工单系统),同时通过 A2A 把任务委托给其他 Agent(比如财务 Agent 算账、法务 Agent 审合同),最后汇总结果返回用户。
加分点:一句话点破设计意图——"MCP 标准化了’能力供给’,A2A 标准化了’能力编排’。二者加起来,才构成完整的 Agent 互联生态。"再补一句 Google 推 A2A 时的那句话:“A2A 之于 Agent,就像 HTTP 之于 Web 页面。”
06|Agent 陷入死循环,怎么从工程上防御?
考点:字节 2026 年最经典的面试题之一:"你的 Agent 调了三个工具就死循环了,异常处理在哪写的?"考的是 Agent 运行时(Runtime)的健壮性。
参考答案:
标准答案讲三层防御,从工具层到推理层再到规划层:
第一层:工具层硬隔离——每条工具调用都包 try-catch,返回结构化错误而非裸字符串:
def safe_call(fn, **kwargs) -> dict:
try:
result = fn(**kwargs)
return {"status": "ok", "data": result}
except TimeoutError:
return {"status": "failed", "error_type": "timeout", "retry_after": 5}
except PermissionError as e:
return {"status": "failed", "error_type": "forbidden", "detail": str(e)}
except Exception as e:
return {"status": "failed", "error_type": "unknown", "detail": str(e)}
第二层:推理层熔断——同一工具连续失败 3 次,或者检测到"调用→失败→再调用"的重复模式,强制中断推理链:
class CircuitBreaker:
def __init__(self, threshold: int = 3):
self.threshold = threshold
self.failures = {}
def track(self, tool_name: str, ok: bool) -> bool:
"""返回 False 表示该工具已被熔断,禁止再调"""
if ok:
self.failures.pop(tool_name, None)
return True
self.failures[tool_name] = self.failures.get(tool_name, 0) + 1
return self.failures[tool_name] < self.threshold
def recent_actions_loop(self, history: list) -> bool:
"""连续 3 次相同的 (工具, 参数) 视为死循环"""
if len(history) < 3:
return False
return all(h == history[-1] for h in history[-3:])
第三层:规划层自修正——失败不只是报错,而是让 Agent 反思"刚才哪里错了?参数不对?要不要换工具?"。这是微软提出的 Reflection Pattern:
REFLEXION_PROMPT = """上一次工具调用失败了,错误信息:{error}
请反思:
1. 失败原因是什么?(参数错误 / 工具不存在 / 权限不足 / 数据不存在)
2. 换一个工具能否达成同样目标?
3. 修改参数后重试,还是放弃这一步直接给用户交代?
请直接输出下一步动作。"""
messages.append({"role": "user", "content": REFLEXION_PROMPT.format(error=last_error)})
next_action = llm.chat(messages) # 把反思结果重新喂回循环
代码解读:
- 结构化错误信息(
error_type+retry_after)是给"熔断器"和"反思模块"吃的,不是给人看的; - 熔断器统计的是"同一工具连续失败次数",防止 Agent 拿一个坏工具反复撞墙;
- Reflection 的核心思想是把失败当成数据——每次失败都变成一次自我纠错的机会,这才是"不崩"的 Agent 和"脆"的 Agent 的本质区别。
加分点:补一句整体预算——“除了步数上限,我还会给整个任务设置最大执行时间和 token 预算,超了就优雅降级,把已有的中间结果整理成部分交付,而不是硬跑到底。”
07|LLM 同时调多个工具,顺序、超时、熔断怎么管?
考点:字节算法岗二面高频题。拆开是三个问题:依赖管理、超时控制、异常熔断。
参考答案:
依赖管理:无依赖的工具可以并行(模型一次返回多个 tool_calls),有依赖的必须串行,或者显式建模成 DAG:
import asyncio
async def run_tools(tool_calls: list[dict], deps: dict[str, list[str]]):
"""deps: {tool_name: [依赖的工具名]},无依赖的并行执行"""
results = {}
ready = [tc for tc in tool_calls if not deps.get(tc["name"])]
blocked = [tc for tc in tool_calls if deps.get(tc["name"])]
async def worker(tc):
# 每条调用独立超时,避免一个慢工具拖垮全局
try:
return await asyncio.wait_for(
dispatch(tc["name"], tc["args"]), timeout=15.0)
except asyncio.TimeoutError:
return {"status": "failed", "error_type": "timeout"}
while ready:
batch = await asyncio.gather(*[worker(tc) for tc in ready])
for tc, r in zip(ready, batch):
results[tc["name"]] = r
# 依赖已完成的工具进入下一批
ready = [tc for tc in blocked
if all(d in results for d in deps[tc["name"]])]
blocked = [tc for tc in blocked if tc not in ready]
return results
代码解读:
asyncio.wait_for(..., timeout=15.0)是硬超时——MCP 社区期望工具在 7~10 秒内返回,客户端应设 15 秒阈值,超时立即中断并返回结构化信息;- 分批执行模拟了 DAG 的拓扑排序:每一轮只执行"依赖都已就绪"的工具;
- 单个工具失败不阻塞其他工具——局部失败应该被记录,而不是让整个对话崩溃。
加分点:主动提"部分成功怎么办"——“工具 A 成功、工具 B 失败时,我会把 B 的失败原因拼进下一轮推理,让 Agent 决定是重试、换工具,还是带着 A 的结果继续。失败信息必须回灌给模型,不能悄悄吞掉。”
08|Agent 的记忆系统到底怎么设计?
考点:高频题。要能区分工作记忆 / 短期记忆 / 长期记忆,并给出可落地的工程方案。
参考答案:
| 类型 | 载体 | 生命周期 | 典型实现 |
|---|---|---|---|
| 工作记忆 | 上下文窗口 | 单次任务 | messages 数组、Scratchpad(便签本) |
| 短期记忆 | 会话上下文 | 单次对话 | 上下文管理、摘要压缩 |
| 长期记忆 | 外部存储 | 跨会话 | SQLite / 向量库 / 全文索引 |
工程上最常用的落地组合是:SQLite 做本地长期记忆 + 全文搜索索引 + 向量检索做语义匹配:
import sqlite3, json
class LongTermMemory:
def __init__(self, db_path="memory.db"):
self.conn = sqlite3.connect(db_path)
self.conn.execute("""
CREATE TABLE IF NOT EXISTS memories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key TEXT UNIQUE, -- 记忆主键,如 user:11242:pref:city
content TEXT, -- 记忆内容
importance REAL DEFAULT 0.5,-- 重要度,用于淘汰
access_count INTEGER DEFAULT 0,
updated_at TEXT
)""")
self.conn.execute("CREATE INDEX IF NOT EXISTS idx_key ON memories(key)")
def write(self, key: str, content: str):
self.conn.execute(
"INSERT INTO memories(key, content, updated_at) VALUES(?,?,datetime('now')) "
"ON CONFLICT(key) DO UPDATE SET content=excluded.content, updated_at=datetime('now')",
(key, content))
self.conn.commit()
def recall(self, prefix: str) -> list[str]:
"""按前缀召回:稳定事实用精确匹配,比向量检索更可靠"""
cur = self.conn.execute(
"SELECT content FROM memories WHERE key LIKE ? "
"ORDER BY importance DESC LIMIT 20", (prefix + "%",))
return [row[0] for row in cur]
代码解读:
- 记忆不是"往里塞向量就行"——稳定事实(用户偏好、配置项)用 key-value 精确存取,比向量召回更可靠、更便宜;
- 向量检索只用于"语义相似"场景(比如"用户之前聊过类似需求吗");
- 上下文窗口快满时的兜底是摘要压缩——把早期对话压成一段摘要,替换原始消息,保住关键信息、控制 token。
加分点(一眼惊艳):主动点出"基于检索的记忆 vs 基于权重的记忆"——港中大与浙大 2025 年的研究指出,当前所有记忆方案本质上是"备忘录(Memo)",只是存起来再检索;真正的记忆是经验内化为模型权重。面试里说出这个区分,说明你真的读过论文而不是背面经。
09|Prompt Injection 在架构层面怎么防御?
考点:Agent 安全治理的高频题。要讲清楚"为什么难防"和"防御必须建在模型外部"。
参考答案:
Prompt Injection 比 SQL 注入难防,根源在于 LLM 架构层面的 Context Mixing——在单一上下文窗口里,模型无法可靠区分系统指令、用户指令和不可信的外部数据(网页、邮件、工具返回内容)。所以防御不能指望模型自觉,必须建在模型外部。
生产级防御四层:
- 前置隔离(Execute-Only 架构):让不可信数据不进推理上下文。研究显示 78.4% 的任务理论上可以在"模型只看指令、不看原始数据"的情况下完成——工具直接消费原始数据,模型只拿到结构化结论;
- 工具调用审查:在工具调用边界加语义审计层——工具名、参数、目标系统是否符合当前任务的权限范围,可疑调用直接拦截;
- 影响溯源:追踪不可信上下文如何传播到 Agent 决策中,定位"哪条外部信息污染了哪个决策";
- 权限最小化:静态最小权限——凭证从 Agent 内部移除,改为网络边界注入;Agent 默认无敏感操作权限,需要时动态申请。
class ToolAuditor:
ALLOWLIST = {"query_order", "get_weather", "send_email_to_support"}
def audit(self, tool_name: str, args: dict, user_role: str) -> tuple[bool, str]:
if tool_name not in self.ALLOWLIST:
return False, f"工具不在白名单: {tool_name}"
# 参数级校验:禁止删除类操作、禁止访问非授权范围
if "delete" in tool_name or args.get("force") is True:
return False, "删除/强制类操作需要人工审批"
if tool_name == "send_email_to_support" and user_role not in ("admin", "agent"):
return False, "权限不足"
return True, "ok"
代码解读:审计器不是加在"模型之前",而是加在"模型和执行器之间"——模型可以"想"任何事,但执行器只认审计结果。这是"模型自由、执行受限"的架构原则,也是 MCP 工具层的标准防御位。
加分点:报出两个数据——OWASP 已将 Prompt Injection 列为 LLM 应用头号漏洞;InjecAgent 基准测试显示超过 50% 的 agentic 任务存在注入漏洞。能说出基准名和数据,面试官立刻知道你关注安全前沿。
10|多 Agent 架构:Subagents 和 Agent Teams 怎么选?
考点:2026 年高频题,通常以"Claude Code 的多 Agent 怎么实现"的形式出现。
参考答案:
主流多 Agent 有两种范式:
| 维度 | Subagents(父子工头制) | Agent Teams(团队协作制) |
|---|---|---|
| 交互方式 | 父 Agent 派活、收结果 | Lead Agent + 共享任务列表 + 消息信箱 |
| 上下文 | 子任务独立、干净上下文 | 各成员独立上下文,靠信箱通信 |
| 通信 | 不支持互相通信 | 支持 |
| 优点 | 隔离好、轻量、token 省 | 适合分工协作、跨模块联调 |
| 缺点 | 无法协作解决复杂任务 | 通信开销大、token 消耗高 |
| 适用 | 互不相关的并行子任务 | 需要反复协商的复杂任务 |
选型决策就一句话:子任务之间不需要通信 → 用 Subagents;需要通信 → 用 Agent Teams。
加分点(三个追问):
- 多 Agent 并发改同一文件怎么办?——乐观并发控制 + 文件级锁:Agent 改文件前先检查共享任务列表里有没有其他 Agent 锁定该文件,冲突则触发人工介入或让 Lead 重新协调;
- Agent 陷入死循环?——复用第 06 题的三层防御;
- Agent Swarms(去中心化集群)怎么评价?——没有固定 Lead,适合大型 CI 流水线;Agent Teams 适合确定性交付项目。同时引用 Anthropic 的提醒:不要过早引入多 Agent——一个强大的单 Agent 往往比一堆简单 Agent 更稳定、更省钱。
11|RAG 和 Agent 怎么结合?检索质量差怎么办?
考点:字节面试不问"什么是 RAG",而是问"检索质量差你怎么调"。考的是真实调参经验。
参考答案:
RAG 在 Agent 里的定位是长期记忆和外部知识的供给方——Agent 需要事实时通过 RAG 拿证据,而不是靠模型脑补。检索质量差的排查顺序:
- 分块策略:块太大噪声多、太小上下文碎片化。通用起点:300~500 token + 10%~15% 重叠,再按文档结构(标题层级)做语义分块;
- 混合检索:BM25 精确匹配(命中术语、ID、专有名词)+ 向量语义召回,用 RRF(Reciprocal Rank Fusion)合并分数,兼顾精度和召回;
- 重排(Rerank):向量召回 Top 50 → 精排模型重排取 Top 5。Rerank 是性价比最高的单点优化;
- 检索结果加引用:给模型返回的内容带来源,让 Agent 能溯源,降低幻觉。
from rank_bm25 import BM25Okapi # BM25 精确匹配
import numpy as np
def hybrid_search(query: str, chunks: list[str], top_k: int = 5):
# 1. 向量召回(伪代码:embed 后余弦相似度)
vec_scores = vector_recall(query, chunks, top_k * 10)
# 2. BM25 精确召回
bm25 = BM25Okapi([c.split() for c in chunks])
bm25_scores = bm25.get_scores(query.split())
# 3. RRF 融合:1/(k+rank) 让两个排序源的靠前结果互相加成
k = 60
rrf = {}
for rank, idx in enumerate(vec_scores):
rrf[idx] = rrf.get(idx, 0) + 1 / (k + rank + 1)
for rank, idx in enumerate(np.argsort(bm25_scores)[::-1][:top_k * 10]):
rrf[idx] = rrf.get(idx, 0) + 1 / (k + rank + 1)
return [chunks[i] for i in sorted(rrf, key=rrf.get, reverse=True)[:top_k]]
代码解读:RRF 融合的精髓是不依赖两个排序源的分数尺度——不管向量分数是 0.9 还是 0.1,只用"排名"做融合,工程上最稳。
加分点:“检索质量差"还有一种隐蔽原因——query 质量问题。用户问"帮我看看上个月项目怎么样了”,直接检索大概率失败,应该先让 Agent 把模糊问题重写/拆解成具体检索词,再进 RAG 管道。
12|开放题:设计一个"不崩"的 Agent 平台
考点:压轴开放题,综合考架构能力。没有标准答案,但回答要覆盖下面五个维度。
参考答案(答题框架):
| 维度 | 关键设计 | 必答要点 |
|---|---|---|
| 推理框架 | 任务层用 Plan-and-Execute + 重规划检查点 | 省 token,且执行中发现偏差能动态改计划 |
| 工具与协议 | 全量走 MCP,工具白名单 + 审计器 | 工具可复用、可治理,边界清晰 |
| 循环治理 | max_steps + 熔断器 + Reflection 自修正 + token 预算 | 三层防御缺一不可 |
| 状态与记忆 | 工作记忆(上下文)+ 长期记忆(SQLite/向量库)+ 摘要压缩 | 长任务不丢状态、不爆上下文 |
| 安全与审计 | Execute-Only 隔离 + 权限最小化 + 全链路日志 | 出问题能溯源、能回滚、能复盘 |
加分结构:先给系统骨架(从上到下一层一层说),再给一个"踩坑案例"——比如"我们的 Agent 曾经连续重试一个坏工具 47 次,后来加了熔断器,P95 延迟从 8 秒降到 2.3 秒"。真实案例比完美架构更有说服力。
高频追问速查表
下面这些追问,回答要短、准、稳,背下来:
| 追问 | 一句话答案 |
|---|---|
| Agent 和 Workflow 的本质区别? | 控制权在 LLM 手里是 Agent,在代码手里是 Workflow;生产环境多是混合架构 |
| ReAct 为什么比 CoT 强? | CoT 只会想,ReAct 边想边做,能与外部世界交互 |
| MCP 三大攻击面? | Context Manipulation(上下文污染)、Server-Side Injection(服务端注入)、Cross-Server Compromise(跨服务沦陷) |
| 上下文窗口满了怎么办? | 摘要压缩 → 分层记忆 → 子 Agent 拆解,三步递进 |
| 模型总把工具参数填错? | 检查 description 是否写了"何时用+边界",必要时加枚举校验或让模型先反思 |
| Agent 怎么评测? | 任务成功率 + 工具调用正确率 + 平均步数 + 成本(token)+ 安全通过率,五个指标一起看 |
| 幻觉怎么治? | 检索增强给证据、关键事实强制验证(Reflection)、不确定就说"不知道" |
| 单 Agent 还是多 Agent? | 默认单 Agent;任务可并行拆解或需专业分工时才上多 Agent |
写在最后
从"知道 Agent 是什么"到"能搭一个不崩的 Agent 系统",中间隔着的是异常处理、状态管理、安全边界、成本控制这一堆工程细节。面试官问的每一道题,本质上都是"你有没有真的搭过、调过、踩过坑"。
把这 10 道题吃透,再拿自己的项目(哪怕是一个本地小工具)把代码跑一遍,比背 100 道面经都管用。如果你觉得这篇对你有帮助,欢迎收藏 + 点赞 + 关注,后续我会继续更新 Agent 系统设计的实战系列(MCP Server 从零实现、Agent 评测体系、多 Agent 编排实战……)。
有拿不准的题目,欢迎在评论区留言,我会挑高频问题出续集。
声明:本文为原创技术文章,代码均为手写示例,仅供学习交流,转载需注明出处。
更多推荐


所有评论(0)