创建和配置文件夹
创建文件夹
安装对应包:
@langchain/langgraph用于组装代理,拼成图(如名:graph)
@langchain/openai使得agent能够调用OpenAI的大语言模型(LLM)
@langchain/community社区安装包,包含Tavily集成,为agent提供搜索功能
LangGraph与LangChain都是组装agent的编排框架,用于构建、管理和部署长时间运行、有状态的agent。
1
| npm install @langchain/core@^1.1.33 @langchain/langgraph @langchain/openai @langchain/community@^1.1.24
|
LangChain
LangChain的promt模板

- 下载
dotenv插件实现环境变量管理。创建.env文件,放入千问密钥
1 2 3
| OPENAI_API_KEY="sk-xxxx" OPENAI_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1 MODEL_NAME=qwen-coder-turbo
|
- 创建agent问答代码:
其中invoke函数来自Runnable接口,LangChain的doc有说明接口。除了创建的模型对象由该函数调用,Prompt模板也有(可以规定对话主题的模板功能)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import dotenv from 'dotenv'; import { ChatOpenAI } from '@langchain/openai';
dotenv.config();
const model = new ChatOpenAI({ modelName: process.env.MODEL_NAME || "qwen-coder-turbo", apiKey: process.env.OPENAI_API_KEY, configuration: { baseURL: process.env.OPENAI_BASE_URL, }, });
const response = await model.invoke("你的知识库截至什么什么"); console.log(response.content);
|
- 可以看到千问模型还是非常老旧的知识库:

- 创建工具函数
tool:用于在 LangChain 中创建工具的函数。第一个参数传入实现的工具,第二个是参数列表,包含工具名字、工具描述、校验。
bindTools:绑定一个tool数组,将工具列表绑在模型链上,后面用llmWithTools.invoke()时不用每次再传一边tools
SystemMessages:元指令,作为指导输入
HumanMessages:用户输入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
| import dotenv from 'dotenv'; import { ChatOpenAI } from '@langchain/openai'; import { tool } from '@langchain/core/tools'; import { z } from 'zod'; import fs from 'node:fs/promises'; import { SystemMessage, HumanMessage, ToolMessage, type BaseMessage, } from '@langchain/core/messages';
dotenv.config();
const model = new ChatOpenAI({ modelName: process.env.MODEL_NAME || "qwen-coder-turbo", apiKey: process.env.OPENAI_API_KEY, configuration: { baseURL: process.env.OPENAI_BASE_URL, }, });
const readFileTool = tool( async ({ filePath }) => { const content = await fs.readFile(filePath, 'utf-8'); console.log(` [工具调用] read_file("${filePath}") - 成功读取 ${content.length} 字节`); return `文件内容:\n${content}`; }, { name: 'tool_read_file', description: '用此工具来读取文件内容。当用户要求读取文件、查看代码、分析文件内容时,调用此工具。输入文件路径(可以是相对路径或绝对路径)。', schema: z.object({ filePath: z.string().describe('要读取的文件路径'), }), } );
const tools = [ readFileTool ];
const modelWithTools = model.bindTools(tools);
const messages: BaseMessage[] = [ new SystemMessage(`你是一个代码助手,可以使用工具读取文件并解释代码。
工作流程: 1. 用户要求读取文件时,立即调用 read_file 工具 2. 等待工具返回文件内容 3. 基于文件内容进行分析和解释
可用工具: - read_file: 读取文件内容(使用此工具来获取文件内容) `), new HumanMessage('请读取 src/tool-file-read.mts 文件内容并解释代码') ];
let response = await modelWithTools.invoke(messages);
messages.push(response);
while (response.tool_calls && response.tool_calls.length > 0) {
console.log(`\n[检测到 ${response.tool_calls.length} 个工具调用]`);
const toolResults = await Promise.all( response.tool_calls.map(async (tc) => { const structuredTool = tools.find((t) => t.name === tc.name); if (!structuredTool) { return `错误: 找不到工具 ${tc.name}`; }
console.log(` [执行工具] ${tc.name}(${JSON.stringify(tc.args)})`); try { const result = await structuredTool.invoke(tc); return typeof result === "string" ? result : String(result); } catch (error) { const err = error as Error; return `错误: ${err.message}`; } }) );
response.tool_calls.forEach((toolCall, index) => { if (toolResults[index] == null) { throw new Error(`工具结果为空,无法构造 ToolMessage: ${toolCall.name}`); } if (toolCall.id == null) { throw new Error(`工具调用缺少 id,无法构造 ToolMessage: ${toolCall.name}`); } messages.push( new ToolMessage({ content: toolResults[index], tool_call_id: toolCall.id, }) ); });
console.log(messages); response = await modelWithTools.invoke(messages); }
console.log('\n[最终回复]'); console.log(response.content);
|
通过学习可以知道,Langchain知识让我们能够调用LLM提取出用户输入问题参数,返回一个HumanMessage对象,如果用到了tool函数,那么通过创建自定义函数,与LLM模型链绑定,则可以得到tool_calls。通过异步调用tool_calls并将结果提取出content和tool_call_id,将其转换成ToolMessage(可以看到以下代码是toolResults的结果),重新push进messages数组,通过大模型分析是否得到用户想要的结果,并把结果输出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| ToolMessage { "content": "文件内容:\nimport dotenv from 'dotenv';\r\nimport { ChatOpenAI } from '@langchain/openai';\r\n import { tool } from '@langchain/core/tools';\r\nimport { z } from 'zod';\r\nimport fs from 'node:fs/promise s';\r\nimport {\r\n SystemMessage,\r\n HumanMessage,\r\n ToolMessage,\r\n type BaseMessage,\r\n} fro m '@langchain/core/messages';\r\n\r\ndotenv.config();\r\n\r\nconst model = new ChatOpenAI({\r\n modelName: process.env.MODEL_NAME || \"qwen-coder-turbo\",\r\n apiKey: process.env.OPENAI_API_KEY,\r\n configurati on: {\r\n baseURL: process.env.OPENAI_BASE_URL,\r\n },\r\n});\r\n\r\nconst readFileTool = tool(\r\n async ({ filePath }) => {\r\n const content = await fs.readFile(filePath, 'utf-8');\r\n console.l og(` [工具调用] read_file(\"${filePath}\") - 成功读取 ${content.length} 字节`);\r\n return `文件内容:\ \n${content}`;\r\n },\r\n {\r\n name: 'tool_read_file',\r\n description: '用此工具来读取文件内 容。当用户要求读取文件、查看代码、分析文件内容时,调用此工具。输入文件路径(可以是相对路径或绝对路径)。',\r \n schema: z.object({\r\n filePath: z.string().describe('要读取的文件路径'),\r\n }),\r\n }\r\n);\r\n\r\nconst tools = [\r\n readFileTool\r\n];\r\n\r\nconst modelWithTools = model.bindTools(tools );\r\n\r\nconst messages: BaseMessage[] = [\r\n new SystemMessage(`你是一个代码助手,可以使用工具读取文件 并解释代码。\r\n\r\n工作流程:\r\n1. 用户要求读取文件时,立即调用 read_file 工具\r\n2. 等待工具返回文件内容\ r\n3. 基于文件内容进行分析和解释\r\n\r\n可用工具:\r\n- read_file: 读取文件内容(使用此工具来获取文件内容)\ r\n`),\r\n new HumanMessage('请读取 src/tool-file-read.mts 文件内容并解释代码')\r\n];\r\n\r\n\r\nlet respo nse = await modelWithTools.invoke(messages);\r\n// console.log(response);\r\n\r\nmessages.push(response);\r\ n\r\nwhile (response.tool_calls && response.tool_calls.length > 0) {\r\n\r\n console.log(`\\n[检测到 ${res ponse.tool_calls.length} 个工具调用]`);\r\n\r\n // 执行所有工具调用\r\n const toolResults = await Promis e.all(\r\n response.tool_calls.map(async (tc) => {\r\n const structuredTool = tools.find((t) => t.name === tc.name);\r\n if (!structuredTool) {\r\n return `错误: 找不到工具 ${tc.name}` ;\r\n }\r\n\r\n console.log(` [执行工具] ${tc.name}(${JSON.stringify(tc.args)})`);\r\n try {\r\n const result = await structuredTool.invoke(tc);\r\n console.log(result); \r\n return typeof result === \"string\" ? result : String(result);\r\n } catch (error) { \r\n const err = error as Error;\r\n return `错误: ${err.message}`;\r\n }\r\n })\r\n );\r\n\r\n // console.log(toolResults);\r\n\r\n // 将工具结果添加到消息历史\r\n response .tool_calls.forEach((toolCall, index) => {\r\n if (toolResults[index] == null) {\r\n throw new Error(`工具结果为空,无法构造 ToolMessage: ${toolCall.name}`);\r\n }\r\n if (toolCall.id == null) {\r\n throw new Error(`工具调用缺少 id,无法构造 ToolMessage: ${toolCall.name}`);\r\n }\r\n messages.push(\r\n new ToolMessage({\r\n content: toolResults[index],\r\n to ol_call_id: toolCall.id,\r\n })\r\n );\r\n });\r\n\r\n // 再次调用模型,传入工具结果\r\n console.log(messages);\r\n response = await modelWithTools.invoke(messages);\r\n}\r\n\r\nconsole.log('\\n[ 最终回复]');\r\n// console.log(response);\r\n// console.log(response.content);", "name": "tool_read_file", "additional_kwargs": {}, "response_metadata": {}, "tool_call_id": "call_cd44802ac14e45729fd4bc" }
|