发布时间:2026/9/7 9:34:11
TypeScript手写Agent:100行代码实现核心循环与工具调用 这次我们直接写代码。不是讲概念而是用 TypeScript 从零手写一个可运行的通用 Agent把 Agent 的循环、工具调用、记忆上下文、任务编排这些核心机制全部拆开。代码量不大核心 Agent 主体大概 100 行左右适合想搞懂 Agent 原理、准备 Agent 面试、或者想脱离框架自己掌控推理流程的开发者。现在市面上的 Agent 框架很多有重型的也有轻量的但如果你只是想知道“Agent 到底是怎么跑起来的”框架反而是干扰项。它本质上就是把用户的输入交给大模型大模型决定是直接回答还是调用工具然后把工具结果回填给模型模型继续推理直到不再请求工具为止。这个循环理解了Agent 的大门就打开了。这篇文章我会按照完整工程的方式带大家走一遍从 TypeScript 工程初始化开始写出 LLM 抽象层、Tool 定义、Agent 主循环再补上记忆裁剪、Harness 任务管理、HTTP API 接口和批量任务队列。全程使用 TypeScript能直接用 openai 兼容接口对接主流模型服务也能通过本地服务的 OpenAI 兼容端点接入本地模型。读完你会得到一套属于自己的最小 Agent 模板后续加工具、加记忆、加 Skill 都很容易。1. 核心能力速览能力项说明实现语言TypeScript约 100 行核心 Agent 循环依赖范围仅 openai SDK 少量 Node 内置模块模型兼容OpenAI 格式接口的模型服务可通过 baseURL 切换核心能力工具调用Tool Calling、多轮推理、记忆窗口裁剪、批量任务Harness 能力循环控制、工具注册、超时与停止、运行日志Skill 能力提示词 工具组合可插拔加载API 接口提供 HTTP 服务用于集成到其他系统批量任务支持并发控制、失败重试、结果聚合硬件要求无特殊要求普通开发机能运行大模型推理在远端或本地服务完成适用人群前端开发者、Node.js 后端、Agent 框架学习者和面试准备者典型场景客服问答、数据分析助手、自动化脚本调用、企业知识库 Agent 原型从这张表可以看到它其实是一个“轻量级 Agent 开发底座”不是重框架。你在它上面可以自己实现记忆向量化、工具安全策略、多人协作调度这些复杂能力。2. 适用场景与使用边界2.1 适合谁用这个手写 Agent 最适合三类人第一类是正在准备 Agent 相关面试的开发者。面试官问“Agent 的 ReAct 循环是什么”“工具调用是怎么实现的”你可以直接把代码画给他看比背概念有说服力得多。第二类是前端或 Node.js 全栈工程师。TypeScript 是主场语言不用为了 Agent 去学 Python也不用被 LangChain 这类重框架的抽象概念绕晕。自己封装 Agent完全可控。第三类是公司内部做 AI 应用原型验证的团队。不想一上来就引入重型 Agent 框架希望快速验证“大模型 工具调用 业务 API”的组合效果这个模板可以直接改。2.2 能解决什么问题它能解决“只会调 API不会让模型真正用工具”的问题。比如你要让模型查询订单状态、计算折扣、调用内部搜索接口手写 Agent 可以让模型自主决定调哪个工具、什么时候调。它还能解决“多轮对话没有状态管理”的问题。Agent 内部维护消息列表并且可以通过上下文裁剪控制 token 消耗。2.3 不适合什么场景它不适合需要复杂规划、多智能体协作、图编排的生产级项目。如果你的场景涉及多个 Agent 互相通信、复杂的条件分支、人工审批流建议在框架层面设计抽象而不是在这个 100 行代码里硬塞。它也不适合实时性要求极高的场景。每个 Agent 循环都要和大模型交互一次任务可能触发多次 HTTP 请求延迟由模型端决定。2.4 安全与合规边界这里必须强调边界。Agent 能调用工具意味着它可能访问内部 API、读取文件、发起网络请求。在生产环境使用前你要明确以下几点调用的模型服务和 API Key 必须通过环境变量注入不能硬编码到代码仓库。工具需要做白名单控制不允许 Agent 调用未注册的内部接口。如果工具涉及外部 HTTP 请求要注意 SSRF 风险对目标地址做限制。如果 Agent 用于客服、创作、分析等场景输出内容需要人工复核避免错误信息扩散。涉及用户隐私、版权素材时必须获得合法授权。3. 环境准备与工程初始化3.1 前置条件建议环境如下Node.js 18 或更高版本pnpm 或 npm 包管理器TypeScript 5.x一个 OpenAI 兼容的模型服务地址和 API Key可选本地模型服务用于离线测试磁盘空间不用太多工程本身不到 10MB。这个项目不依赖 GPU也不需要装 CUDA 或 PyTorch。3.2 初始化 TypeScript 工程先创建一个项目目录并初始化mkdir ts-agent cd ts-agent npm init -y安装依赖npm install typescript openai tsx types/node express npm install -D types/express提示一下tsx是用来直接运行 TypeScript 文件的开发阶段非常方便。实际部署时可以用tsc编译到dist目录。创建tsconfig.json{ compilerOptions: { target: ES2022, module: NodeNext, moduleResolution: NodeNext, outDir: ./dist, rootDir: ./src, strict: true, esModuleInterop: true, skipLibCheck: true, forceConsistentCasingInFileNames: true }, include: [src] }这里把moduleResolution设为NodeNext是为了让 openai 这类 ESM 包正常解析。如果你用的是 CommonJS 风格遇到ERR_REQUIRE_ESM错误时要重点检查这里。3.3 准备环境变量在项目根目录创建.env文件OPENAI_API_KEY你的密钥 OPENAI_BASE_URLhttps://api.openai.com/v1 OPENAI_MODELgpt-4o-mini注意把.env加入.gitignoreecho .env .gitignore4. 手写 Agent 核心约 100 行的背后4.1 消息与工具的数据结构Agent 的循环本质上是在维护一个Message[]数组。每个消息有角色和内容工具调用结果也是消息的一种。我们先定义基础类型。// src/types.ts export type Role system | user | assistant | tool; export interface ToolCall { id: string; name: string; arguments: string; } export interface Message { role: Role; content: string; toolCalls?: ToolCall[]; toolCallId?: string; name?: string; }然后定义工具的接口。一个工具必须包含名称、描述、参数 JSON Schema 和执行函数。// src/tool.ts export interface ToolArgs Recordstring, unknown { name: string; description: string; parameters: Recordstring, unknown; execute(args: Args): Promisestring; }这里的关键是parameters字段。它会透传给大模型让模型学会根据 JSON Schema 生成正确的参数。4.2 LLM 抽象层为了让 Agent 支持不同模型服务我们定义一个LLMProvider接口。这样后面可以换任何兼容 OpenAI 格式的服务。// src/llm.ts import OpenAI from openai; export interface LLMResult { content: string; toolCalls: ToolCall[]; } export interface LLMProvider { chat(messages: Message[], tools: Tool[]): PromiseLLMResult; } export class OpenAIProvider implements LLMProvider { private client: OpenAI; private model: string; constructor() { this.client new OpenAI({ apiKey: process.env.OPENAI_API_KEY, baseURL: process.env.OPENAI_BASE_URL || undefined, }); this.model process.env.OPENAI_MODEL || gpt-4o-mini; } async chat(messages: Message[], tools: Tool[]): PromiseLLMResult { const response await this.client.chat.completions.create({ model: this.model, messages, tools: tools.map((tool) ({ type: function, function: { name: tool.name, description: tool.description, parameters: tool.parameters, }, })), }); const message response.choices[0].message; const toolCalls message.tool_calls?.map((call) ({ id: call.id, name: call.function.name || , arguments: call.function.arguments || {}, })) || []; return { content: message.content || , toolCalls }; } }注意message.tool_calls是 OpenAI SDK 返回的结构不同版本可能字段名略有差异。如果字段为空就说明模型这次没有调用工具Agent 循环可以结束。4.3 Agent 主循环这是最核心的部分也是“100 行”的底气。// src/agent.ts import { Message, ToolCall } from ./types.js; import { Tool } from ./tool.js; import { LLMProvider } from ./llm.js; export interface AgentOptions { systemPrompt?: string; maxIterations?: number; memoryWindow?: number; } export class Agent { private messages: Message[] []; private toolMap new Mapstring, Tool(); private maxIterations: number; private memoryWindow: number; private systemPrompt: string; constructor(private llm: LLMProvider, options: AgentOptions {}) { this.systemPrompt options.systemPrompt || DEFAULT_SYSTEM_PROMPT; this.maxIterations options.maxIterations ?? 10; this.memoryWindow options.memoryWindow ?? 40; } use(tool: Tool) { this.toolMap.set(tool.name, tool); return this; } reset() { this.messages []; } async run(userInput: string): Promisestring { this.messages.push({ role: user, content: userInput }); for (let i 0; i this.maxIterations; i) { const trimmed this.applyMemoryWindow(); const result await this.llm.chat(trimmed, this.tools()); this.messages.push({ role: assistant, content: result.content, toolCalls: result.toolCalls, }); if (!result.toolCalls || result.toolCalls.length 0) { return result.content; } for (const call of result.toolCalls) { const observation this.executeTool(call); this.messages.push({ role: tool, toolCallId: call.id, name: call.name, content: observation, }); } } throw new Error(Agent 达到最大迭代次数 ${this.maxIterations}未能结束); } private tools(): Tool[] { return Array.from(this.toolMap.values()); } private executeTool(call: ToolCall): string { const tool this.toolMap.get(call.name); if (!tool) { return 错误工具 ${call.name} 不存在; } try { const args JSON.parse(call.arguments || {}); const promise tool.execute(args); return Promise.resolve(promise); } catch (error) { return 错误工具执行失败 ${(error as Error).message}; } } private applyMemoryWindow(): Message[] { const systemCount this.messages[0]?.role system ? 1 : 0; const start Math.max(systemCount, this.messages.length - this.memoryWindow); return this.messages.slice(start); } }这段代码的核心逻辑总结下来就是把用户输入追加到消息列表。调用模型传入消息和工具列表。模型如果返回toolCalls就逐个执行工具把工具结果作为role: tool的消息继续塞回去。模型如果没有toolCalls就返回最终文本循环结束。加上maxIterations防止无限循环。这样 Agent 就有了“思考 → 行动 → 观察 → 再思考”的完整闭环。4.4 系统提示词设计默认的系统提示词对 Agent 行为有直接影响。// src/agent.ts const DEFAULT_SYSTEM_PROMPT 你是一个通用智能体。你可以根据用户的问题决定是否需要调用工具。 如果工具可以帮你得到更准确的答案你应该先调用工具然后根据工具结果回答。 如果工具调用失败你需要根据错误信息调整策略或者向用户说明当前无法完成该任务。 请始终用中文回答。 ;这段提示词可以根据业务场景修改。比如做客服 Agent可以加上“态度友好不承诺无法实现的服务”做数据分析 Agent可以加上“所有结论必须给出数据来源”。5. 给 Agent 加上记忆与上下文管理5.1 简单记忆窗口上面的代码里已经有了memoryWindow参数。它的作用是当消息列表超过一定数量时丢弃最旧的非系统消息。这样可以避免对话越长token 越多的问题。默认值设成 40 是一个比较稳妥的选择。太小的窗口会让模型丢失历史太大的窗口会增加 token 消耗而且模型可能被无关上下文干扰。5.2 Token 估算与裁剪消息数量不代表 token 数量。更严谨的做法是按字符数或 token 数裁剪。下面给一个简化的估算方案// src/memory.ts export function estimateTokens(text: string): number { // 粗略估算中文约 1 字符 1 token英文约 4 字符 1 token return Math.ceil(text.length / 1.5); } export function trimMessages(messages: Message[], maxTokens 8000): Message[] { let total 0; const kept: Message[] []; const indexed messages.map((m, index) ({ m, index })); for (let i indexed.length - 1; i 0; i--) { const item indexed[i]; const tokens estimateTokens(item.m.content); total tokens; if (total maxTokens) break; kept.push(item.m); } return kept.reverse(); }实际生产中可以把这个估算函数替换成 tokenizer 或直接调用模型服务的计数接口。5.3 总结式记忆如果任务真的需要长时间记忆更合适的做法是“总结 保留最近窗口”。在 Agent 每次结束后把当前历史消息发给模型生成摘要之后把这摘要作为 system 的一部分。async function summarize(messages: Message[], llm: LLMProvider): Promisestring { const text messages.map((m) ${m.role}: ${m.content}).join(\n); const res await llm.chat([ { role: system, content: 请用 200 字以内总结这段对话的关键信息保留用户偏好、已完成任务、重要数据。 }, { role: user, content: text }, ], []); return res.content; }这种方案适合做“记忆 Agent”的起点后面升级成向量库存储时替换这个函数就行。6. 理解 Harness 与 Skill从循环到任务编排6.1 Harness 是执行容器如果你去看 Agent 相关的资料会发现一个高频词汇叫 Harness。它和 Agent 的关系很多人搞不清楚。简单理解Agent 是推理循环本身Harness 是控制 Agent 运行的外壳。Harness 通常负责这些事维护最大迭代次数和超时时间统一注册和暴露工具给 Agent处理运行中的日志、事件回调在出现异常时停止 Agent 运行管理多轮任务的会话状态下面的AgentRunner就是一个轻量 Harness 实现。// src/harness.ts import { Agent } from ./agent.js; import { Tool } from ./tool.js; export interface RunEvent { type: start | tool_call | tool_result | finish | error; message?: string; toolName?: string; data?: unknown; } export class AgentRunner { constructor(private agent: Agent) {} private listeners: Array(event: RunEvent) void []; on(listener: (event: RunEvent) void) { this.listeners.push(listener); } async run(input: string) { this.emit({ type: start, message: input }); try { const result await this.agent.run(input); this.emit({ type: finish, message: result }); return result; } catch (error) { this.emit({ type: error, message: (error as Error).message }); throw error; } } private emit(event: RunEvent) { this.listeners.forEach((listener) listener(event)); } }这样外部系统不用直接操作 Agent 内部的消息队列而是通过 Harness 的事件机制感知 Agent 的每一步方便对接日志系统、监控面板。6.2 Skill 是可复用能力包Skill 的概念也很简单它是一组“提示词 工具”的组合。比如你有一个“数据分析 Skill”它会自动注入“你是一名数据分析师所有回答都要基于数据表格”的提示词并注册查询数据库的工具。// src/skill.ts import { Agent } from ./agent.js; import { Tool } from ./tool.js; export interface Skill { name: string; instructions: string; tools: Tool[]; } export function applySkill(agent: Agent, skill: Skill): Agent { agent.use(...skill.tools); const runtime agent as unknown as { systemPrompt: string }; runtime.systemPrompt [runtime.systemPrompt, skill.instructions].join(\n); return agent; }通过applySkill我们可以把一个通用 Agent 快速转成“客服 Agent”“数据分析 Agent”“内容创作 Agent”而不需要修改主循环代码。6.3 设计原则从这段你可以发现手写 Agent 并不代表所有功能都在一个文件里堆砌。Agent 主循环保持通用工具通过注册机制扩展Skill 是更高层的组装单元Harness 负责运行期控制。分好这四层后面无论接入多复杂的业务系统思路都是清晰的。7. 实现几个工具并跑通第一个真实任务7.1 时间工具// src/tools/currentTime.ts import { Tool } from ../tool.js; export const currentTimeTool: Tool{ timezone?: string } { name: current_time, description: 获取当前日期和时间。参数 timezone 可选例如 Asia/Shanghai。, parameters: { type: object, properties: { timezone: { type: string }, }, }, async execute(args) { const now new Date(); return 当前时间是 ${now.toLocaleString(zh-CN, { timeZone: args.timezone || Asia/Shanghai })}; }, };7.2 计算器工具// src/tools/calculator.ts import { Tool } from ../tool.js; export const calculatorTool: Tool{ expression: string } { name: calculator, description: 计算数学表达式例如 (1 2) * 3 - 4/2。, parameters: { type: object, properties: { expression: { type: string }, }, required: [expression], }, async execute(args) { const result Function(use strict; return (${args.expression}))(); return 计算结果${result}; }, };这里使用Function构造器执行表达式仅适合测试。生产环境建议使用mathjs这类安全表达式解析库避免任意代码执行风险。7.3 通用 HTTP 请求工具这个工具可以让 Agent 调用外部 API。要注意限制访问范围避免 SSRF。// src/tools/httpRequest.ts import { Tool } from ../tool.js; export const httpRequestTool: Tool{ url: string; method?: string } { name: http_request, description: 发起 HTTP GET 请求从指定 URL 获取 JSON 数据。, parameters: { type: object, properties: { url: { type: string }, method: { type: string, enum: [GET], default: GET }, }, required: [url], }, async execute(args) { const res await fetch(args.url, { method: args.method || GET }); const text await res.text(); return text.slice(0, 3000); }, };实际业务中你应该把 URL 限制在白名单域名内并禁止访问内网 IP 段。7.4 注册工具并运行创建入口文件src/chat.ts// src/chat.ts import dotenv/config; import { Agent, AgentRunner } from ./agent.js; import { OpenAIProvider } from ./llm.js; import { currentTimeTool } from ./tools/currentTime.js; import { calculatorTool } from ./tools/calculator.js; import { httpRequestTool } from ./tools/httpRequest.js; const llm new OpenAIProvider(); const agent new Agent(llm, { systemPrompt: 你是一个通用智能体请根据问题调用合适的工具最后用中文回答。, maxIterations: 5, memoryWindow: 20, }); agent.use(currentTimeTool); agent.use(calculatorTool); agent.use(httpRequestTool); const runner new AgentRunner(agent); runner.on((event) { console.log([${event.type}], event.message || event.toolName || ); }); const prompt process.argv[2] || 235 19 等于多少; const answer await runner.run(prompt); console.log(\n最终回答, answer);运行命令tsx src/chat.ts 现在几点了如果一切正常你会看到控制台打印tool_call和tool_result事件然后输出最终回答。如果模型选择直接回答不调用工具也不会报错Agent 会自动返回文本结果。7.5 判断任务是否成功判断标准有三个。第一Agent 能根据问题选择正确的工具第二工具结果能正确回填到模型上下文第三最终回答不对用户造成误导。比如问“235 19 等于多少”模型应该先调用calculator拿到 254 之后再回答。7.6 可能的失败点如果模型始终不调用工具优先检查系统提示词里是否明确“你可以使用工具”。另外工具描述写得越具体模型越容易在需要时触发调用。如果工具参数报错检查 JSON Schema 是否正确。模型生成的arguments是字符串必须经过JSON.parse再传给 execute 函数格式错误时要捕获异常并返回可读的错误信息。8. 接口 API 与批量任务8.1 启动 HTTP 服务为了让 Agent 能接入其他系统我们提供一个基于 Express 的 HTTP 服务。// src/server.ts import dotenv/config; import express from express; import { Agent, AgentRunner } from ./agent.js; import { OpenAIProvider } from ./llm.js; import { currentTimeTool } from ./tools/currentTime.js; import { calculatorTool } from ./tools/calculator.js; import { httpRequestTool } from ./tools/httpRequest.js; const app express(); app.use(express.json()); const llm new OpenAIProvider(); const agent new Agent(llm, { maxIterations: 5 }); agent.use(currentTimeTool); agent.use(calculatorTool); agent.use(httpRequestTool); const runner new AgentRunner(agent); app.post(/api/chat, async (req, res) { const { prompt } req.body || {}; if (!prompt) { return res.status(400).json({ error: 缺少 prompt 参数 }); } try { const answer await runner.run(prompt); res.json({ answer }); } catch (error) { res.status(500).json({ error: (error as Error).message }); } }); app.get(/health, (_req, res) { res.json({ status: ok }); }); const port Number(process.env.PORT || 3000); app.listen(port, () { console.log(Agent HTTP 服务启动http://127.0.0.1:${port}); });启动命令tsx src/server.ts用 curl 测试curl -X POST http://127.0.0.1:3000/api/chat \ -H Content-Type: application/json \ -d {prompt:187 23 等于多少}预期返回 JSON{ answer: 187 23 等于 210。 }服务启动后可以用浏览器直接访问http://127.0.0.1:3000/health检查服务状态。如果端口被占用修改环境变量PORT即可。8.2 批量任务与并发控制批量任务场景下我们要避免一次性把所有任务都打到模型接口上否则很容易触发限流。这里实现一个简单的并发控制。// src/batch.ts import dotenv/config; import { Agent } from ./agent.js; import { OpenAIProvider } from ./llm.js; interface BatchItem { id: string; prompt: string; } interface BatchResult { id: string; status: ok | error; answer?: string; error?: string; } async function runBatch(items: BatchItem[], concurrency 2): PromiseBatchResult[] { const llm new OpenAIProvider(); const agent new Agent(llm, { maxIterations: 5 }); const results: BatchResult[] []; let index 0; const worker async () { while (index items.length) { const item items[index]; index; try { const answer await agent.run(item.prompt); results.push({ id: item.id, status: ok, answer }); } catch (error) { results.push({ id: item.id, status: error, error: (error as Error).message }); } } }; const workers Array.from({ length: Math.min(concurrency, items.length) }, () worker()); await Promise.all(workers); return results.sort((a, b) a.id.localeCompare(b.id)); } const testItems: BatchItem[] [ { id: 1, prompt: 今天星期几 }, { id: 2, prompt: 计算 12 * 12 }, { id: 3, prompt: 计算 99 1 }, { id: 4, prompt: 现在几点了 }, ]; const results await runBatch(testItems, 2); console.log(JSON.stringify(results, null, 2));运行tsx src/batch.ts这里每次agent.run都是独立会话。如果你希望批量任务共享同一个 Agent 的上下文需要调整 Agent 的reset()策略并考虑消息历史的裁剪问题。8.3 失败重试策略批量任务中经常遇到模型接口 429 限流或 5xx 错误。建议在runBatch的捕获逻辑里增加重试机制。async function runWithRetry(fn: () Promisestring, retries 3): Promisestring { for (let i 0; i retries; i) { try { return await fn(); } catch (error) { if (i retries - 1) throw error; const delay 1000 * Math.pow(2, i); await new Promise((resolve) setTimeout(resolve, delay)); } } throw new Error(重试失败); }重试时要注意接口是否幂等。对于 Agent 场景因为多轮工具调用可能产生副作用所以更稳妥的设计是在工具层保证幂等或者在错误后重置会话重新执行。9. 资源占用与性能观察9.1 本地资源占用手写 Agent 本身是纯 TypeScript 代码不涉及 GPU 计算和模型推理。进程启动后内存占用主要是 Node.js 运行时和 openai 依赖通常在几十 MB 到一百多 MB 之间具体取决于消息列表长度和工具注册数量。真正耗时的环节是模型 API 请求。一轮 Agent 循环至少一次请求工具调用越多请求次数越多。比如一个需要调用两次工具的任务可能发生三次模型请求第一次决定调用工具第二次处理工具结果并决定再调一个工具第三次返回最终答案。9.2 如何观察性能要观察 Agent 性能可以从三个维度入手。第一是请求耗时。在 Harness 里打印每次模型调用的耗时const start Date.now(); const result await this.llm.chat(trimmed, this.tools()); console.log(模型调用耗时 ${Date.now() - start}ms);第二是 token 消耗。在 LLM Provider 里打印 response 的 usage 字段。console.log(usage: prompt_tokens${response.usage?.prompt_tokens}, completion_tokens${response.usage?.completion_tokens});第三是工具调用成功率。记录每次工具调用是否成功。如果工具频繁失败说明工具描述或参数 Schema 设计有问题。9.3 优化思路如果觉得 Agent 响应太慢先看是不是工具调用次数过多。很多问题其实不需要多次工具调用改善系统提示词让模型一次请求返回多个工具调用能明显降低耗时。显存、GPU 相关问题在这个项目里不存在因为推理都是在模型服务端完成。如果你接入的是本地模型服务瓶颈主要在本地模型的推理速度和显存占用属于模型服务侧的调优范围和这个 TypeScript Agent 没有直接关系。10. 常见问题与排查方法问题现象可能原因排查方式解决方案openai 包导入报错moduleResolution 配置不对查看报错中的模块路径设置moduleResolution: NodeNextAPI Key 未生效环境变量没加载检查.env文件和dotenv/config是否引入在入口文件第一行import dotenv/config模型始终不调用工具工具描述不够明确或系统提示词缺少引导打印工具列表确认工具已注册在系统提示词中加入“你可以调用以下工具获取信息”工具参数 JSON 解析失败模型返回的参数格式有误捕获JSON.parse异常并打印原文将错误信息作为 tool 消息返回让模型自行修正端口占用3000 端口被其他服务使用执行lsof -i :3000检查换端口PORT3001 tsx src/server.tsAgent 无限循环模型反复请求工具但无法收敛检查 maxIterations 设置调低 maxIterations 到 5 或 8大批量任务触发限流并发过高查看模型服务报错状态码降低并发数增加退避重试上下文过长导致费用高memoryWindow 设置太大观察请求的 prompt_tokens调小 memoryWindow 或实现 token 级裁剪10.1 模型调用报错处理如果chat.completions.create报错可能是 baseURL 或 API Key 错误。先打印出实际使用的配置再做判断。console.log(baseURL:, process.env.OPENAI_BASE_URL); console.log(model:, process.env.OPENAI_MODEL);10.2 工具调用结果被模型忽略有时工具结果明明正确模型却在最终回答中给出错误结论。这种情况可以先检查消息顺序。标准流程应该是user: 235 19 等于多少 assistant: (toolCall calculator) tool: 计算结果254 assistant: 结果等于 254如果顺序乱了说明代码里消息 push 的逻辑有误。重点检查toolCallId是否对应上。11. 最佳实践与安全建议11.1 工程化建议第一次测试时先把maxIterations设小一点避免模型进入循环后白白消耗 token。确认整个链路稳定后再调大。模型文件、输入素材、输出结果要分目录管理。建议项目结构如下src/ agent.ts llm.ts tool.ts tools/ server.ts batch.ts data/ inputs/ outputs/批量任务要加日志和失败重试。每个批次任务都记录输入 prompt、工具调用轨迹、输出结果和 token 消耗。问题出现时可以快速定位是哪一轮循环出了问题。接口服务要限制访问范围。如果只是本机测试app.listen绑定127.0.0.1不要暴露到公网。如果要在内网使用建议增加简单的 Token 鉴权中间件。11.2 安全边界再次强调Agent 的工具系统是双刃剑。模型生成的参数不等于安全参数所有工具执行前都要做参数校验。尤其是 HTTP 请求工具、文件读写工具、命令执行工具必须有白名单和沙箱机制。涉及用户隐私数据、公司业务数据时必须先获得授权不能随意把数据发给第三方模型服务。如果需要离线部署可以换成基于本地模型的 OpenAI 兼容服务配合开发环境测试。模型输出内容也要设置审核环节。Agent 可能生成带有错误引导或不当表述的内容生产环境接入建议增加输出过滤和人工抽检。11.3 从模板到生产的升级路径当前这个 100 行 Agent 是一个底座。你可以在它上面继续实现向量记忆把历史消息转成 embedding存到本地向量库实现长期记忆。多工具编排在工具 execute 内部调用其他工具实现组合工具。子 Agent 调度Agent 的某个工具可以是另一个 Agent 的run()实现多智能体协作。可观测性在 Harness 里接 OpenTelemetry把 Agent 运行轨迹导出到监控系统。11.4 与前端结合的方向搜索热词里有 React Vite TypeScript这里也提一个扩展方向把上面写的/api/chat接口接到 React 前端就是一个完整的 Web Agent 对话应用。前端负责展示消息流和工具调用状态后端负责 Agent 循环。可以封装一个useAgenthook管理对话历史和控制请求状态。这种方式比直接用纯流式接口更容易调试也更容易做权限控制。12. 总结与下一步这 100 行 TypeScript 的价值不在于代码量少而在于它把 Agent 的骨架完整暴露出来了消息队列、模型调用、工具注册、循环收敛、记忆裁剪、Harness 事件机制、批量并发控制。你现在应该能回答这几个问题Agent 循环什么时候停止工具调用结果怎么回填为什么需要 maxIterationsHarness 和 Agent 到底有什么区别最值得先验证的功能是“让 Agent 根据问题自动选择工具并完成计算”。先跑通这个最小闭环再逐步加工具、加记忆、加 API。最容易踩的坑有两个一是模型不调用工具二是上下文无控制地增长。前者通过优化系统提示词和工具描述解决后者通过memoryWindow和 token 裁剪解决。后续建议往三个方向延伸难度从低到高。先做 HTTP API 接入让外部系统能调你的 Agent然后做批量任务和错误重试掌握工程化基础最后做向量记忆和 Skill 机制让 Agent 拥有长期能力和领域知识。把这条路走完你对 Agent 的理解会超过大多数只会查文档调包的同学。

相关新闻

2026/9/7 9:34:11

Slopcodebench:AI代码生成质量评估与工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/7 9:34:11

java-web-day6

1.请求参数:1.简单参数参数名和形参变量名一致不一致, 可以通过RequestParam进行映射2.实体参数3.数组集合参数集合使用RequestParam注解, 因为接受默认是数组格式4.日期参数使用LocalDateTime接受,需要使用DateTimeFormat(pattern "yyyy-MM-dd HH:mm:ss")注解5.Jso…

2026/9/7 9:29:10

tar.gz 包解压安装实战:以 base-1.4.5 为例的避坑指南

简介:BASE 1.4.5 是一份基于 PHP 的安全事件分析引擎源码包,面向 IDS 运维人员、安全日志分析者和 PHP 安全工具二次开发学习者。它可对接 Snort 等入侵检测系统、防火墙、网络监控工具产生的安全事件,提供便捷的漏洞搜索界面、数据包解码器&…

2026/9/7 23:41:47

基于DDPG的多动作并行异步强化学习在选矿智能决策中的应用

简介:这是一篇来自《控制与决策》期刊的学术论文PDF,标题为“基于多动作并行异步深度确定性策略梯度的选矿运行指标决策方法”,面向工业智能、强化学习及流程工业自动化领域的研究者和工程师。论文针对深度确定性策略梯度(DDPG&am…

2026/9/7 23:41:47

选矿运行指标决策的强化学习实践:多动作并行异步DDPG解析

简介:选矿运行指标决策是流程工业智能优化中的关键问题,相关学术论文PDF提出多动作并行异步深度确定性策略梯度(MPADDPG)算法,以弥补深度确定性策略梯度(DDPG)算法探索能力不足的局限。该研究面…

2026/9/7 23:41:47

Django+DeepSeek实战:新能源汽车销量预测与推荐系统设计

每年的毕业设计选题,总有一批人绕不开“系统 算法 可视化”这个三角。这款题目把 Django、DeepSeek 大模型、新能源汽车销量预测、可视化大屏和推荐系统塞在一个项目里,表面看是“什么都想要”,实际上是一个很标准的大数据方向的完整闭环&a…

2026/9/7 23:41:46

网络工程师面试题核心考点:从TCP三次握手到OSPF配置实战

简介:面向网络工程师岗位求职的面试题整理文档,覆盖网络协议、设备原理、日常配置与故障排查、硬件及综合布线等考察方向,适合准备技术面试的初学者和需要查漏补缺的应聘者。压缩包内为1个doc文档,整体仅93KB,内容精炼…

2026/9/7 23:36:46

Windows本地部署Dify全攻略:从Docker到WSL2的避坑指南

简介:面向参加 Dify Hackathon 的开发者,内容聚焦在 Windows 10/11 下从零搭建 Dify 本地开发环境,适合具备一定编程基础、熟悉 Git、Docker 与 Python 的技术爱好者,尤其能帮赛前准备不足的团队快速补齐环境短板。文档以清晰步骤…

2026/9/7 0:47:43

超人会飞不算本事:系统稳定依赖清晰规则与边界设计

开头先不绕弯子。“#斯坦李吐槽dc 所以超人是无缘无故会飞的嘛哈哈哈哈哈哈哈锤哥真是技术人才啊!#雷神 #复联”这类调侃式短标题,第一波冲击力在于它把两个宇宙的角色塞进同一个吐槽箱里,但细想一下就能发现,它真正碰到的根本不是…

2026/9/7 0:14:19

超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论

把“蜘蛛侠 vs 超人”放在 CSDN 上聊,可能很多人第一反应是走错片场了。但如果把这两个角色看成“两个持续运营了 80 多年的文化产品”,你会发现,这场比较本质上是两个不同 IP 策略的长期结果对比:超人赢在定义了整个超级英雄题材…

2026/9/7 0:14:17

基于CNN的调制信号识别:MATLAB实现时频图分类实战

简介:本资源是一套面向通信工程与信号处理方向学习者、研究者的深度学习实践方案,聚焦调制信号自动检测与识别这一典型无线通信任务,解决传统方法依赖人工特征、低信噪比下性能下降等痛点。压缩包共12个文件(10.73MB)&…

2026/9/7 0:03:36

基于YOLOv8和PyQt5的麦穗稻穗检测识别系统设计与实现

这次我们来看一个把目标检测算法和桌面端工具结合得很典型的项目:基于 YOLOv8 PyQt5 的麦穗稻穗检测识别系统。这个项目本身不是新概念,但它的价值在于落地形态很完整。YOLOv8 负责核心的麦穗稻穗目标检测,PyQt5 负责提供可视化的桌面交互界…

2026/9/7 0:03:36

UL 1642锂电池安全标准全解析:测试项目、认证流程与避坑指南

简介:UL 1642是锂电池安全领域的重要规范,本中文版资源适合锂电池制造商、检测机构工程师及产品认证相关人员阅读,用于理解电池在设计与制造层面的安全要求、测试方法与合规要点。资源共1个PDF文件,压缩包大小834KB,便…

2026/9/7 0:03:36

BS EN 13814-1-2019游乐设施安全标准:设计与制造核心要点解析

简介:BS EN 13814-1:2019是英国采纳欧洲标准EN 13814-1:2019的正式版本,由BSI标准出版,重点规定游乐设施和游乐设备在设计与制造环节的安全准则,与BS EN 13814-2:2019、BS EN 13814-3:2019共同取代旧版BS EN 13814:2004。该标准面…

2026/9/7 16:23:03

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/7 22:46:00

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/7 22:45:59

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…