如果你希望大模型不仅能回答你的问题,还能读取本地的文件,就需要为它绑定上对应的工具。在 `langchain` 中,绑定的方式很简单——直接调用 `model.bindTools` 即可。
接下来,我们来探讨如何定义工具。
## 定义工具
在定义工具时,首先要弄清楚它的“使用说明书”是什么样的。工具本质上是一个函数,既然是函数,就必须接收参数,也就是有入参。因此,你需要明确三件事:名称、描述、入参。
```ja vascript
const obj = {
// 工具的名称
name: 'read_file',
// 对这个工具的人类语言描述
description: '对这个工具的人类语言描述',
// 工具入参
schema: z.object({
filePath: z.string().describe('要读取的文件路径'),
}),
}
```
编写完说明书后,接下来就是工具本身——也就是那个函数。逻辑非常简单:传入文件路径,读取内容并返回。
```ja vascript
// 很简单,就是一个函数,函数中的逻辑如下,实现了读取文件内容并返回
const fun = async ({ filePath }) => {
const content = await fs.readFile(filePath, 'utf-8');
console.log(` [工具调用] read_file("${filePath}") - 成功读取${content.length}字节`);
return `文件内容:\n${content}`;
}
```
最后一步,用 langchain 的 `tool` 函数把上面这两个参数传进去,工具就算定义好了。
```ja vascript
tool(fun, obj)
```
## 调用工具
在前面绑定好工具之后,我们调用了模型的 `invoke` 函数。但请注意,模型并不会直接去执行工具,而是返回当前用户问题可以使用的工具列表。
- 用户问:“你好”。模型返回一个工具空数组——它自己判断,这次请求不需要调用任何工具。
- 用户问:“读取某个文件”。大模型会检索自身,看是否有合适的工具。如果有,就在返回数据中把可用的工具带出来。
数组中的每一个工具都包含:工具的实际入参(模型通过第一次用户的提问识别到的)、工具的唯一 id(每次调用都会随机生成一个新的)、工具的名称。
当模型返回了工具调用数组,我们需要遍历这个数组,依次调用每一个工具函数。注意,调用工具不是使用 `模型的 invoke`,而是 `工具的 invoke`。
```ja vascript
// invoke的参数就是工具的入参,这个入参可以从模型返回的数组中获取
const result = await tool.invoke(toolCall.args);
```
拿到工具的结果之后,用 langchain 的 `new ToolMessage` 来新建一条消息:
```ja vascript
new ToolMessage({
content: toolResults[index],
tool_call_id: toolCall.id,
})
```
最后,将所有消息再次发送给模型,模型输出最终回答。
### Q&A
**Q1:为什么需要 `new ToolMessage`,而不是将工具返回结果直接拼接到前面用户的提问后面?**
**A1:**
(1)像 GPT-4、Claude 这类支持 function calling 的模型,在训练过程中大量接触的对话格式就是这种结构化模式:
```
User → Assistant(tool_calls) → ToolMessage → Assistant(回复)
```
也就是说,模型在训练时学会了:收到带有 tool_call_id 的 ToolMessage = 这是我刚才调用的工具返回的结果,需要借助这些内容来作答。如果改用字符串拼接,将工具结果塞进 HumanMessage 或普通文本中,这种模式在训练数据里非常罕见,模型就没有明确的“如何使用”的经验。
(2)角色与用途清晰
在结构化消息体系里,每一条消息的语义都非常明确:
| 消息类型 | 模型的理解 |
| --- | --- |
| HumanMessage | 用户说了什么、想实现什么 |
| AIMessage + tool_calls | 我决定调用这些工具 |
| ToolMessage | 这些工具返回的结果,是对我上一轮决策的反馈 |
| AIMessage | 基于工具结果给出最终回答 |
模型会将 ToolMessage 视为“我主动调用的工具的结果”,而不是“用户新说的话”。如果采用字符串拼接,工具结果很容易被当作普通上下文或用户输入,导致模型不清楚这是自己刚才调用的工具的结果,从而处理方式产生偏差。
(3)注意力与因果关系
在多轮对话中,模型对不同消息类型的处理权重各不相同。ToolMessage 在训练阶段被视作“对上一次 tool_calls 的回应”,因此模型会更自然地把注意力集中在:用这些结果来回答用户最初的问题。这样能够维持清晰的因果链条,避免干扰。
```Agent开发工具01实战指南
```html 大语言模型(LLM)本身不具备直接操控外部资源的能力,例如无法读取你本地存储的文件。那么,如何让它实现这些功能呢?关键在于为它“绑定”相应的工具。在 Langchain 框架中,这一操作非常简洁——只需使用 `model bindTools` 即可完成绑定。接下来,自然是定义所需的工
```html
大语言模型(LLM)本身不具备直接操控外部资源的能力,例如无法读取你本地存储的文件。那么,如何让它实现这些功能呢?关键在于为它“绑定”相应的工具。在 Langchain 框架中,这一操作非常简洁——只需使用 `model.bindTools` 即可完成绑定。接下来,自然是定义所需的工具。
## 工具
大语言模型本身并不具备工具的功能,比如读取文件的特性。
如果你希望大模型不仅能回答你的问题,还能读取本地的文件,就需要为它绑定上对应的工具。在 `langchain` 中,绑定的方式很简单——直接调用 `model.bindTools` 即可。
接下来,我们来探讨如何定义工具。
## 定义工具
在定义工具时,首先要弄清楚它的“使用说明书”是什么样的。工具本质上是一个函数,既然是函数,就必须接收参数,也就是有入参。因此,你需要明确三件事:名称、描述、入参。
```ja vascript
const obj = {
// 工具的名称
name: 'read_file',
// 对这个工具的人类语言描述
description: '对这个工具的人类语言描述',
// 工具入参
schema: z.object({
filePath: z.string().describe('要读取的文件路径'),
}),
}
```
编写完说明书后,接下来就是工具本身——也就是那个函数。逻辑非常简单:传入文件路径,读取内容并返回。
```ja vascript
// 很简单,就是一个函数,函数中的逻辑如下,实现了读取文件内容并返回
const fun = async ({ filePath }) => {
const content = await fs.readFile(filePath, 'utf-8');
console.log(` [工具调用] read_file("${filePath}") - 成功读取${content.length}字节`);
return `文件内容:\n${content}`;
}
```
最后一步,用 langchain 的 `tool` 函数把上面这两个参数传进去,工具就算定义好了。
```ja vascript
tool(fun, obj)
```
## 调用工具
在前面绑定好工具之后,我们调用了模型的 `invoke` 函数。但请注意,模型并不会直接去执行工具,而是返回当前用户问题可以使用的工具列表。
- 用户问:“你好”。模型返回一个工具空数组——它自己判断,这次请求不需要调用任何工具。
- 用户问:“读取某个文件”。大模型会检索自身,看是否有合适的工具。如果有,就在返回数据中把可用的工具带出来。
数组中的每一个工具都包含:工具的实际入参(模型通过第一次用户的提问识别到的)、工具的唯一 id(每次调用都会随机生成一个新的)、工具的名称。
当模型返回了工具调用数组,我们需要遍历这个数组,依次调用每一个工具函数。注意,调用工具不是使用 `模型的 invoke`,而是 `工具的 invoke`。
```ja vascript
// invoke的参数就是工具的入参,这个入参可以从模型返回的数组中获取
const result = await tool.invoke(toolCall.args);
```
拿到工具的结果之后,用 langchain 的 `new ToolMessage` 来新建一条消息:
```ja vascript
new ToolMessage({
content: toolResults[index],
tool_call_id: toolCall.id,
})
```
最后,将所有消息再次发送给模型,模型输出最终回答。
### Q&A
**Q1:为什么需要 `new ToolMessage`,而不是将工具返回结果直接拼接到前面用户的提问后面?**
**A1:**
(1)像 GPT-4、Claude 这类支持 function calling 的模型,在训练过程中大量接触的对话格式就是这种结构化模式:
```
User → Assistant(tool_calls) → ToolMessage → Assistant(回复)
```
也就是说,模型在训练时学会了:收到带有 tool_call_id 的 ToolMessage = 这是我刚才调用的工具返回的结果,需要借助这些内容来作答。如果改用字符串拼接,将工具结果塞进 HumanMessage 或普通文本中,这种模式在训练数据里非常罕见,模型就没有明确的“如何使用”的经验。
(2)角色与用途清晰
在结构化消息体系里,每一条消息的语义都非常明确:
| 消息类型 | 模型的理解 |
| --- | --- |
| HumanMessage | 用户说了什么、想实现什么 |
| AIMessage + tool_calls | 我决定调用这些工具 |
| ToolMessage | 这些工具返回的结果,是对我上一轮决策的反馈 |
| AIMessage | 基于工具结果给出最终回答 |
模型会将 ToolMessage 视为“我主动调用的工具的结果”,而不是“用户新说的话”。如果采用字符串拼接,工具结果很容易被当作普通上下文或用户输入,导致模型不清楚这是自己刚才调用的工具的结果,从而处理方式产生偏差。
(3)注意力与因果关系
在多轮对话中,模型对不同消息类型的处理权重各不相同。ToolMessage 在训练阶段被视作“对上一次 tool_calls 的回应”,因此模型会更自然地把注意力集中在:用这些结果来回答用户最初的问题。这样能够维持清晰的因果链条,避免干扰。
```
如果你希望大模型不仅能回答你的问题,还能读取本地的文件,就需要为它绑定上对应的工具。在 `langchain` 中,绑定的方式很简单——直接调用 `model.bindTools` 即可。
接下来,我们来探讨如何定义工具。
## 定义工具
在定义工具时,首先要弄清楚它的“使用说明书”是什么样的。工具本质上是一个函数,既然是函数,就必须接收参数,也就是有入参。因此,你需要明确三件事:名称、描述、入参。
```ja vascript
const obj = {
// 工具的名称
name: 'read_file',
// 对这个工具的人类语言描述
description: '对这个工具的人类语言描述',
// 工具入参
schema: z.object({
filePath: z.string().describe('要读取的文件路径'),
}),
}
```
编写完说明书后,接下来就是工具本身——也就是那个函数。逻辑非常简单:传入文件路径,读取内容并返回。
```ja vascript
// 很简单,就是一个函数,函数中的逻辑如下,实现了读取文件内容并返回
const fun = async ({ filePath }) => {
const content = await fs.readFile(filePath, 'utf-8');
console.log(` [工具调用] read_file("${filePath}") - 成功读取${content.length}字节`);
return `文件内容:\n${content}`;
}
```
最后一步,用 langchain 的 `tool` 函数把上面这两个参数传进去,工具就算定义好了。
```ja vascript
tool(fun, obj)
```
## 调用工具
在前面绑定好工具之后,我们调用了模型的 `invoke` 函数。但请注意,模型并不会直接去执行工具,而是返回当前用户问题可以使用的工具列表。
- 用户问:“你好”。模型返回一个工具空数组——它自己判断,这次请求不需要调用任何工具。
- 用户问:“读取某个文件”。大模型会检索自身,看是否有合适的工具。如果有,就在返回数据中把可用的工具带出来。
数组中的每一个工具都包含:工具的实际入参(模型通过第一次用户的提问识别到的)、工具的唯一 id(每次调用都会随机生成一个新的)、工具的名称。
当模型返回了工具调用数组,我们需要遍历这个数组,依次调用每一个工具函数。注意,调用工具不是使用 `模型的 invoke`,而是 `工具的 invoke`。
```ja vascript
// invoke的参数就是工具的入参,这个入参可以从模型返回的数组中获取
const result = await tool.invoke(toolCall.args);
```
拿到工具的结果之后,用 langchain 的 `new ToolMessage` 来新建一条消息:
```ja vascript
new ToolMessage({
content: toolResults[index],
tool_call_id: toolCall.id,
})
```
最后,将所有消息再次发送给模型,模型输出最终回答。
### Q&A
**Q1:为什么需要 `new ToolMessage`,而不是将工具返回结果直接拼接到前面用户的提问后面?**
**A1:**
(1)像 GPT-4、Claude 这类支持 function calling 的模型,在训练过程中大量接触的对话格式就是这种结构化模式:
```
User → Assistant(tool_calls) → ToolMessage → Assistant(回复)
```
也就是说,模型在训练时学会了:收到带有 tool_call_id 的 ToolMessage = 这是我刚才调用的工具返回的结果,需要借助这些内容来作答。如果改用字符串拼接,将工具结果塞进 HumanMessage 或普通文本中,这种模式在训练数据里非常罕见,模型就没有明确的“如何使用”的经验。
(2)角色与用途清晰
在结构化消息体系里,每一条消息的语义都非常明确:
| 消息类型 | 模型的理解 |
| --- | --- |
| HumanMessage | 用户说了什么、想实现什么 |
| AIMessage + tool_calls | 我决定调用这些工具 |
| ToolMessage | 这些工具返回的结果,是对我上一轮决策的反馈 |
| AIMessage | 基于工具结果给出最终回答 |
模型会将 ToolMessage 视为“我主动调用的工具的结果”,而不是“用户新说的话”。如果采用字符串拼接,工具结果很容易被当作普通上下文或用户输入,导致模型不清楚这是自己刚才调用的工具的结果,从而处理方式产生偏差。
(3)注意力与因果关系
在多轮对话中,模型对不同消息类型的处理权重各不相同。ToolMessage 在训练阶段被视作“对上一次 tool_calls 的回应”,因此模型会更自然地把注意力集中在:用这些结果来回答用户最初的问题。这样能够维持清晰的因果链条,避免干扰。
```来源:https://juejin.cn/post/7626578013837328425
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。
相关推荐
补充同频道和同主题内容,方便继续浏览更多相关内容。
同类最新
继续查看同栏目最近更新的文章。
Claude Token节省十大实用技巧方案
通过编辑而非追加消息、每15-20条消息开新对话、合并问题、利用Projects缓存、预设记忆、关闭附加功能、按任务选择模型、分散时段、避开高峰及开启超额使用,能有效减少上下文重读,节省Token。
硅基流动冲刺Token工厂第一股亏损反更值钱?
硅基流动冲刺港交所“Token工厂第一股”,2025年营收5533万元,净亏损3 45亿元,毛利率-24%。两条业务线分化:公有云服务亏损严重,本地部署毛利率达82 5%。依赖中立第三方定位吸引资本,但面临原厂降价、大厂竞争及供应链风险,估值77亿背后存隐忧。
AI Agent的真正价值在于长在业务流程中
AIAgent需嵌入企业业务流程,而非仅作聊天工具。以零售品类管理为例,通过趋势识别、选品与货架规划,预计可带来2%—5%销售提升及10%P&L改善。设计需模块化、可整合,确保可解释性,重新界定人、AI与工具的关系。
后张雪峰时代大厂抢滩AI志愿填报
AI高考志愿填报工具在大厂推动下普及,能快速整合信息、生成方案,但存在数据幻觉、同质化风险。它无法替代张雪峰式实用主义建议和信誉责任,志愿填报仍需个性化判断与深度信息。
阿里禁用Anthropic全系产品的理性风控决策
阿里自7月10日起全员禁用Anthropic全系产品,因其ClaudeCode被发现存在隐蔽身份识别与隐写标记机制,且Anthropic曾指控阿里进行模型蒸馏。此举源于安全信任崩塌、中美AI博弈加剧,阿里同步换装自研工具Qoder,推动国产AI编码工具替代。
