Appearance
Agent Tool Calling (Function Calling) 架构与安全防护
回到总览:AI 时代的工程师能力
相关模块:AI 辅助开发与 Agentic Workflow 全景总览 · Prompt 工程、Tool Calling 与 Agent 记忆机制 (Memory Workflow)
一句话定义
Agent Tool Calling(也称 Function Calling,函数调用)是指大模型根据用户意图,自动从预定义的工具库中选择最匹配的工具,并输出符合 JSON Schema 规范的工具参数请求,由客户端/IDE 宿主环境执行真正的代码或 API,再将结果反馈给大模型的双向自治交互架构。
代码索引
| 主题 | Lab 说明 | 源码 |
|---|---|---|
| Tool Calling + 最小 RAG + SSE 闭环 | tool-calling-rag-streaming-demo | server.js · client.js |
为什么需要
- 为什么仅靠纯 LLM 无法完成“实时天气查询”、“修改本地 Git 代码”或“执行 SQL 语句”?
- 一句话答:LLM 本质只是静态的概率预测模型,不具备连接互联网实时数据或执行本地操作系统指令的直接能力;Tool Calling 让 LLM 拥有了“手和脚”,可以通过调用外部 API 和运行 Shell 命令完成真实的物理操作。
- 为什么 10 年 Android 工程师架构设计 Tool Calling 时必须设立“安全沙箱与用户权限确认”?
- 一句话答:如果将高危 Shell 命令(如
rm -rf /或写数据库)无限制交由 Agent 自主执行,在遭遇 Prompt 注入攻击或模型幻觉时会导致灾难性的数据丢失;必须引入窄权限作用域(Least Privilege) 与二次人工确认(Human-in-the-loop) 。
- 一句话答:如果将高危 Shell 命令(如
底层机制
1. Tool Calling 标准 JSON Schema 声明
大模型并不直接运行代码,它只读取工具的 JSON Schema 定义:
json
{
"name": "search_code_file",
"description": "在项目目录中使用 ripgrep 搜索包含特定关键字的文件",
"parameters": {
"type": "object",
"properties": {
"query": { "type": "string", "description": "要搜索的关键字或正则" },
"path": { "type": "string", "description": "搜索的目标目录绝对路径" }
},
"required": ["query", "path"]
}
}2. ReAct 循环架构 (Reasoning + Acting)
text
[用户输入] ──► LLM 思考 (Thought: 需要查看文件) ──► 输出 Tool Call (Action: view_file)
│
▼
LLM 最终总结 ◄─── 思考 (Thought: 根据文件内容...) ◄─── 执行工具并返回结果 (Observation)3. 最小闭环里 Tool Calling 到底怎么落地
这类能力真正落地时,至少要有四个可分开的角色:
- 工具声明层:告诉模型有哪些工具、参数 schema 是什么。
- 决策层:根据问题判断“要不要调工具、调哪个工具”。
- 执行层:真正跑 shell / 搜文档 / 调 API。
- 结果回灌层:把工具结果重新喂回模型,进入下一轮推理或生成。
在本仓库的最小 demo 里,这四层被压进了同一个 server.js,但边界仍然保留:工具声明是 tools.search_docs,决策发生在 streamChat,执行落在 searchDocs(),最后再通过 SSE 把 tool_call / tool_result / token 逐段推给客户端。
js
const toolCall = {
name: 'search_docs',
arguments: {
query: question,
limit: 3,
},
};
sendEvent(res, 'tool_call', {
thought: '问题涉及知识库内容,先检索 docs 获取上下文。',
toolCall,
});
const results = tools.search_docs.run(toolCall.arguments);
sendEvent(res, 'tool_result', {
tool: toolCall.name,
matches: results,
});- 可能执行顺序
- 模型判断当前问题需要外部上下文。
- 产出结构化
toolCall。 - 宿主执行
search_docs。 - 把命中片段送回生成阶段。
- 答案按 token 流式返回。
- 可能输出text
[tool_call] {"name":"search_docs","arguments":{"query":"为什么 RAG 需要 chunk overlap?","limit":3}} [tool_result] {"matches":[{"file":"docs/08-ai-engineering/04-rag-and-knowledge-retrieval.md","score":0.44}]} 问题:为什么 RAG 需要 chunk overlap? - 预期现象:工具调用与最终答案是两段独立事件,前端可以先展示“正在检索”,而不是等最终整段回答生成完。
- 观察重点:Tool Calling 的关键不是“模型帮你直接运行代码”,而是模型产出结构化意图、宿主决定如何执行。
Android / Flutter / Web / Backend 对照
| 维度 | AI Agent 架构 | 移动端 Native 架构 |
|---|---|---|
| 契约定义 | JSON Schema (Function Declaration) | AIDL / Protobuf / API Interface |
| 调度核心 | ReAct 循环 (Reasoning + Acting) | Handler / Looper / TaskRunner |
| 安全控制 | Human-in-the-loop 人工二次确认 | Android Permission / Dynamic Sandbox |
常见场景
1. 实现安全的 Tool 拦截器 (带有权限确认)
kotlin
fun executeAgentToolCall(toolCall: ToolCall): ToolResult {
return when (toolCall.name) {
"view_file" -> {
val path = toolCall.args["path"] as String
val content = File(path).readText()
ToolResult.Success(content)
}
"run_command" -> {
val cmd = toolCall.args["command"] as String
// 针对写操作或高危 Shell,必须要求用户显式弹窗确认!
if (isHighRiskCommand(cmd) && !askUserPermission(cmd)) {
return ToolResult.Error("用户拒绝了高危命令的执行!")
}
val output = executeShell(cmd)
ToolResult.Success(output)
}
else -> ToolResult.Error("未知的工具: ${toolCall.name}")
}
}常见误配、事故后果与排障
1. 事故:未做沙箱隔离允许 Agent 执行 cd / 导致操作系统根目录被篡改
- 误配原因:给 Agent 提供了未经限权的 Shell 执行工具,且未校验
Cwd(当前工作目录)的绝对路径范围。 - 后果:Agent 误将修改目标定到了
/tmp或系统根目录,甚至执行了删库指令,造成主机灾难。 - 排障与修法:硬性校验 Cwd 必须在 Workspace 工作空间内;限制命令前缀(只放行
git,npm,gradle),禁止全局sudo。
2. 误配:Tool Calling 已经接入,但工具结果没有重新喂回生成阶段
- 后果:前端能看到“工具跑了”,但模型最终回答仍像没读过结果一样,表现为“形式上用了工具,实际上还是幻觉”。
- 排障与修法:明确把
tool_result当作下一轮 Prompt 的 Observation,而不是只打印日志;本仓库 demo 里是把matches直接传给buildAnswer()进入最终回答组装。
与相近概念对比
| 机制 | 触发者 | 结果消费者 | 适合场景 |
|---|---|---|---|
| Tool Calling | LLM 模型自主决定 | LLM 模型自身(做下一步推理) | 智能 Agent、自动化 Coding、复杂任务排查 |
| 常规 REST API | 程序员代码手动指定 | 客户端 UI 组件展示 | 标准业务增删改查 |
对应实验
| Lab | 说明 | 源码 |
|---|---|---|
| tool-calling-rag-streaming-demo | Tool Calling + 最小 RAG + SSE 流式闭环(与文首「代码索引」一致) | server.js · client.js |
复习检查题
在 LLM 的 Tool Calling 机制中,大模型自身真的会去调用执行真实的 Shell 命令或网络 API 吗?
答:不会。大模型本身只是一个运行在服务器上的文本生成模型,没有任何直接访问操作系统或网络的物理能力。当大模型判定需要调用工具时,它只是输出了一段符合 JSON Schema 规范的文本描述(包含要调用的工具名和提取出的参数);真正的命令执行是由 IDE 或 Agent 客户端宿主程序完成的,执行完毕后再将结果传回给大模型。
为什么在 Agent 架构中设计高危工具(如修改文件、执行命令)时,必须引入“Human-in-the-loop(人工在环确认)”?
答:因为大语言模型存在发生“Prompt 注入攻击”或“模型幻觉”的风险。如果完全让 Agent 盲目无限制地自主执行任意写操作或删除指令,可能导致误删重要文件或泄漏隐私。引入“Human-in-the-loop”机制要求宿主在执行高危操作前向用户弹窗展示即将执行的命令并请求显式批准,构成了保障系统安全不可突破的最后防线。
速记
- Schema 契约:工具声明靠 JSON Schema,描述要清晰明确。
- ReAct 循环:Thought 思考 → Action 调工具 → Observation 读结果。
- 安全第一:高危 Shell 必须加沙箱限制,重要写操作坚守用户批准防线。