1. 为什么我要自己造一个 Agent 框架市面上现成的 Agent 框架已经不少了LangChain、AutoGen、CrewAI还有 OpenAI 官方出的 Agents SDK功能一个比一个全。但我还是花了两个周末用 TypeScript 从零搭了一个能跑通 ReAct 循环、能接 MCP 工具、能流式输出的最小 Agent 框架。原因很简单用别人的框架你永远不知道 Agent 到底在干什么。我最早用某个流行框架做一个读取本地文件并总结的小工具结果它跑了 40 秒烧了 3 万 token最后告诉我无法访问文件系统。我翻日志翻了半小时才发现是工具注册时 schema 描述写得太模糊模型压根没选对工具而框架把这次失败静默吞掉了。那一刻我就决定核心链路必须自己写一遍。这篇文章不是教你如何用某个框架而是带你从零把 Agent 的骨架搭出来。读完你会清楚一次 Agent 调用里消息是怎么组织的、工具是怎么被模型选中的、ReAct 循环什么时候该停、MCP 协议到底解决了什么问题。适合有 TypeScript 基础、想真正搞懂 Agent 内部机制的前端或全栈开发者。不需要你懂 Python也不需要你装一堆依赖一个 Node 环境加一个 OpenAI API Key 就能跑起来。我先把结论放前面Agent 的本质就是一个带工具调用的 while 循环。听起来简单但魔鬼全在细节里——消息历史怎么裁剪、工具调用失败怎么重试、流式输出怎么和工具执行穿插、循环什么时候该强制退出。这些细节才是框架真正值钱的地方也是你自己写一遍才能掌握的东西。2. 动手前的技术选型为什么是 TypeScript 而不是 Python2.1 TypeScript 做 Agent 的三个真实优势很多人默认 Agent 开发就该用 Python毕竟生态在那。但我实际写下来TypeScript 在这个场景里有几个被低估的优势。第一是类型系统对工具 schema 的约束。Agent 的核心是工具调用而工具的参数结构必须和 JSON Schema 严格对应。TypeScript 的zod配合类型推导可以做到定义一次schema 和类型同时生成模型传错参数在编译期就能发现一部分问题。Python 的 Pydantic 也能做但 TS 的类型收窄在编辑器里的体验更顺滑。第二是流式处理天然契合。Agent 的输出是 SSE 流工具执行是异步的前端展示又要实时。Node 的ReadableStream和AsyncGenerator处理这种边收边处理的场景非常自然我整个流式解析器不到 80 行就写完了。第三是部署链路短。写完之后直接tsc编译扔到一个 Node 服务或者 Serverless 函数里就能跑不用折腾虚拟环境和依赖隔离。对于做前端出身、想快速验证 Agent 想法的人来说这条路径最短。2.2 依赖清单能少则少我给自己定的规矩是核心依赖不超过 3 个。最终清单是这样的依赖用途是否可替代openai调用模型 API官方 SDK 对流式和工具调用支持最完整可换成任意兼容 OpenAI 协议的 SDKzod定义工具参数 schema同时推导 TS 类型可换成ajv但类型体验差很多tsx开发时直接跑 TS免编译可换成ts-node或先tsc注意这里我没有引入任何 Agent 框架也没有引入 MCP 的官方 SDK。MCP 部分我会手写 JSON-RPC 消息因为协议本身很简单手写一遍你才能真正理解它。提示如果你用的是兼容 OpenAI 协议的其他模型服务只需要改baseURL和apiKey两个参数。但要注意不同服务对tool_calls字段的支持程度不一样有些会把流式工具调用拆得很碎后面第 5 节我会讲怎么处理。2.3 项目结构按职责切不按技术切我不喜欢按controllers/services/utils这种技术维度切目录Agent 项目应该按职责切src/ agent/ loop.ts # ReAct 主循环 message.ts # 消息类型与历史管理 stream.ts # 流式响应解析 tools/ registry.ts # 工具注册中心 builtin/ # 内置工具读文件、计算等 mcp/ client.ts # MCP 客户端 transport.ts # 传输层stdio / http llm/ client.ts # 模型调用封装 index.ts # 入口这样切的好处是当你想换模型、换工具、换传输方式时改动范围是局部的。我后来把模型从一家换到另一家只动了llm/client.ts一个文件。3. 消息模型Agent 的骨架其实是消息数组3.1 四种角色各有各的坑OpenAI 的消息模型有四种角色system、user、assistant、tool。看起来简单但实际用起来有几个反直觉的地方。system消息不是必须放在第一条。我试过把工具使用规范放在对话中间作为一条 system 消息模型确实会更重视它。但要注意有些模型服务只认第一条 system后面的会被忽略所以关键约束还是放开头稳妥。assistant消息里可以同时有content和tool_calls。这是最容易出错的地方——当模型决定调用工具时content往往是null或者空字符串但这条消息必须原样存进历史因为下一轮你要把tool角色的结果和它配对。我一开始图省事只存了工具结果没存 assistant 的 tool_calls结果模型完全不知道自己在等什么直接开始胡说。tool消息必须带tool_call_id而且要和对应的assistant.tool_calls[].id严格匹配。这个 id 是模型生成的你只管透传千万别自己造。3.2 消息历史管理裁剪比追加更重要Agent 跑多轮之后消息历史会迅速膨胀。一个读文件加总结的任务轻松就能堆到 20 条消息、上万 token。如果不做裁剪成本和延迟都会失控。我的策略是分层保留永久保留第一条 system 消息、最近 N 轮完整对话N 默认 6压缩保留更早的 assistant 文本回复合并成一条摘要直接丢弃更早的 tool 调用细节只保留最终结论这里有个坑裁剪时不能把 assistant 的 tool_calls 和对应的 tool 结果拆散。如果保留了一条 tool 消息却丢了它的 tool_callsAPI 会直接报错。我的做法是裁剪的最小单位是一个完整的工具调用回合要么整组留要么整组丢。// 按回合裁剪保证 tool_calls 和 tool 结果成对出现 function trimHistory(messages: Message[], keepRounds 6): Message[] { const system messages.filter(m m.role system); const rest messages.filter(m m.role ! system); // 从后往前数完整的回合 const rounds: Message[][] []; let current: Message[] []; for (const msg of rest) { current.push(msg); if (msg.role assistant !msg.tool_calls) { rounds.push(current); current []; } } if (current.length) rounds.push(current); const kept rounds.slice(-keepRounds).flat(); return [...system, ...kept]; }3.3 token 估算别等 API 报错才后悔精确算 token 需要 tokenizer但那个库不小。我用的是粗略估算英文按 4 字符 1 token中文按 1.5 字符 1 token工具 schema 单独算。这个估算误差在 15% 以内足够用来做要不要裁剪的判断。真正要卡的是硬上限。我会在每次调用前检查估算值超过模型上下文窗口的 70% 就触发裁剪。留 30% 余量是因为模型的输出也要占 token而且工具调用的 JSON 有时候会很长。4. 工具系统让模型会用比能用更重要4.1 工具定义的三要素一个工具要能被模型正确调用必须说清三件事叫什么、干什么、参数长什么样。前两个靠name和description第三个靠parameters的 JSON Schema。我踩过最大的坑就是description写得太随意。比如我写了个readFile工具描述是读取文件。结果模型在需要读目录的时候也调它因为描述里没说清楚它只能读文件不能读目录。后来我改成读取指定路径的单个文件内容路径必须是文件而非目录如需列出目录请用 listDir命中率立刻上来了。description 要写成给一个完全不懂你项目的人看的说明书把边界条件、失败情况、替代工具都写进去。这不是啰嗦这是在给模型做 prompt engineering。4.2 用 zod 一次定义schema 和类型同时出手写 JSON Schema 又长又容易和实际类型对不上。我用 zod 做单一数据源import { z } from zod; const ReadFileParams z.object({ path: z.string().describe(要读取的文件绝对路径), encoding: z.enum([utf8, base64]).default(utf8) .describe(文件编码文本文件用 utf8二进制用 base64), }); type ReadFileParams z.infertypeof ReadFileParams; const readFileTool { name: readFile, description: 读取指定路径的单个文件内容..., parameters: zodToJsonSchema(ReadFileParams), execute: async (args: ReadFileParams) { // 这里 args 已经是强类型的 return await fs.readFile(args.path, args.encoding); } };zodToJsonSchema需要自己写一个转换函数或者用现成的库。我手写了一个简化版只支持 object、string、enum、number、boolean 这几种常用类型30 行搞定比引库轻。4.3 工具执行失败别把异常直接扔给模型工具执行出错时有两种处理方式抛异常中断整个循环或者把错误信息作为工具结果返回给模型。我强烈建议后者。原因很实际模型调用工具时经常传错参数比如路径写错、格式不对。如果你直接抛异常整个 Agent 就挂了用户看到的是报错。但如果你把错误信息返回模型下一轮往往能自己纠正。我实测下来路径类错误模型自我修正的成功率超过 70%。async function safeExecute(tool: Tool, args: unknown) { try { const parsed tool.schema.parse(args); // zod 校验 const result await tool.execute(parsed); return { success: true, content: String(result) }; } catch (err) { // 把错误变成模型能读懂的文字 return { success: false, content: 工具执行失败${(err as Error).message}。请检查参数后重试。 }; } }注意错误信息要对模型友好别把一长串堆栈扔进去那只会浪费 token。提炼成一句人话就够了。4.4 工具数量不是越多越好我一开始注册了 15 个工具结果模型选择困难经常选错。后来砍到 5 个核心工具准确率明显提升。经验值是单次对话暴露给模型的工具不超过 8 个超过就要考虑分组或者按需加载。如果确实工具很多可以用工具检索的思路先让模型根据任务描述选出一组相关工具再把这组工具的完整 schema 给它。这样既控制了上下文长度又提高了选择准确率。5. ReAct 循环Agent 的心跳5.1 循环的四个阶段ReAct 的核心是推理-行动-观察的循环。落到代码上每一轮是这样的组装消息把历史消息加上当前状态发给模型解析响应看模型是返回文本还是工具调用执行工具如果有工具调用逐个执行收集结果判断终止如果模型返回纯文本说明它认为任务完成循环结束听起来简单但每一步都有细节。比如第 2 步模型可能同时返回文本和工具调用这时候文本通常是它的思考过程要保留但不能当作最终答案。5.2 终止条件三种情况必须停循环不能无限跑我设了三个终止条件正常终止模型返回了没有 tool_calls 的 assistant 消息轮次上限默认 10 轮超过就强制停返回当前所有文本重复检测连续两轮调用了完全相同的工具和参数判定为死循环强制停第三个条件特别重要。我遇到过模型陷入读文件-发现读不了-再读一次的死循环烧了 5 万 token 才发现。加上重复检测后这类问题基本绝迹。function isDuplicate(prev: ToolCall[], curr: ToolCall[]): boolean { if (prev.length ! curr.length) return false; return prev.every((p, i) p.function.name curr[i].function.name p.function.arguments curr[i].function.arguments ); }5.3 流式输出与工具执行的穿插这是整个框架里最绕的部分。流式响应里文本是逐字来的但工具调用是分片来的——tool_calls的arguments会分成好几个 chunk 传过来你得自己拼。我的处理方式是维护一个工具调用累加器const toolCallBuffer new Mapnumber, { name: string; args: string }(); for await (const chunk of stream) { const delta chunk.choices[0]?.delta; if (delta?.content) { // 文本直接推给前端 yield { type: text, content: delta.content }; } if (delta?.tool_calls) { for (const tc of delta.tool_calls) { const idx tc.index; if (!toolCallBuffer.has(idx)) { toolCallBuffer.set(idx, { name: , args: }); } const buf toolCallBuffer.get(idx)!; if (tc.function?.name) buf.name tc.function.name; if (tc.function?.arguments) buf.args tc.function.arguments; } } }关键点index字段是分片的唯一标识同一个工具调用的所有分片 index 相同。拼完之后再JSON.parse(buf.args)这时候才能执行工具。注意有些模型服务在流式模式下不返回index或者把name和arguments混在一个 chunk 里。遇到这种情况我的兜底方案是如果index缺失就按id分组如果id也缺失就假设只有一个工具调用。这个兜底逻辑救过我好几次。5.4 工具执行要不要并行如果模型一轮里调了多个工具理论上可以并行执行。但我的建议是默认串行除非你确定这些工具之间没有依赖。原因很实际并行执行时如果其中一个工具修改了文件另一个工具读同一个文件结果就不确定了。而且并行执行会让日志变得难以追踪。我只有在明确知道工具是只读的情况下才开并行。6. 接入 MCP让 Agent 用上外部工具生态6.1 MCP 到底解决了什么问题MCPModel Context Protocol说白了就是一套让模型和外部工具对话的标准协议。在它出现之前每个 Agent 框架都有自己的工具定义格式你想把一个工具从 A 框架搬到 B 框架得重写一遍。MCP 把这个格式统一了工具提供方只要实现一次 MCP Server所有支持 MCP 的客户端都能用。它的通信基于 JSON-RPC 2.0消息格式很朴素。核心方法就几个initialize握手、tools/list列工具、tools/call调工具。理解了这三个你就理解了 MCP 的 80%。6.2 手写一个最小 MCP 客户端我不建议一上来就用官方 SDK手写一遍你会对协议有肌肉记忆。核心逻辑是这样的class McpClient { private nextId 1; private pending new Mapnumber, (result: any) void(); async request(method: string, params: any): Promiseany { const id this.nextId; const message { jsonrpc: 2.0, id, method, params }; this.send(message); return new Promise((resolve) { this.pending.set(id, resolve); }); } private handleMessage(raw: string) { const msg JSON.parse(raw); if (msg.id this.pending.has(msg.id)) { this.pending.get(msg.id)!(msg.result); this.pending.delete(msg.id); } } }握手流程是先发initialize拿到 server 的能力声明再发notifications/initialized通知。然后就能tools/list拿工具列表把 MCP 工具转换成我们框架内部的工具格式。6.3 传输层stdio 和 HTTP 的取舍MCP 支持多种传输方式最常用的是 stdio 和 HTTP。stdio适合本地工具比如文件操作、执行命令。客户端启动一个子进程通过标准输入输出通信。优点是简单、无需网络、权限可控。缺点是只能本地用跨机器不行。HTTP适合远程工具比如调用某个云服务。优点是跨网络缺点是得处理鉴权、超时、重连。我的框架里两种都支持通过配置切换。stdio 的实现要注意消息边界——MCP 用换行分隔消息但 JSON 里可能有换行所以得按行读再拼。我一开始没处理这个遇到多行 JSON 就解析失败。// stdio 传输按行读取处理跨行 JSON const rl readline.createInterface({ input: child.stdout }); let buffer ; rl.on(line, (line) { buffer line; try { const msg JSON.parse(buffer); buffer ; this.handleMessage(msg); } catch { // 解析失败说明 JSON 还没收完继续攒 } });6.4 MCP 工具的 schema 转换MCP 返回的工具定义里inputSchema就是标准 JSON Schema可以直接转成 OpenAI 的parameters格式。但有个细节MCP 的 schema 里可能包含$schema、title这些字段OpenAI 不认得过滤掉。function mcpToolToOpenAI(tool: McpTool) { const { $schema, title, ...params } tool.inputSchema; return { type: function, function: { name: tool.name, description: tool.description, parameters: params, } }; }转换完之后MCP 工具就和内置工具一样统一进注册中心模型根本分不清哪个是内置的、哪个是 MCP 来的。这就是协议标准化的好处。7. 实测中踩过的坑与排查思路7.1 模型不调工具只输出文本这是最常见的问题。模型明明该调工具却直接给你一段我建议你这样做的文本。排查顺序是这样的先看工具描述是否清晰。如果描述模糊模型会倾向于不调。我遇到过一次工具叫search描述是搜索模型完全不知道搜什么、怎么搜干脆不调。改成在项目代码库中按关键词搜索文件内容返回匹配的文件路径和行号之后立刻就调了。再看system prompt 有没有明确要求。如果 system 里没提你可以使用工具有些模型会保守地不调。加一句当需要获取外部信息时优先使用提供的工具能显著提升调用率。最后看模型本身。不同模型对工具调用的积极性差别很大。同一个 prompt有的模型调得很勤有的几乎不调。这个只能靠实测选型。7.2 工具参数 JSON 解析失败流式拼接时arguments拼出来的字符串偶尔不是合法 JSON。原因通常是分片顺序错乱或者某个分片丢了。我的排查方法是把原始分片全部打日志看拼接过程。有一次发现某个服务的分片里arguments字段是null而不是空字符串我直接就变成了null{...}自然解析失败。加上判空之后就正常了。兜底方案是解析失败时尝试用正则提取出 JSON 部分或者直接把原始字符串作为错误信息返回给模型让它重新生成参数。7.3 循环停不下来前面提过重复检测但还有一种情况模型每轮调用的工具不同但目标始终达不到。比如它想读一个不存在的文件先试readFile失败后试listDir再试readFile换个路径来回折腾。我的处理是加一个总 token 预算。每次调用前累加估算 token超过预算就强制终止返回任务因超出预算而中断。这个预算默认设 5 万 token对大多数任务够用也能防止失控。7.4 流式输出和工具执行顺序错乱前端展示时用户希望看到模型思考-调用工具-工具结果-继续思考的顺序。但如果工具执行是异步的而文本流还在继续顺序就会乱。我的做法是在流里插入事件标记。文本 chunk 推text事件工具开始执行推tool_start事件工具结束推tool_end事件。前端按事件顺序渲染就不会乱。这个设计后来证明非常有用前端可以据此做工具执行中的 loading 状态。8. 一些让框架更好用的经验补充8.1 日志要能还原完整决策链Agent 出问题时你需要的不是报错了而是模型为什么这么决策。我的日志记录了每一轮的完整消息、模型的原始响应、工具调用的参数和结果。这样出问题时我能像看录像一样回放整个决策过程。日志格式我用 JSON Lines每行一个事件方便用jq过滤。比如查所有工具调用cat agent.log | jq select(.typetool_call)。8.2 给模型一个思考字段我在工具 schema 里加了一个可选的reasoning参数让模型在调用工具前先说明为什么调这个工具。这个字段不参与实际执行但会出现在日志里。实测下来加了之后模型的工具选择准确率有提升因为说出来本身就是一种约束。8.3 超时控制要分层模型调用、工具执行、整个循环三层都要有超时。模型调用超时设 60 秒工具执行超时设 30 秒整个循环超时设 5 分钟。任何一层超时都要能优雅退出返回已完成的部分结果而不是直接崩掉。8.4 别急着上多 Agent我见过太多项目一上来就搞多 Agent 协作结果调试成本爆炸。单 Agent 加好工具能解决 80% 的问题。多 Agent 的价值在于任务可以明确拆分且需要不同专长时否则只是徒增复杂度和 token 消耗。我的建议是先把单 Agent 循环跑稳工具生态建好真的遇到瓶颈了再考虑多 Agent。8.5 版本兼容的坑TypeScript 生态变化快有些配置项会弃用。比如baseUrl选项在较新版本里已经标记弃用未来会移除。如果你在tsconfig.json里用了它做路径别名建议尽早迁移到paths配合moduleResolution: bundler的方案。这类问题不影响功能但会在编译时刷一堆警告早点处理省心。9. 写在最后的一点个人体会这个框架我从第一版能跑到现在稳定用前后改了七八轮。最大的收获不是代码本身而是对 Agent 行为的理解。以前用现成框架模型不调工具我就换 prompt调错了我就加描述全靠试。自己写完之后我能从消息历史、工具 schema、循环状态三个层面去定位问题效率完全不一样。如果你也想动手我的建议是先跑通最小闭环一个模型、一个工具、一个循环能完成读文件并总结就行。别一上来就搞 MCP、搞多 Agent、搞复杂工具链。最小闭环跑通之后你会发现后面加什么都是在这个骨架上挂东西心里特别有底。代码我整理成了一个不到 600 行的仓库核心逻辑都在agent/loop.ts和tools/registry.ts两个文件里。真正值钱的不是行数是每一行背后为什么这么写的判断。这些判断只有你自己写一遍才会长在身上。
企业数字化 ERP 产品动态
相关推荐
零基础Python AI学习路线:从NumPy、Pandas到PyTorch实战 1. 为什么我建议你走这条Python AI学习路线先说说我自己的情况。三年前我从一个只会写Excel公式的运营岗,硬生生靠着自学Python和AI相关技术,转到了现在的算法工程岗位。中间踩过的坑、浪费过的时间、买过没看完的课,加起来能写一本血泪史。所… · 2026/11/4 21:48:01
VL53L1X精准驱动:绕过HAL库实现符合时序规范的GPIO模拟I2C 简介:本资源是一套面向嵌入式初学者与STM32开发者的VL53L1X飞行时间(ToF)激光测距传感器驱动实践例程,聚焦于STM32平台下的硬件配置、I2C通信实现与高精度距离测量功能开发。项目基于STM32CubeMX图形化配置生成初始化代码… · 2026/11/4 21:48:01
BSPC投稿实战:从选题匹配到审稿回复的完整流程与避坑指南 1. 为什么要把BSPC投稿当成一个“项目”来做第一次往Biomedical Signal Processing and Control(下面统一简称 BSPC)投稿的时候,我把它当成了一次“写完点提交”的动作,结果前后折腾了将近七个月,经历了一次 desk reje… · 2026/11/4 21:48:01
OpenStack Cinder备份可靠性增强:三重校验与状态机治理 简介:这是一份面向OpenStack运维工程师与云平台开发者的Python工具脚本,专为简化Cinder卷备份/还原流程而设计,有效应对Cinder Backup Service在多租户支持、备份可见性控制及自动轮换等方面的原生限制。资源包共4个文件(11KB&… · 2026/11/6 7:58:33
电商履约系统架构设计与核心流程解析 1. 项目概述 "履约全局"这个概念在电商领域已经存在多年,但真正能把商品从仓库到用户手中的全链路讲透的人并不多。作为经历过完整电商系统开发的老兵,我想用这篇长文把履约环节的每个技术细节掰开揉碎,分享给正在这个领域奋斗的同… · 2026/11/6 7:58:33
字符串解码算法:栈结构处理嵌套编码 1. 问题背景与需求分析 字符串解码是算法面试中的经典题型,力扣第394题要求实现一个能够处理嵌套编码字符串的解码器。这类问题在实际开发中有着广泛的应用场景,比如JSON解析、模板引擎处理、配置文件读取等场景都会遇到类似的字符串解析需求。 给定一个… · 2026/11/6 7:58:33
基于Prometheus的企业网站自动化监控方案 1. 项目概述 在企业IT运维工作中,网站可用性监控是保障业务连续性的基础环节。传统的人工巡检方式效率低下,而市面上的SaaS监控服务又往往存在价格昂贵、数据隐私等问题。本文将分享一套基于开源工具Prometheus的网站监控方案,实现从探测到告… · 2026/11/6 7:58:33
电商履约系统架构设计与高并发优化实践 1. 项目概述 在电商行业摸爬滚打多年,我深刻体会到履约系统才是真正考验技术团队功力的战场。商品从下单到交付用户手中,看似简单的流程背后隐藏着数十个关键环节和上百个技术决策点。今天我就以一线开发者的视角,带大家完整拆解这个"黑… · 2026/11/6 7:58:33
E3-1231 v3+B85黑苹果EFI配置与排错指南 简介:这份EFI引导包面向使用Intel E3 1231V3处理器与B85 D3H主板的黑苹果用户,提供一套围绕该硬件组合整理的引导配置,重点覆盖声卡、显卡及常见外设的兼容性适配,适合具备一定黑苹果安装基础、希望减少反复调试的玩家参考。压缩包… · 2026/11/6 7:57:33
双极步进电机控制实战: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/5 10:27:59
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/5 10:28:03
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/5 10:28:07
三菱PLC数据采集到数据库的时序设计:Python+MC协议实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 1:02:11
ASIC主模式RTL设计:状态机与三态驱动的流片级实现 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 1:02:11
STM32嵌入式C++开发:GDB调试与Renode仿真实战指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 1:02:11
双极步进电机控制实战: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/5 10:27:59
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/5 10:28:03
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/5 10:28:07