工具调用
面向初学者的 AI Agent「工具调用」面试八股文:从 Function Calling、Tool 设计,到 MCP、路由与编排,再到安全与常见实现。每个知识点尽量包含:概念解释、原理详解、面试问答(Q/A)、追问应对、Python 代码示例。
目录
- Function Calling 基础
- Tool Use / Tool Calling
- MCP 协议(Model Context Protocol)
- 工具路由(Tool Routing)
- 工具编排(Tool Orchestration)
- 安全性
- 常见工具实现
- 综合面试题精选(28 题)
1. Function Calling 基础
1.1 什么是 Function Calling
概念解释
Function Calling(函数调用)指:大语言模型在生成回复时,不是直接输出最终答案,而是先输出「要调用哪个外部函数、用什么参数」的结构化指令;宿主程序(你的 Python 服务)真正执行该函数,再把结果喂回模型,形成多轮对话闭环。这样模型可以查实时数据、算复杂表达式、操作数据库等,突破「仅靠训练记忆回答」的限制。
原理详解
从接口角度:多数厂商把「可调用的函数列表」和「用户问题」一起发给模型;模型在概率分布上被训练/对齐为:在合适时输出 tool_calls(或等价字段),而不是胡编乱造函数结果。从安全角度:模型不直接执行代码,执行权在应用层,便于鉴权、审计、限流。与「纯文本里写 call foo(1,2) 」的区别:Function Calling 使用 机器可解析 的 JSON/结构化格式,便于框架自动解析与校验。
1.2 OpenAI Function Calling 的工作原理
概念解释
以 OpenAI Chat Completions 为例:你在请求里提供 tools (函数元数据数组)和 tool_choice (可选,控制是否必须调用工具);模型返回的 message 里可能带有 tool_calls ,每一项包含 id 、 function.name 、 function.arguments (JSON 字符串)。
你的代码解析后执行本地函数,再以 role=tool 的消息把结果追加到对话里,再次请求模型生成面向用户的自然语言答案。
原理详解
- 首轮: system + user + tools → 模型可能返回 assistant + tool_calls 。
- 执行:应用根据 name 路由到 Python 函数, arguments 反序列化后调用。
- 次轮:把每条工具结果作为 tool 消息(带 tool_call_id )写回,再请求 → 模型综合工具输出作答。
- 并行:若模型一次返回多个 tool_calls ,可并行执行(注意线程安全与副作用)。
- 流式:流式响应里 tool_calls 可能分片到达,需要增量拼接 arguments 。
Q1:OpenAI 的 Function Calling 大致流程是什么?
A: 客户端把工具定义(名称、描述、参数 JSON Schema)随对话发给 API;模型在 需要时返回 tool_calls ;宿主解析并执行真实函数;将结果以 tool 角色消息写回并再次调 用 API;直到模型不再请求工具或达到轮次上限。核心是「模型只负责决策与参数,执行在应用 侧」。
追问:tool_choice 有什么用?
应对:auto 由模型决定; none 禁止工具; required 强制至少调用一次;也可指定某个function 强制调用。用于调试、合规场景(必须走某工具)或 A/B。
追问:和 RAG 的关系?
应对:RAG 是「检索再生成」;Function Calling 是「模型选择动作」。常组合:检索用工具或向量库,生成阶段再决定是否调用计算器/数据库。
1.3 函数定义(JSON Schema)
概念解释
每个可调用函数在 API 里通常描述为: type: function , function.name (唯一标识),function.description (给模型看的自然语言说明), function.parameters (符合 JSON Schema 的对象,描述参数类型、是否必填、枚举等)。
原理详解
JSON Schema 让运行时可以做校验( jsonschema 库),也让模型有明确字段名与类型提示。
建议:description 写清「何时调用、不何时调用」;对模糊词(如「最近」)在描述里约定格式(如 ISO 日期)。
复杂结构可用 object 、 array 、 enum 、 oneOf 等;但过于复杂的 Schema 可能增加模型填错概率,可适当拆分多个小函数。
Q2:为什么用 JSON Schema 描述参数?
A: 三方面——(1)跨语言标准,各 SDK 统一;(2)可自动校验,避免脏参数进业务;(3)作为 模型「字段说明」,减少胡编参数名。缺点是 Schema 过长会占 token,需要精简描述或工具路由。
追问:必填字段怎么表示?
应对:在 JSON Schema 里用 required: ["a","b"] ,同时 properties 里声明各字段type 。OpenAI 工具格式与 JSON Schema Draft 兼容(具体以厂商文档为准)。
1.4 模型如何决定调用哪个函数
概念解释
模型并非运行「真正的 if-else 规则引擎」,而是基于训练与对齐后,在看到用户意图 + 工具描述时,输出概率最高的结构化动作。等价于:在「续写」空间里,工具描述把某些 token 序列(函 数名、JSON 参数)的概率抬高。
原理详解
描述质量决定区分度:两个工具功能重叠时,模型容易混淆。用户表述触发关键词与语义:例如「明天天气」更可能触发 get_weather 。系统提示可约束:「涉及计算必须用 calculate 」。部分实现会做两阶段:先小模型/分类器选工具,再大模型填参(见「工具路由」)。
Q3:模型选错工具怎么办?
A: 工程上(1)优化 description 与示例边界;(2)工具路由缩小候选集;(3)执行前做规则 校验或二次确认;(4)对高风险操作要求人类确认;(5)记录 bad case 做 prompt 迭代。不能 假设模型 100% 正确。
1.5 参数提取与验证
概念解释
参数提取:从 tool_calls[].function.arguments 得到 JSON 字符串并 json.loads 。验 证:用 JSON Schema 校验类型、范围、枚举;业务层再校验权限与资源是否存在。
原理详解
模型可能输出不完整 JSON(流式)或多余字段(若你未禁止);需在服务端additionalProperties: false (若支持)并剥离未知键。
对数字、日期做规范化(时区)。
失败策略:返回错误信息给模型「请重试」或降级为只读工具。
Q4:如何做参数校验?
A: 分层——语法层 json.loads ;结构层 jsonschema.validate ;语义层业务函数(如用户 ID 是否存在);安全层鉴权与输入清洗(见第 6 节)。
1.6 完整代码示例(OpenAI API 调用)
下面示例使用 OpenAI 官方 Python SDK 风格(需安装 openai ,并设置环境变量 OPENAI_API_KEY )。为便于阅读,省略生产级重试与日志。
import json
from typing import Any
from jsonschema import validate, ValidationError
from openai import OpenAI
# 工具 JSON Schema(parameters)
WEATHER_PARAMS = {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名,中文或英文"},
"date": {"type": "string", "description": "日期 YYYY-MM-DD"},
},
"required": ["city"],
"additionalProperties": False,
}
TOOLS = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市在某日期的天气。用户只说「今天」时,请换算为具体日期再调用。",
"parameters": WEATHER_PARAMS,
},
}
]
# 假实现:真实项目里对接 HTTP API
def get_weather(city: str, date: str | None = None) -> dict[str, Any]:
return {"city": city, "date": date or "today", "condition": "晴", "temp_c": 22}
def run_tool(name: str, arguments: str) -> str:
args = json.loads(arguments or '{}')
validate(instance=args, schema=WEATHER_PARAMS) # 与 TOOLS 中一致
if name == 'get_weather':
return json.dumps(get_weather(**args), ensure_ascii=False)
raise ValueError(f'unknown tool: {name}')
def chat_with_tools(user_message: str) -> str:
client = OpenAI()
messages: list[dict[str, Any]] = [
{"role": "system", "content": "你是助手,需要数据时调用工具,不要编造天气。"},
{"role": "user", "content": user_message},
]
for _ in range(5): # 防止死循环
resp = client.chat.completions.create(
model='gpt-4o-mini',
messages=messages,
tools=TOOLS,
tool_choice='auto',
)
msg = resp.choices[0].message
messages.append(msg.model_dump())
if not msg.tool_calls:
return msg.content or ''
for tc in msg.tool_calls:
try:
result = run_tool(tc.function.name, tc.function.arguments)
except (json.JSONDecodeError, ValidationError, ValueError) as e:
result = json.dumps({'error': str(e)}, ensure_ascii=False)
messages.append(
{
'role': 'tool',
'tool_call_id': tc.id,
'content': result,
}
)
return '超过最大工具调用轮次'
if __name__ == '__main__':
print(chat_with_tools('北京明天天气怎么样?'))追问:为什么要循环 for _ in range(5) ?
应对:多轮工具调用(先查列表再查详情)需要多次 API;上限防止逻辑错误导致无限循环。
追问:tool_call_id 作用?
应对:把工具结果与某次 tool_calls 中的条目一一对应,支持并行多个调用。
2. Tool Use / Tool Calling
2.1 Tool 的定义与注册
概念解释
在应用内部,「Tool」= 可执行能力单元:名称、描述、参数规范、处理函数。注册指在 Agent 启动时把工具对象加入注册表(字典或列表),运行时由路由器/模型选择并派发。
原理详解
注册表常见结构:name -> callable 或 List[BaseTool] 。
与纯 Function Calling 映射:把同一套元数据转成各厂商 API 需要的 tools 格式(适配层)。
Q5:Tool 与业务里的普通 Python 函数有何不同?
A: Tool 多了面向模型的元数据(描述、Schema)和统一执行接口(记录日志、超时、权限)。
业务函数关注领域逻辑;Tool 是 Agent 可调度的「外壳」。
2.2 工具描述的最佳实践
概念解释
description 是模型判断是否调用、如何填参的主要依据。应写:功能一句话、何时用、何时不 要用、参数含义与格式、返回值语义(若影响后续推理)。
原理详解
避免两个工具描述语义重叠;重叠时加「优先使用 A 当...,否则 B」。
对易混参数(如 user_id vs email )举例说明。
英文或中文与模型服务语言一致可减少混淆。
追问:描述太长怎么办?
应对:分层——核心描述保持短;细节放 parameter.description ;超大工具集用路由先筛选(见第 4 节)。
2.3 工具参数设计
概念解释
参数宜少而精、类型明确;能用枚举就不用自由文本;日期时间统一 ISO 8601;避免「万能字符 串」承载多种含义。
原理详解
多参数强依赖时,可拆成链式多个工具,降低单次 JSON 复杂度。对可选参数默认值在 Schema 或文档中写清。
2.4 工具返回值处理
概念解释
工具返回值会作为 tool 消息内容进入上下文。应稳定、可解析:优先 JSON 字符串;错误用统 一结构 { "error": "--." } ,便于模型纠错。
原理详解
超大结果需摘要或分页(再提供 fetch_more 工具),避免撑爆上下文。
二进制内容应转为文本描述或 URL,不要直接塞原始字节。
Q6:工具返回 10MB 日志怎么办?
A: 不应直接返回。应(1)截断 + 摘要;(2)写入对象存储返回链接;(3)提供 grep_in_log 等缩小工具;(4)向量索引仅检索相关片段。
2.5 错误处理与重试
概念解释
网络与模型都可能失败。策略包括:可重试错误(429、5xx)指数退避;不可重试(4xx 参数错)把错误给模型;工具内部超时;熔断防止拖垮依赖。
原理详解
幂等:重试前确认接口幂等或使用去重键。
部分成功:多工具并行时,单个子失败可只重试该分支。
2.6 LangChain 中的 Tool 定义代码示例
以下使用 LangChain 1.x 风格( langchain-core 的 @tool ;版本差异请以官方文档为准)。
若你环境版本不同,可改为 StructuredTool.from_function 。
from typing import Literal
from langchain_core.tools import tool
@tool
def search_product(
query: str,
category: Literal['book', 'electronics', ''] = '',
) -> str:
"""在电商站内搜索商品。用户要找商品、比价、看库存时用;不要用于闲聊。
Args:
query: 搜索关键词
category: 可选类目过滤,不知道则留空
"""
# 伪实现
return f"[dummy] results for {query!r} in {category or 'all'}"
# 注册到 Agent 时通常传入 tools=[search_product]
# 例如 create_react_agent(llm, tools=[search_product], ...)追问:@tool 和手写 StructuredTool 区别?
应对:@tool 从 docstring 推断描述与参数,开发快;复杂 Schema 或需自定义校验时用StructuredTool 更可控。
3. MCP 协议(Model Context Protocol)
3.1 MCP 是什么
概念解释
MCP(Model Context Protocol)是由 Anthropic 推动的开放标准,用于在 AI 应用(Host) 与外部数据源/工具(MCP Server) 之间建立统一、可插拔的通信方式。可理解为「AI 侧的 USBC」:一次实现 Server,多个客户端(Claude Desktop、IDE、自研 Agent)可复用。
原理详解
协议定义了能力发现、资源读取、工具调用、提示模板等消息格式与生命周期。目标:把「每个产品各写一套插件」变成「标准协议 + 多实现」。
3.2 核心组件:Client、Server、Transport
概念解释
MCP Server:暴露工具/资源/提示的实现进程(如连接 GitHub、数据库)。MCP Client:运行在 Host 内,与 Server 建立会话,转发模型侧请求与结果。
Transport:传输层,常见 stdio(子进程标准输入输出)、HTTP/SSE 等。
原理详解
stdio 适合本地子进程;HTTP 适合远程服务。Client 负责能力协商、把 Server 工具映射为模型可用的工具列表(与 Function Calling 衔接)。
Q7:MCP 里 Client 和你在 OpenAI 里写的「执行工具的 Python 代码」是什么关系?
A: OpenAI 场景下你手写 run_tool ;MCP 下 Client 把远端/子进程 Server 的工具列表拉平, 调用时按协议发 RPC,结果再转成 tool 消息。你仍要写 Host 逻辑,但工具实现可独立进程、 独立语言。
3.3 MCP vs Function Calling 的区别
| 维度 | Function Calling | MCP |
|---|---|---|
| 层级 | 多为“单次 API 能力”,模型输出调用指令 | 系统级协议,关注如何发现与调用工具、资源实现 |
| 实现位置 | 通常在应用进程内函数 | 常在独立 Server,可远程部署 |
| 复用 | 每个应用往往单独集成 | 标准 Server 可被多个 Host 复用 |
互补关系: Host 常把 MCP 工具转成 FC 的 tools 定义交给模型。
标准答案:Function Calling 解决「模型怎么表达调用」;MCP 解决「工具能力怎么暴露与连接」。二者常一起出现:模型侧用 FC,工具侧来自 MCP。
3.4 MCP 的优势
概念解释
标准化:消息与能力模型统一,降低集成成本。可复用:同一 MCP Server 给桌面、IDE、Agent 用。生态:社区可共享 Server 实现;企业可内网部署私有 Server。隔离:工具崩溃不拖垮主进程(进程边界)。
3.5 MCP Server 的实现示例
以下为示意代码:真实项目请使用官方 mcp Python 包( pip install mcp ),并以最新文档为准。下面用常见「FastMCP」风格说明结构。
# 需要: pip install mcp
# 以下为概念示例,包名与 API 请以官方文档为准
try:
from mcp.server.fastmcp import FastMCP
except ImportError:
FastMCP = None # 环境未安装时仅作结构说明
if FastMCP:
mcp = FastMCP("demo")
@mcp.tool()
def add(a: int, b: int) -> int:
"""返回两个整数之和。"""
return a + b
# 通常以 `mcp.run(transport="stdio")` 由 Host 拉起子进程若未使用 FastMCP,也可用手写 Server + stdio,核心是:声明工具列表、处理 tools/call 类请求、返回结构化内容。面试中讲清「进程边界 + JSON-RPC 风格消息」即可得分。
追问:MCP 和 OpenAPI 网关区别?
应对:OpenAPI 面向通用 HTTP 客户端;MCP 面向 AI Host 与模型工具循环,带会话、资源、提示等 AI 原生语义。
3.6 MCP 在企业级应用中的价值
概念解释
治理:工具集中在 MCP Server,便于审计、版本与权限。复用:数据中台、工单、内部 Wiki 各做一个 Server,多产品接入。安全边界:敏感系统只对内网 Server 开放,模型不直连数据库。合规:在 Server 侧落日志与审批,比散落在每个 Agent 代码里更可控。
Q8:企业为什么愿意接 MCP 而不是每个业务线自己写 Function?
A: 降低重复建设、统一安全与观测、加快试点(换模型不换工具链)、利于平台组与业务组分 工。
4. 工具路由(Tool Routing)
4.1 当工具数量多时如何高效选择
概念解释
工具过多时,一次性把所有 description 塞进上下文会:费 token、干扰模型、增加误选。解 决思路是先缩小候选集再让大模型填参,或用小模型专门做路由。
原理详解
典型阈值:几十到上百个工具就要开始考虑路由(视模型与描述长度而定)。
方法:向量检索、分类/意图模型、层级目录(先选类再选工具)、规则前缀(用户以 /db 开头走数据库类)。
4.2 基于向量检索的工具路由
概念解释
离线:把每个工具的 name + description + 关键词 做成 embedding,存向量库。在线:用户 问题 embedding,Top-K 相似工具作为本轮唯一候选,再交给 LLM。
原理详解
优点:实现快、可跨语种模糊匹配。
缺点:极依赖描述质量;边界 case 需加规则或混合检索(关键词 + 向量)。
4.3 基于分类模型的工具路由
概念解释
训练或提示一个轻量分类器(BERT、小 LLM、或结构化输出),输入用户句,输出工具 ID 或工 具组 ID。比全量工具更省、更稳。
原理详解
数据:历史日志标注「正确工具」可finetune。与向量路由混合:分类器粗分 + 向量细分。
4.4 工具分组与层级
概念解释
按域分组:database_* 、 hr_* 、 crm_* 。第一轮只暴露组级 meta-tool(如list_hr_tools ),或让模型先选组再选具体工具。
原理详解
层级过深会增加对话轮次,需在「token 节省」与「轮次增加」间权衡。
4.5 代码示例(向量路由示意)
from dataclasses import dataclass
import numpy as np
from openai import OpenAI
@dataclass
class ToolSpec:
name: str
description: str
def embed_texts(client: OpenAI, model: str, texts: list[str]) -> np.ndarray:
resp = client.embeddings.create(model=model, input=texts)
vecs = [np.array(d.embedding, dtype=np.float32) for d in resp.data]
mat = np.stack(vecs, axis=0)
norms = np.linalg.norm(mat, axis=1, keepdims=True) + 1e-8
return mat / norms
def route_tools(
query: str,
tools: list[ToolSpec],
client: OpenAI,
embed_model: str = "text-embedding-3-small",
top_k: int = 3,
) -> list[ToolSpec]:
corpus = [f"{t.name}\n{t.description}" for t in tools]
doc_emb = embed_texts(client, embed_model, corpus)
q_emb = embed_texts(client, embed_model, [query])[0]
scores = doc_emb @ q_emb
idx = np.argsort(-scores)[:top_k]
return [tools[i] for i in idx]
# 使用:仅把 route_tools 返回的子集塞进 chat.completions 的 tools 参数Q9:向量路由选出来的工具不对怎么兜底?
A: Top-K 调大、混合关键词打分、加一层 LLM「是否适用」二分类、允许用户澄清、保留「通 用搜索」工具作后备。
5. 工具编排(Tool Orchestration)
5.1 串行工具调用
概念解释
后一步依赖前一步输出,例如:先 lookup_user_id 再 get_orders(user_id) 。实现上必须 等前一个 tool 消息返回后再发起下一轮模型请求(或在同轮若模型一次输出多个有依赖的调用,需谨慎,通常仍按顺序执行)。
原理详解
在图工作流里体现为有向边;LangGraph、Temporal 等可显式建模。
5.2 并行工具调用
概念解释
无相互依赖的多个查询(查天气 + 查股价)可并行 HTTP,缩短延迟。OpenAI 可能在一次 assistant 消息返回多个 tool_calls 。
原理详解
注意速率限制与连接池;写操作并行可能导致竞态,需业务锁或串行。
5.3 工具链(Tool Chain)
概念解释
把多个工具按固定或动态顺序组合成复合流程,如「检索 → 摘要 → 存储」。可以是代码写死的Pipeline,也可以是 LLM 每步决定下一步(ReAct / Agent)。
原理详解
静态链:适合稳定 SOP;动态链:适合开放域任务,但要防循环与成本失控。
5.4 条件工具调用
概念解释
根据中间结果分支:仅当 risk_score > 0.8 才调用 human_review 。可用规则引擎、小模型 分类、或让主模型输出结构化「分支字段」(需校验)。
5.5 工具调用的依赖管理
概念解释
显式维护 DAG:节点是工具调用,边是数据依赖。调度器拓扑排序执行;检测环;失败时重试或补偿。
原理详解
对长事务用 Saga 或幂等重试;对 AI 步骤用「检查点」持久化状态。
Q10:并行与串行如何取舍?
A: 读多且无依赖并行;有写冲突、强一致、或后一步参数必须来自上一步精确字段时串行;可并 行读再串行写(Quorum 读/写视业务而定)。
5.6 代码示例(简单编排:先路由再并行)
import concurrent.futures
import json
from typing import Any, Callable
ToolFn = Callable[..., Any]
def safe_call(name: str, fn: ToolFn, kwargs: dict[str, Any]) -> dict[str, Any]:
try:
return {"tool": name, "ok": True, "result": fn(**kwargs)}
except Exception as e:
return {"tool": name, "ok": False, "error": str(e)}
def run_parallel_tools(
calls: list[tuple[str, ToolFn, dict[str, Any]]],
) -> list[dict[str, Any]]:
with concurrent.futures.ThreadPoolExecutor(max_workers=8) as ex:
futs = [ex.submit(safe_call, n, f, k) for n, f, k in calls]
return [f.result() for f in futs]
# 例:先串行拿 user_id,再并行查订单与积分(伪函数)
def lookup_user_id(email: str) -> str:
return "u_123"
def fetch_orders(uid: str) -> list:
return [{"id": 1}]
def fetch_points(uid: str) -> int:
return 42
def orchestrate(user_email: str) -> str:
uid = lookup_user_id(user_email)
second = run_parallel_tools(
[
("orders", fetch_orders, {"uid": uid}),
("points", fetch_points, {"uid": uid}),
]
)
return json.dumps(second, ensure_ascii=False)6. 安全性
6.1 工具调用的权限控制
概念解释
模型本身没有用户身份,必须在服务端把「当前会话用户」与角色/权限绑定,执行工具前检查: 是否可读该表、是否可操作该租户。
原理详解
禁止把服务账号密钥交给模型侧推理环境。
使用用户 OAuth token 或后端代持且按最小权限。
6.2 输入验证与清洗
概念解释
防 Prompt 注入 诱导工具执行越权参数;防 SQL 注入、路径穿越( -./-./etc/passwd )。对所有进入工具的字符串做白名单、参数化查询、chroot/沙箱。
6.3 敏感操作确认
概念解释
删除、转账、对外发邮件等,需 人在回路(HITL) 或二次令牌;或把工具设计为「创建草稿」而 非「直接发送」。
6.4 调用频率限制
概念解释
按用户/IP/工具维度 rate limit,防刷与成本失控;指数退避应对 429。
6.5 审计日志
概念解释
记录:时间、用户、工具名、参数摘要、结果状态、模型请求 ID。用于合规与事后追溯。
Q11:如何防止模型通过工具泄露敏感数据?
A: 最小权限、结果脱敏(掩码)、行级权限、禁止把密钥放进工具返回值、敏感字段仅后端可见 且不出现在模型上下文。
7. 常见工具实现
7.1 搜索工具(Web Search)
概念解释
封装搜索引擎 API(Bing、SerpAPI、自建爬虫需合规)。返回摘要与链接,避免整页 HTML 直接进入上下文。
Python 示意
import os
import urllib.parse
import urllib.request
def web_search(query: str, max_results: int = 5) -> list[dict]:
"""占位:真实环境使用官方搜索 API 并处理分页。"""
q = urllib.parse.quote(query)
url = f"https://duckduckgo.com/html/?q={q}" # 示例仅作结构说明,生产请用合规 API
req = urllib.request.Request(url, headers={"User-Agent": "AgentBot/1.0"})
with urllib.request.urlopen(req, timeout=10) as resp:
html = resp.read(200_000)
return [{"title": "stub", "snippet": html[:200].decode(errors="ignore"), "url": url}]7.2 数据库查询工具
概念解释
永远参数化查询,禁止字符串拼接 SQL。最好只允许只读账号 + 白名单表 + 行级权限。
def query_user_orders(conn, user_id: str, limit: int = 20) -> list:
sql = (
"SELECT id, amount, created_at "
"FROM orders WHERE user_id = %s "
"ORDER BY created_at DESC LIMIT %s"
)
with conn.cursor() as cur:
cur.execute(sql, (user_id, limit))
return list(cur.fetchall())7.3 API 调用工具
概念解释
对内部 REST 封装: GET/POST 、超时、重试、鉴权头从服务端保险柜取,不让模型看见token。
7.4 代码执行工具
概念解释
高风险:必须在沙箱(Docker、gVisor、WASM)中执行,限制 CPU/内存/网络,禁用危险模 块;默认应关闭或仅对内网。
7.5 文件操作工具
概念解释
限制根目录(chroot 或路径规范化),禁止任意路径;写操作需备份或 diff;大文件分块读。
import os
SANDBOX_ROOT = "/var/agent_sandbox"
def safe_read_file(path: str, max_bytes: int = 50_000) -> str:
full = os.path.realpath(os.path.join(SANDBOX_ROOT, path))
if not full.startswith(os.path.realpath(SANDBOX_ROOT) + os.sep):
raise PermissionError("path escapes sandbox")
with open(full, "rb") as f:
return f.read(max_bytes).decode("utf-8", errors="replace")7.6 计算器工具
概念解释
对数学表达式用 AST 解析或 numexpr ,禁止 eval 任意字符串,以防代码执行。
import ast
import operator
_ALLOWED = {
ast.Add: operator.add,
ast.Sub: operator.sub,
ast.Mult: operator.mul,
ast.Div: operator.truediv,
ast.USub: operator.neg,
ast.Pow: operator.pow,
}
def eval_expr(node: ast.AST) -> float:
if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)):
return float(node.value)
if isinstance(node, ast.BinOp) and type(node.op) in _ALLOWED:
return _ALLOWED[type(node.op)](eval_expr(node.left), eval_expr(node.right))
if isinstance(node, ast.UnaryOp) and type(node.op) in _ALLOWED:
return _ALLOWED[type(node.op)](eval_expr(node.operand))
raise ValueError("unsupported expression")
def calculator(expr: str) -> float:
tree = ast.parse(expr, mode="eval")
return eval_expr(tree.body)8. 综合面试题精选(28 题)
下列题目覆盖前文各模块,便于系统复习。背诵时建议理解「为什么」,而非死记句子。
Q1:Function Calling 和「让模型输出 JSON」有什么本质区别?
A: Function Calling 是厂商提供的结构化工具调用通道,字段名、类型与对话轮次都有明确约束。
纯 JSON 输出更依赖 prompt 约束,解析更脆弱,也更容易混入闲聊文本。实践中也可结合使用:FC 负责调度,JSON 负责业务负载。
追问:若模型不支持 FC 怎么办?——可用 JSON mode / 约束解码 / 后处理抽取;或用小模型 做「动作分类」。
Q2:描述 OpenAI 兼容接口里 tool_calls 与 tool 消息的对应关系。
A: 每条 assistant.tool_calls[] 都有唯一 id。
执行后,每条结果都要作为一条 role=tool 消息写回,并带上相同的 tool_call_id,保证多并行调用时不会错配。
Q3:为什么工具 description 比函数名更重要?
A: 模型主要依据自然语言描述区分相似工具,函数名更多是给程序路由用。
所以 description 要写清边界与反例。
Q4:如何设计 JSON Schema 降低模型填错概率?
A: 减少可选参数的模糊性,多用 enum,并在 description 里给示例。
同时避免深层嵌套;必要时把一个大函数拆成多个小函数。
Q5:LangChain Tool 的 docstring 为什么要写「何时不要用」?
A: 减少误触发(false positive),尤其在工具功能重叠时,这是线上质量关键。
Q6:MCP 解决的主要痛点是什么?
A: MCP 主要解决工具集成碎片化、重复建设、难以跨产品复用的问题。
它提供标准边界(Server)与传输方式,使工具在生态支持前提下更接近“即插即用”。
Q7:MCP 与 Function Calling 是替代关系吗?
A: 不是。FC 是模型侧表达;MCP 是工具侧集成。
Host 常把 MCP 工具列表映射成 FC 的 tools。
Q8:工具路由什么时候必须上?
A: 当工具数量已经导致上下文膨胀、误选率上升,或延迟/成本明显增加时,就该考虑上工具路由。
具体阈值依赖模型与描述长度,实践里常见从几十个工具开始考虑。
Q9:向量路由的缺陷与改进?
A: 缺陷是 description 写得不好时 embedding 会不准,而且对 OOV 专有名词偏弱。
改进方式包括:混合检索、同义词表、用户域特征、日志驱动迭代 description,以及增加轻量分类器。
Q10:并行工具调用要注意什么?
A: 要注意幂等性、后端并发限制、数据竞争(尤其写操作)。
同时还要设计好结果合并顺序与部分失败的重试策略。
Q11:什么是工具编排中的「依赖 DAG」?
A: 把工具调用当节点、数据依赖当边,再按拓扑排序执行。
这样可以避免环与竞态,也更便于失败重试与可视化监控。
Q12:敏感操作为什么推荐「两阶段提交」式工具设计?
A: 第一阶段先生成草稿或待确认对象。
第二阶段再在用户确认后真正执行,这样可以降低模型误触发带来的损失。
Q13:工具返回为什么要尽量结构化(JSON)?
A: 便于模型解析下一步推理,也便于程序做校验与日志记录。
如果只返回纯自然语言,就更容易产生歧义。
Q14:如何做工具调用的权限控制?
A: 会话先绑定真实用户身份,再由服务端校验租户与角色,同时坚持最小权限原则。
敏感操作走 HITL,长期密钥也不要暴露给模型上下文。
Q15:代码执行工具如何做到基本安全?
A: 先做沙箱隔离,再加资源与网络限制、禁用危险模块、超时、只读默认和审计。
即便这样,生产环境里也要谨慎使用。
Q16(加一):审计日志至少记哪些字段?
A: 至少要记时间、trace/request id、用户/租户、工具名、参数摘要(脱敏)、结果状态、耗时、模型版本。
合规场景下,是否额外保留策略与不可篡改存储,再按要求加上。
Q17(加一):Calculator 为什么禁止 eval ?
A: eval 可以执行任意 Python,本质上等同于远程代码执行。
更安全的做法是使用 AST 白名单或专门的安全数学库。
小结
- Function Calling:模型产出结构化调用意图,应用在本地执行并回传,是 Agent 的「手」。
- Tool 工程:描述、Schema、返回值与错误模式与路由同样重要。
- MCP:标准化工具与上下文连接,利于复用与治理。
- 路由与编排:解决规模与依赖问题;安全贯穿权限、输入、确认、限流与审计。
- 建议结合自家业务画一张「用户请求 → 路由 → 工具 → 依赖 → 回传模型」的时序图,面试时能用白板讲清楚,比背诵定义更有说服力。