1. 为什么我要自己造一个 Agent 框架1.1 现成框架的“甜蜜陷阱”过去大半年我几乎把市面上能叫得出名字的 Agent 框架都摸了一遍。从 LangChain 到 AutoGen从各种“一行代码接入大模型”的封装库到号称开箱即用的智能体平台踩过的坑比写过的业务代码还多。刚开始确实爽几行代码就能让模型调用工具、记住上下文、跑一个 ReAct 循环demo 跑通那一刻感觉自己已经站在了风口上。但真到了要往生产环境推的时候问题就一个接一个冒出来了。最典型的是黑盒感太重框架帮你把 prompt 拼装、工具调用、结果解析、循环控制全包了可一旦模型返回的格式稍微偏一点整个链路就崩而你根本不知道它内部到底把什么塞进了上下文。我遇到过最离谱的一次某个框架在工具调用失败后默默把错误信息当成“工具返回结果”又喂回给模型模型一本正经地基于错误信息继续推理最后输出了一段看起来逻辑自洽、实际上完全跑偏的答案。排查了整整一个下午翻源码才发现是它内部的异常处理逻辑写死了。另一个痛点是依赖地狱。很多框架为了兼容各种模型供应商、各种向量库、各种工具协议把依赖树搞得极其庞大。你只想用它的 ReAct 循环结果被迫装进来一堆你根本用不到的东西版本冲突、打包体积、冷启动时间全都上去了。更别提有些框架更新频率极高今天能跑的代码明天升个版本就报错API 说改就改迁移成本全压在业务方身上。1.2 自己动手的收益账所以当“不用框架用 TypeScript 从零搭一个 Agent 框架”这个念头冒出来的时候我几乎没有犹豫。这里的“不用框架”不是说要拒绝一切库而是不依赖那些大而全的 Agent 编排框架把核心的控制权拿回自己手里。TypeScript 是我选定的语言原因很直接Agent 的核心是“结构化地处理模型输入输出”而 TypeScript 的类型系统恰好能在编译期就帮你把工具参数、消息结构、状态流转约束住这在动态语言里要靠大量运行时校验才能做到。自己搭的好处我总结下来有这么几条。第一是完全透明每一轮循环里上下文长什么样、工具调用是怎么解析的、失败之后怎么重试全是你自己写的出问题直接断点调试不用去猜框架的黑魔法。第二是按需裁剪你只需要 ReAct 就只写 ReAct不需要为了用一个小功能引入一整套生态。第三是可控的演进节奏业务需要什么就加什么不会被上游框架的 breaking change 牵着鼻子走。第四也是最实在的一点这套东西本身就是最好的学习材料你把 Agent 的每个环节都亲手实现一遍以后再看到任何框架的文档一眼就能看穿它在做什么。这篇文章适合两类人一类是已经用过 Agent 框架、但被黑盒和依赖问题折磨过、想搞清楚底层到底怎么回事的开发者另一类是有一定 TypeScript 基础、想从零理解 Agent 运行机制、为后续做更复杂智能体打地基的同学。我会把整个搭建过程拆开讲包括设计取舍、核心代码、踩过的坑以及怎么用 MCP 和 ReAct 这两个当下最热的概念把框架的能力撑起来。2. 整体架构设计与技术选型2.1 核心模块怎么切分搭框架最怕一上来就写代码写着写着发现模块之间耦合得一塌糊涂。我习惯先把职责边界画清楚再动手。一个最小可用的 Agent 框架我把它切成五块模型适配层、工具注册层、消息与状态管理层、循环控制层、协议接入层。这五块各管一摊层与层之间通过明确的接口通信任何一块想换实现都不影响其他部分。模型适配层负责把不同供应商的 API 差异抹平对外只暴露一个统一的chat方法输入是消息数组和工具定义输出是标准化的响应对象。工具注册层负责管理所有可被 Agent 调用的能力每个工具要有名字、描述、参数 schema 和执行函数。消息与状态管理层负责维护对话历史、工具调用记录、中间状态这是 Agent 的“记忆”。循环控制层是大脑决定什么时候该让模型思考、什么时候该执行工具、什么时候该停下来。协议接入层则是把 MCP 这类外部协议接进来让 Agent 能调用外部工具服务器。这么切分的好处是每一层都可以单独测试。比如我想验证工具解析逻辑直接构造一个假的模型响应喂给循环控制层就行不需要真的去调模型。这种可测试性在后期排查问题时价值巨大。2.2 为什么是 TypeScript 而不是别的选 TypeScript 不只是因为它“类型安全”这个笼统的理由而是 Agent 这个场景对类型有非常具体的诉求。工具调用的参数是模型生成的 JSON这个 JSON 的结构必须和工具定义的 schema 严格对应否则执行时就会出错。如果用 JavaScript你只能在运行时用 zod 之类的库去校验校验失败还得自己处理。而 TypeScript 配合 zod可以做到schema 定义即类型定义工具函数的参数类型直接从 schema 推导出来编译期就能发现不匹配。另一个原因是消息结构的复杂性。一个 Agent 的消息流里混着系统提示、用户输入、模型回复、工具调用请求、工具执行结果每种消息的字段都不一样。用联合类型union type来描述这些消息配合类型收窄type narrowing在处理消息时能获得极好的编辑器提示和编译期检查。这在动态语言里全靠约定和文档很容易写错。还有一点是生态。Node.js 生态里处理 HTTP、流式响应、JSON 解析的库非常成熟而且 TypeScript 对异步流程的类型支持很好Agent 的循环本质上是异步的async/await配合类型推导写起来很顺。至于运行环境我建议直接用 Node.js 20 以上原生支持 ESM 和顶层 await省去很多配置麻烦。2.3 依赖清单能少则少既然是自己搭依赖就要克制。我的核心依赖只有这么几个zod用来做 schema 定义和运行时校验这是整个框架的基石openai官方 SDK 用来做模型适配层的默认实现因为它同时支持 OpenAI 兼容接口很多国产模型也能直接接modelcontextprotocol/sdk用来接入 MCP 协议这是当下工具生态的事实标准。除此之外HTTP 请求用 Node 内置的fetch不需要 axios日志用console加一层简单封装不需要 winston测试用 Node 内置的node:test不需要 jest。提示依赖越少长期维护成本越低。每引入一个库都要问自己“这个功能我能不能用二十行代码自己写”如果能就别引。这里要特别说一下 MCP。MCP 全称 Model Context Protocol是一个让模型和外部工具、数据源通信的开放协议。它的价值在于标准化以前你给 Agent 加一个工具要自己写调用逻辑、自己处理鉴权、自己定义参数格式有了 MCP工具提供方按协议暴露一个 server你的 Agent 作为 client 连上去就能自动发现工具列表、自动获取参数 schema、自动调用。这大大降低了工具接入的成本也是我把它作为协议接入层核心的原因。3. 核心模块的落地实现3.1 消息与工具的类型定义一切从类型开始。我先把消息和工具的类型定义清楚后面所有代码都围绕这些类型展开。消息我用一个联合类型来描述区分系统消息、用户消息、助手消息和工具结果消息。助手消息里可能包含文本内容也可能包含工具调用请求所以它的结构要能同时容纳这两种情况。import { z } from zod; // 工具调用请求 interface ToolCall { id: string; name: string; arguments: Recordstring, unknown; } // 消息联合类型 type Message | { role: system; content: string } | { role: user; content: string } | { role: assistant; content: string | null; toolCalls?: ToolCall[] } | { role: tool; toolCallId: string; content: string }; // 工具定义 interface ToolDefinitionT extends z.ZodTypeAny { name: string; description: string; schema: T; execute: (args: z.inferT) Promisestring; }这里有个设计细节值得展开。工具结果消息我用了toolCallId而不是工具名来关联因为同一个工具可能在一轮里被调用多次用名字关联会串。toolCallId是模型在发起调用时生成的唯一标识工具执行完把结果和这个 id 一起回传模型就能对上号。这个细节很多自己搭框架的人一开始会忽略等到模型一轮调用多个工具时就出问题了。工具定义用了泛型schema的类型会被execute的参数类型自动推导。这意味着你写工具的时候参数类型是自动出来的不用手动标注也不会写错。这是 TypeScript 在这个场景下最爽的地方。3.2 模型适配层的统一封装模型适配层的目标是让上层不关心底层用的是哪家模型。我定义了一个ModelProvider接口只有一个chat方法。默认实现基于 OpenAI SDK因为它的接口格式被广泛兼容。interface ChatParams { messages: Message[]; tools?: ToolDefinitionz.ZodTypeAny[]; temperature?: number; } interface ChatResult { content: string | null; toolCalls: ToolCall[]; finishReason: string; } interface ModelProvider { chat(params: ChatParams): PromiseChatResult; }实现的时候有个关键点把工具定义转换成模型能理解的 JSON Schema。zod 提供了zodToJsonSchema的能力但官方没有直接导出我用的是社区维护的转换逻辑或者手动写一个简单的转换器。转换的时候要注意模型对 JSON Schema 的支持是有限的比如oneOf、anyOf这种复杂结构很多模型处理不好所以工具参数尽量用扁平的对象结构避免嵌套过深。function toOpenAITools(tools: ToolDefinitionz.ZodTypeAny[]) { return tools.map((t) ({ type: function as const, function: { name: t.name, description: t.description, parameters: zodToJsonSchema(t.schema), }, })); }调用模型时把消息和工具一起传过去然后把响应解析成统一的ChatResult。这里要处理一个坑不同模型返回工具调用的字段名可能不一样有的叫tool_calls有的叫function_call有的把参数放在arguments字符串里需要二次解析。适配层的职责就是把这些差异全部吃掉让上层拿到干净的数据。注意模型返回的arguments是一个 JSON 字符串不是对象必须JSON.parse。而且模型偶尔会返回不合法的 JSON比如多一个逗号、少一个引号所以解析要包在 try/catch 里解析失败时把原始字符串作为错误信息回传给模型让它自己修正。3.3 工具注册与执行工具注册层我做成一个ToolRegistry类内部用一个 Map 存工具定义。注册的时候校验工具名不重复执行的时候先按名字找到工具再用 zod schema 校验参数校验通过才执行。class ToolRegistry { private tools new Mapstring, ToolDefinitionz.ZodTypeAny(); register(tool: ToolDefinitionz.ZodTypeAny) { if (this.tools.has(tool.name)) { throw new Error(工具 ${tool.name} 已注册); } this.tools.set(tool.name, tool); } async execute(call: ToolCall): Promisestring { const tool this.tools.get(call.name); if (!tool) { return 错误未找到工具 ${call.name}; } const parsed tool.schema.safeParse(call.arguments); if (!parsed.success) { return 参数校验失败${parsed.error.message}; } try { return await tool.execute(parsed.data); } catch (err) { return 工具执行异常${(err as Error).message}; } } list(): ToolDefinitionz.ZodTypeAny[] { return Array.from(this.tools.values()); } }这里有个非常重要的设计决策工具执行失败时不要把异常抛出去而是把错误信息作为字符串返回。为什么因为 Agent 的循环需要继续模型需要知道工具失败了然后决定是重试、换工具还是直接回答。如果你把异常抛出去整个循环就断了模型失去了自我修正的机会。这个设计在 ReAct 模式里尤其关键ReAct 的核心就是“推理-行动-观察”的循环观察结果里包含错误信息模型才能基于错误调整下一步。参数校验用safeParse而不是parse也是同样的道理。校验失败不抛异常而是返回错误信息给模型。模型看到“参数校验失败xxx 字段应该是数字”之后下一轮往往会自己修正参数重新调用。我实测下来这种“把错误当反馈”的机制能显著提升 Agent 的鲁棒性。3.4 ReAct 循环控制循环控制是整个框架的心脏。ReAct 模式的本质是一个 while 循环把当前消息历史发给模型模型要么返回最终答案要么返回工具调用请求如果是工具调用就执行工具、把结果追加到消息历史然后继续循环如果是最终答案就结束。async function runAgent( provider: ModelProvider, registry: ToolRegistry, userInput: string, options: { maxSteps?: number; systemPrompt?: string } {} ): Promisestring { const maxSteps options.maxSteps ?? 10; const messages: Message[] []; if (options.systemPrompt) { messages.push({ role: system, content: options.systemPrompt }); } messages.push({ role: user, content: userInput }); for (let step 0; step maxSteps; step) { const result await provider.chat({ messages, tools: registry.list(), }); messages.push({ role: assistant, content: result.content, toolCalls: result.toolCalls.length ? result.toolCalls : undefined, }); if (result.toolCalls.length 0) { return result.content ?? ; } for (const call of result.toolCalls) { const output await registry.execute(call); messages.push({ role: tool, toolCallId: call.id, content: output, }); } } return 达到最大步数限制任务未完成。; }这段代码不长但每一行都有讲究。maxSteps是必须的否则模型可能陷入无限循环一直调用工具停不下来。我一般设 10 到 15具体看任务复杂度。系统提示是可选的但强烈建议给因为系统提示里可以写清楚 Agent 的角色、可用工具的使用规范、输出格式要求这些对稳定性影响很大。消息追加的顺序也很关键。模型返回的助手消息包含工具调用请求必须先追加到历史里然后再追加工具结果这样消息序列才是合法的。如果你先追加工具结果模型下一轮会看到“工具结果出现在工具调用之前”很多模型会直接报错或者行为异常。还有一个细节当模型返回了工具调用同时也返回了一段文本内容时这段文本通常是模型的“思考过程”要保留在助手消息里。有些实现会把它丢掉只保留工具调用这样模型下一轮就失去了自己的推理上下文效果会变差。4. 接入 MCP 与工具生态扩展4.1 MCP 到底解决了什么问题在没有 MCP 之前给 Agent 加工具是一件很繁琐的事。你要为每个工具写调用代码处理鉴权定义参数格式还要在系统提示里描述这个工具怎么用。工具一多系统提示就爆炸模型也容易搞混。MCP 的出现把这件事标准化了工具提供方实现一个 MCP server声明自己有哪些工具、每个工具的参数 schema 是什么Agent 作为 MCP client 连上去通过协议自动获取工具列表然后像调用本地工具一样调用它们。MCP 的通信方式主要有两种标准输入输出stdio和 HTTP。stdio 适合本地工具比如你写一个脚本暴露成 MCP serverAgent 启动时把它作为子进程拉起来通过标准输入输出通信。HTTP 适合远程工具工具提供方部署一个服务Agent 通过网络连过去。两种方式在协议层面是一致的只是传输层不同。4.2 把 MCP 工具接进注册表接入 MCP 的思路是启动时连接 MCP server拉取工具列表把每个 MCP 工具包装成一个本地的ToolDefinition注册到ToolRegistry里。这样上层循环完全不用改MCP 工具和本地工具对循环来说是一样的。import { Client } from modelcontextprotocol/sdk/client/index.js; import { StdioClientTransport } from modelcontextprotocol/sdk/client/stdio.js; async function loadMcpTools( registry: ToolRegistry, command: string, args: string[] ) { const transport new StdioClientTransport({ command, args }); const client new Client({ name: my-agent, version: 1.0.0 }); await client.connect(transport); const { tools } await client.listTools(); for (const tool of tools) { registry.register({ name: tool.name, description: tool.description ?? , schema: jsonSchemaToZod(tool.inputSchema), execute: async (args) { const result await client.callTool({ name: tool.name, arguments: args, }); return JSON.stringify(result.content); }, }); } }这里有个转换问题MCP 工具的参数 schema 是 JSON Schema 格式而我的工具定义用的是 zod。所以需要一个jsonSchemaToZod的转换函数。这个转换不需要支持全部 JSON Schema 特性覆盖常用的对象、字符串、数字、布尔、数组、枚举就够了。我写了一个简化版遇到不支持的类型就退化成z.any()保证不会因为 schema 太复杂而注册失败。提示MCP server 返回的工具结果格式可能很复杂包含文本、图片、资源引用等多种类型。我一般只取文本部分用JSON.stringify序列化后回传给模型。如果结果里有图片需要额外处理成模型能理解的格式这部分可以后续扩展。4.3 工具描述的质量决定 Agent 的上限接入工具只是第一步真正决定 Agent 好不好用的是工具描述的质量。模型是根据工具的名字和描述来决定调不调、怎么调的。描述写得好模型一次就能调对描述写得含糊模型要么不调要么调错参数。我总结了几条写工具描述的经验。第一描述里要写清楚“什么时候用”而不只是“这个工具是什么”。比如一个查天气的工具描述写“查询指定城市的天气”就不如写“当用户询问某个城市的天气、温度、是否下雨时使用此工具参数 city 必须是城市中文名”。第二参数描述要具体说明格式和取值范围。第三避免功能重叠的工具如果两个工具都能干同一件事模型会纠结最好合并或者明确区分使用场景。对于 MCP 工具描述是工具提供方写的你控制不了。这时候可以在系统提示里补充说明告诉模型这些工具的使用规范。我一般会在系统提示里列一个工具使用清单把每个工具的适用场景和注意事项写清楚效果比只依赖工具自带的描述好很多。5. 踩坑实录与排查技巧5.1 模型不按格式返回工具调用这是最常见的问题。你期望模型返回结构化的工具调用结果它返回了一段自然语言比如“我需要调用 get_weather 工具参数是北京”。这种情况通常有几个原因模型本身对工具调用的支持不好、系统提示没有强调要用工具、或者工具描述不够清晰。排查思路是这样的。先确认模型是否支持 function calling有些小模型或者老模型根本不支持那你怎么调都没用。如果支持检查系统提示里有没有明确要求“需要调用工具时必须使用工具调用格式不要用自然语言描述”。再检查工具描述是否清晰。如果都排除了可以在适配层加一层兜底当模型返回的文本里包含工具名时尝试用正则提取参数构造一个工具调用。这个兜底不优雅但能救急。5.2 工具调用陷入死循环模型反复调用同一个工具参数几乎不变循环停不下来。这种情况一般是工具返回的结果没有给模型足够的信息模型以为没成功就一直重试。解决办法是在工具结果里明确告诉模型“成功了”或者“失败了原因是 xxx”。另外maxSteps是最后一道防线一定要设。我遇到过一次特别隐蔽的死循环工具返回的结果太长超过了模型的上下文窗口模型看不到完整结果就以为工具没返回继续调用。后来我在工具结果里加了长度截断超过一定字符数就截断并提示“结果过长已截断”问题就解决了。5.3 上下文膨胀导致成本飙升Agent 跑多轮之后消息历史会越来越长每轮都要把全部历史发给模型token 消耗快速增长。我见过一个任务跑了二十轮最后一轮的输入 token 是前几轮的十几倍。解决办法有几个一是限制历史长度只保留最近 N 轮的消息更早的做摘要二是工具结果精简不要把工具的原始返回全塞进去提取关键信息三是及时终止任务完成就停不要让它继续“思考”。5.4 常见问题速查表问题现象可能原因排查方向模型不返回工具调用模型不支持、提示不清、描述模糊换模型、改系统提示、优化工具描述工具参数解析失败模型返回非法 JSON加 try/catch把错误回传让模型修正循环停不下来工具结果信息不足、无 maxSteps补充结果信息、设置步数上限上下文过长历史累积、工具结果过大截断历史、精简结果、及时终止MCP 工具注册失败schema 转换不支持降级为 z.any()记录日志6. 一些实操心得搭完这套框架我最大的感受是Agent 的难点不在代码而在对模型行为的理解和约束。代码本身并不复杂几百行就能跑起来但要让它在真实场景下稳定工作需要大量的调试和迭代。系统提示怎么写、工具描述怎么措辞、错误信息怎么回传这些细节决定了 Agent 是“能用”还是“好用”。另一个心得是日志要打全。Agent 的每一轮循环、每一次工具调用、每一个模型响应都要记下来。出问题的时候这些日志就是你的救命稻草。我一般会把完整的消息历史、工具调用参数、工具返回结果都打到日志里虽然有点占空间但排查问题时非常值。最后说一个扩展方向。这套框架目前是单 Agent 的如果你想做多 Agent 协作可以在循环控制层之上再加一层编排让多个 Agent 通过消息传递协作。MCP 也可以用来做 Agent 之间的通信协议一个 Agent 把另一个 Agent 暴露成 MCP server这样协作就标准化了。这个方向我还在摸索等有成熟经验了再单独写一篇。
企业数字化 ERP 产品动态
相关推荐
从零实现AI工程:手写神经网络与反向传播核心指南 “ai-engineering-from-scratch”这种命名方式,我太熟了。GitHub上一搜一大把,几乎每个热门方向都会冒出来几个“from scratch”仓库,但真正能让人从头走到尾、不半路劝退的,少之又少。这个标题的核心诱惑力在于“我什么都不要&am… · 2026/11/4 10:24:45
SAP物料主数据编号范围维护全流程与避坑指南 简介:这份资源聚焦SAP系统后台物料主数据的编号范围维护,面向SAP实施顾问、运维人员及正在学习后勤模块配置的从业者,帮助解决物料类型号码组划分、内外编号段设定与冲突处理等实际问题。压缩包内共1个docx文档,约494KB࿰… · 2026/11/4 10:24:45
Windows开发到Linux部署:前端项目跨平台实践指南 1. 项目背景与核心需求 最近在帮朋友调试一个前端项目时遇到个典型场景:开发环境是Windows系统,但最终部署目标服务器是Linux环境。这种跨平台部署的需求在实际工作中很常见,特别是当团队使用Windows作为开发机,而生产环境采用Lin… · 2026/11/4 10:24:45
无人机水质监测系统设计与实战经验分享 1. 无人机水质监测的技术革新 最近在环境监测领域出现了一个有趣的现象:越来越多的科研团队开始采用无人机进行水质采样和监测。这种创新方法不仅解决了传统人工采样效率低、危险性高的问题,还能获取更全面的水域数据。作为一名长期从事环境监测的技术人… · 2026/11/4 10:56:32
Playwright自动化测试:跨浏览器Web测试实战指南 1. Playwright 工具概述Playwright 是微软开源的现代化 Web 测试和自动化库,支持 Chromium、WebKit 和 Firefox 三大浏览器引擎。与传统的 Selenium 相比,它具有以下显著优势:原生支持无头模式(headless)和带界面模式(headed)自动等待元素加载… · 2026/11/4 10:56:32
CSES算法题库:面试高频考点与动态规划/图论建模实战指南 简介:CSES Problem Set 是面向C算法学习者与编程竞赛选手的高质量题解资源,覆盖基础算法、动态规划、图论、贪心、数据结构、数论、回溯、位运算及模拟建模等10大核心模块,精准匹配ACM/ICPC、校招笔试及LeetCode进阶训练需求。资源包为ZIP格式… · 2026/11/4 10:56:32
Delphi7嵌入Chrome内核:CEF4Delphi最新版实战指南 简介:本资源是目前最新版CEF4Delphi开源控件集成包,专为Delphi7开发者设计,用于在原生Delphi应用中无缝嵌入Chromium内核浏览器,解决传统WebBrowser组件功能弱、兼容差、无法支持现代前端技术栈的痛点,适用于桌面端混合… · 2026/11/4 10:56:32
CSES题库:算法面试的考点解剖图与破题心法 简介:CSES Problem Set 是面向算法学习者与编程竞赛选手的 C 专项题解资源,覆盖基础算法、动态规划、图论、贪心、数据结构、数论、回溯、位运算及模拟建模等十大核心模块,精准对标 ACM/ICPC、Code Jam 等赛事及大厂技术面试高频考点。资源包… · 2026/11/4 10:56:32
Redis核心数据结构与通用命令实战指南 1. Redis入门:从零掌握通用命令与核心数据结构 作为从业十年的全栈开发者,我见证了Redis从一个小众工具成长为互联网基础设施的全过程。无论你是刚接触Redis的新手,还是想系统梳理知识的开发者,掌握通用命令和五大核心数据结构都是… · 2026/11/4 10:55:31
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38
AI辅助技术栈评估:提升研发决策效率60% 1. 项目背景与核心价值技术决策一直是研发团队面临的重大挑战。随着技术生态的快速演进,技术栈的选择、维护和迭代变得愈发复杂。我们团队在过去两年里,先后经历了三次重大技术栈迁移,每次都需要投入大量人力进行技术评估和风险分析。直到去年… · 2026/11/4 0:00:17
从生成Token到直接决策:Jev如何重塑AI Agent工程实践 1. 从"生成文字"到"拍板决策":Jev 到底在解决什么问题大多数人第一次接触大模型,脑子里建立的模型是"输入问题、输出文字"。你问它今天天气怎么样,它给你一段描述;你让它写个周报,它给你… · 2026/11/4 0:00:17
32G统一内存跑大模型:内存账、TPS实测与端云分流 想在一台 32G 统一内存的小主机上把大模型跑成"日常可用",这件事我前后折腾了小半年。标题里说的这台 Mac mini M6,是我们这次讨论的目标平台,具体到手规格以实物为准;但下面这套推算方法和实测思路,对任何统… · 2026/11/4 0:00:17
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38