题目 1:为什么说“大模型没有真的执行函数”?
假设模型返回 read_file(path="app.py"),请从模型输出、Harness、Python 运行时、真实环境四个层次解释整个过程,并说明如果 Harness 拒绝执行,会发生什么。
模型只负责生成 Tool Call,也就是“希望调用 read_file,参数是 app.py”这一结构化意图。Harness 接收后负责查找工具、校验参数和安全规则;真正打开文件的是 Python 运行时中的
read_file()。因此模型本身没有直接文件系统权限。如果 Harness 判断该动作不允许并拒绝执行,文件不会被读取,真实环境也不会发生变化。这个边界说明 Tool Call 本质上是请求,而不是执行事实。
题目 2:代码阅读——下面代码有什么风险?
tool = TOOLS[tool_call["name"]]
result = tool(**tool_call["arguments"])
请至少指出四个风险,并给出改进方向。
风险包括:工具名不存在会触发 KeyError;参数缺失或多余会产生 TypeError;参数类型和值可能不合法;工具内部异常会直接中断 Agent;对于有副作用工具还缺少权限与安全检查。改进方向是先做 Tool Lookup,再做参数 Schema / Signature 校验,再经过业务与安全校验,最后用 try/except 执行,并统一返回 ToolResult。高风险工具还应经过 Middleware 或 Human Approval。
题目 3:判断题——Tool Schema 已经定义了参数类型,所以 Harness 不需要再校验,对吗?
请判断,并至少举两个“Schema 看起来合法,但业务上仍然危险”的例子。
不对。Schema 主要约束结构和基本类型,但很多业务规则无法只靠类型表达。例如
limit是合法整数,但如果值是 999999999,可能拖垮数据库;path是合法字符串,但如果是../../../../etc/passwd,存在路径逃逸风险。再比如日期是合法字符串,但范围可能超过允许的 30 天。因此 Tool Schema 只是第一层,Harness 和 Middleware 仍需要业务规则与安全校验。
题目 4:编程实现——写一个最小 Tool Executor
请实现 ToolCall、ToolResult、TOOLS 注册表和 execute_tool()。要求至少支持 read_file、list_files,并处理未知工具和执行异常。
#下面是一种完整实现:
from dataclasses import dataclass
from typing import Any, Callable
@dataclass
class ToolCall:
name: str
arguments: dict[str, Any]
@dataclass
class ToolResult:
ok: bool
tool_name: str
result: Any = None
error: str = ""
FILES = {
"README.md": "Agent Demo"
}
def read_file(path: str) -> str:
if path not in FILES:
raise FileNotFoundError(path)
return FILES[path]
def list_files() -> list[str]:
return list(FILES.keys())
TOOLS: dict[str, Callable[..., Any]] = {
"read_file": read_file,
"list_files": list_files,
}
def execute_tool(
call: ToolCall,
) -> ToolResult:
if call.name not in TOOLS:
return ToolResult(
ok=False,
tool_name=call.name,
error="UnknownTool",
)
try:
result = TOOLS[call.name](
**call.arguments
)
return ToolResult(
ok=True,
tool_name=call.name,
result=result,
)
except Exception as exc:
return ToolResult(
ok=False,
tool_name=call.name,
error=str(exc),
)
if __name__ == "__main__":
print(
execute_tool(
ToolCall(
name="list_files",
arguments={},
)
)
)
print(
execute_tool(
ToolCall(
name="read_file",
arguments={
"path": "README.md"
},
)
)
)
#这个版本还没有参数 Schema 和权限层,但已经具备最基础的 Tool Lookup、Execution 和结构化结果。
题目 5:综合应用——为什么 Tool Result 必须重新进入 Main Loop?
假设 Agent 第一次调用 read_file("app.py") 返回 FILE_NOT_FOUND。如果 Tool Result 只打印日志、不放回下一轮 Context,会发生什么?如果正确回传,模型可能怎么修正?
如果 Tool Result 只写日志,模型下一轮看不到上一轮真实结果,因此可能继续认为 app.py 存在,甚至重复调用同一个工具。正确回传后,FILE_NOT_FOUND 会成为 Observation,模型可以据此判断自己的路径假设错误,下一步改为
list_files()或search_files()。因此 Tool Calling 真正形成 Agent 能力的关键,不只是执行动作,而是把执行反馈重新送入下一轮决策。
题目 6:对比分析——直接生成 SQL 和调用 query_metric Tool 有什么差别?
请从灵活性、安全性、参数校验、权限控制、审计几个角度比较,并说明哪一种更适合什么场景。
直接生成 SQL 灵活性更高,适合受信任、受控环境中的复杂查询,但模型自由度也更大,需要更强 SQL Parser、安全校验、表白名单和权限控制。
query_metric这类业务 Tool 把动作空间收窄成结构化参数,Harness 可以统一限制 metric、dimension、日期范围、limit,因此更容易治理和审计。生产系统常会优先暴露高层业务 Tool,把原始 SQL 能力放在更严格的安全边界后。
题目 7:概念理解——为什么 Tool Calling 是 Agent 从“会想”到“会做”的分界线?
请说明 Tool Calling 自身为什么仍然不够,以及要形成完整 Agent 行动闭环还需要哪些组件。
Tool Calling 让模型第一次能够把“下一步想做什么”表达成程序可执行的结构化动作,因此是从纯文本推理走向外部行动的重要分界线。但它本身仍然不等于完整 Agent,因为还需要 Harness 查找与执行 Tool、参数和安全校验、Tool Result 结构化、Observation 回流、Main Loop 持续决策,以及 State 保存执行过程。只有这些组件连起来,模型的动作意图才能真正转化为可控的多步任务执行。
进一步阅读
- Tool / Function Calling 官方文档:建议重点观察“模型输出 Tool Call”和“应用程序执行 Tool”之间的职责边界。
- JSON Schema 相关资料:下一节会进一步学习如何用 Schema 描述 Tool 参数,让模型和程序共享统一协议。
- Python
inspect.signature文档:可以用于函数参数绑定与基础校验。 - Adapter Pattern 相关资料:理解不同 Provider 的 Tool Call 如何统一成内部 ToolCall。
- Command Pattern 相关资料:Tool Call 可以从“命令对象”的角度理解,便于执行、记录和重放。
- Input Validation 相关资料:Agent Tool Call 本质上也是外部输入,应采用不信任输入的工程思维。
- Sandbox / Capability Security 相关资料:为后面 Shell、文件系统与高风险 Tool 的安全控制打基础。
- Human in the Loop 相关资料:理解为什么部分 Tool Call 需要在真正执行前暂停审批。
术语表
- 工具调用(Tool Calling):模型根据当前上下文选择某个外部能力,并以结构化形式输出工具名称和参数的机制。它表达的是动作意图,而不是动作已经发生。
- 函数调用(Function Calling):Tool Calling 的一种常见形式,模型输出希望调用的函数名和参数。现代 Agent 中 Tool 的范围通常比普通 Python Function 更广。
- 工具调用对象(Tool Call):Harness 内部表示模型动作请求的数据结构,通常包含
name、arguments,后续还可能包含id、来源模型等信息。 - 工具执行器(Tool Executor):负责根据 Tool Call 查找实际工具、校验参数、执行函数并统一返回结果的 Harness 组件,是模型与真实环境之间的关键执行边界。
- 工具结果(Tool Result):Tool 真正执行后的结构化结果,可以包含成功值、错误类型、错误信息、耗时等。它通常会进一步转成 Observation。
- 参数校验(Argument Validation):在执行工具前检查参数是否完整、类型是否正确、业务值是否合法的过程。模型输出不应被默认视为可信输入。
- 未知工具(Unknown Tool):模型请求了 Registry 中不存在的工具。成熟 Harness 应把它结构化成可观察错误,而不是直接让程序崩溃。
- 副作用(Side Effect):Tool 执行后会改变真实环境的行为,例如写文件、删除数据、发送邮件、执行 Shell。具有副作用的工具通常需要更严格的权限和审批。
本章总结
这一节是 Agent 真正开始“长出手脚”的第一步。
前面我们已经让 Agent 会循环、会保存 State、会通过 Provider 调模型。
但如果模型只能返回文本,它仍然只能“说”。
Tool Calling 则让模型第一次可以输出:
{
"name": "read_file",
"arguments": {
"path": "app.py"
}
}
不过这一节最重要的不是记住 JSON 长什么样,而是彻底建立一个边界,大模型没有真的执行函数。
模型只是,告诉 Harness:“我想调用这个工具。”
真正执行过程是:
模型产生 Tool Call
↓
Harness 解析
↓
查找工具
↓
参数校验
↓
权限与安全检查
↓
Python / API / SQL / Shell 真正执行
↓
Tool Result
↓
Observation
↓
回到 Main Loop
所以 Tool Calling 只是动作的开始,不是动作的结束。
我们还看到,Tool Executor 最少要处理:
Unknown Tool
Missing Arguments
Invalid Arguments
Tool Exception
而以后更复杂的 Tool 还要处理:
Permission
Timeout
Side Effect
Human Approval
Sandbox
如果你想验证自己是否真的学会这一节,可以不接任何真实 LLM。
自己造一个 FakeProvider,让它第一轮输出:
read_file("app.py")
Harness 执行后返回:
FILE_NOT_FOUND
第二轮再让模型输出:
list_files()
如果你能把这条链完整跑通:
Tool Call
↓
Tool Executor
↓
Tool Result
↓
Observation
↓
Next Tool Call
那么这一节最核心的能力就已经掌握了。
下一节我们会正式解决一个现在已经出现的问题,工具越来越多以后,怎么统一管理?
如果继续写:
if name == "read_file":
...
elif name == "list_files":
...
elif name == "run_python":
...
代码会越来越难维护。
所以第 07 节,我们会自己实现,Tool Registry
让所有工具可以注册、查询、枚举和统一执行。
















请登录后查看评论内容