Agent 面试通关 / 02

工具管理:参数校验、工具路由与百级工具库

工具调用是 Agent 区别于普通对话模型的核心能力。面试官在这个维度考的不是“你知不知道 function calling”,而是“模型不听话怎么办”和“工具多了怎么管”——这两个问题决定了你的 Agent 能不能上生产。


参数校验与工具路由

Q:工具描述写得再好,模型也瞎传参数怎么办?

来源:腾讯 Agent 岗终面

新手答:“加强 Prompt,让模型输出 JSON。”

高手答

三层防线:

  1. Schema 里写“负面描述”:比如“城市名参数,请直接使用用户输入文本,切勿从地址中自行解析提取”。告诉模型不该做什么,比告诉它该做什么更有效。
  2. 输出后做硬校验:用 Pydantic 模型校验,类型不对、枚举值不匹配的直接拒掉,让模型重试。
  3. 关键参数业务兜底:比如查询订单的工具,模型传回的 order_id,必须先调用一次“订单是否存在”的校验接口。绝不让未经核实的 ID 直接下发给数据库。

差距在哪:新手的答案停留在 Prompt 层面——这是最弱的防线,模型不一定听。高手的三层防线是递进的:Prompt 引导 → 格式硬校验 → 业务逻辑兜底。即使模型完全不听话,后两层也能拦住。面试官考的是“你有没有做过需要防御性编程的系统”。

追问:模型调用工具出现参数幻觉或语法错误时,有哪些自动化修正手段?

来源:蚂蚁 AI应用开发 二面

参数幻觉(模型编造不存在的参数值)和语法错误(JSON 格式错误、类型不匹配)是工具调用最常见的两类失败。自动化修正手段分三个层次:

1. 格式层自动修复:JSON 语法错误可以用规则修复——缺少引号、多余逗号、未闭合括号等常见错误有明确的修复规则。不需要模型参与,纯代码处理。

2. 语义层重试:参数值不合法时(如枚举值不在范围内、日期格式错误),将错误信息拼接到 Prompt 中让模型重新生成参数。关键是错误信息要具体——不是“参数错误”,而是“city 参数值‘北京市’不在合法列表中,合法值为:beijing, shanghai, …”。

3. 降级策略:重试 1-2 次仍失败时,不继续重试(避免 token 浪费),而是降级处理——用默认参数、跳过该工具、或转人工。降级比无限重试更符合生产需求。


Q:你们工具库有上百个工具,怎么让模型快速选对?

来源:腾讯 Agent 岗终面

新手答:“把所有工具描述发给模型让它选。”

高手答

不可能全发,上下文不够,且会干扰。我们做了个轻量级工具路由层

  1. 用户请求来时,先用 FastText 之类的小模型快速提取意图关键词
  2. 用关键词去工具向量库做语义检索,召回 top 5
  3. 最关键的一步:用一个精调过的 7B 小模型,给这 5 个工具的相关性做精排序,只把 top 2 工具的完整 schema 交给大模型做最终调用

这个流程让工具选择准确率从 ~70% 提到了 95%+。

候选工具超过 100 个时的召回偏差问题

当工具规模达到百级以上,纯语义检索会出现召回偏差——高频工具的描述被模型见过更多次,embedding 和 query 的匹配度天然更高,导致低频但正确的工具被排挤。

偏差类型 表现 解决方案
高频偏差 常用工具总是排在前面,冷门工具几乎不被召回 对工具 embedding 做频率逆加权,降低高频工具的匹配分
描述偏差 描述写得好的工具比描述差的更容易被选中 统一工具描述模板,确保描述质量一致
语义模糊偏差 多个工具描述相似,模型分不清选哪个 在描述中加“反面说明”——“本工具不用于XX场景”

更根本的解法是分层路由——不让模型在 100 个工具中直接选,而是先分类再选:

第一层:意图分类器 → 5-8 个工具大类(检索/操作/分析/通信...)
第二层:大类内语义检索 → 每类 10-15 个工具中选 top-3
第三层:最终选择 → 模型从 3 个候选中选

每层缩小 5-10 倍,三层下来从 100+ 缩到 3,大幅降低选错概率。

差距在哪:新手的方案在工具超过 20 个时就会崩——上下文被工具描述占满,模型反而选不好。高手的回答展示了一个完整的召回-排序管线(和搜索引擎的架构一样),每一层都有明确的职责。面试官考的是“你能不能把 Agent 的子问题抽象成一个工程问题来解决”。


Q:多工具场景下的调度策略?

来源:阿里 AI Agent 开发一面

新手答:“模型自己选。”

高手答

多工具场景的核心问题是选哪个、按什么顺序、并行还是串行

  1. 意图路由前置:用户请求先经过轻量分类器或规则,判断需要哪类工具,不让大模型在全量工具里大海捞针
  2. 依赖分析决定编排:分析工具之间的输入输出依赖关系——无依赖的并行执行,有依赖的串行编排,减少总耗时
  3. 优先级与降级:多个工具都能用时,按可靠性、延迟、成本排优先级;主工具超时自动降级到备选
  4. 结果合并与冲突处理:多工具返回结果可能矛盾,需要合并策略——取最新的、取置信度最高的、或让模型做最终判断

调度策略不是模型能力问题,是工程编排问题——模型负责决策“做什么”,编排层负责“怎么执行”。

差距在哪:新手把调度交给模型“自己想办法”。高手的回答把多工具调度拆成四个环节:路由、编排、降级、合并——每个环节都有明确策略。面试官考的是你能不能把多工具协作从“模型自己选”变成一个可控的工程流程。


工具返回与中间层

Q:Mock 是怎么实现的?在自动化生成测试的场景下

来源:抖音基础架构 Agent 一面

新手答:“用 unittest.mock 替换依赖。”

高手答

Mock 的实现分三步:识别依赖 → 生成 Mock 对象 → 注入测试

第一步:识别需要 Mock 的依赖

从 AST 和 import 分析中提取外部调用——数据库连接、HTTP 请求、文件 I/O、第三方 SDK 调用。判断标准是:这个依赖在测试环境里能不能正常运行。能运行的不 Mock,不能运行的才 Mock。

第二步:生成 Mock 对象

根据依赖的接口签名生成 Mock:

  • 函数调用:Mock 返回值,类型和实际返回一致
  • 类实例:Mock 整个类,保留方法签名,返回值用合理的默认值
  • 异步调用:生成 AsyncMock,保持 await 语义
# 自动生成的 Mock 示例
@patch('module.db_client.query')
def test_get_user(mock_query):
    mock_query.return_value = {"id": 1, "name": "test"}
    result = get_user(1)
    assert result["name"] == "test"
    mock_query.assert_called_once_with(user_id=1)

第三步:注入策略

不同语言注入方式不同——Python 用 @patch 装饰器或 with 语句,Java 用依赖注入框架(Mockito),JavaScript 用 jest.mock。关键是 Mock 的粒度:Mock 太多会让测试脱离真实行为,Mock 太少会因为环境依赖跑不起来。

差距在哪:新手只知道用 Mock 库替换依赖。高手的回答展示了自动化场景下的完整 Mock 生成流程——先识别、再生成、再注入——且点出了 Mock 粒度的核心取舍。面试官考的不是你会不会用 Mock 库,而是你能不能让 Agent 自动决定“Mock 什么、怎么 Mock”。


Q:如果工具调用是成功的,但返回结果语义不完整,模型很容易误判,你怎么设计中间层?

来源:腾讯大模型应用开发二面

新手答:“让模型自己判断返回值是否完整。”

高手答

这个问题非常常见。很多工具从接口层面看是 200 成功,但业务语义上其实不够用——比如只返回了一个 code 没有返回解释信息,或者字段含义不清,模型会自行脑补。

解决方式是加一个 tool adapter 或 semantic wrapper,把原始结果转成统一、可解释的中间表示:

  1. 字段补全:缺失字段用默认值或“未知”显式标注,而不是让模型猜
  2. 错误翻译:把错误码转成自然语言描述,比如 code: 404"未找到对应记录"
  3. 单位归一:统一数据格式,比如日期统一成 ISO 格式、金额统一带币种
  4. 空值处理和置信度标注:区分“查了没有”和“没查到”,标注数据可信度

核心原则:不要把外部 API 的脏数据直接回喂给模型。中间层先清洗,让模型看到的是“可推理对象”,而不是原始接口垃圾。

差距在哪:新手让模型自己判断——模型没有业务语义知识,判断不了。高手在工具和模型之间加了一个 semantic wrapper 层,做字段补全、错误翻译、单位归一和置信度标注。面试官考的是你有没有意识到“工具调用成功 ≠ 结果可用”,以及怎么在中间层做数据治理。

追问:外部工具返回的数据格式和 Agent 期望的不匹配,怎么做自动映射?

来源:淘天 AI Agent 二面

这在接入第三方 API 时非常常见——天气 API 返回 temp_f(华氏度),Agent 期望 temperature_celsius(摄氏度);电商 API 返回嵌套 JSON,Agent 期望扁平 key-value。

三层映射方案

层次 处理内容 实现方式
结构映射 字段重命名、嵌套展平、数组提取 JSONPath / JMESPath 表达式
类型转换 单位换算、日期格式、枚举值映射 预定义转换函数库
语义补全 补充缺失字段、添加业务含义注释 模板填充 + 默认值规则

追问:MCP 接入多个测评工具,同一问题返回格式不统一,怎么处理?

来源:阿里淘天 AI应用开发一面

MCP 接多工具后,同一个“代码质量评测”请求,SonarQube 返回 JSON、ESLint 返回文本列表、Pylint 返回 CSV 格式——格式不统一模型根本无法做交叉对比。

解决方案是在 MCP Server 内部做标准化输出层

层级 做什么 示例
格式归一 所有工具输出统一为同一 JSON Schema {tool, severity, message, location}
语义对齐 不同工具的等级/标签映射到统一枚举 SonarQube “BLOCKER” = ESLint “error” = “严重”
结果聚合 多工具结果合并去重后再返回 Agent 相同位置的相同问题只保留一条

核心原则:格式统一是 MCP Server 的职责,不是模型的职责——让模型去理解三种不同格式是浪费 token 且不可靠。

工程实现——Schema 适配器模式

工具注册时定义两份 Schema:
  raw_schema:工具实际返回的原始格式
  agent_schema:Agent 期望的标准格式

映射规则(声明式配置,不写代码):
  {
    “temperature_celsius”: “$.main.temp_f | fahrenheit_to_celsius”,
    “city_name”: “$.name”,
    “wind_speed_mps”: “$.wind.speed | mph_to_mps”,
    “description”: “$.weather[0].description”
  }

声明式配置的好处是新增工具不需要写代码——只需要写一份映射规则。转换函数(如 fahrenheit_to_celsius)是预置的可复用单元。

当映射规则覆盖不了的复杂情况(如返回格式不固定),才降级到用 LLM 做动态格式转换——但这会引入延迟和不确定性,只作为兜底方案。


Q:工具多导致 token 数过多,怎么解决?

来源:字节 Agent 实习二面 【阿里国际一面追问:Skill描述过长导致上下文爆炸】

新手答:“减少工具数量。”

高手答

砍工具是下策——工具是 Agent 的能力,砍多了能力就残了。核心思路是让模型在每次调用时只看到需要的工具,而不是全量工具

1. 动态工具加载

  • 不把所有工具的 schema 一次性塞进 system prompt
  • 根据用户当前意图,只加载相关工具。比如用户在问天气,就只加载天气/地理类工具,不加载数据库/文件操作类工具
  • 意图识别可以用轻量分类器或关键词规则,成本极低

2. 工具描述压缩

  • 很多工具描述写得太冗长——把 200 字的描述压缩到 50 字以内,只保留“这个工具做什么”和“关键参数”
  • 参数的详细约束放在二级描述中,模型选中工具后再加载完整 schema

3. 分层工具组织

第一层:工具大类(5-8 个)
  → 信息查询类 / 数据操作类 / 文件处理类 / 通信类 / ...

第二层:具体工具(每类 5-10 个)
  → 天气查询 / 股票查询 / 地图搜索 / ...

模型先选大类,再在大类下选具体工具。两次选择的 token 开销远小于一次展示全量工具。

4. 工具向量检索(已有工具百级以上时):

  • 用户 query 做 embedding,和工具描述做语义匹配,只召回 top 3-5 的工具
  • 这就是把“工具选择”变成了一个“检索”问题

差距在哪:新手的“减少工具”是削足适履。高手用动态加载、描述压缩、分层组织、语义检索四个方法,在不砍工具的前提下把 token 开销控制住。面试官考的是你面对 token 预算约束时的工程化解题能力。


MCP 与 Function Calling

Q:MCP Server 是怎么构建的?

来源:字节 Agent 实习二面

新手答:“就是写个 API 接口。”

高手答

MCP(Model Context Protocol)是 Anthropic 提出的模型与外部工具/数据源的标准化通信协议,目标是让 Agent 以统一的方式调用不同工具,不需要为每个工具写专门的适配代码。

MCP Server 的核心职责是:把外部能力(API、数据库、文件系统等)封装成符合 MCP 协议的标准工具,供 Agent 调用。

构建一个 MCP Server 的关键步骤

  1. 定义 Tool Schema
    • 每个工具需要声明名称、描述、输入参数(JSON Schema)和输出格式
    • 描述要写给模型看——清晰说明“什么时候该用这个工具”和“参数怎么填”
  2. 实现 Handler
    • 每个工具对应一个 handler 函数,接收标准化的参数,执行实际操作,返回标准化的结果
    • Handler 内部做参数校验、错误处理、超时控制
  3. 选择传输方式
    • stdio:通过标准输入输出通信,适合本地工具(如文件操作、命令行工具)
    • HTTP/SSE:通过 HTTP 请求 + Server-Sent Events 通信,适合远程服务
    • 本地开发用 stdio 简单快速,生产部署用 HTTP/SSE 做服务化
  4. 注册与发现
    • Client 端(Agent)通过配置或服务发现机制找到 MCP Server
    • Server 启动时声明自己提供的工具列表,Client 按需选择

和直接写 API 的区别

  • 普通 API:每个工具一套接口定义、一套调用方式、一套错误处理
  • MCP:所有工具统一协议,Agent 不需要知道底层是 REST 还是 gRPC 还是本地调用——只需要知道 tool name 和参数

差距在哪:新手把 MCP 等同于“写 API”。高手理解 MCP 是一个协议层抽象——统一了工具发现、参数定义、调用方式和错误处理,让 Agent 能以即插即用的方式扩展能力。面试官考的是你对 Agent 工具生态标准化趋势的理解。


Q:大厂开源的 CLI 工具(如 lark-cli)和 MCP 有什么区别?它们跟直接调 API 又有什么不同?

来源:大厂 Agent 面试高频题

新手答:“CLI 就是命令行工具,MCP 就是协议,API 就是接口,三个不同的东西。”

高手答

这三者解决的问题不同,服务的“用户”也不同:

1. API——给程序用的原始接口

API 是最底层的能力暴露方式。比如飞书开放平台的 REST API,你要自己处理鉴权、拼参数、解析响应、处理错误码。它的“用户”是开发者写的代码。

开发者代码 → HTTP 请求 → 飞书 API → 响应 JSON

2. CLI 工具——大厂跟进 Agent 生态的抢位之作

关键背景:lark-cli、coze-cli 这批大厂 CLI 工具集中涌现,不是偶然——它们是 MCP 和 Agent 生态爆火之后,各平台为了抢占 Agent 工具生态入口而快速推出的。

CLI 本质上是 machine-friendly 的接口形态。和 GUI(人类友好)不同,CLI 天然适合被程序和 Agent 调用——结构化的输入输出、可脚本化、可管道组合。大厂推 CLI 而不是只提供 API,是因为 CLI 降低了 Agent 接入的门槛:不用写 SDK 集成代码,直接 lark-cli send --chat "xxx" --text "hello" 就能调用。

Agent / 脚本 → lark-cli send --chat "xxx" --text "hello"
                    ↓ 内部封装鉴权、参数、错误处理
               飞书 API 调用 → 结构化输出

但 CLI 的局限很明显:每个平台一套 CLI,Agent 需要为每个平台学一套命令

3. MCP——Agent 工具调用的统一协议

MCP 解决的是 CLI 解决不了的问题:标准化。它不是封装某一个平台的 API,而是定义了 Agent 发现和调用任意工具的统一协议。不管底层是飞书 API、数据库还是本地文件系统,Agent 只需要按 MCP 协议交互。

Agent → MCP 协议 → MCP Server A(封装飞书能力)
                  → MCP Server B(封装数据库)
                  → MCP Server C(封装本地文件系统)

MCP 的价值在于:Agent 不需要知道底层是 REST、gRPC 还是 CLI——一套协议打通所有工具

那大厂为什么不直接做 MCP Server,还要出 CLI?

因为 CLI 是更低成本的试水方式:不需要实现完整的 MCP 协议栈,先用 CLI 把平台能力暴露给 Agent 生态,抢个身位。而且 CLI 可以被 MCP Server 二次封装——社区已经有大量“CLI → MCP Server”的适配层了。

核心区别总结

维度 API CLI 工具 MCP
服务对象 程序代码 Agent / 脚本(machine-friendly) AI Agent(协议级)
设计动机 开放平台能力 抢占 Agent 工具生态入口 统一 Agent 工具标准
抽象层级 原始接口 平台级封装 协议级抽象
覆盖范围 单一平台 单一平台 跨平台统一
工具发现 查文档 --help 协议内置 tools/list
典型场景 后端集成 Agent 快速接入单一平台 Agent 统一工具管理

它们的关系是递进的:API 是原始能力 → CLI 是大厂面向 Agent 生态的快速封装 → MCP 是最终的协议层标准。一个 MCP Server 内部可以调 API,也可以调 CLI,它们不互斥。

差距在哪:新手把三者当成并列的“不同的东西”。高手看到的是 Agent 工具生态的演进脉络——API 一直在,CLI 是大厂看到 MCP/Agent 趋势后的抢位动作,MCP 是最终统一标准。面试官考的是你能不能看到这波 CLI 扎堆出现背后的产业逻辑,以及理解为什么 Agent 时代的终局是协议层标准化而不是各家出各家的 CLI。


Q:大模型的 Function Call 是什么?Tool Use 一般怎么用?

来源:蚂蚁集团智能体与大模型应用一面 【字节实习Agent开发一面追问:工具注册/解析/调用/回传全链路】

新手答:“就是让模型调用函数。”

高手答

Function Call(Tool Use)是让大模型不只生成文本,还能结构化地调用外部工具的核心机制。

工作原理

sequenceDiagram
    participant U as 用户
    participant M as 大模型
    participant T as 工具/API
    U->>M: "帮我查北京明天天气"
    M->>M: 分析意图,决定调用 weather_query 工具
    M-->>T: {"name": "weather_query", "arguments": {"city": "北京", "date": "明天"}}
    T-->>M: {"temp": "22°C", "weather": "晴"}
    M->>U: "北京明天晴,气温 22°C,适合出行"

模型不是直接执行代码——它输出的是结构化的调用意图(工具名 + 参数 JSON),由外部编排层执行实际调用,再把结果回传给模型做最终整合。

和普通 Prompt 的关键区别

维度 普通 Prompt Function Call
输出 自由文本 结构化 JSON(工具名 + 参数)
能力边界 只能用训练数据 可接入实时数据和外部系统
可控性 低(自由发挥) 高(Schema 约束参数类型和取值)
典型场景 问答、创作 查天气、操作数据库、发消息、执行代码

在 Agent 项目中的典型 Function Call

  1. 信息查询类:知识库检索、数据库查询、API 调用——Agent 不靠记忆回答,而是实时查
  2. 操作执行类:创建工单、发送通知、修改配置——Agent 不只回答问题,还能执行动作
  3. 代码执行类:运行 Python 代码做计算、执行 SQL 查询——把模型的“推理”变成“验证”

工程上的关键设计

  • Schema 定义要精准:工具描述写给模型看,参数 Schema 约束类型和枚举值。描述越精确,模型选错工具和瞎传参的概率越低
  • 返回值要结构化:工具返回不要是一大段文本,而是 JSON——模型解析结构化数据比理解自然语言更稳定
  • 错误处理要前置:工具调用可能失败,返回值里必须带状态码和错误信息,让模型能根据错误类型决定下一步

差距在哪:新手把 Function Call 等同于“调函数”。高手理解它是模型从“文本生成器”升级为“行动执行器”的关键机制,且清楚 Schema 设计、返回值规范和错误处理这些工程细节决定了 Tool Use 的稳定性。面试官考的是你对 Agent 核心能力的理解深度。

追问:如果不做训练(不 SFT),怎么让 Agent 知道怎么调用工具?调用格式是什么?

来源:小红书 AI应用开发

这道题的关键在于理解:应用开发者不需要自己训练模型的 tool use 能力,但模型的 tool use 能力不是凭空出现的

两种实现路径

路径 原理 适用场景
API 原生 Function Calling 模型厂商(OpenAI/Anthropic)在训练阶段已做了 tool use 的 SFT + RLHF,开发者只需传 tool schema 使用商业 API 的主流方案
Prompt-based(ReAct) 通过提示词教模型按固定格式输出工具调用,再由代码解析执行 没有原生 FC 能力的开源模型

API 原生 FC 的工作方式——开发者只做三件事:

  1. 定义 Tool Schema:用 JSON Schema 描述工具的名称、功能、参数类型和约束
  2. 传给 API:在请求的 tools 参数中传入 schema 列表
  3. 解析响应:模型返回结构化的 tool_calls(工具名 + 参数 JSON),编排层执行后回传结果
开发者定义:
  tools: [{
    name: “search_order”,
    description: “根据订单号查询订单状态”,
    parameters: {
      order_id: { type: “string”, description: “订单编号” }
    }
  }]

模型输出:
  tool_calls: [{
    name: “search_order”,
    arguments: { “order_id”: “2024050312345” }
  }]

模型“知道”怎么调用,是因为厂商在训练阶段已经用大量 tool use 数据做了对齐——开发者不需要训练,但不代表没有训练。

Prompt-based 方案——当模型没有原生 FC 时:

在 System Prompt 中定义 ReAct 格式,让模型按固定模式输出,再用正则/JSON 解析提取调用意图:

System Prompt:
  你可以使用以下工具:
  - search_order(order_id: str): 查询订单状态
  
  使用工具时,严格按以下格式输出:
  Thought: 我需要查询订单
  Action: search_order
  Action Input: {“order_id”: “2024050312345”}
  
  等待工具返回后继续推理。

两种方案的工程差异

  • 可靠性:原生 FC 远高于 Prompt-based——前者有专门的训练和 Schema 约束,后者依赖模型遵循格式的能力
  • 并行调用:原生 FC 支持一次返回多个 tool_calls;Prompt-based 通常只能单步
  • 错误率:Prompt-based 容易输出格式不规范(多一个逗号、少一个引号),需要额外的输出修复逻辑

核心认知:“不做训练”不等于“没有训练”——应用开发者不需要训练,是因为模型厂商已经做了。真正需要开发者做的是写好 Tool Schema,这是 tool use 准确率最大的杠杆。


Q:MCP 和 Skills 的本质区别是什么?都是工具调用,为什么需要两套机制?

来源:蚂蚁集团智能体与大模型应用二面 【蚂蚁AI应用开发二面同题:Skill 与 MCP 核心差异】

新手答:“MCP 是协议,Skills 是能力,不太一样。”

高手答

MCP 和 Skills 解决的是不同层次的问题,虽然都和“Agent 如何使用工具”相关,但它们不在同一个抽象层级上:

MCP(Model Context Protocol)——工具调用的“通信协议”

MCP 解决的是:Agent 如何发现、调用、获取结果。它定义了一套标准的交互格式——工具怎么注册(tools/list)、参数怎么传(JSON Schema)、结果怎么返回。类比网络协议:MCP 是 HTTP,定义了请求/响应的格式,不关心你用这个请求做什么。

Skills——任务执行的“能力单元”

Skills 解决的是:Agent 面对某类任务时,怎么思考、用什么工具、按什么流程执行。一个 Skill 包含触发条件、专用 Prompt、可用工具集、输出约束。类比:Skill 是一个“微型 Agent 配置”,告诉 Agent“遇到这类问题时按这个方案办”。

flowchart TB
    subgraph skill["Skill(能力单元)"]
        direction TB
        S1["触发条件:用户说「整理面经」"]
        S2["专用 Prompt:分类+格式化指令"]
        S3["工具集:文件读写、Grep 搜索"]
        S4["输出约束:Markdown 格式"]
    end

    subgraph mcp["MCP(通信协议)"]
        direction TB
        M1["工具发现:tools/list"]
        M2["参数规范:JSON Schema"]
        M3["调用方式:stdio / HTTP"]
        M4["结果格式:标准化响应"]
    end

    skill -->|"Skill 内部通过 MCP 调用工具"| mcp

核心区别对照

维度 MCP Skills
抽象层级 通信协议层 业务能力层
解决的问题 “怎么调工具” “什么场景用什么方案”
包含内容 工具描述、参数规范、传输方式 触发条件 + Prompt + 工具集 + 输出约束
类比 HTTP 协议 Web 应用的一个 Controller
复用粒度 单个工具 一套完整方案

为什么需要两套机制

只有 MCP 没有 Skills → Agent 知道怎么调工具,但不知道什么时候该调哪个,需要每次都靠模型自己推理,不稳定。

只有 Skills 没有 MCP → Agent 知道该怎么办,但每个工具要单独写适配代码,不可扩展。

两者结合:Skills 定义“策略”,MCP 提供“基础设施”。Skill 里的工具调用通过 MCP 协议完成——这样新增工具只需要写 MCP Server,不需要改 Skill 逻辑;新增能力只需要写 Skill,不需要改工具接口。

差距在哪:新手把 MCP 和 Skills 当成两个并列的概念。高手看到的是分层架构——MCP 在协议层解决“怎么调”,Skills 在业务层解决“怎么用”,两者是上下层关系而非替代关系。面试官考的是你能不能把 Agent 架构按层次拆清楚。


Q:Function Calling 的本质价值是什么?它解决的是“模型能力问题”还是“系统约束问题”?

来源:Agent 开发面试 30 题

新手答:“让模型能调工具,解决的是能力问题。”

高手答

Function Calling 解决的主要是系统约束问题,不是模型能力问题。

没有 Function Calling 的时候,模型也能“调工具”——你在 Prompt 里告诉模型“如果需要查天气,请输出 {"tool": "weather", "city": "北京"}”,模型大概率能输出正确的 JSON。但这个方案有三个致命问题:

  1. 输出格式不可靠:模型可能在 JSON 前面加一句“好的,我来查一下”,或者少一个引号,导致解析失败
  2. 调用时机不可控:你不知道模型这次输出是“要调工具”还是“在正常回复”,必须靠正则或关键词猜
  3. 参数类型无约束:price 字段可能传成字符串“一百”,而不是数字 100

Function Calling 的本质价值是把“模型想调工具”这件事从自由文本变成了结构化协议

没有 FC:模型生成文本 → 你用正则猜它要不要调工具 → 手动解析参数 → 祈祷格式正确
有了 FC:模型明确返回 tool_call 结构 → 系统确定性地知道要调工具 → 参数有 schema 约束

这不是让模型“更聪明”了,而是给模型和系统之间建立了一个明确的通信协议——模型的意图(调什么工具、传什么参数)不再是需要“猜”的自由文本,而是有格式保证的结构化消息。

差距在哪:新手觉得 FC 是一种“新能力”。高手理解 FC 本质是模型和系统之间的接口协议——解决的是“怎么可靠地把模型意图传递给系统”这个工程问题。面试官考的是你对 FC 的理解停留在“会用”还是“理解设计动机”。

追问:有了 Function Calling,是不是可以没有 MCP?

来源:蚂蚁 Agent 开发一面

技术上可以——FC 完全能实现 MCP 做的事。但 MCP 的价值不在技术能力,而在标准化带来的生态效应

没有 MCP 时,每接入一个新工具都要:定义 FC schema → 写调用适配 → 处理鉴权 → 维护版本。10 个工具写 10 套适配代码。

有了 MCP,工具提供方按协议实现一次 Server,所有支持 MCP 的 Agent 都能直接用——工具侧一次实现,Agent 侧零适配

类比:HTTP 出现之前,两个系统也能通信(自定义 TCP 协议)。HTTP 的价值不是「让通信变得可能」,而是「让通信有了通用标准,生态可以爆发」。MCP 之于 FC,就是 HTTP 之于 TCP。


工具设计与实现

Q:你会如何设计工具 schema,才能降低模型传错参数、漏参数、乱调用的问题?

来源:Agent 开发面试 30 题

新手答:“写清楚参数说明就行。”

高手答

工具 schema 设计的核心原则是降低模型的决策负担——参数越少、约束越紧、描述越具体,出错概率越低。

1. 参数设计——越少越好,越受限越好

  • 合并冗余参数:不要同时暴露 start_datestart_timestamp,选一种格式,内部转换
  • 用枚举代替自由文本sort_by 不要让模型自由填,用 enum: ["price_asc", "price_desc", "rating"]
  • 必填和选填分清楚required 字段必须标明,可选参数给合理的 default 值,减少模型需要做的决定

2. 描述设计——说“别做什么”比“该做什么”更重要

{
  "name": "search_hotel",
  "parameters": {
    "city": {
      "type": "string",
      "description": "城市名,直接使用用户原文(如'北京'),不要从地址中自行提取城市"
    },
    "check_in": {
      "type": "string",
      "description": "入住日期,格式 YYYY-MM-DD。如果用户说'下周五',请转换为具体日期"
    }
  }
}

3. 命名设计——名字要自解释

  • get_weatherfetch_data 好——名字越具体,模型越不容易误调
  • 相似工具要通过命名区分清楚:search_hotel_by_city vs search_hotel_by_id,而不是一个 search_hotel 靠参数区分

4. 工具粒度——一个工具做一件事

一个“大而全”的工具(既能搜索、又能预订、还能取消)会让模型在参数选择上出错。拆成 search_hotelbook_hotelcancel_booking 三个工具,每个的参数空间小而确定。

差距在哪:新手觉得“描述写清楚”就够了。高手从参数设计、描述技巧、命名规范、工具粒度四个角度系统性地降低出错率。面试官考的是你有没有做过“把模型对接到真实工具”的工程经验——只有踩过坑的人才知道 schema 设计有这么多讲究。


Q:同一个能力是做成“一个大而全工具”还是“多个小工具”,怎么权衡?

来源:Agent 开发面试 30 题

新手答:“拆小一点好,职责单一。”

高手答

不能一刀切。拆不拆、怎么拆,取决于模型的工具选择负担和参数填充难度

维度 大而全工具 多个小工具
模型选择负担 低(只需选一个工具) 高(需从多个里选对的)
参数复杂度 高(参数多、组合多) 低(每个工具参数少)
错误定位 难(哪步出错不好查) 易(哪个工具失败一目了然)
编排灵活性 低(绑定了固定流程) 高(可以自由组合)

什么时候用大工具

  • 操作有强事务性——查库存 + 锁库存 + 扣款必须原子执行,拆开会有一致性风险
  • 用户感知是一个动作——“帮我订机票”不应该让用户看到拆成了五步

什么时候拆小工具

  • 子步骤可以独立使用——搜索酒店和预订酒店是两个独立需求
  • 需要灵活编排——“先搜再比再订”的顺序可能因用户需求变化
  • 参数空间差异大——搜索只需要城市和日期,预订还需要用户信息和支付方式

实际工程中的折中方案:对外给模型暴露小工具(降低选择和参数负担),对内用编排层把小工具组合成事务(保证一致性)。模型只需要说“我要订这个酒店”,编排层自动执行“校验 → 锁房 → 扣款 → 确认”的事务流程。

差距在哪:新手只记住了“职责单一”的教条。高手从模型负担、参数复杂度、事务性、编排灵活性四个维度做权衡,且给出了“对外小工具 + 对内事务编排”的折中方案。面试官考的是你在工具设计时有没有系统性的权衡框架。


Q:手撕一个 ReAct 架构的 Agent,实现文件操作(找文件、删除文件)

来源:AI 工程师面试(手撕代码,可借助 AI)

新手答:写了个 while 循环拼 Prompt,没有工具抽象,逻辑和 I/O 混在一起。

高手答

这道题考的是能不能用 ReAct 范式把“工具定义 → Agent 编排 → 多轮交互”串起来。用 LangGraph 的 create_react_agent 实现最清晰:

第一步:定义工具集

每个文件操作封装成独立工具,用 @tool 装饰器标注名称和描述(模型通过描述选择工具):

import os
import glob
from langchain_core.tools import tool

@tool
def list_py_files(directory: str) -> list[str]:
    “””列出指定目录下所有 .py 文件的路径”””
    return glob.glob(os.path.join(directory, **/*.py), recursive=True)

@tool
def check_has_main(file_path: str) -> bool:
    “””检查指定 Python 文件中是否包含 main 函数定义”””
    with open(file_path, r) as f:
        content = f.read()
    return def main in content or 'if __name__' in content

@tool
def count_lines(file_path: str) -> int:
    “””统计指定文件的行数”””
    with open(file_path, r) as f:
        return len(f.readlines())

@tool
def delete_file(file_path: str) -> str:
    “””删除指定文件返回操作结果”””
    os.remove(file_path)
    return f已删除: {file_path}

第二步:创建 ReAct Agent

from langgraph.prebuilt import create_react_agent
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model=gpt-4o)
tools = [list_py_files, check_has_main, count_lines, delete_file]

agent = create_react_agent(llm, tools)

create_react_agent 内部实现的就是 ReAct 循环:思考(选工具 + 定参数)→ 执行(调工具)→ 观察(读返回)→ 思考 → …,直到模型认为任务完成。

第三步:执行任务

# 任务 1:找包含 main 的 py 文件
result = agent.invoke({
    messages: [{role: user, content: 找出 /workspace 目录下所有包含 main 函数的 .py 文件}]
}, config={configurable: {thread_id: task-1}})

# 任务 2:删除行数最短的 py 文件
result = agent.invoke({
    messages: [{role: user, content: 找出 /workspace 下行数最短的 .py 文件并删除它}]
}, config={configurable: {thread_id: task-2}})

Agent 的实际执行链路(任务 2 为例)

思考:要找行数最短的 py 文件,先列出所有 py 文件
  ↓
行动:list_py_files('/workspace')
  ↓
观察:返回 5 个文件路径
  ↓
思考:逐个统计行数
  ↓
行动:count_lines 逐个调用
  ↓
观察:a.py=10, b.py=3, c.py=25...
  ↓
思考:b.py 行数最短,删除它
  ↓
行动:delete_file('b.py')
  ↓
观察:已删除
  ↓
思考:任务完成,输出结果

面试官可能追问的细节

  1. 为什么要用 @tool 装饰器而不是直接写函数@tool 会自动从类型注解和 docstring 生成 JSON Schema,这是模型做 function calling 时需要的工具描述
  2. thread_id 的作用:支持多轮对话,同一个 thread_id 下的对话共享记忆,Agent 能引用之前的执行结果
  3. 如果要加安全限制怎么办:对 delete_file 加权限检查——限制只能删除特定目录下的文件、删除前要求用户确认(interrupt_before
  4. 和直接写 Python 脚本的区别:脚本是写死的流程。ReAct Agent 能根据中间结果动态调整——比如文件读不出来时自动换一种方式查找,不需要提前枚举所有异常路径

差距在哪:新手写一堆 if-else 硬编码。高手用标准的 ReAct 框架——工具定义(@tool)、Agent 创建(create_react_agent)、任务执行(invoke),代码简洁且可扩展。面试官考的是你能不能用 Agent 范式(而非脚本思维)解决问题,且理解 ReAct 循环的执行机制。


高级工具调度

Q:如何在多智能体环境中实现动态发现并注册跨协议工具?

来源:淘天 AI Agent 一面

新手答:“在配置文件里列出所有工具。”

高手答

多智能体系统中,每个 Agent 可能需要来自不同来源的工具——MCP Server、REST API、甚至其他 Agent 暴露的能力。静态配置无法扩展:新工具上线要改配置重启,Agent 之间的能力共享需要人工同步,协议不同还要分别适配。

解决方案是构建动态工具发现与注册机制

1. 工具注册中心(Tool Registry Service)

所有工具向中心化的注册中心注册自己的能力描述、Schema、端点地址和鉴权要求。注册中心就像一个“工具市场”:

注册信息示例:
{
  "tool_id": "product_search_v2",
  "capability": "按关键词搜索商品,支持价格、品类筛选",
  "schema": { ... },          // 输入输出的 JSON Schema
  "endpoint": "mcp://product-server:8080",
  "protocol": "MCP",
  "auth": "bearer_token",
  "owner_agent": "product-agent"
}

2. 基于能力的发现(Capability-based Discovery)

Agent 不需要知道工具名,只需描述自己需要什么能力:

Agent 请求:"我需要一个能搜索商品的工具"
注册中心:语义匹配 → 返回 product_search_v2(MCP)、catalog_api(REST)两个候选
Agent:根据协议偏好和延迟选择最优工具

这比按名字查找更灵活——新工具上线后,只要能力描述匹配,就能被自动发现。

3. 运行时注册(Runtime Registration)

新的 MCP Server 或 Agent 上线后,无需重启系统即可注册新工具:

新 MCP Server 启动 → 向注册中心发送注册请求 → 注册中心更新索引
→ 其他 Agent 下次查询时自动发现新工具

4. 跨协议适配层(Cross-Protocol Adaptation)

不同来源的工具协议各异,但调用方 Agent 不应关心底层细节。通过统一工具接口层 + 协议适配器解耦:

flowchart LR
    A["调用方 Agent"] -->|"统一 Tool Schema"| R["工具注册中心"]
    R --> PA["MCP 适配器"]
    R --> PB["REST 适配器"]
    R --> PC["A2A 适配器"]
    PA --> T1["MCP Server\n(商品搜索)"]
    PB --> T2["REST API\n(支付接口)"]
    PC --> T3["其他 Agent\n(数据分析能力)"]

每个适配器负责将统一的调用格式转换为目标协议的原生格式:

  • MCP 适配器:直接透传,协议天然兼容
  • REST 适配器:将 Tool Schema 参数映射为 HTTP 请求参数/body
  • A2A 适配器:将工具调用转化为对目标 Agent 的任务请求

5. 安全与权限控制

  • 工具注册需要身份认证——防止恶意工具注入
  • Agent 只能发现自己有权限使用的工具——注册中心按角色过滤
  • 跨 Agent 能力共享需要双向授权——提供方授权暴露,使用方授权访问

差距在哪:新手用静态配置列出所有工具——工具少时能用,工具多了或跨团队协作时根本管不过来。高手设计了一套完整的动态发现机制:注册中心提供工具目录、能力描述实现语义发现、运行时注册支持热扩展、协议适配层屏蔽底层差异。面试官考的是你对多智能体工具生态的架构设计能力。


Q:MCP 协议的完整调用过程是怎样的?

来源:高德 AI 应用开发实习一面 【腾讯AI应用开发一面追问:领域MCP工具(慢SQL诊断)与Agent系统串联】

新手答:“就是调 API。”

高手答

MCP(Model Context Protocol)不是简单的 API 调用——它是一个三角色架构 + 能力协商 + 标准化通信的完整协议。

三个核心角色

角色 职责 典型实例
Host 发起任务的宿主环境,内含 LLM IDE(如 Cursor)、Agent 框架
Client 协议客户端,管理与 Server 的连接生命周期 SDK 内置的 MCP Client
Server 能力提供方,暴露工具/资源/Prompt 文件系统 Server、数据库 Server

完整调用流程

sequenceDiagram
    participant H as Host(IDE/Agent)
    participant C as MCP Client
    participant S as MCP Server

    Note over C,S: 阶段一:初始化
    C->>S: initialize 请求(协议版本、客户端能力)
    S-->>C: 响应(服务端能力:支持的工具/资源/Prompt)
    C->>S: initialized 通知(握手完成)

    Note over C,S: 阶段二:工具发现
    C->>S: tools/list 请求
    S-->>C: 返回工具列表(name + description + inputSchema)

    Note over H,S: 阶段三:工具调用
    H->>H: LLM 分析用户意图,决定调用某工具
    H->>C: 请求调用工具(name + arguments)
    C->>S: tools/call 请求(JSON-RPC 2.0 格式)
    S->>S: 执行工具逻辑
    S-->>C: 返回执行结果
    C-->>H: 回传结果

    Note over H: 阶段四:结果回传
    H->>H: LLM 将工具结果纳入上下文,继续推理

每个阶段的关键细节

  1. 初始化(Handshake):Client 和 Server 交换能力声明——Client 告诉 Server 自己支持什么功能,Server 告诉 Client 自己提供哪些能力(工具、资源、Prompt 模板)。这一步是能力协商,双方对齐后才开始正式通信。

  2. 工具发现(Discovery):Client 调用 tools/list,Server 返回所有可用工具的 Schema,包括名称、描述和参数定义(JSON Schema 格式)。这些 Schema 会被注入到 LLM 的上下文中,供模型选择工具。

  3. 工具调用(Invocation):Host 中的 LLM 根据用户意图和工具 Schema,生成结构化的调用请求(工具名 + 参数 JSON)。Client 将请求以 JSON-RPC 2.0 格式发送给 Server,Server 执行后返回结果。

  4. 结果回传(Feedback):Client 将工具执行结果回传给 Host,LLM 将结果纳入上下文,决定是继续调用其他工具,还是生成最终回答。

传输层的两种模式

传输方式 通信机制 适用场景
stdio 标准输入/输出(进程间通信) 本地工具(文件操作、CLI 工具)
Streamable HTTP / SSE HTTP 请求 + Server-Sent Events 远程服务、跨网络调用

协议设计的核心原则

  • 每次调用无状态:Server 不保留调用间的状态,幂等性好
  • Server 声明能力:Client 不需要提前知道 Server 有什么工具,通过协议动态发现
  • Client 管理生命周期:连接的建立、维护和关闭都由 Client 负责
  • JSON-RPC 2.0:所有消息采用统一的 JSON-RPC 2.0 格式,请求和响应结构清晰

差距在哪:新手把 MCP 等同于调 API——完全忽略了三角色架构、初始化握手、能力协商这些协议层设计。高手能完整描述从初始化到工具发现到调用到结果回传的全链路,且清楚 stdio 和 HTTP/SSE 两种传输模式的适用场景。面试官考的是你对 MCP 协议的理解是停留在“听说过”还是“真的看过协议规范”。


Q:LLM 是怎么从用户意图匹配到具体工具参数的?

来源:高德 AI 应用开发实习一面

新手答:“模型自己就能理解。”

高手答

这不是“模型自己理解”——而是一个Schema 注入 + 模型微调 + 参数校验的工程化管线。

核心机制:Schema 注入 + 结构化输出

LLM 并不是凭空猜出工具参数的。实际流程是:

  1. Schema 注入:工具的 JSON Schema(名称、描述、参数类型、枚举值等)被注入到系统 Prompt 中,作为模型的上下文信息
  2. 意图识别:模型对用户自然语言做隐式 NLU(自然语言理解),提取关键实体和意图
  3. 参数生成:模型根据 Schema 约束,生成符合格式的 JSON 参数输出
  4. 模型微调基础:模型在训练阶段经过 SFT(有监督微调)和 RLHF(人类反馈强化学习),学会了在特定格式约束下生成结构化输出

参数匹配的四大难题

难题类型 用户输入示例 模型需要做的事 出错风险
意图模糊 “帮我订明天的机票” 缺出发城市、到达城市、舱位 → 需要追问 模型可能自行脑补参数
类型转换 “三百块以下” 提取数字 300 + 运算符 ≤ 可能传成字符串而非数值
必填缺失 “查一下航班” 缺航班号或日期 → 必须追问 模型可能编造一个航班号
枚举映射 “头等舱” 映射到枚举值 first_class 可能传中文而非枚举值

工程解决方案

1. Schema 设计优化

  • 在参数描述中加入示例值和格式说明
  • 枚举值列表写全,减少模型猜测空间
  • 用“负面描述”说明不该怎么填

2. 参数校验层

  • 模型输出的 JSON 必须经过 Schema 校验(如 Pydantic / JSON Schema Validator)
  • 类型不对、必填缺失、枚举不匹配的直接拒绝,不执行

3. 多轮纠错机制

  • 校验失败时,把错误信息回传给模型,让模型修正参数
  • 比如“city 字段是必填的,请向用户询问出发城市”

4. 默认值与上下文推理

  • 可选参数用合理的默认值填充
  • 利用对话历史和用户画像推断缺失信息(如用户常用出发城市)

三种参数获取策略对比

策略 适用场景 优点 缺点
直接提取 用户意图明确、参数齐全 一轮完成,体验好 信息不全时会瞎猜
多轮追问 关键参数缺失、意图模糊 参数准确,不会瞎编 交互轮次多,体验差
画像推理 可选参数、用户有历史偏好 减少追问,体验流畅 依赖画像数据,冷启动难

实际系统中三种策略混合使用:必填参数缺失 → 追问;可选参数缺失 → 用默认值或画像推理;所有参数齐全 → 直接提取。

差距在哪:新手把参数匹配当成模型的“魔法”。高手清楚这背后是 Schema 注入提供约束、模型微调提供能力、校验层提供兜底的三层机制,且知道意图模糊、类型转换、必填缺失、枚举映射这四类难题各自的解法。面试官考的是你有没有在真实项目中处理过“模型传错参数”的问题。


Q:Agent 做多轮工具调用和单轮调用相比,会面临哪些额外挑战?

来源:阿里国际大模型算法一面

新手答:“多调几次工具而已,没什么区别。”

高手答

区别非常大。单轮工具调用的链路是“LLM → 调一次工具 → 拿结果 → 回复”,流程短、风险可控。但多轮工具调用的链路是“LLM → 工具 1 → 结果 1 → LLM 再推理 → 工具 2 → 结果 2 → … → 最终回复”,每多一轮,系统复杂度都在指数级增长

具体来说,多轮工具调用面临五个单轮不存在的额外挑战

1. 错误累积(Error Propagation)

单轮调用即使工具返回了不完美的结果,模型直接用就行。但多轮场景下,工具 1 返回的微小偏差会被工具 2 放大——比如第一步查到的商品 ID 有误,后续所有基于这个 ID 的操作(查价格、查库存、下单)全部错误。错误不是加法,是乘法

2. 上下文膨胀

每次工具调用都会往上下文里塞入请求参数和返回结果。5-6 轮调用之后,上下文的 50% 以上可能都是工具的中间结果,真正关键的用户意图和任务目标被淹没了,模型的推理质量随之下降。

3. 规划与依赖管理

多轮调用意味着工具之间存在依赖关系——工具 B 需要工具 A 的输出作为输入。模型必须正确规划执行顺序,理解哪些工具可以并行、哪些必须串行。单轮调用完全没有这个问题。

4. 状态一致性

中间状态可能在调用间隙发生变化。经典场景:第一步查库存显示“有货”,第二步处理支付,第三步准备发货时发现库存已经被别人抢走了。多轮调用需要类似事务的语义来保证状态一致。

5. 延迟叠加

N 次工具调用 = N ×(网络延迟 + 工具执行时间 + 模型推理时间)。单轮调用可能 2 秒完成,5 轮调用可能要 15-20 秒,用户体验急剧恶化。

单轮 vs 多轮的系统性对比

维度 单轮工具调用 多轮工具调用
错误传播 无——一次调用,错了就错了 逐步累积放大,后续步骤全部受影响
上下文压力 低——只有一组工具输入输出 高——N 组中间结果挤占上下文
依赖管理 无——没有工具间依赖 必须正确规划执行顺序和依赖图
状态一致性 无风险——单次调用无时间窗口 调用间隙状态可能变化
延迟 可控——单次往返 线性叠加,用户感知明显

每个挑战对应的工程解法

flowchart LR
    E1["错误累积"] --> S1["中间结果校验 +\n失败回滚机制"]
    E2["上下文膨胀"] --> S2["工具结果摘要化 +\n选择性上下文注入"]
    E3["依赖管理"] --> S3["显式依赖图 /\nDAG 编排引擎"]
    E4["状态一致性"] --> S4["幂等操作 +\n乐观锁"]
    E5["延迟叠加"] --> S5["无依赖调用并行化 +\n中间结果流式返回"]
  • 错误累积 → 每步工具返回后做中间结果校验,不合理的立即重试或回滚,不让脏数据流入下一步
  • 上下文膨胀 → 对工具返回结果做摘要压缩(只保留下游需要的字段),或用选择性上下文注入,只把当前步骤需要的历史结果放进去
  • 依赖管理 → 构建显式的工具依赖图(DAG),由编排引擎而非模型来决定执行顺序和并行策略
  • 状态一致性 → 关键操作设计为幂等的(重复调用不会产生副作用),配合乐观锁或预留/确认机制处理并发冲突
  • 延迟叠加 → 分析依赖图,把无依赖关系的工具调用并行执行;同时对用户做中间结果的流式返回,降低等待感

差距在哪:新手觉得多轮和单轮就是”多调几次”的区别。高手能准确识别出错误累积、上下文膨胀、依赖管理、状态一致性、延迟叠加这五个多轮特有的系统性挑战,且每个挑战都有对应的工程解法。面试官考的是你对多轮工具调用的系统复杂度有没有真实的认知——只有做过多步 Agent 的人才会踩到这些坑。


Q:为什么将 Agent 工具注册到微服务注册中心(如 Nacos)而不是用 MCP?工具的自动注入怎么实现?

来源:遥望科技 Agent开发一面

新手答:”MCP 是标准协议,应该统一用 MCP 就好了。”

高手答

这两种方案解决的是不同层面的问题,选型取决于你的系统架构和工具的生命周期管理需求。

MCP 的定位:标准化的模型-工具通信协议。它定义了 Host/Client/Server 三层架构,解决的是”模型如何发现和调用一个工具”的接口标准化问题。适合:工具相对固定、单机或小规模部署、对接多个不同的 LLM。

注册中心(Nacos/Consul)的定位:分布式服务治理。它解决的是”工具作为微服务如何动态上下线、负载均衡、灰度发布”的运维问题。适合:工具本身是后端微服务、需要弹性伸缩、多实例负载均衡、灰度切流。

选 Nacos 不选 MCP 的典型场景

  1. 工具就是已有的后端接口 — 团队已经有成熟的微服务体系(Spring Cloud + Nacos),工具只是在已有接口上包一层 Tool Description,没必要再引入一套 MCP Server
  2. 动态上下线 — 工具服务需要根据负载弹性扩缩容,Nacos 天然支持健康检查和实例摘除,MCP 没有这个能力
  3. 灰度和版本管理 — 同一个工具的 v1 和 v2 可以通过 Nacos 的 metadata 做流量切分,无需改 Agent 代码

工具自动注入的实现

1. 每个工具服务启动时向 Nacos 注册,metadata 中携带 Tool Schema(名称、描述、参数 JSON Schema)
2. Agent Gateway 订阅 Nacos 的服务变更事件
3. 有新工具上线 → Gateway 自动拉取其 Schema → 注入到 Agent 的可用工具列表
4. 工具下线 → Gateway 自动移除,Agent 不再调用

这样实现了零代码热插拔:新工具上线不需要重启 Agent,不需要改 Prompt,不需要重新部署。

两者不矛盾:在更大规模的系统里,MCP 管协议标准(模型怎么调工具),Nacos 管服务治理(工具实例怎么管理)。可以在 MCP Server 内部用 Nacos 做服务发现。

差距在哪:新手把工具管理等同于”协议选型”。高手区分了协议层(MCP)和治理层(注册中心)的职责差异,且能根据团队已有基础设施做务实选型。面试官考的是你有没有在真实微服务环境中做过工具集成——纯 Demo 项目不会遇到动态上下线和灰度切流的问题。


Q:推理模型(如 o1/DeepSeek-R1)为什么不支持工具调用?技术原因是什么?

来源:秋招AI面经问题汇总

新手答:”可能还没开发完吧,以后会支持的。”

高手答

推理模型不支持(或难以支持)工具调用有几个技术原因:

  1. 训练目标冲突:推理模型(如 o1)在训练时强化的是”长链思维推理”——鼓励模型在内部 CoT 中持续思考。而工具调用要求模型在推理中途”暂停思考、输出结构化调用、等待外部结果”,这两种行为模式在 RL 训练中互相干扰
  2. 输出格式约束:推理模型的 CoT 是自由文本流,插入结构化的 JSON tool_call 会破坏思维链的连贯性,导致推理质量下降
  3. 延迟敏感:推理模型的 thinking token 是连续生成的,中途插入工具调用意味着要暂停生成→等待外部响应→恢复生成,这打断了 KV Cache 的连续性
  4. 训练数据稀缺:推理模型的 RL 训练数据主要是数学/代码/逻辑题,很少包含”边推理边调工具”的轨迹数据
  5. 变通方案:在推理模型外包一层 orchestrator——先用推理模型做规划,再用普通模型执行工具调用

差距在哪:面试官要看你对”不同模型有不同能力边界”的理解——不是所有模型都适合做 Agent 的执行引擎,选型很重要。


Q:多工具场景下怎么定工具调用的优先级?

来源:字节AI产品(智能体方向)

新手答:”按用户说的顺序来呗。”

高手答

工具调用优先级决策是 Agent 规划能力的核心体现,策略包括:

  1. 依赖关系优先:有些工具的输入依赖另一个工具的输出(如”查航班”必须在”确认目的地”之后),形成 DAG 拓扑排序
  2. 时效性约束:有时间窗口的操作优先(如”订票”有库存变化风险,”约饭”时间灵活),越容易失效的越先做
  3. 不可逆性排序:可撤销的操作可以先做试试,不可逆操作(如支付、发送消息)放最后并加人工确认
  4. 资源锁定:需要锁定外部资源的操作(如预订座位)优先执行,避免并发竞争导致失败
  5. Prompt 引导:在 system prompt 中给出优先级规则(”涉及资源锁定的操作优先于信息查询类操作”),让模型在规划时遵循
  6. 动态调整:执行过程中根据前序工具的返回结果动态调整后续工具的优先级(如航班取消→优先改签而非继续订酒店)

差距在哪:面试官考的是你对”Agent 不是简单串行执行”的理解——优先级涉及依赖分析、风险评估、资源约束多个维度。


Q:开源模型的 Function Calling 能力较弱,如何通过微调或 Prompt Engineering 提升?

来源:Agent开发八股合集(南京大学)

新手答:“换更大的模型就好了,或者加几个 few-shot 示例。”

高手答

开源模型(如 Qwen、Llama、Mistral)的 FC 能力弱主要表现在三个层面:工具选择错误、参数格式错误、幻觉调用不存在的工具。优化手段分 Prompt 侧和训练侧两条路:

Prompt 侧(零成本,立即生效):

  1. Schema 精简:工具描述越短越好,去掉冗余字段;参数用 enum 约束可选值而非自由文本
  2. 格式强约束:在 system prompt 中用 JSON Schema + 严格示例约束输出格式,加 "你只能使用以下工具,不可编造工具名" 的硬约束
  3. Few-shot 对齐:给 2-3 个“用户意图 → 正确工具调用”的示例,尤其覆盖参数边界情况
  4. ReAct 格式引导:让模型先输出 Thought(思考为什么选这个工具),再输出 Action,推理链降低盲目调用概率
  5. 结构化输出:用 response_format: json_object 或 Outlines/Guidance 等约束解码库强制格式合法

训练侧(效果更稳,需要数据和算力):

  1. FC-SFT 数据构造
    • 从业务日志中提取“用户 query → 正确工具调用”对
    • 用强模型(如 GPT-4/Claude)生成 FC 标注数据,覆盖正例 + 负例(不该调用的场景也要标注为“不调用”)
    • 数据格式要和推理时的 Prompt 格式完全一致
  2. LoRA 微调
    • 在基座模型上用 LoRA r=16-32 微调,只训 FC 相关能力
    • 训练集要覆盖:工具选择、参数提取、多工具串联、拒绝调用四类场景
    • 加入 “none” 类样本,防止模型对所有 query 都硬调工具
  3. Constrained Decoding(约束解码)
    • 不改模型权重,在解码阶段用 Grammar/FSM 约束只能输出合法的工具名和参数结构
    • 工具:Outlines、llama.cpp 的 GBNF grammar、vLLM 的 guided decoding

工程组合拳(实际生产推荐):

Prompt 强约束 + Few-shot → 解决 80% 的格式和选择问题 LoRA 微调 → 解决模型“理解不了复杂意图”的根本能力问题 约束解码 → 兜底,保证输出格式 100% 合法

差距在哪:面试官考的是你对“FC 能力不足”的拆解能力——不是一句“换大模型”,而是能区分格式问题、选择问题、能力问题,分别用 Prompt/约束解码/微调三层手段组合解决。


Skill 边界模糊时的工具披露

Q:对于边界不好定义的场景,Skill 形式不能很好区分场景披露工具,怎么办?

来源:阿里暑期Agent算法二面

新手答:“那就把所有工具都给模型看呗。”

高手答

当场景边界模糊时,静态的 Skill → 工具映射会失效。解决思路是从“硬规则匹配”转向“动态推理 + 渐进披露”。

问题本质:Skill 的核心假设是“场景可以被清晰划分”——每个 Skill 对应一组明确的工具。但真实对话中,用户意图经常在多个 Skill 的边界上(比如“帮我查一下上周的销售数据,然后画个图发给老板”——涉及数据查询、可视化、通信三个 Skill)。

分层解决方案:

层级 策略 适用场景
意图预分类 LLM 先判断属于哪几个 Skill,按置信度披露 意图基本可判但有交叉
渐进式披露 先给核心工具,执行中发现需要就动态追加 意图需要多步才能明确
工具描述自带触发条件 在 tool description 中写明“当用户需要 XX 时使用”,让 LLM 自主判断 工具间无强耦合
Fallback 全局工具池 设置一批“全场景可用”的通用工具(如搜索、计算) 兜底

工程实践:

  1. 两阶段路由:第一阶段用轻量分类器(或规则)粗筛 Top 3 Skill;第二阶段把这些 Skill 的工具合并后送给 LLM 选择
  2. 动态工具注册:Agent 执行中途发现当前工具不够,主动请求“追加工具”——类似人类在做事时发现需要新权限
  3. 相似度兜底:当分类器置信度低于阈值时,用 query 和所有工具描述做 embedding 相似度,取 Top K 直接送给模型

差距在哪:面试官考的是你对“Skill 系统设计假设”的理解——Skill 本质是静态场景划分,但现实是模糊的。能说出多阶段路由 + 渐进披露 + 动态追加的组合方案,说明你在生产中遇到过这个问题并做了系统性解决。


多 Skill 串行嵌套的容错设计

Q:多 Skill 串行/嵌套时,依赖冲突、参数不兼容怎么做容错?有无编排优先级调度?

来源:百度 AI Agent前端研发实习生一面

新手答:“按顺序执行就行了,失败就报错。”

高手答

多 Skill 编排的核心挑战是上游 Skill 输出不一定能被下游 Skill 消费——格式不匹配、字段缺失、语义偏移都可能发生。

容错机制设计:

flowchart TD
    A[Skill A 输出] --> V{输出校验}
    V -->|格式合法| B[参数适配层]
    V -->|格式异常| R1[重试 Skill A / 换 Prompt]
    B -->|兼容| C[Skill B 执行]
    B -->|不兼容| R2[降级:用默认参数 / 跳过 Skill B]
    C -->|成功| D[继续下游]
    C -->|失败| R3[Skill B 内部重试 / Fallback Skill]

关键设计点:

  1. 参数适配层:Skill 之间插入一个 adapter,做格式转换和字段映射。不让 Skill 直接互相传参——解耦后每个 Skill 独立演化
  2. Schema 契约:每个 Skill 声明输入/输出 Schema(JSON Schema),编排引擎在运行前做静态校验——能提前发现不兼容而非运行时崩溃
  3. 优先级调度:当多个 Skill 可以并行时,按紧急程度和依赖关系排序。有硬依赖的串行(A→B),无依赖的并行(A∥C),部分依赖的条件触发(B 完成后看结果决定是否触发 D)
  4. 幂等保证:同一个 Skill 重复执行不产生副作用。这样任何一步失败后可以安全重试整条链
  5. 部分成功语义:链条中 Skill B 失败不应让整个任务失败——返回“A 已完成,B 因 XX 原因失败,C 跳过”

差距在哪:面试官考的是你对“工具编排是一个分布式系统问题”的认知。只说“顺序执行”说明你没处理过多工具协作的复杂性。能说出适配层 + Schema 契约 + 幂等 + 部分成功,说明你有生产级的编排设计经验。


这类题的答题模式

工具管理题的核心是防御性思维

1. 不信任模型的输出——总会有瞎传参数的时候
2. 分层防御——Prompt 引导 + 格式校验 + 业务兜底
3. 大规模工具管理用检索思路——召回 + 排序,不要全塞给模型
4. 每一层都要有明确的兜底策略

面试官听到“加强 Prompt”就知道你只在 Demo 层面做过。听到 Pydantic 校验、业务接口校验、工具向量库检索,才会觉得你做过真实系统。

下一篇建议继续看: