DeepSeek Harness 源码解读(一):从 dsh 入口追到第一次模型请求

发布时间:2026/10/27 23:08:09

DeepSeek Harness 源码解读(一):从 dsh 入口追到第一次模型请求 第一次打开 DeepSeek Harness 仓库我做了一件很自然、但没什么用的事搜索main再找一个像AgentApp或AgentRuntime的总控类。没找到。packages/下面是 Agent、Session、Tools、LLM、Shell、Sandbox 等几十个包。每个名字都认识放在一起却很难看出程序究竟从哪里启动。沿着目录挨个读很快就会在接口、插件和配置文件之间迷路。后来我换了个办法不再猜哪个类最重要而是从一条真的能跑起来的命令往下追pnpmdsh--profileheadless列出这个仓库最值得先读的三个文件我只追一个问题这段文字经过哪些代码才变成第一次模型请求这条线不算长却会经过 CLI 分发、Profile 组合、Cordis 装载、服务注册、Agent 创建和 Session 记录。走完以后仓库里那些看起来分散的包就有了位置。项目地址deepseek-ai/deepseek-harness源码基线0.1.0-rc.5bin.ts没有启动 Agent入口在apps/cli/src/bin.ts。我原本以为这里至少会创建模型客户端实际代码只做命令解析和模式分发。删去另外两个分支后profile路径大致是这样constinvocationparseDshArgs(process.argv.slice(2),readVersion())switch(invocation.mode){caseprofile:{const{runProfile}awaitimport(./profile-boot.ts)awaitrunProfile({environment:loadLayeredEnv(dsh),profile:invocation.profile,patchFiles:invocation.patches,args:invocation.args,})break}// plugin、dump-config 等分支略}bin.ts能看懂--profile headless也能把后面的任务参数交给runProfile()但它不知道本次使用哪个模型、有哪些工具甚至不知道 Agent Loop 是否存在。这里的动态导入也很实在运行 Profile 时才加载profile-boot.ts执行插件管理或配置导出时不必顺手拉起整套启动逻辑。所以入口文件更像一个分诊台。它把命令送到对应流程真正的运行时还在后面。headless不是布尔开关接着看packages/boot/app-boot/src/profile.ts。仓库内置的 Profile 模板只有两组exportconstPROFILE_TEMPLATES:Recordstring,readonlystring[]{web:[deepseek-ai/dsh-base,deepseek-ai/dsh-web-app],headless:[deepseek-ai/dsh-base,deepseek-ai/dsh-headless],}headless并不是if (headless) ...这样的功能开关。它表示先装基础 Bundle再叠加 Headless Bundle。这里的 Bundle 也不是常说的前端打包产物。每个 Bundle 提供一层cordis.patch.yml往插件树里插入或修改配置。dsh-base放入模型、Session、Agent、工具等通用能力dsh-headless再补上一条命令行任务的直接驱动器。apps/cli/src/profile-boot.ts会按顺序合并这些层Bundle patches → 当前 Profile 的 cordis.patch.yml → $DSH_HOME/cordis.patch.yml → 命令行 --patch → 必要时追加 telemetry 禁用补丁后面的补丁可以覆盖前面的配置。也就是说源码里有某个插件不等于这次进程一定装了它同一个插件存在也不等于它使用仓库里的默认参数。读到这里我先记住了一个排查命令pnpmdsh--profileheadless --dump-config与其只翻packages/猜运行时不如先看这次启动最终合成了哪棵插件树。boot()先造一个空的 Context配置准备好以后流程进入packages/boot/app-boot/src/index.ts的boot()。把错误包装和退出检查暂时拿掉主干很短constctxnewContext()try{ctx.baseUrlpathToFileURL(dirname(absoluteConfigPath)).href/ctx.provide(dshHomePath,dshHomePath)awaitctx.plugin(Loader)awaitprepare?.(ctx)awaitmountRootInclude(ctx,absoluteConfigPath,patches,bareModuleBaseUrl)awaitctx.get(loader)?.await()awaitassertEntriesActivated(ctx,binName)returnctx}catch(cause){awaitctx.fiber.dispose()throwcause}这里最值得注意的不是 Loader而是没有出现一长串new没有new SessionStore()没有new ToolRuntime()也没有new AgentLoop()。boot()只先创建一个 CordisContext再让 Loader 按刚才合成的配置逐项挂载插件。Session、LLM、Tools 等服务由各自插件注册进 Context不由启动函数集中组装。这种写法起初比手工创建对象绕一点但生命周期很清楚。某个插件注册的服务、监听器和清理动作都挂在 Fiber 上启动中途失败时释放根 Fiber 就能沿插件树回收已经创建的资源。我把前半段调用链画成了一张图。后面的文章还会不断回到这张图上Agent Loop 是配置装进来的再打开packages/bundle/base/cordis.patch.yml才能看到核心服务怎样进入 Context-id:llmname:deepseek-ai/dsh-llm-id:sessionname:deepseek-ai/dsh-session-id:agentname:deepseek-ai/dsh-agent-id:toolsname:deepseek-ai/dsh-tools-id:system-promptname:deepseek-ai/dsh-system-prompt-id:agent-loopname:deepseek-ai/dsh-agent-loop这些条目不是文档目录而是启动时真正会被 Loader 处理的插件项。packages/core/agent-loop/src/index.ts里的AgentLoop明确写出了自己依赖的服务exportclassAgentLoopextendsServiceimplementsAgentFactory{staticinject[agents,sessions,llm,tools,systemPrompt]constructor(ctx:Context,config:Config){super(ctx,agentLoop)// 其他初始化略ctx.effect(()ctx.agents.setFactory(this),agentLoop.setFactory())}}这五个名字基本就是主干ctx.agents保存活动 Agent并提供统一的创建入口ctx.sessions管理追加式会话日志ctx.llm负责找到模型 Provider 并发起请求ctx.tools维护工具注册和执行管线ctx.systemPrompt组装每一步要交给模型的提示词与工具定义。AgentLoop把它们串起来但不把具体模型、存储实现或 Shell 工具写死在自己内部。构造函数中的setFactory(this)才是关键交接Agent Registry 仍是公共入口当前装载的 Agent Loop 成为它背后的创建工厂。如果只看类名很容易把AgentLoop当成整个程序放回插件树里看它只是其中一个协调服务。Headless Runner 只负责投递任务基础能力都装好后packages/bundle/headless/src/index.ts的 Runner 才开始处理命令行里的那段文字。省去模型选择和输出汇总主要动作是这些const{agent}awaitagents.create({sessionId:SessionId(session-${randomUUID()}),meta:{cwd:process.cwd()},agentOptions:{provider:selection.provider,model:selection.model,},})awaitagent.whenIdle()agent.followup(createUserMessage({content:[{type:text,text:task}],source:{kind:user},}))awaitagent.whenIdle()awaitsessions.flush(agent.session)Runner 没有把task直接塞给某个模型 SDK。它先通过ctx.agents创建 Agent再把命令行文本包装成普通用户消息用followup()投进 Agent 的 Inbox。两个whenIdle()也不是多余的。第一个等 Agent 初始化完成第二个等这次任务把 Agent 跑回空闲随后sessions.flush()确保会话落盘Runner 才从事件中整理最终文本并决定退出码。这段代码解释了为什么 Web、Headless、ACP 和 SDK 不需要各写一套推理循环。它们接收输入的方式不同进入运行时以后走的仍是同一个 Agent 接口。第一次模型请求藏在 Step 里任务进入 Inbox 后Agent 会打开 Turn再进入 Step。真正的模型调用在packages/core/agent-loop/src/agent.tsconst{request,preparedCall}awaitthis.buildRequest(turn,step,assembly.tools,system,this.session.deriveMessages(),signal,)constassemblernewBlockAssembler()constchunkSeqs:number[][]conststreampreparedCall?.stream(request)??this.loopCtx.llm.stream(request)forawait(constchunkofstream){chunkSeqs.push(this.session.append(assistant/chunk,{turn,step,chunk}).seq,)assembler.push(chunk)}走到stream(request)这条命令才第一次碰到模型。请求也不是临时拼一段字符串历史消息来自session.deriveMessages()工具定义来自本 Step 的 Prompt 组装结果模型返回的流式分片则逐个写成assistant/chunk。分片收齐后还会生成一条assistant/message作为后续回放和下一步推理使用的正式消息。如果这条消息只有文本当前 Step 可以结束如果里面带着 tool callAgent Loop 会执行工具、记录tool/call和tool/result再开下一个 Step 请求模型继续回答。所以“一次命令行任务”不等于“一次模型请求”。一次 Turn 里可以有多个 Step每个 Step 才对应一次模型交互。这里先停住后面再单独拆 Turn、Step 和工具循环。走完以后仓库里仍然没有“主程序”这条调用链可以压成下面几步apps/cli/src/bin.ts → apps/cli/src/profile-boot.ts → packages/boot/app-boot/src/index.ts → packages/bundle/base/cordis.patch.yml → packages/bundle/headless/src/index.ts → packages/core/agent-loop/src/agent.ts我最后还是没找到一个包办所有事情的AgentApp因为这套代码本来就没有把运行时收进一个大对象。CLI 选择启动方式Profile 决定插件组合Cordis 管理装载和生命周期Agent Loop 协调服务Session 保存可恢复的事实具体模型和工具则从各自的 Provider 进入。所谓 Harness不是给模型 API 再包一层方法而是把这些原本散落在外面的运行条件组织起来。如果只做一次问答直接调用模型当然更短。可一旦要恢复会话、切换 Provider、挂载工具、处理审批与沙箱还要让 Web 和 CLI 共用同一套执行逻辑真正麻烦的就不再是那一行llm.stream()。下一篇从new Context()这里接着读Cordis 的 Service、Fiber、Effect 和waterfall究竟怎样把这棵插件树托起来。
延伸阅读

更多相关文章

2026/10/25 2:26:53

Python爬虫与数据可视化实战:天气轮播图期末大作业完整案例

这次我们来看一个特别适合 Python 期末大作业的完整项目:用 Python 爬取天气数据,再把结果做成数据可视化图表,最后以「天气轮播图」的形式在网页上展示。项目本身不复杂,但覆盖了数据采集、数据清洗、数据存储、可视化、Web 展示…

2026/10/26 22:12:40

Python金融统计实战:Pandas数据处理与回归策略回测

港科大这套《Python金融统计:Pandas到回归策略完整课》,核心就是一条主线:用 Python 处理金融数据,再用统计模型做回归分析,最后把回归结果落到一个可回测的策略框架里。它不是单纯讲 Pandas API,也不是单独…

2026/10/24 21:54:05

Malody乱力进阶:用replay复盘把96.14变成稳定输出的起点

打完 Extra-4 的 Pure Ruby,屏幕上跳出 96.14 的时候,我做的第一件事不是立刻换下一张谱,而是先把 replay 保存下来。很多玩 Malody 乱力进阶的人到这个阶段都会有类似的体感:成绩已经过了 95,但回看过程时总觉得有一部…

2026/10/27 23:07:09

园区科技服务平台建设:数字化升级与智能匹配实践

1. 园区科技服务平台建设背景与核心价值上周刚帮本地一个产业园区做完数字化升级,他们最头疼的问题就是企业服务效率低下。传统园区管理存在三大痛点:服务响应慢(平均48小时)、资源对接效率低(60%企业表示找不到合适供…

2026/10/27 23:07:09

T3技术栈实战:从tRPC到Prisma的类型安全全栈开发指南

1. 我为什么最终倒向 T3 技术栈:类型安全才是团队的隐形契约先说个让我印象特别深的经历。之前在一个全栈项目里,后端接口返回的字段叫userName,前端某个页面里手滑写成了username。这种错误在开发环境根本不会报错,因为前后端各有…

2026/10/27 23:07:09

Hadoop与Spark大数据处理框架对比与实战指南

1. 大数据处理框架的演进与现状2004年Google发表MapReduce论文时,可能没想到它会开启一个新时代。当时我在某互联网公司第一次接触Hadoop 0.19版本,那个需要手动配置每个DataNode的年代,现在想来恍如隔世。如今大数据生态已经枝繁叶茂&#x…

2026/10/27 23:07:09

跌倒检测数据集与YOLO模型训练实战指南

1. 项目背景与数据集价值跌倒检测是计算机视觉在健康监护领域的重要应用场景。这个包含4250张标注图像的数据集,同时提供VOC和YOLO两种格式,为算法研发者提供了开箱即用的训练素材。在实际养老院监控和家庭看护场景中,这类数据集能有效解决样…

2026/10/27 23:07:09

Hadoop与Spark大数据处理框架对比与性能优化

1. 大数据处理框架的演进与现状2004年Google发表MapReduce论文时,可能没想到它会开启一个新时代。当时我在大学实验室第一次接触Hadoop 0.1版本,那个需要手动编译、配置复杂的系统,如今已发展成企业级大数据标配。而Spark的诞生则像一场及时雨…

2026/10/27 23:02:08

华为逆变器Modbus TCP数据采集实战:从寄存器映射到本地落库

简介:这份资源面向从事智能设备监控、能源管理系统或工业自动化的开发者,聚焦如何通过Modbus TCP协议远程采集华为逆变器的运行数据。华为逆变器是光伏发电系统的核心部件,借助Modbus TCP可实时读取功率输出、电压、电流、效率等参数&#xf…

2026/10/27 14:24:03

全国计算机二级C语言真题PDF高效刷题指南

简介:这份PDF是全国计算机二级C语言考试的试题与答案解析,专为备考二级C语言的考生准备。内容涵盖C语言特点与设计理念、程序基本组成单位、main函数执行流程、函数嵌套调用规则、标识符命名规范、整型实型字符型数据类型、八进制十六进制数合法形式、运…

2026/10/27 9:34:54

requests+BeautifulSoup:爬虫抓取h1标题的入门实战指南

简介:一份以实战为导向的Python爬虫入门资源,面向具备基本编程基础、希望快速掌握网络数据抓取的初学者,可解决从零编写简单爬虫的常见困惑。实例以requests库发送HTTP请求、BeautifulSoup解析HTML文档,完整演示从网页中提取 标题…

2026/10/27 0:59:21

递推最小二乘参数辨识MATLAB编程:二阶系统与渐消记忆实现

简介:这是一篇面向控制系统、信号处理与机器学习等领域学习者的最小二乘参数辨识程序说明文档。资源共包含一个docx格式文档,压缩包大小七十八KB,内容围绕递推最小二乘算法和渐消记忆最小二乘算法展开:先构造一个极点位于单位圆内…

2026/10/27 0:02:28

WorkBuddy_新建任务与工作模式19_“模式选错”的典型代价

“模式选错”的典型代价 摘要 在软件工程中,设计模式、架构模式、并发模式乃至团队协作模式的选择,往往在项目早期就决定了后续的开发效率和维护成本。本文通过多个真实场景,剖析“模式选错”带来的典型代价——包括返工成本、技术债务累积、…

2026/10/27 0:02:28

WorkBuddy_新建任务与工作模式20_新手推荐路径

新手推荐路径 摘要 在 AI 辅助开发工具快速普及的今天,新手面对最多的困惑往往不是"工具够不够强",而是"我该从哪一步开始用"。本文提出一条经过实践验证的三阶段学习路径:先从 Ask(问答式交互)起…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑