1. 为什么裸调大模型接口做不出 Agent
很多人第一次做 AI 应用,思路都是「拿个 API Key,调一下 chat completions,把返回文本显示出来」。这确实能跑通一个聊天框,但离 Agent 智能体还差得远。核心问题在于:大模型本身只会「说」,不会「做」。你问它「帮我看看项目里 package.json 写了什么」,它只能凭训练数据猜一个大概,没法真的去读你磁盘上的文件。
这就是 Tool Use(工具调用)要解决的事。给大模型挂上几个可执行的函数,比如读文件、跑命令、查数据库,模型在推理时会主动判断「这个任务我需要调用 read_file」,然后按约定格式吐出调用参数,由你的代码去真正执行,再把结果喂回去让它继续推理。这一来一回,模型就从「嘴替」变成了「能干活的手」。
本篇聚焦 Agent 智能体 Tool Use 入门实战,用 LangChain 调用大模型自动执行工具,交付一套可复制的 TaoToken 统一 Key/API 通道配置骨架,并跑通一次完整的工具调用链路。适合刚接触 Agent、想用 Node.js + LangChain 写出第一个「自动干活」智能体的开发者。读完你能拿到三样东西:一份能直接用的 settings.json / config.toml 配置、一段可运行的 Tool 定义代码、一次真实工具调用的验证结果。
2. TaoToken 统一 Key 与 API 通道准备
做 Agent 开发最烦的一件事是:今天用这个模型,明天换那个模型,每换一次就要改 baseURL、改 Key 环境变量、改模型名,代码里到处是硬编码。TaoToken 的价值就在于它提供统一的 API 通道,兼容 OpenAI 接口格式,你只需要维护一份 Key 和一份 baseURL,切换模型时改一个 modelName 就行。
先拿到统一 Key。访问控制台创建 API Key:
https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=tool_use_console创建后在 API Keys 页面复制那串sk-开头的密钥,后面所有配置都用它。接入文档在这里,遇到参数疑问可以对照:
https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=tool_use_docAPI 基础地址统一用:
https://taotoken.net/api注意这个地址不带任何查询参数,直接作为 baseURL 使用。如果你用的是 Claude Code 这类编码 Agent,Anthropic 兼容通道的配置方式在:
https://taotoken.net/ClaudeCodeAnthropic?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=tool_use_claude提示:Key 只存在服务端环境变量或本地
.env里,绝对不要提交到 Git 仓库。下面所有配置示例都用process.env读取,不写死明文。
3. 可复制的配置骨架:settings.json 与 config.toml
不同工具链读不同格式的配置文件。VS Code 系插件、部分 CLI 读settings.json;一些终端 Agent 和 Rust 系工具读config.toml。我把两份都给你,按需取用。
3.1 settings.json 配置
放在项目根目录或工具指定的配置目录下。核心是baseUrl指向 TaoToken 统一通道,apiKey从环境变量注入:
{ "llm": { "provider": "openai-compatible", "baseUrl": "https://taotoken.net/api", "apiKey": "${TAOTOKEN_API_KEY}", "model": "deepseek-v4-flash", "temperature": 0 }, "agent": { "maxToolRounds": 8, "parallelToolCalls": true, "toolTimeoutMs": 30000 } }temperature设成 0 是为了让工具调用更确定,模型不会因为随机性漏掉该调的工具。parallelToolCalls打开后,模型一次返回多个 tool_calls 时框架会并发执行,这个后面第六节会讲。
3.2 config.toml 配置
终端类 Agent 常用 TOML。字段含义和上面一致,只是语法不同:
[llm] provider = "openai-compatible" base_url = "https://taotoken.net/api" api_key = "${TAOTOKEN_API_KEY}" model = "deepseek-v4-flash" temperature = 0.0 [agent] max_tool_rounds = 8 parallel_tool_calls = true tool_timeout_ms = 300003.3 环境变量与依赖安装
无论用哪份配置,Key 都通过环境变量传入。在项目根目录建.env:
TAOTOKEN_API_KEY=sk-你的密钥然后安装 LangChain 相关依赖:
npm init -y npm install @langchain/openai @langchain/core zod dotenv@langchain/openai虽然名字带 openai,但它走的是 OpenAI 兼容协议,所以 TaoToken 统一通道可以直接用。zod负责工具参数的 schema 校验,dotenv负责加载.env。
4. 定义第一个 Tool 并跑通调用链路
配置就绪,现在写代码。目标:定义一个读文件的工具,让模型自己决定什么时候调用它。
4.1 初始化模型并绑定工具
新建tool.mjs:
import 'dotenv/config'; import { ChatOpenAI } from '@langchain/openai'; import { tool } from '@langchain/core/tools'; import { HumanMessage, SystemMessage, ToolMessage, } from '@langchain/core/messages'; import fs from 'fs/promises'; import { z } from 'zod'; const model = new ChatOpenAI({ modelName: 'deepseek-v4-flash', apiKey: process.env.TAOTOKEN_API_KEY, temperature: 0, configuration: { baseURL: 'https://taotoken.net/api', }, });这里baseURL指向 TaoToken 统一通道,apiKey从环境变量读。换模型只改modelName,其余不动。
4.2 用 tool() + zod 定义工具
工具由两部分组成:一个异步执行函数,一个描述对象。描述对象里的description是模型理解工具用途的唯一途径,写得越具体,模型越知道何时该调、传什么参数:
const readFileTool = tool( async ({ filePath }) => { const content = await fs.readFile(filePath, 'utf-8'); console.log(`[工具调用] read_file(${filePath}) 读取 ${content.length} 字符`); return content; }, { name: 'read_file', description: '用此工具读取文件内容。当用户要求读取文件、查看代码、分析文件内容时调用。输入文件路径,支持相对路径或绝对路径。', schema: z.object({ filePath: z.string().describe('要读取的文件路径'), }), } ); const tools = [readFileTool]; const modelWithTools = model.bindTools(tools);bindTools会把工具的description和schema注入到发给模型的请求里,模型据此判断是否调用。
4.3 消息交互与工具结果回填
工具调用不是一次请求就完事,而是一个循环:模型返回 tool_calls → 你执行工具 → 把结果作为 ToolMessage 拼回消息列表 → 再发给模型。先看第一次调用:
const messages = [ new SystemMessage( '你是一个代码助手,可以使用工具读取文件并解释代码。用户要求读取文件时,立即调用 read_file 工具,等待结果后再分析。' ), new HumanMessage('请读取文件 ./package.json 并解释它的作用'), ]; let response = await modelWithTools.invoke(messages); messages.push(response); console.log('模型返回的 tool_calls:', JSON.stringify(response.tool_calls, null, 2));运行后你会看到模型没有直接生成文本,而是返回了tool_calls数组,结构大致是:
[ { "id": "call_abc123", "name": "read_file", "args": { "filePath": "./package.json" } } ]id是这次调用的唯一标识,回填结果时必须带上,模型靠它把「哪个结果对应哪个调用」对上号。接下来执行工具并回填:
for (const call of response.tool_calls) { const result = await readFileTool.invoke(call.args); messages.push( new ToolMessage({ tool_call_id: call.id, content: result, }) ); } const finalResponse = await modelWithTools.invoke(messages); console.log('最终回答:\n', finalResponse.content);4.4 验证成功结果
完整跑一遍:
node tool.mjs预期输出分三段。第一段是模型返回的 tool_calls,能看到它自己选了read_file并填好了filePath。第二段是工具执行日志[工具调用] read_file(./package.json) 读取 xxx 字符。第三段是模型的最终回答,它会基于真实读到的文件内容解释这个项目依赖了什么、脚本有哪些。
如果你看到这三段,说明工具调用链路完全跑通了:模型自主决策 → 工具真实执行 → 结果回填 → 模型二次推理。这就是 Agent 从「说」到「做」的关键一跃。
5. 本篇常见错误排查
工具调用第一次跑,报错基本集中在下面几类。
报 401 或 invalid api key:九成是.env没加载或变量名写错。确认import 'dotenv/config'在文件最顶部,且.env里变量名和代码里process.env.TAOTOKEN_API_KEY完全一致。Key 前后不要有空格和引号。
报 model not found:modelName拼错了,或者该模型在当前通道不可用。换成文档里列出的可用模型名再试。验证模型是否可用,可以直接在模型对话页发一条消息测试:
https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=tool_use_chat模型不调用工具,直接编答案:这是最常见的坑。原因通常是description写得太笼统,模型没意识到该用工具。把使用场景写进描述,比如「当用户要求读取文件时调用」,并确认bindTools确实执行了。另外temperature别设太高。
ToolMessage 报 missing tool_call_id:回填结果时忘了带tool_call_id,或者id和模型返回的对不上。必须用call.id原样回填,不能自己造。
工具执行报 ENOENT:文件路径不对。相对路径是相对于你运行node命令的目录,不是脚本所在目录。不确定就用绝对路径先验证。
多个工具时结果错位:模型一次返回多个 tool_calls,你串行执行没问题,但如果用Promise.all并发,回填时仍要按各自的id对应,不能按数组顺序硬塞。
6. 下一步:从单次调用到 Agent Loop
本篇完成了 Tool Use 的最小闭环:定义工具、绑定模型、执行调用、回填结果。但你可能注意到,上面的代码只处理了一轮工具调用。真实 Agent 任务往往需要多轮:读文件 → 分析 → 写文件 → 跑命令 → 看输出 → 再改。这个「思考-执行-反馈-再思考」的循环,就是下一课 Agent Loop 要展开的内容。
如果你打算长期做编码类 Agent、把工具调用能力接到日常开发流里,建议直接上 Coding Plan,省去自己维护循环和并发调度的功夫:
https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=tool_use_plan想先把本篇的 Key 和通道配置固化下来,去 API Keys 页面把密钥管理好,后续所有课程复用同一份配置:
https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=tool_use_keys一个实用技巧:把read_file的description里加上「不要用于写入或修改文件」,能明显减少模型误用工具的情况。工具描述写得越像给新同事的交接说明,模型用得越准。