当前位置: 首页
AI教程
手把手教你打造一个能干活的AI Agent

手把手教你打造一个能干活的AI Agent

热心网友 时间:2026-07-11
转载

Agent由大模型、记忆、工具和知识库构成,弥补大模型无记忆、无法行动及缺乏私有知识的不足。通过LangChain与DeepSeek实现ReAct循环,LLM自主判断调用工具并处理结果,直至任务完成。

别再手动调 API 了!手把手教你打造一个能真正干活的 AI Agent

如果你也曾有过类似的困惑,那这篇文章正是为你准备的。

别再傻傻调 API 了!手把手教你搓一个能干活的 AI Agent

先搞清楚一个问题:Agent 到底是什么?

很多人觉得 Agent 这个概念高深莫测,其实说白了,你可以把大模型想象成一个脑袋聪明但什么实际工作也做不了的实习生:

  • 脑子好使,但缺乏记忆——上次聊的内容,转身就忘得一干二净。
  • 懂很多理论,但双手是摆设——你让它读个文件,它却给你写一篇教程。
  • 对你的业务场景一无所知——你问它内部 API 怎么用,它敢当场给你编造一套。

所以,Agent 的核心任务其实很简单:给这个聪明但无能的实习生配上趁手的工具、装上持久记忆、补齐背景知识。

Agent = 大模型 + 记忆系统 + 工具集 + 知识库

就这么简单,别被那些花里胡哨的技术名词吓住了。

大模型的三大"致命短板"

来,我们逐一剖析大模型到底在哪些方面力不从心:

1. 它记不住你

大模型本质上是无状态的——每一次对话都像一张崭新的白纸。你上周跟它讨论了两小时的方案,今天它完全不记得。所以你必须自己维护一个 messages 数组,让它看起来好像"记住了"。

从实现角度看也很直接——就是每次把完整的对话历史全部塞进请求中:

// 每次调用都携带完整对话历史 const messages = [ new SystemMessage('你是...'), new HumanMessage('你好'), // 第1轮 new AIMessage('你好!有什么...'), // 第1轮回复 new HumanMessage('帮我读...'), // 第2轮 ]; const response = await model.invoke(messages);

但这会引发一个新问题:对话越长,token 开销就会指数级增长。一个真正成熟的 Agent 需要引入 Memory 模块——借助 Redis、数据库或滑动窗口策略来管理历史记录,绝不能每次都把所有内容原封不动地塞进去。

2. 它干不了实际工作

你跟它说"帮我把这段代码重构一下",它会给你输出一大篇分析——然后呢?然后就没了。它只会纸上谈兵,无法真正执行操作。

3. 它不了解你的私有数据

你公司的内部文档、私有 API、数据库表结构,它一概不知。训练数据里没有包含的内容,它只能靠猜测,猜错了就会胡编乱造。

Agent 的工作流程:一个不断"循环迭代"的过程

Agent 的执行逻辑本质上就是一个循环:

用户抛来一个任务 ↓ LLM 开始思考:我需要做什么? ↓ 是否需要调用工具? ├── 是 → 调用工具 → 获取结果 → 塞回 messages → 继续推理 └── 否 → 直接回复用户,任务结束

这就是经典的 ReAct 模式:Reason(推理)→ Act(行动)→ Observe(观察结果)→ 再次推理。

循环往复,直到任务完成为止。

动手实践!用 LangChain + DeepSeek 打造一个 Agent

技术栈说明:

  • LangChain JS:大模型应用开发框架,兼容多种主流模型
  • DeepSeek:性价比极高的大语言模型
  • Node.js:运行后端逻辑

第一步:跑通基础调用

mkdir hello-langchain && cd hello-langchain pnpm init pnpm add @langchain/core @langchain/openai dotenv zod

然后编写一个最简单的调用示例:

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 提供的 ChatOpenAI 是一个统一接口——想切换模型只需修改两行配置,无需重写业务逻辑。它内部实际上是一个适配层,负责将 LangChain 的消息格式转换成 OpenAI 兼容的 API 格式,发送 HTTP 请求,再把响应结果翻译回来。

第二步:给它装配一个工具

光能对话远远不够,必须让大模型具备执行实际任务的能力。我们先给它装一个"读取文件"的工具。

在 LangChain 中,一个 Tool 由两个核心部分组成:

  1. 功能函数:真正干活的 async 函数
  2. 描述对象:告诉 LLM 这个工具的用途以及需要哪些参数

import { tool } from '@langchain/core/tools'; import { z } from 'zod'; import fs from 'node:fs/promises'; 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('要读取的文件路径'), }), } );

这里有几个技术细节值得深入探讨。

Zod schema 究竟做了什么?

z.object({ filePath: z.string().describe('...') }) 的作用远不止参数校验。当 LLM 接收到这个 schema 时,它会自动将其转换为 OpenAI function calling 所需的 JSON Schema 格式,最终变成这样:

{ "type": "function", "function": { "name": "read_file", "description": "用此工具来读取文件内容...", "parameters": { "type": "object", "properties": { "filePath": { "type": "string", "description": "要读取的文件路径" } }, "required": ["filePath"] } } }

LLM 看到这个数据结构,就会清楚:调用这个工具时必须传入一个 filePath 字符串参数。Schema 本质上就是工具与大模型之间的一份"契约"。

description 为什么如此关键?

LLM 判断"是否应该调用这个工具"的主要依据就是 description。描述写得越清晰准确,LLM 的决策就越靠谱。比如:

  • "读文件" → 太笼统,LLM 不清楚何时该触发
  • "当用户要求读取文件、查看代码、分析文件内容时,调用此工具" → 完整覆盖了使用场景

第三步:LLM 是如何"决定"调用工具的?

这里有一个非常精妙的设计——LLM 并不会硬编码工具的返回值。

当你把工具绑定到模型上时:

const modelWithTools = model.bindTools([readFileTool]);

bindTools 做的事情是:将工具的 JSON Schema 注入到 LLM 的请求参数中(通过 OpenAI 的 tools 字段)。LLM 在推理时就会"知道"自己拥有这些工具可供使用。

当 LLM 判断"我需要调用工具"时,它返回的并不是普通文本,而是这样的数据结构:

{ "tool_calls": [ { "id": "call_abc123", "name": "read_file", "type": "function", "arguments": { "filePath": "src/index.mjs" } } ] }

它坦然承认自己无法直接完成,然后抛出一个工具调用请求。这种"自知之明"正是 Agent 能够正常工作的根基。

另外请注意 id 字段——后续工具执行的结果需要借助这个 id 实现关联,否则 LLM 无法区分哪个结果对应哪次调用。

第四步:Message 的四种形态

在 LangChain 中,对话由四种消息类型构成,理解了它们就能掌握 Agent 的对话结构:

消息类型发送者作用关键字段
SystemMessage开发者(你)定义 AI 的角色定位、能力边界和行为规范无特殊字段
HumanMessage用户用户的输入内容无特殊字段
AIMessageLLMAI 的回复(可能包含 tool_calls)tool_calls 数组
ToolMessageAgent 框架工具执行后的返回结果tool_call_id(必须匹配)

一个完整的 Agent 会话,就是这四种消息的有序组合。随着对话的推进,messages 数组会不断增长:

[ SystemMessage, HumanMessage, AIMessage(tool_calls), ToolMessage, AIMessage(tool_calls), ToolMessage, AIMessage ]

当最后一个 AIMessage 不再包含 tool_calls,就说明 LLM 认为任务已经完成,直接输出了文本结果。

第五步:ReAct 循环——Agent 的核心引擎

这是整个项目最关键的一段代码,也是深入理解 Agent 工作原理的重中之重:

const messages = [ new SystemMessage('你是一个代码助手,可以使用工具读取文件并解释代码。'), new HumanMessage('请读取 src/tool.mjs 文件内容并解释代码'), ]; let response = await modelWithTools.invoke(messages); messages.push(response); // ReAct 循环 while (response.tool_calls && response.tool_calls.length > 0) { console.log(`[检测到 ${response.tool_calls.length} 个工具调用]`); // 并行执行所有工具调用 const toolResults = await Promise.all( response.tool_calls.map(async (toolCall) => { const targetTool = tools.find(t => t.name === toolCall.name); if (!targetTool) return `错误:找不到工具 ${toolCall.name}`; try { return await targetTool.invoke(toolCall.args); } catch (err) { return `错误:${err.message}`; } }) ); // 将工具结果塞回 messages,通过 tool_call_id 建立关联 response.tool_calls.forEach((toolCall, index) => { messages.push( new ToolMessage({ content: toolResults[index], tool_call_id: toolCall.id, tool_call_type: toolCall.type, }) ); }); // 再次调用 LLM,让它根据工具结果决定下一步行动 response = await modelWithTools.invoke(messages); messages.push(response); } // 循环结束,LLM 不再需要调用工具,输出最终结果 console.log(response.content);

几个容易出错的细节:

1. 为什么要使用 Promise.all?

一次任务可能会触发多个工具调用。例如用户说"读取 index.mjs 和 tool.mjs 两个文件",LLM 会返回两个 tool_calls。用 Promise.all 并行执行,效率远高于串行处理。

简单回顾一下 Promise 的三种状态:

  • Pending:等待中
  • Fulfilled(成功):调用 resolve() 触发
  • Rejected(失败):调用 reject() 触发

Promise.all 会等待所有 Promise 全部完成,然后一次性返回结果数组。结果顺序与 Promise 数组的顺序保持一致——即使第一个 Promise 比第二个更晚完成,返回顺序依然是 [result1, result2]

2. tool_call_id 必须正确关联

ToolMessage 中的 tool_call_id 必须与 tool_calls 中的 id 完全一致,否则 LLM 无法判断哪个结果对应哪次调用。这是最容易引发 bug 的地方。

3. 循环的退出条件

当 LLM 返回的 tool_calls 为空或不存在时,循环自然结束。这意味着 LLM 认为任务已经完成,可以直接输出最终文本。

4. messages 数组会持续膨胀

每轮循环都会向 messages 中追加 ToolMessageAIMessage。如果工具返回的内容很长(比如读取了一个大文件),token 开销会迅速飙升。在生产环境中需要实施消息裁剪策略——比如只保留最近 N 轮对话,或者对早期消息进行摘要压缩。

一个容易被忽略的关键点:给用户实时反馈

Agent 执行的任务可能相当耗时——读取大文件、调用远程接口、运行命令行指令。如果你不给用户任何反馈,用户会误以为程序已经卡死而直接关闭。

所以,工具函数中一定要记得添加日志输出:

console.log(`[工具调用] read_file(${filePath}) 成功读取 ${content.length} 字节`);

这个细节看似不起眼,却是决定用户体验的关键要素。Claude Code 的体验为什么那么出色?就是因为它的每一步操作都有清晰的执行反馈。

进阶技巧:用 Node.js 子进程执行 CLI 命令

如果你希望 Agent 能够执行 npm installvite create 这类终端命令,Node.js 主线程是扛不住的(它是单线程模型),这时就需要借助 child_process 创建子进程:

import { spawn } from 'node:child_process'; function execCommand(cmd, args = []) { return new Promise((resolve, reject) => { const child = spawn(cmd, args, { shell: true, stdio: 'pipe' }); let stdout = ''; let stderr = ''; child.stdout.on('data', (data) => { stdout += data; }); child.stderr.on('data', (data) => { stderr += data; }); child.on('close', (code) => { if (code === 0) resolve(stdout); else reject(new Error(stderr || `Exit code ${code}`)); }); }); }

整体架构如下:

主进程(Agent 逻辑) ↓ spawn 创建子进程 子进程(执行 CLI 命令) ↓ IPC 进程间通信 主进程(获取结果,继续循环)

这样一来,Agent 的主循环就不会被 CLI 命令阻塞,可以继续处理其他逻辑任务。

更深入一层:为什么 Agent 采用 ReAct 而非简单的链式调用?

很多人会问:为什么不能直接 prompt → LLM → 结果 一步到位?

原因很简单:现实世界中的任务往往是非线性的。举个例子,用户说"帮我创建一个 React 项目并启动运行"。LLM 的推理过程会是:

1. 需要先创建项目 → 调用 vite create 2. 需要安装依赖 → 调用 pnpm install 3. 需要启动项目 → 调用 pnpm dev 4. 检查启动是否成功 → 可能需要读取日志 5. 如果启动失败 → 调整配置,重新尝试

每一步都可能出现意外,每一步的执行结果都会影响后续的决策。这种"边做边想"的动态模式,是传统的线性 chain 根本无法实现的。

ReAct 的 while 循环天然支持这种动态决策——LLM 每次拿到工具的执行结果后,都可以重新评估当前局势,灵活决定下一步该做什么。

总结:Agent 真的没那么神秘

拆开来看,无非就是这几个核心组件:

模块核心作用一句话概括
LLM推理引擎负责"思考",决定调用什么工具以及如何组合结果
Memory上下文管理负责"记忆",确保多轮对话不丢失信息
Tool能力扩展负责"执行",让模型能完成实际任务
RAG知识补全负责"查询",从私有知识库中补充训练数据的不足
MCP外部接入负责"连接",对接第三方服务和外部数据源

一个能够思考、能够记忆、能够执行、能够查阅资料的增强型大模型,就是一个真正的 Agent。

你不需要掌握什么高深的理论,亲自跑一遍代码,一切都豁然开朗了。

来源:https://juejin.cn/post/7659664463018606644

游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。

同类文章
更多
Hy3+WorkBuddy组合国产顶级Agent附完整提示词

Hy3+WorkBuddy组合国产顶级Agent附完整提示词

五个 Case 跑完,总结一下整体体验。工具使用能力:能不能自己开网页、找信息、标出处。规划能力:能不能把多约束需求拆成可执行步骤。长程执行能力:跨多步任务时会不会丢状态。复杂推理能力:能不能先推导、再写代码、再执行验证。WorkBuddy+Hy3 的表现完全符合预期。趁着 WorkBuddy 里的

时间:2026-07-11 16:50
AI Agent是什么?一文理解大语言模型、记忆、技能、工具、MCP、工作流与上下文

AI Agent是什么?一文理解大语言模型、记忆、技能、工具、MCP、工作流与上下文

智能体并非单一模型,而是由大语言模型、记忆、工具、工作流等模块协同构成的自主系统。它通过理解目标、检索记忆、调用工具、构建上下文、推理决策,并基于反馈闭环持续迭代,最终自主完成复杂任务。

时间:2026-07-11 16:50
DeepSeek决定自研芯片打造人工智能全新算力芯脏

DeepSeek决定自研芯片打造人工智能全新算力芯脏

被“逼”出来的第三条路。 一直以模型技术见长的DeepSeek,这次在算力供应链上迈出了让所有人侧目的一步。 2026年7月7日,路透社援引三位知情人士消息,DeepSeek正在悄然开发自有AI芯片。值得玩味的是,这颗芯片的定位非常精准——专攻推理,不涉足训练。消息人士称,项目大约启动于一年前,目前

时间:2026-07-11 16:28
开闭源模型实测:横评Sonnet 5与四大国产开源模型编程能力

开闭源模型实测:横评Sonnet 5与四大国产开源模型编程能力

今天,我们来深入实测一组闭源与开源模型的编程能力。Sonnet 系列上次更新停留在今年2月,随着Fable 5的解禁,Sonnet 5终于迎来升级。根据Claude官方数据,其整体表现优于Oups 4 6和4 7,略逊于Oups 4 8,但实际运行时,Sonnet 5的调用成本甚至比Opus更高。

时间:2026-07-11 16:28
SAM3与FastAPI实战:搭建智能图像标注工具

SAM3与FastAPI实战:搭建智能图像标注工具

基于SAM3与FastAPI构建智能图像标注工具,解决传统标注耗时痛点。SAM3支持开放词汇分割,输入文本即可自动分割。后端封装SAM3Service实现懒加载、特征缓存及多种分割模式,掩码可视化由后端生成PNGbase64。前端采用react-konva实现画布交互,API遵循RESTful风格,支持批量标注与导出。

时间:2026-07-11 14:38
热门专题
更多
刀塔传奇破解版无限钻石下载大全 刀塔传奇破解版无限钻石下载大全
洛克王国正式正版手游下载安装大全 洛克王国正式正版手游下载安装大全
思美人手游下载专区 思美人手游下载专区
好玩的阿拉德之怒游戏下载合集 好玩的阿拉德之怒游戏下载合集
不思议迷宫手游下载合集 不思议迷宫手游下载合集
百宝袋汉化组游戏最新合集 百宝袋汉化组游戏最新合集
jsk游戏合集30款游戏大全 jsk游戏合集30款游戏大全
宾果消消消原版下载大全 宾果消消消原版下载大全
  • 热门数据榜