
核心机制:模型从不执行代码
新手常误以为存在某种黑盒机制,但事实更为简单:模型本身绝不运行任何代码。开发者需提供一份允许调用的函数列表。当模型判定需要调用某函数时,它会暂停文本生成,输出一段包含函数名及JSON参数的结构化数据。宿主程序读取该数据,执行本地函数,并将返回值重新注入对话上下文,随后再次请求模型。
这一过程构成一个四步循环。图中不存在独立的记忆或规划模块。对话数组即为状态,循环本身即为程序。无工具的模型仅是写作者;结合此循环与工具的模型,才构成智能体(Agent)。
模型交互细节
开发者只需在JSON Schema中定义工具名称、描述及参数结构。模型像处理其他文本一样阅读这些描述,并生成符合结构的参数。这带来两点关键启示:
- 必须解析JSON:参数以JSON格式返回,严禁通过字符串匹配处理。
- 描述即提示词:若模型频繁误用工具,通常源于描述不清。编写模型可准确理解的描述是进阶关键。
底层逻辑无需依赖框架。尽管后续课程介绍的框架会自动运行此循环,但其核心依然是上述四个步骤。
代码实现范例
以下提供基于Anthropic SDK的完整可运行代码。需安装相应包并配置ANTHROPIC_API_KEY。
JavaScript 实现
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
// 1. 工具:普通函数,无特殊之处
function getWeather({ city }) {
const readings = { Paris: "18°C, light rain", Tokyo: "27°C, clear" };
return readings[city] ?? `no reading for ${city}`;
}
// 2. 描述:模型阅读的提示词
const tools = [{
name: "get_weather",
description: "Current weather for one city. Use this for any question " +
"about temperature, rain, or conditions right now.",
input_schema: {
type: "object",
properties: {
city: { type: "string", description: "City name, e.g. Paris" },
},
required: ["city"],
},
}];
// 3. 循环:智能体的核心逻辑
const messages = [
{ role: "user", content: "Do I need an umbrella in Paris?" },
];
while (true) {
const reply = await client.messages.create({
model: "claude-opus-5",
max_tokens: 4096,
tools,
messages,
});
// 原样追加回复,保留所有块信息
messages.push({ role: "assistant", content: reply.content });
// 若无需工具,则输出文本并结束
if (reply.stop_reason !== "tool_use") {
console.log(reply.content.filter(b => b.type === "text")
.map(b => b.text).join(""));
break;
}
// 处理工具请求并回传结果
const results = reply.content
.filter(b => b.type === "tool_use")
.map(b => ({
type: "tool_result",
tool_use_id: b.id, // ID是契约核心
content: String(getWeather(b.input)),
}));
messages.push({ role: "user", content: results });
}
注意:工具结果通过role: "user"返回。虽看似怪异,但这符合协议规定:模型回合后紧跟回应回合。
Python 实现
import anthropic
client = anthropic.Anthropic();
# 1. 工具
def get_weather(city):
readings = {"Paris": "18°C, light rain", "Tokyo": "27°C, clear"}
return readings.get(city, f"no reading for {city}")
# 2. 描述
tools = [{
"name": "get_weather",
"description": "Current weather for one city. Use this for any question "
"about temperature, rain, or conditions right now.",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name, e.g. Paris"},
},
"required": ["city"],
},
}]
# 3. 循环
messages = [
{"role": "user", "content": "Do I need an umbrella in Paris?"},
]
while True:
reply = client.messages.create(
model="claude-opus-5",
max_tokens: 4096,
tools=tools,
messages=messages,
)
messages.append({"role": "assistant", "content": reply.content})
if reply.stop_reason != "tool_use":
print("".join(b.text for b in reply.content if b.type == "text"))
break
results = [
{
"type": "tool_result",
"tool_use_id": block.id,
"content": str(get_weather(**block.input)),
}
for block in reply.content if block.type == "tool_use"
]
messages.append({"role": "user", "content": results})
其中get_weather(**block.input)对应JS中的解构赋值,将Schema属性名直接映射为函数参数。
对话形态与记忆机制
每轮交互都在数组中新增条目。模型本身无记忆,数组即记忆。十步长的智能体在最后一次请求时携带全部十个步骤的历史。这也解释了为何上下文长度直接影响费用。
成本核算与优化
每次请求均需发送完整对话数组。假设五次请求的输入令牌分别为0.3k、0.9k、2.0k、2.9k和4.2k,总计10.3k输入令牌,而最终对话长度仅为4.2k。用户需为构建内容的2.5倍大小付费。以Claude Opus 5每百万输入令牌5美元计,此例成本约0.05美元。
若工具返回数据臃肿,成本将激增。例如,第二步返回8k令牌的文件,将在后续八次请求中重复发送,产生+64k输入令牌(约0.32美元)。若每日运行千次,仅因未修剪read_file结果,日成本可达320美元。
测试环境中默认最大步数为40,旨在限制此类计费上限。优化策略包括:
修剪工具结果,而非提示词。提示词仅发送一次,而工具结果会在后续每轮重复发送。
- 减少返回量:返回行范围、计数或部分匹配项,而非全量数据。
- 缓存前缀:使用
cache_control可使重复前缀计费率降至输入率的0.1倍(首次写入收取1.25倍溢价)。
四条最佳实践
- 回应所有调用:即使函数抛出异常,也应返回含
is_error: true的tool_result。缺失结果属协议违规,错误信息则有助于模型重试。 - 合并返回结果:若模型并行请求多个工具,应在单个用户消息中返回所有
tool_result。拆分消息会抑制模型的并行请求能力,降低效率。 - 整体追加回复:直接推送
reply.content,避免重建字符串。丢弃非文本块会导致下一轮质量下降且难以排查。 - 限制循环次数:生产环境中应设置计数器(如20次),防止因模型误解描述而无限调用,避免高额账单。
权衡:手动循环 vs SDK运行器
掌握底层逻辑后,建议使用SDK提供的自动化工具。Python中使用@beta_tool装饰器的client.beta.messages.tool_runner(),TypeScript中使用betaZodTool的client.beta.messages.toolRunner()。这可大幅简化代码并自动应用上述最佳实践。
代价在于失去对控制流的细粒度掌控。如需在回合间插入审批、日志或重试逻辑,须通过钩子(hooks)实现,而非直接修改循环体。建议初学者先手动实现一次循环,以深入理解机制。
主流技术栈均遵循同一逻辑:
- Python/TS:Anthropic官方SDK提供
tool_runner。 - Vercel AI SDK:封装为
generateText({ tools })。 - 其他语言:Go、Java等官方SDK均含工具运行器入口。
- 本地模型:Ollama和llama.cpp支持OpenAI风格
tools,逻辑一致。 - MCP:作为获取工具的方式,由服务器填充
tools数组,不改变循环本质。
练习与安全说明
建议在测试环境中实践:连接虚拟文件系统,执行写入与读取操作,观察tool_use与tool_result的交替过程。尝试禁用工具或拼错ID,观察模型反应及错误处理。
测试环境仅在浏览器localStorage中操作虚拟文件,不涉及真实磁盘。/wipe可清除数据,/undo可恢复。唯一真实成本为令牌费,极其低廉,适合大胆试错。
下周预告
后续课程将深入探讨如何编写精准的工具描述,并在第3课中拆解循环与目标设定,解决模型永不停止调用工具等极端情况。从“描写天气”到“检查天气”,二十行代码与四个站点构成了智能体的基石。真正的技艺在于选择哪些工具值得加入该数组。