Agent 开发实战:LangChain工具链与 Promise.all并行优化

0 阅读6分钟

摘要:LLM天然无状态不会调工具不懂内部文档。Agent给它加上Memory、Tool、RAG、MCP和Skills五层外挂,让它从聊天机器人进化为能自主执行任务的智能体。本文用LangChain手写readFileTool并注册到模型,再以Promise.all并行优化多工具调用性能,走通Agent开发全流程。


目录

  1. Agent = LLM + 五层外挂
  2. LangChain:统一的大模型开发框架
  3. 实战:手写一个 Agent 工具并注册到模型
  4. Promise.all:并行执行多工具的秘籍
  5. 总结

一、Agent = LLM + 五层外挂

大语言模型本身只是一个"预测下一个 Token"的概率引擎——它没有记忆、不会打开网页、读不到内部文档、不知道今天的新闻、也不能操作文件。直接用 LLM 接口开发的 Chatbot,本质是一个无状态的对话接口。

Agent(智能体)要做的事情,就是给 LLM 装上五层外挂

问题外挂作用
LLM 不记得上周的对话Memory 模块数据库/前端存储/Redis 管理记忆
LLM 不能操作网页和文件Tool Use 模块让 LLM 调用外部函数和 API
LLM 不懂公司内部文档RAG 模块检索内部知识库,注入 Prompt
LLM 不知道最新资讯MCP / 第三方 Tool接入外部工具协议获取实时数据
LLM 不能自动化复杂任务Skills 技能将多步流程封装为可复用的能力

aa0d76fb6a792a3a09ab047ac0c28381.png

Agent = LLM + Memory + Tool + RAG + MCP + Skills

Claude Code、Codex 是 Coding Agent;Manus 等产品是自动化任务 Agent。它们的共同架构是:用户提一个复杂任务 → LLM 规划/推理 → 决定调用哪些外挂 → 执行 → 返回结果。


二、LangChain:统一的大模型开发框架

LangChain 是一个比 OpenAI SDK 诞生更早的 LLM 开发框架,核心优势是统一且兼容各家大模型。无论底层是 DeepSeek、GPT 还是其他模型,上层代码几乎不用改。

import { ChatOpenAI } from '@langchain/openai';
import 'dotenv/config';

const model = new ChatOpenAI({
    modelName: 'deepseek-v4-flash',
    apiKey: process.env.DEEPSEEK_API_KEY,
    configuration: {
        baseURL: 'https://api.deepseek.com/v1',
    },
});

const response = await model.invoke('棍王杯台球比赛应该设什么奖励?');
console.log(response.content);

LangChain 的技术栈通常搭配:

层级技术职责
后端NestJS业务逻辑、路由、认证
单 AgentLangChainLLM + Tool + Memory 的组合编排
多 AgentLangGraph多个 Agent 之间的协作与状态流转

三、实战:手写一个 Agent 工具并注册到模型

在 Agent 体系中,工具就是函数 + 描述 + Schema 约束。LangChain 通过 @langchain/core/toolstool() 方法来定义工具,用 zod 库做参数校验。

定义读文件工具

import { tool } from '@langchain/core/tools';
import fs from 'node:fs/promises';
import { z } from 'zod';

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('要读取的文件路径')
        })
    }
);

一个工具由两部分组成:

部分内容说明
处理函数async ({ filePath }) => { ... }异步执行真实任务,返回结果
描述对象name + description + schemaLLM 判断何时调用、参数该怎么传的依据

description 的质量直接决定 LLM 能否在正确时机选中这个工具——模糊的描述会导致漏调用或错调用。

注册工具并发送任务

import { ChatOpenAI } from '@langchain/openai';
import { HumanMessage, SystemMessage } from '@langchain/core/messages';

const model = new ChatOpenAI({
    modelName: 'deepseek-v4-flash',
    apiKey: process.env.DEEPSEEK_API_KEY,
    temperature: 0,
    configuration: { baseURL: 'https://api.deepseek.com/v1' },
});

const tools = [readFileTool];
const modelWithTools = model.bindTools(tools);  // 关键:工具绑定到模型

const messages = [
    new SystemMessage(`
        你是一个代码助手,可以使用工具读取文件并解释代码。
        工作流程:
        1. 用户要求读取文件时,立即调用 read_file 工具。
        2. 等待工具返回文件内容。
        3. 基于文件内容进行分析和解释。
    `),
    new HumanMessage('请读取代码文件内容并解释'),
];

let response = await modelWithTools.invoke(messages);
messages.push(response);  // 将 LLM 的 tool_calls 响应存入历史

工具调用流程

HumanMessage → LLM 推理 → 识别到需调 read_file → 生成 tool_calls →
→ Runtime 执行 readFileTool → 结果注入消息历史 → 再次调用 LLM → 最终回复

LangChain 的消息类型体系清晰地区分了不同角色的消息:

消息类型角色用途
HumanMessageuser用户的原始提问
SystemMessagesystem设定 Agent 行为规则和工具使用指引
AIMessageassistantLLM 的回复(可能包含 tool_calls)
ToolMessagetool工具执行后的返回结果

Agent 任务可能复杂且耗时——如果用户太久没看到反馈就可能退出。工具函数中 console.log 打印执行进度是一个好习惯。


四、Promise.all:并行执行多工具的秘籍

真实场景中,LLM 可能同时需要调用多个独立工具——查天气和拉推文不需要互相等待。串行调用会让总耗时等于各工具耗时之和,而并行调用才是高性能 Agent 的关键。

function getWeather() {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve({ temp: 38, conditions: 'Sunny with Clouds' });
        }, 2000);
    });
}

function getTweets() {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(['I like cake', 'BBQ is good too!']);
        }, 500);
    });
}

串行写法 — 总耗时 ≈ 2000 + 500 = 2500ms:

const weatherData = await getWeather();   // 等 2000ms
const tweetsData = await getTweets();     // 再等 500ms

并行写法 — 总耗时 ≈ max(2000, 500) = 2000ms:

const [weatherData, tweetsData] = await Promise.all([
    getWeather(),
    getTweets()
]);

Promise 三态回顾

Promise 是 ES6 提供的异步语法,有三种互斥的状态:

Pending ──→ Fulfilled (resolve 调用)
   │
   └──→ Rejected  (reject 调用)

一旦从 Pending 变为 Fulfilled 或 Rejected,状态就锁死了——不可二次变更。

Promise.all([promise数组]) —— 并行执行数组内所有 Promise,等待全部 resolve 后返回结果数组,结果顺序与 Promise 数组顺序一致。如果有任意一个 reject,整个 Promise.all 立即 reject。

在 LangChain 的 Agent 中,当 LLM 的 tool_calls 返回了多个无关工具调用时,应该用 Promise.all 并发执行它们,而不是用 for...of 串行 await。这是 Agent 性能优化的第一原则。


五、总结

  1. Agent = LLM + 外挂:Memory 记东西,Tool 做事情,RAG 查文档,MCP 接入外部工具,Skills 封装能力。Claude Code 就是 LLM + Tool(fs + cli) 的产物。
  2. LangChain 统一模型层ChatOpenAI 兼容各家 LLM,tool() + zod 定义工具,model.bindTools() 完成注册。
  3. 工具两部分:处理函数(执行任务)+ 描述对象(name / description / schema),description 决定 LLM 能否选中它。
  4. 消息类型体系HumanMessage / SystemMessage / AIMessage / ToolMessage 各司其职,构造完整调用上下文。
  5. Promise.all 并行:多工具互不依赖时并发执行,总耗时等于最慢那个而非所有之和。这是 Agent 性能的底层优化。

理解 Agent 的核心一句话:它不是一个更聪明的 LLM,而是一个被 Memory、Tool、RAG 武装起来的 LLM。


—— 一个好 Agent,是能记住、会动手、懂查找的 LLM。