ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

DeepSeek Harness + Cordis:可插拔Agent工具链的插件架构实践

DeepSeek Harness + Cordis:可插拔Agent工具链的插件架构实践 从“能用”到“好用”模型服务化绕不开的一个坎就是工具链的编排能力。最近我一直在折腾一个基于 DeepSeek 的自主 Agent 运行框架官方能力固然强但真到了业务落地那一步各种自定义工具、私有数据源、内部 API 的接入需求就会扑面而来。DeepSeek Harness 本身把模型调用、上下文管理、工具调用的底座做得比较干净但真正让它从一个“模型壳子”变成一个“生产力平台”的是我挂上去的那套 Cordis 插件架构。这篇文章就围绕我如何给这个项目设计并落地一套可靠、可插拔的插件体系把核心思路、基础概念、具体配置、踩过的坑一次性交代清楚。项目的定位是给有一定开发基础、希望把 DeepSeek 这类模型服务真正接进自己系统的人做参考不是科普文全是实打实的技术细节和工程取舍。1. 背景与架构思路1.1 为什么模型服务需要一层插件架构先说我之前遇到的实际问题。在没有插件架构之前我的 DeepSeek Harness 是一个单体服务模型请求进来之后直接走内置的几个固定工具像个搜索引擎、数据库查询、简单计算器这类。东西能跑但真要用起来会非常痛苦。比如要给某个业务加一个内部工单查询工具我要改主服务代码重新走一遍测试和部署流程要调整工具的参数结构又得连带改模型侧的提示词和工具描述。几轮下来主服务代码膨胀得厉害业务方也没有办法自己上手扩展功能所有需求都堆我这儿。发现根本矛盾点在哪没有工具调用链路的本质是动态的而单体的服务设计是静态的。模型要执行什么工具的决策在运行时才发生如果工具清单在编译时写死那等于强迫系统只服务已知场景任何一个新需求都要动主工程。我需要的是一个能让我在不停机的情况下挂载新工具、新处理逻辑、新的数据源的架构插件化是唯一合理的解法。Cordis 在这里承担的角色是插件容器。插件不直接和 DeepSeek Harness 的核心逻辑耦合它通过统一接口注册进来。每次模型发起一次工具调用容器负责查找对应插件、传递参数、执行逻辑、收集结果再把结果回传给模型整个过程对插件而言是黑盒的。插件的开发者只关心“给我这个输入我返回一个输出”至于这个请求是怎么来的、执行完的结果要去哪里都不需要考虑这些脏活累活全在 Cordis 容器里处理。1.2 Cordis 的核心设计目标我把这套插件架构的设计目标归纳成四条每一条都是从前面的痛苦经历里反向推导出来的。第一解耦。插件绝对不允许直接引用主项目内部的数据结构和函数大家只对一份稳定的接口协议负责。第二生命周期可控。插件不能是“装上了就永远活着”必须支持启动、停止、重载、卸载这些完整状态否则生产环境一跑就是几个月版本升级只能靠重启硬扛。第三资源隔离。某个插件出现 OOM 或者死循环不能拖垮整个 Harness 进程所以插件不能直接干在主进程里进程级别的隔离是底线。第四可观测。每个插件从接到请求到返回结果全链路都得有日志和追踪信息不然在生产环境里排查问题就是大海捞针。基于这四条我最终采用的方案是Cordis 容器作为一个独立的管理进程插件以子进程方式运行容器和插件之间用管道通信。这个设计谈不上多新颖但胜在简单可靠后面遇到的很多坑都能在这一层设计上找到解释。2. 插件协议与生命周期管理2.1 插件接口的标准化设计插件协议的标准化是整个架构里最关键的一步。协议定得好插件可以无限扩展而主框架纹丝不动协议定得烂插件写一个崩一个最后变成人人都不想碰的大坑。我定义的插件接口核心是三个组成部分。第一部分是插件描述文件一个插件目录下必须有一个 manifest 文件里面写清楚插件名字、版本号、作者、权限声明、入口文件路径这些元信息。第二部分是插件的入口逻辑入口文件必须导出一个注册函数这个函数返回一个描述插件能力的对象比如支持哪些工具、工具的调用签名是什么。第三部分是执行函数每个工具对应一个具体的函数函数接收一个上下文对象执行完返回一个结果这个结果会被容器序列化后回传给模型。看一下插件描述文件的具体例子。manifest 的格式我用的是最通用的 JSON结构大致长这样{ name: internal-ticket-plugin, version: 1.2.0, entry: src/index.js, permissions: [http:internal-api, fs:read-temp], tools: [ { name: query_ticket, description: 根据工单号查询内部系统的工单详情, parameters: { type: object, properties: { ticket_id: { type: string, description: 工单号 } }, required: [ticket_id] } } ] }注意看 manifest 里出现的permissions字段。这个设计我后来反思过很多次它本身是一个安全加分项但也带来了不小的复杂度。插件声明自己需要什么权限容器在加载的时候校验运行的时候拦截。好处是安全边界清晰坏处是权限声明太粗粒度会导致插件写起来不够灵活。比如一个插件既想读文件又想发 HTTP 请求在 manifest 里声明了多个权限万一某个操作只用在某个边缘场景这个权限就被无谓地放大了。现阶段我把权限校验做成一个纯注册表插件声明了就允许不声明就拒绝后续可以考虑做成更细的“运行时按需授权”但这个优先级目前不高。2.2 插件生命周期的五个状态插件架构里生命周期管理是最容易做出“看起来很完善但根本没人用”的部分。我不希望搞一堆华而不实的状态机。实际运行下来我的插件生命周期只需要五个状态注册、激活、运行、停止、卸载。注册状态发生在插件第一次被扫描到的时候。Cordis 容器启动时扫描指定目录下的所有 manifest 文件把它们登记到内部注册表里此时插件代码尚未执行只是信息被记录。激活状态意味着容器真正加载了插件的入口模块执行了注册函数拿到了插件暴露出来的工具清单。运行状态就很好理解了插件正处理着某次工具调用的请求。停止状态不是删除仅仅是让插件退出可被调用的列表但代码和注册信息都还在可以随时再激活。卸载状态是把插件的注册信息彻底从容器里移除数据清理干净。这里有一个非常重要的细节激活和运行一定要分开。一个插件可能在激活时初始化一堆连接池或者本地缓存这些东西在插件停止之后不应该继续占资源但代码和注册信息又可以随时恢复上下文。如果把这些状态混在一起插件重启的成本就会变得很高。我测试的时候试过一个插件激活后又停止再激活如果连接池没能正确释放第二次激活往往就出现端口被占、文件句柄泄漏这些问题。所以我的设计是激活阶段只做轻量初始化重资源延迟到第一次真正调用时才初始化需要的时候现拉。2.3 进程隔离与通信机制前面提到插件执行在子进程里这里详细说一下工作原理。Cordis 容器主进程会为每一个插件拉起一个独立的子进程子进程内部跑的是插件的入口文件。子进程和容器之间通过进程标准输入输出stdio上的管道来通信数据格式是 JSON-RPC 风格的消息。为什么选这个而不是更炫的 gRPC 或者消息队列因为插件进程和容器进程必然在同一个节点上跨网络的方案在这里是杀鸡用牛刀。管道的延迟在微秒级别性能完全够用JSON 格式又足够通用任何语言都能解析进程间通信的模型也足够简单不会引入新的故障点。插件崩溃了容器只需要重启子进程状态做一次快照恢复即可不影响其他插件。每次工具调用的处理流程是这样的容器收到模型发来的工具调用请求先解析出目标工具名和参数从注册表里查到对应的插件进程标识然后把请求消息写到该进程的管道里阻塞等待响应收到响应后把结果返回给模型。这个流程逻辑是同步的但 IO 层实际是异步多路复用所以同一个容器的多个插件可以并发处理不同的请求互不阻塞。3. 核心机制拆解上下文、配置与回调3.1 上下文传递机制插件在被调用时很多情况下需要知道一些调用背景信息。比如用户问了一句“帮我查一下最近的五张工单”模型决定调用工单查询插件但“五个”这个数字已经包含在工具参数里了插件其实还需要知道一个类似于请求 ID、会话 ID 这样的外部上下文这样才能把查询结果和当前的会话挂上钩。我在 Cordis 里定义了一个独立的上下文对象每次模型发起工具调用时由容器构建并传给插件。上下文对象里包含这些核心字段请求的唯一标识、当前会话标识、调用触发源、可用的 API 令牌、以及一个 SDK 客户端实例。SDK 客户端实例是给插件用来回调 Harness 主服务的。比如插件发现结果需要进一步检索它可以通过 SDK 再发起一次独立的处理请求而不需要自己处理跟主服务通信的细节。SDK 客户端是如何注入的这里我偷了个懒没有用依赖注入这类重型框架直接通过进程启动时的环境变量透传一个内部通信端点插件入口在激活时通过这个端点初始化自己的 SDK 客户端。后续每次请求都会带一个请求级令牌SDK 客户端负责把令牌附加到所有请求头里代码里不用关心鉴权细节SDK 内部已经处理了。3.2 配置管理和动态更新插件不能把配置写死在代码里。一个查询类插件数据源地址可能因环境而异开发环境、测试环境、生产环境的配置肯定不一样。我把插件的配置拆成两层静态配置和动态配置。静态配置放在插件自己的 config 目录下部署的时候由运维统一分发主要面向那些不需要频繁改动的参数比如服务端口、超时时间、默认的并发数。动态配置则由 Cordis 容器统一管理容器提供一个配置服务中心插件可以在运行时通过 SDK 拉取自己的配置并且支持配置变更时推送通知。这种设计的好处是当业务上需要临时调整某个插件的阈值时我只需要通过容器暴露的管理接口修改配置通知插件更新内部状态完全不用触发插件重启。配置动态更新最怕出现的问题就是插件在更新配置的过程中收到了请求结果前后行为不一致。我踩过一次坑一个插件更新了缓存策略配置但更新流程里有一个竞态窗口一部分请求走了新策略一部分走了旧策略最后返回给模型的结果口径都不统一。后来我在更新流程里加了一个配置版本号请求上下文中带上版本号如果发现配置在请求处理过程中发生过变更就强制让这次请求重跑一遍确保一次请求使用的是同一个配置版本。3.3 回调机制与结果返回很多插件做完一次工具调用后结果不应该直接吞掉而是需要触发后续动作。典型场景是把处理结果写回到会话记录或者通知另一个插件做二次处理。为了让这种联动不把插件和插件之间直接耦合起来我设计了一套基于事件代理的回调机制。插件在结果返回时可以选择附带一个回调事件事件内容包括目标事件主题和负载数据。容器收到插件执行结果后除了把结果回传给模型同时会把回调事件分发到事件总线上。任何关心这个事件的插件都可以订阅对应主题收到事件后做自己的处理。这个机制让插件之间形成了一个松耦合的事件流每个插件只关心自己感兴趣的事件不需要知道事件是从哪来的也不需要知道事件是被谁订阅的。一个特别实用的场景是用户授权流。一个插件处理完某次需要用户额外授权的操作它无法自己等待用户授权结果因为用户可能在十分钟之后才回来点“同意”。这时候插件返回一个挂起状态同时注册一个授权完成事件回调。授权服务挂载在另外的插件上用户授权完成后发布事件原始的插件通过订阅这个事件恢复未完成的操作。这整个链路都是异步的模型侧则通过轮询拿到最终结果。4. 实操落地从零搭建一个 Cordis 插件4.1 插件骨架代码的组织方式理论部分讲了这么多直接上手写一个插件是最快的理解方式。我以第一个实际用起来的内网知识库插件为例从头到尾说清楚搭建过程。这个插件的功能是模型在对话过程中如果遇到需要查询内部知识库的问题就调用它完成检索并返回相关文档摘要。插件目录结构保持极简我倾向于把能省的都省掉怎么直观怎么来。internal-knowledge-plugin/ ├── manifest.json ├── package.json ├── src/ │ ├── index.js │ └── searcher.js └── config/ └── static.yaml入口文件src/index.js是插件对外暴露的唯一窗口。它导出一个register函数这个函数在容器激活插件时被调用返回值告诉容器“我能做什么”。具体代码如下module.exports.register function (context) { return { tools: [ { name: search_internal_kb, description: 在内部知识库中搜索指定关键词返回相关文档的标题和摘要, parameters: { type: object, properties: { query: { type: string, description: 搜索关键词 }, limit: { type: integer, description: 最多返回多少条结果默认是 5 } }, required: [query] } } ], worker: createSearchWorker(context) }; };context参数是容器注入的 SDK 客户端和配置实例worker则是一个具体负责执行工具调用的对象。看这个结构就知道插件的核心逻辑其实不在注册函数里而在 worker 对象中。4.2 工具调用处理逻辑的编写worker 对象里最重要的方法是execute它接收工具名、参数和上下文返回执行结果。知识库插件的核心查找逻辑相对直接拿到参数中的query和limit后去调用内部知识库的检索 API再从返回结果中提取关键信息。// src/searcher.js const axios require(axios); class KnowledgeSearcher { constructor(config, sdk) { this.apiBase config.apiBase; this.sdk sdk; this.maxRetry 3; } async search(query, limit 5) { const url ${this.apiBase}/api/v1/search; for (let attempt 1; attempt this.maxRetry; attempt) { try { const response await axios.post(url, { query, limit }, { timeout: 3000, headers: { X-SDK-Token: this.sdk.getRequestToken() } }); return response.data.results.map((item) ({ title: item.title, snippet: item.snippet || item.content.slice(0, 200), url: item.url })); } catch (error) { if (attempt this.maxRetry) { throw new Error(知识库检索请求失败: ${error.message}); } // 简单退避等待 200ms * attempt await new Promise((resolve) setTimeout(resolve, 200 * attempt)); } } return []; } } module.exports KnowledgeSearcher;这里注意一个细节搜索请求里我主动加上了 SDK 的请求令牌。这个令牌是容器在构建上下文时生成的并随本次工具调用一起传给了插件。如果内部知识库的 API 本身也是完全受控的内部服务这里甚至可以通过令牌做身份透传确保每一个检索请求都能追踪到是哪一次对话触发的对审计来说非常关键。4.3 配置声明与权限管理再来说配置和权限。这个插件在manifest.json里声明了自己需要访问内网知识库 API以及可能需要临时读取本地缓存文件的权限。权限声明落在permissions数组里容器在加载插件时会做一次字符串匹配发现声明了不支持的权限就直接拒绝激活并返回报错。{ permissions: [ http:search-internal, fs:read-temp, callback:publish ] }配置方面静态配置我放在config/static.yaml下因为涉及的知识库 API 地址在不同环境肯定不一样这属于部署相关的信息让运维统一管理比较省心。apiBase: http://kb-internal.example.internal searchTimeoutMs: 3000 defaultResultLimit: 5容器加载插件时会把 static 配置解析成一个字典对象和 SDK 客户端一起作为参数传入注册函数。插件内部拿到config.apiBase就知道将来要往哪个端点发送检索请求了。动态配置的话可以通过 SDK 客户端的getConfig()方法随时拉取不必通过文件的方式硬加载。4.4 在 Cordis 容器里完成注册与调试插件写完后并不是复制到目录里就能自动跑起来。Cordis 容器需要执行一次扫描操作把新发现的插件注册进来。我的工作流一般是先用本地模式启动容器指定--dev-plugins参数指向插件目录然后对插件做一次单独的调用测试确认核心功能正常后才允许它进入“激活”状态。调试阶段有一个非常实用的手段容器为每个插件开放了一个独立的管理接口路径通过浏览器或者命令行工具可以直接调用插件的单个工具并看到返回的原始 JSON。这样我就不需要每次调试都经过模型判断这一层省掉了大量不可控的中间步骤。例如可以这样curl -X POST http://localhost:8701/api/v1/plugins/dev/knowledge/execute \ -H Content-Type: application/json \ -d {tool: search_internal_kb, arguments: {query: 什么是事件溯源, limit: 3}}返回的结果直接就是插件处理后的工具结果原文格式清晰定位问题非常高效。等这个插件在开发和测试环境都跑得比较稳了再把它从开发模式切到正式加载模式并入正常的插件管理流程。5. 安全、异常处理与性能调优5.1 插件进程的安全边界插件在带来扩展性的同时也引入了新的安全攻击面。一个插件可能是内部团队写的也可能是从社区下载的甚至可能是某个临时工匆匆忙忙写的别笑这种场景真实存在。如果插件代码能直接访问主进程内存或者宿主机的任意文件那这个架构整体的安全性就形同虚设。我在 Cordis 中实现了三层安全措施。最基础的是操作系统层面插件子进程以低权限用户运行只具备插件目录的读写权限宿主机的其他目录一概不可访问。第二层是容器的权限校验插件 manifest 声明了多少权限容器就只给多少权限比如一个插件没有声明外呼能力容器就会在消息层面上禁止它访问外部地址。第三层是运行时限制容器对插件进程的 CPU 和内存占用设置配额一旦超过阈值直接终止进程并告警避免某个失控的插件拖垮整个节点。这里必须强调一个容易被忽视的地方安全措施必须在插件设计阶段就考虑绝对不能后补。我最初为了跑一个 Python 写的插件给所有插件子进程统一开了大权限后来排查一个异常时发现那个 Python 插件进程已经在系统里留下了若干不该出现的临时文件。虽然没有造成实质损失但这个教训告诉我权限的粗粒度化会掩盖掉真实的风险暴露。现在我的规则是权限从严绝不为了省事放开闸门。5.2 异常处理与故障恢复插件执行过程中抛异常是家常便饭关键是容器以什么样的策略来应对。我把异常分成几类每类的处置逻辑不一样。一类是参数校验错误模型给的参数少了字段或者类型不对这类错误应该直接返回给模型让模型修正调用参数后重试。一类是业务逻辑错误插件本身能感知到问题的原因这类错误要带上错误码和人类可读的描述方便排查。还有一类是基础设施错误比如网络超时、下游服务不可用这类错误我会触发插件的重试机制。重试不能无脑重试。我定义了一个指数退避加抖动exponential backoff with jitter的策略第一次重试等待 200ms第二次 400ms第三次 800ms最大三次每次等待时间上附加 0 到 50ms 的随机抖动。为什么要加抖动如果多个插件同时失败并同时重试它们会在同一时刻发起风暴式请求导致下游系统进一步过载。加上抖动后请求的时机被打散整体压力曲线会平滑很多。插件进程崩溃则是另一个棘手的问题。子进程如果直接挂掉了容器要能检测到并决定是立即重启还是延迟恢复。我在容器里维护了一个插件健康状态表进程心跳丢失超过阈值就标记为不健康同时进入一段冷却期。冷却期内容器会拒绝为该插件分配新的调用请求并把错误原因透传给模型建议用户稍后再试。冷却期结束后容器自动尝试重启插件并做一次轻量健康检查确认插件重新进入可用状态。5.3 请求超时与并发控制模型调用工具时一般会设置一个总的响应超时时间比如 30 秒而一个工具调用的耗时往往会远超这个数。比如一个插件需要调用一个慢速外部 API3 秒超时可能根本不够用。为了不让插件的执行时间拖垮整个 Harness 的响应链路我在容器层设置了三种超时控制。连接超时负责控制插件与外部服务建立连接的最长时间一般 2 秒到 5 秒。读取超时控制从外部服务读取响应数据的最大等待时间这个根据服务特性而定像知识库检索这类的内部服务一般 8 秒。最后是整体执行超时从容器接收到调用请求到插件返回结果的最长允许时间我一般设 25 秒略短于模型对 Harness 的总超时这样即使插件超时了容器还有裕量把错误信息封装回传给模型。并发控制方面容器维护一个插件的并发令牌桶。每个插件可以配置自己的最大并发数默认是 4。当某个插件收到的请求数超过令牌配额时新增请求直接进入等待队列而不是直接压给插件进程。这个设计非常有效因为一个脆弱的外部 API 完全可能因为插件侧的并发过高而被打挂限制插件并发实际上是保护下游服务。我实际遇到过一次典型事故一个数据同步插件在白天正常工作到了晚上任务集中触发的时间段插件收到的外部 API 请求瞬间翻了几倍外部服务扛不住开始大量返回 503插件内部的重试逻辑又给服务补了一记耳光整个链路雪崩。后来我把该插件的并发上限从 8 降到 3并增加请求缓冲队列服务就再也没有被打挂过。6. 常见问题与排查技巧6.1 插件无法加载的原因排查插件写好了但容器就是加载不进来这个问题的排查路径有一定的规律可循我总结成了一个小清单。先检查 manifest 文件是不是合法 JSON。这个听起来很像废话但实际上我自己就犯过不止一次错误某次手滑在结尾多留了一个逗号容器做 JSON 解析时报错但报错信息非常隐晦根本没指向 manifest 文件。排查了半天才发现是逗号的问题。所以现在的习惯就是 manifest 写好之后先单独做一轮 json 校验再丢到容器里加载。再检查插件的入口文件是不是真的存在、路径是不是和 manifest 里声明的完全一致。路径有个大坑是 Windows 风格的反斜杠Linux 环境下会直接失效。我的建议是无论什么平台manifest 里的路径一律用正斜杠/作为分隔符容器内部统一做路径标准化处理。还有一个挺隐蔽的问题插件的依赖没有安装完整。入口文件 require 了一个模块但插件目录下的 node_modules 并没有这个包容器加载时直接报 MODULE_NOT_FOUND。虽然报错很直白但如果你用了 pnpm 这类对镜像文件结构有特殊处理的包管理器偶尔会出现包本体在但入口找不到的诡异情况需要留意一下排查顺序。6.2 工具执行结果不符合预期工具能执行但返回的结果和模型期望的不一致这个问题的排查难度比直接报错要大得多。因为流程中涉及模型、容器、插件三方的信息交互任何一个环节产生偏差最终表现出来的都是“结果不对”。我一般先看模型传到容器里的工具参数原文。很多时候模型对参数理解的偏差会让插件拿到奇怪的值比如模型把时间格式传成了2025/12/17而插件期望的是2025-12-17不经过数据显示很难发现这种隐性偏差。容器管理接口正好支持查看每次工具调用的原始请求和响应我是强烈建议在调试阶段把原始请求日志开起来这个日志在问题定位时的价值怎么强调都不过分。接下来要确认插件的返回格式与协议定义是否一致。协议要求插件返回一个 JSON 对象但插件内部某段逻辑偷偷返回了一个数组容器把结果回传给模型时模型可能就无法正常解析了。这种问题一般也通过管理接口的响应体一眼就能发现但如果你不开日志只看对话层的表现排查难度会直线上升。最后还要考虑模型提示词层面对工具结果的依赖。有些工具的结果是给模型阅读的文本有些工具的结果是需要模型继续推理的结构化数据如果插件把结构化数据直接塞进了面向模型的纯文本模型理解起来就会比较吃力。这类问题就要回头去看工具描述字段里对返回格式的说明是否足够清晰。6.3 性能瓶颈的定位与优化插件架构带来的性能开销主要在进程间通信上。按我实测的数据单次管道通信本身的延迟几乎可以忽略但大规模并发请求时容器主进程的 IO 调度会成为瓶颈。插件越多、请求越密集这种压力就越明显。我的优化手段有三个层面。第一个层面是减少不必要的序列化和反序列化。插件返回的大块数据比如长文档原文如果在协议层传一遍在容器层又要解析一遍再传给模型时又要序列化一遍这个开销是好几层的。我目前的方案是对大负载的返回结果做直接透传容器只保留头部的元信息正文部分原样转发。第二个层面是插件进程的复用和预热。插件激活后进程常驻内存避免反复拉起的开销。但是常驻也有问题就是时间长了插件内部的状态可能变得很脏。我后来实验出的方案是容器在插件闲置超过一定时间后自动做一次轻量的年轻代回收定期创建一个新的空白进程作为候选等下次请求到来时切换到新进程老进程再安全退出。这样既保住了进程复用的效率又规避了状态残留的隐患。第三个层面是容量规划。当你准备在一个节点上跑几十个插件时要重新考虑资源配额。插件子进程占用的内存总量会随着插件数量的增长线性膨胀我建议在容器层面做一次总体内存预算以插件为单位做额度分配防止个别插件贪心吃掉所有可用内存。我在实践中通常把单个插件的内存上限设在 200MB 到 500MB 之间具体取决于插件实际执行的业务类型有的插件处理的数据量本身就大给 500MB 也合理。7. 后续演进与经验沉淀把 Cordis 插件架构从设计到落地完整跑了一遍之后我个人的体会是插件架构的价值不在于“能插能拔”这个形式而在于它迫使你把系统的边界界定清楚。边界清楚了系统才能既保持稳定又具备敏捷扩展的可能性。DeepSeek Harness 的模型推理能力再强如果周边的工具生态无法生长那它终究只是一个演示级别的玩具。插件架构相当于给这个框架装了一个组织骨架各个子能力在这个骨架上各安其位模型则作为一个调度层坐镇中央。现在这个架构在我的部署环境里已经稳定运行了一段时间中间也经历过几次插件崩溃和配置误操作的事故但整体容错能力经受住了考验。后续我打算继续做几件事一是把插件的远程开发调试能力做得更顺手二是让插件的配置管理可视化三是尝试把插件协议迁移到 WASM 上提升跨语言兼容性。这些都是后续工作的方向但从当前的产品形态来说DeepSeek Harness 加 Cordis 插件架构这套组合已经让我从“频繁改主服务代码”的泥潭里彻底走了出来也让团队里其他成员能够独立地开发插件而不必理解整个 Harness 的内部机制。这个过程踩过的坑就是这篇文章想交代的所有的价值所在。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进