书接上文。
一、Tools
Tools 本质上是封装了特定功能的可调用模块,是Agent、Chain或LLM可以用来与世界互动的接口。
具体实现
使用 tool() 函数
tools.ts
import { tool } from '@langchain/core/tools';
import { z } from 'zod';
/**
* 定义加法工具
*/
export const addTwoNumberTool = tool(
async (input: { a: number; b: number }) => {
// 工具的具体执行逻辑
return input.a + input.b;
},
{
name: 'add_two_number',
description: 'two number add',
schema: z.object({
a: z.number().describe('第一个整数'),
b: z.number().describe('第二个整数'),
}),
// 注意:LangChain.js 的 tool 函数配置中通常没有 returnDirect 选项,
// 这种行为通常在 Agent 层面控制,或者通过返回特定格式字符串实现。
},
);
agents.service.ts
import { addTwoNumberTool } from 'src/tools/tools';
async useAddTool() {
console.log(`name = ${addTwoNumberTool.name}`);
console.log(`description = ${addTwoNumberTool.description}`);
console.log(`schema = ${JSON.stringify(addTwoNumberTool.schema, null, 2)}`);
const result = await addTwoNumberTool.invoke({ a: 5, b: 10 });
console.log(result);
}
输出结果:
name = add_two_number
description = two number add
schema = {
"def": {
"type": "object",
"shape": {
"a": {
"def": {
"type": "number",
"checks": []
},
"type": "number",
"minValue": null,
"maxValue": null,
"isInt": false,
"isFinite": true,
"format": null
},
"b": {
"def": {
"type": "number",
"checks": []
},
"type": "number",
"minValue": null,
"maxValue": null,
"isInt": false,
"isFinite": true,
"format": null
}
}
},
"type": "object"
}
15
继承 StructuredTool 类
import { StructuredTool } from '@langchain/core/tools';
import { z } from 'zod';
export class AddTwoNumberTool extends StructuredTool {
name = 'add_two_number';
description = '两数相加';
schema = z.object({
a: z.number().describe('第一个整数'),
b: z.number().describe('第二个整数'),
});
constructor() {
super();
}
async _call(input: { a: number; b: number }): Promise<string> {
// 注意:StructuredTool 通常返回字符串,以便 LLM 处理
return (input.a + input.b).toString();
}
}
// 实例化
export const addTwoNumberTool = new AddTwoNumberTool();
工具调用举例
大模型会决定是否调用工具,并分析出调用哪个工具,但不会自动调用该工具
tools.ts
import { StructuredTool } from '@langchain/core/tools';
import * as fs from 'fs';
import * as path from 'path';
// 自定义 MoveFileTool
export class MoveFileTool extends StructuredTool {
name = 'move_file';
description = '移动文件从源路径到目标路径';
schema = z.object({
sourcePath: z.string().describe('源文件的绝对路径或相对路径'),
destinationPath: z.string().describe('目标文件夹的绝对路径或相对路径'),
});
async _call(input: {
sourcePath: string;
destinationPath: string;
}): Promise<string> {
const source = input.sourcePath;
const destDir = input.destinationPath;
// 确保目标目录存在
if (!fs.existsSync(destDir)) {
fs.mkdirSync(destDir, { recursive: true });
}
const fileName = path.basename(source);
const destPath = path.join(destDir, fileName);
fs.renameSync(source, destPath);
return `文件已成功从 ${source} 移动到 ${destPath}`;
}
}
agents.service.ts
async runMoveFileToolExample() {
// 使用自定义工具
const tools = [new MoveFileTool()];
// 绑定工具到 LLM
// bindTools 是 LangChain.js 中处理函数调用的标准方式
// 它会自动处理 schema 转换和提示词注入
const modelWithTools = this.llm.bindTools(tools);
const messages = [new HumanMessage('将文件 a.txt 移动到桌面文件夹')];
console.log('--- Invoking LLM with Tools ---');
try {
// 调用模型
// 模型会决定是返回文本还是调用工具
const response = await modelWithTools.invoke(messages);
console.log('LLM Response:', response);
// 检查是否有工具调用
if (response.tool_calls && response.tool_calls.length > 0) {
console.log('--- Tool Calls Detected ---');
response.tool_calls.forEach((tc) => {
console.log(`Tool Name: ${tc.name}`);
console.log(`Arguments: ${JSON.stringify(tc.args)}`);
});
// 执行工具并获取结果
// 通常 Agent 框架会自动处理这一步。如果手动处理,需要根据 name 找到对应的工具实例并执行
const toolCall = response.tool_calls[0];
const selectedTool = tools.find((t) => t.name === toolCall.name);
if (selectedTool) {
console.log('--- Executing Tool ---');
const result = await selectedTool.invoke(toolCall.args);
console.log('Tool Execution Result:', result);
}
} else {
console.log('No tool calls, direct response:', response.content);
}
} catch (error) {
console.error('Error during tool invocation:', error);
}
}