ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

DeepSeek Harness 高手进阶(11)综合实战:10 期教程读完,还是接不进自己的项目?一个项目走完,直接毕业

DeepSeek Harness 高手进阶(11)综合实战:10 期教程读完,还是接不进自己的项目?一个项目走完,直接毕业 1. 为什么教程读完十期项目还是接不进去我见过太多人把 DeepSeek Harness下称 dsh的教程从头刷到尾笔记记了一整本真到要把 dsh 塞进自己项目的时候卡在第一步不知道从哪下手。这不是你笨是教程的天然缺陷——每一期都在讲一个独立能力点但真实项目是这些能力点的组合中间那层怎么拼没人给你演示。这篇就是补这层。我们用一个不大不小的真实项目走完全程一个日志目录转 Markdown 周报的 CLI 工具weekly-report.mjs。它足够小一个会话能写完又足够全把 dsh 的 headless 模式、项目技能、定制插件、profile 配置、CI 接入全部串一遍。做完这个项目你对 dsh 的理解会从知道有这些功能变成知道它们怎么配合。适合谁读已经跑通过 dsh 基础会话、会写 profile 和插件骨架、想把它接进自己工具链或 CI 的人。如果你还没装过 dsh建议先补前几期的安装和 headless 入门本篇不铺垫基础。核心检索词先摆出来DeepSeek Harness 的 headless 模式怎么接入 CI、dsh 插件怎么写、dsh profile 怎么配、dsh 的 Key 和 API 通道怎么统一走 TaoToken。这几个问题下面每一节都会落到可复制的文件和命令上。我试过最省事的做法是先别急着写代码把这个项目要用到 dsh 的哪几个能力列成一张表然后一个一个接。周报生成器用到的能力清单是这样的——headless 调用把 dsh 当子进程跑、项目技能定义周报风格、定制插件统计 token 用量、profile 配置成本上限和插件装配、CI 流水线定时生成。五个能力对应五个交付物一个都不多。下面从环境隔离开始一步步走到流水线通过。2. TaoToken 前置把 Key 和 API 通道统一起来在动手写项目之前先把模型访问这层理顺。dsh 支持自定义模型接入你需要一个 Base URL、一个 API Key、一个 Model ID。这三样东西如果散落在各个项目里后面接 CI 会非常痛苦——每个仓库都要配一遍密钥管理一团乱。统一走 TaoToken 的通道能解决这个问题。它的 API 地址是https://taotoken.net/api兼容 OpenAI 风格的接口dsh 的 DeepSeek 模型配置直接填这个 Base URL 就行。你可以在控制台创建 API Key然后在 dsh 的凭据文件里引用。具体操作路径先到模型对话页面确认你要用的模型 ID比如deepseek-v4-flash这类再到 API Keys 页面生成一个 Key。这两个动作都在 TaoToken 官网完成地址是https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end。生成 Key 之后不要写进任何项目文件这一点后面会反复强调。dsh 的凭据解析顺序是继承环境变量 .credentials.yaml.env。在 CI 环境里环境变量注入是最干净的在本地开发时用隔离的DSH_HOME下的.credentials.yaml更稳妥。两种方式本篇都会用到。为什么强调统一通道因为一旦你把 Base URL 和 Key 收敛到一处后面换模型、调价格、加限流都只改一个地方。项目代码里只透传DSH_HOMEKey 的解析完全交给 dsh 的凭据服务工具代码一行都不碰密钥。这个纪律在接 CI 的时候价值最大——你不需要在 workflow 里硬编码任何东西只需要在仓库 secrets 里配一个DEEPSEEK_API_KEY。如果你打算长期跑编码类任务或者 Agent 工作流可以了解一下 Coding Plan它把模型调用和额度管理打包在一起适合需要稳定跑量的场景。接入文档在https://taotoken.net/api对应的文档页里面有各语言的调用示例和参数说明。前置工作就这些一个 Base URL、一个 Key、一个 Model ID。记住这三件套后面配置文件里会反复出现。3. 可复制配置profile、技能与插件骨架这一节是全文的核心所有配置片段都可以直接抄。先建工作区再配 profile然后写技能和插件。3.1 工作区与 DSH_HOME 隔离第一步永远是隔离环境。演示用的DSH_HOME和日常配置分开避免污染$env:DSH_HOME D:\work\tmp\11\home项目目录结构长这样D:\work\ ├── weekly-report\ │ ├── 需求.md │ ├── .dsh\skills\weekly-style\SKILL.md │ ├── plugins\weekly-usage\ │ ├── weekly-report.mjs │ ├── README.md │ └── .github\ │ ├── workflows\weekly.yml │ └── dsh\weekly.patch.yml └── tmp\11\home\ ├── .credentials.yaml └── profiles\weekly\日志素材放在项目外..\notes模拟日志目录由外部产生的真实场景。3.2 weekly profile 的 manifest 与补丁层profile 的本质是$DSH_HOME\profiles\name\目录里面用 manifest 声明 bundle 层叠用补丁层覆盖配置。先写package.json{ name: dsh-profile-weekly, private: true, dependencies: {}, dsh: { profile: { bundles: [deepseek-ai/dsh-base, deepseek-ai/dsh-headless] } } }再写补丁层cordis.patch.yml这里做两件事设成本上限、挂载插件# 成本上限按任务体积设定输出上限防预算失控 - id: llm config: maxTokens: 8192 # 定制插件weekly-usage - insert: - id: weekly-usage name: weekly-usage注意maxTokens这个 patch 是实打实省钱的——单次输出封顶长任务不会失控。验证配置是否生效用--dump-configdsh --profile weekly --dump-config输出里应该能看到三处关键行bundle 层叠、maxTokens: 8192、以及weekly-usage插件行。如果插件行没出现说明装配还没做下一步补上。3.3 项目技能 SKILL.md项目技能放在.dsh/skills/weekly-style/SKILL.mdheadless 会话的 cwd 落在项目根时会自动发现rank 100。全文如下--- name: weekly-style description: 工作日志转自然周 Markdown 周报的写作风格。 --- # weekly-style周报写作风格 **只输出报告正文**不加报告标题、前言、结语、解释或代码块围栏。 ## 结构 1. 每周一个小节标题为 ## YYYY-MM-DD ~ MM-DD原样保留 2. 周标题下第一行为 **本周概览**1~2 句话概括主线 3. 之后按日期升序每天一个三级标题 ### MM-DD 周X 4. 每周末尾为 **本周遗留**无遗留则写「无」 ## 条目写法 - 保留原始 [时段] 信息 - 动词开头、一句话一条、不编造 - 文件名、命令、路径用反引号包裹 ## 语言与语气 简体中文客观、简洁不使用表格、emoji、编号列表。写完的当刻当前会话就会出现 skill-catalog 上下文注入说明技能被自动发现了。3.4 插件骨架插件包只有两个文件。先写package.json{ name: weekly-usage, version: 0.1.0, type: module, main: lib/index.js }再写lib/index.js核心是监听session/event折叠assistant/message事件里的 TokenUsageimport fs from node:fs import path from node:path export const name weekly-usage const USAGE_FILE report.usage.json function normalize(usage) { return { inputTokens: usage.inputTokens ?? 0, outputTokens: usage.outputTokens ?? 0, cacheReadTokens: usage.cacheReadTokens ?? 0, cacheWriteTokens: usage.cacheWriteTokens ?? 0, reasoningTokens: usage.reasoningTokens ?? 0, } } export function apply(ctx) { const cursors new WeakMap() ctx.on(session/event, (session) { try { const state cursors.get(session) ?? { consumed: 0, calls: [] } const before state.calls.length while (state.consumed session.events.length) { const event session.events[state.consumed] state.consumed 1 if (event.type assistant/message event.data?.usage ! undefined) { state.calls.push({ seq: event.seq, turn: event.data.turn, step: event.data.step, usage: normalize(event.data.usage), }) } } cursors.set(session, state) if (state.calls.length before) writeUsage(state.calls) } catch (err) { ctx.logger?.warn?.(weekly-usage: ${err instanceof Error ? err.message : String(err)}) } }) }三个细节值得抄WeakMap 游标防止回放重复统计、缺省桶补 0、try/catch 不外泄插件崩了不能带崩任务。装配插件dsh plugin --profile weekly add D:\work\weekly-report\plugins\weekly-usage首次装配会看到declares no dsh.bundle的 warning这不是错误——我们的插件是靠补丁行的- insert:挂载的普通依赖不是 profile 模板层。3.5 凭据文件Key 只写进隔离DSH_HOME的.credentials.yamlversion: 1 refs: DEEPSEEK_API_KEY: sk-****工具代码完全不碰凭据spawn dsh 时只透传DSH_HOMEKey 解析交给 dsh 自己。4. 验证请求从本地跑通到流水线通过配置齐了现在验证。分三步本地生成、JSON 输出、CI 流水线。4.1 本地真实生成主程序weekly-report.mjs的关键是resolveDshCommand()——Windows 上spawn(dsh)会 ENOENT因为 Node 不做 PATHEXT 解析。解法是解析 shim 里的 Node 入口直连nodefunction resolveDshCommand() { if (process.platform ! win32) { /* PATH 找 dsh */ } const found findInPath([dsh.exe, dsh.cmd, dsh.bat, dsh]) if (!found) throw new Error(cannot find dsh in PATH) if (!found.toLowerCase().endsWith(.cmd) !found.toLowerCase().endsWith(.bat)) { return { file: found, args: [] } } const text fs.readFileSync(found, utf8) const m /((?:%~dp0|%dp0%\\)?[^]*\.js)/i.exec(text) if (!m) throw new Error(cannot parse node entry from dsh shim: ${found}) const entry path.resolve(path.dirname(found), m[1].replace(/^%~dp0/i, )) return { file: process.execPath, args: [entry] } }跑生成node weekly-report.mjs --in ..\notes --out report.md --json成功的话report.md落盘含 4 周完整周报周标题格式## 2026-08-03 ~ 08-07。--json模式 stdout 只输出一个 JSON 对象{conclusion:OK,report:## 2026-08-03 ~ 08-07\n...,weeks:4,files:20,usage:{inputTokens:677,outputTokens:9469,cacheReadTokens:20096,cacheWriteTokens:0,reasoningTokens:7676}}usage字段来自插件说明插件生效了。再看账本report.usage.json能看到两次调用的明细标题辅助请求输出 79 tok和主请求输出 9390 tok含 reasoning 7641。两笔的cacheReadTokens说明同一会话内第二次请求命中了缓存。4.2 失败路径验证Remove-Item Env:DSH_HOME node weekly-report.mjs --in ..\notes --out report.md应该看到 stderr 一行weekly-report: dsh failed (1: MISSING_CREDENTIAL: ...)退出码 1无堆栈。4.3 CI 流水线.github/workflows/weekly.yml的核心是三步引导 profile、生成、留存产物。name: weekly report on: schedule: - cron: 0 0 * * 1 workflow_dispatch: permissions: contents: read actions: write jobs: generate: runs-on: ubuntu-latest timeout-minutes: 30 steps: - uses: actions/checkoutv4 - uses: actions/setup-nodev4 with: { node-version: 24 } - name: Install dsh CLI run: | corepack enable npm install -g deepseek-ai/dsh0.1.1-rc.2 dsh --version - name: Bootstrap weekly profile shell: bash env: DSH_HOME: ${{ runner.temp }}/dsh-home run: | mkdir -p $DSH_HOME/profiles/weekly cat $DSH_HOME/profiles/weekly/package.json JSON {name:dsh-profile-weekly,private:true,dependencies:{},dsh:{profile:{bundles:[deepseek-ai/dsh-base,deepseek-ai/dsh-headless]}}} JSON cp $GITHUB_WORKSPACE/.github/dsh/weekly.patch.yml $DSH_HOME/profiles/weekly/cordis.patch.yml dsh plugin --profile weekly add $GITHUB_WORKSPACE/plugins/weekly-usage - name: Generate weekly report env: DSH_HOME: ${{ runner.temp }}/dsh-home DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }} run: | node weekly-report.mjs --in notes --out report.md --json result.json node -e const rrequire(./result.json); if(r.conclusion!OK) process.exit(1) - name: Upload artifacts uses: actions/upload-artifactv4 with: name: weekly-report-${{ github.run_id }} path: [report.md, report.usage.json, result.json] if-no-files-found: error几个关键点profile 由项目自带的 patch 文件在 CI 里重建配置即代码密钥只注入生成那一个 steppermissions收窄到contents: readactions: write。注意所有文件用 UTF-8 无 BOM 写入——带 BOM 的package.json会让 manifest 解析器报Unexpected token带 BOM 的 YAML 会被直接拒绝。诚实声明这个 workflow 没在真实远程 runner 端到端跑过没有远程仓库和密钥但命令级语义都本机实测过YAML 结构引自官方仓库自己的 workflow。5. 本篇常见错排查这一节对照真实报错每个都给出定位和解法。401 或 MISSING_CREDENTIAL最常见。dsh 会对 Agent 的工具子进程做环境净化服务器进程有的DEEPSEEK_API_KEY传不到 Agent 的 shell。解法是把 Key 写进隔离DSH_HOME的.credentials.yaml由凭据服务自动解析。CI 里则用 secrets 注入到生成 step 的环境变量。local proxy failed如果你在 dsh 前面挂了本地代理检查代理进程是否在跑、端口是否对。dsh 的 Base URL 配置要指向代理地址而不是直连。用 TaoToken 通道的话Base URL 填https://taotoken.net/api不需要额外代理。reading choices 报错通常是模型返回格式不符合预期。检查 Model ID 是否填对以及maxTokens是否设得太小导致输出被截断。周报生成建议至少 4096。OAuth 相关报错dsh 的某些集成需要 OAuth 授权如果报 token 过期重新走一遍授权流程。CI 环境里建议用 API Key 而不是 OAuth避免 token 刷新问题。spawn ENOENTWindows 上spawn(dsh)找不到命令。解法是解析 shim 里的 Node 入口直连见 §4.1 的resolveDshCommand()。spawn EINVALWindows 上.cmd不能由CreateProcess直接执行。同样走解析入口的路线。cmd.exe /c 传多行参数被截断退出码是 0 看起来成功但 stdout 只剩第一行。这是最迷惑的坑——多行参数在换行处被截断后面的内容变成幽灵参数。解法是绕开 shell直接spawn(node, [entry, ...])。管道捕获 EPERM沙箱内 Node spawn 带 pipe 被拒。需要升级权限走审批流或者改文件重定向。UTF-8 BOM 三连.env首键静默丢失、JSON manifest 解析失败、YAML patch 被拒。统一用无 BOM 写入PowerShell 的Set-Content -Encoding utf8会写 BOM改用[System.IO.File]::WriteAllText()。Glob 出不了工作区Glob ../notes/**/*返回空。换pwsh列目录这是工具语义差异合理存在。CC Switch / Cline MCP / Codex auth.json 三件套如果你用这些工具接 dsh配置里必须写全 Base URL、Key、Model ID 三样。缺任何一个都会连接失败。Base URL 用https://taotoken.net/apiKey 从 TaoToken 控制台生成Model ID 填你要用的模型。排障时优先看 stderr 和退出码不要只看 stdout。dsh 的退出码契约是0 成功、非 0 失败失败时 stderr 有一行可读的错误信息。6. 把项目接进你的工作流到这里一个完整的项目走完了需求规划、编码自测、headless 接入、插件开发、CI 流水线、成本复盘。你现在手里有的东西是一个可复用的 weekly profile、一个项目技能写法、一个 headless 封装模式、一个能嵌入任何工具链的 CI 模板。接下来怎么用三个方向。第一把周报生成器换成你自己的小工具。逻辑完全一样定义需求、写技能、配 profile、接 CI。区别只是输入输出格式。第二把 headless 封装模式用到其他场景。resolveDshCommand()那套 Windows spawn 解法是通用的任何需要从 Node 调 dsh 的地方都能抄。第三把成本账本用起来。插件统计的 token 用量能帮你判断哪些任务值得跑、哪些该错峰。缓存命中是最大的省钱杠杆——同一会话别开新会话长会话的重复输入几乎免费。如果你要长期跑编码类任务或 Agent 工作流Coding Plan 把模型调用和额度管理打包在一起适合需要稳定跑量的场景。接入文档在https://taotoken.net/api对应的文档页里面有各语言的调用示例。模型对话页面可以快速验证你的 Key 和模型 ID 是否配对正确。最后一句实话dsh 的价值不在于它有多少功能而在于你能把多少功能组合成一个能跑的东西。这篇演示的组合只是一个起点你的项目才是终点。
返回列表