☰
DeepSeek Harness插件开发入门:从公式渲染到Agent工程化
2026/10/8 4:40:26 网站建设 项目流程

我很少愿意为同一个工具写第二遍教程,但 DeepSeek Harness 是个例外。原因特别简单:当你在一个项目里反复复制同一段 prompt、来回搬运模型输出、或者半夜还在手工把 Markdown 里的公式截图成图片的时候,就会意识到自己缺的不是一个问答 API,而是一个能把模型能力“组装”进日常工程流程的插件化外壳。Harness 就是干这个的。它不像裸调 DeepSeek API 那样一次一问答,而是给你一个带状态、能跑工具、能加载插件的 Agent 运行框架,插件生态里管这些可复用能力叫 skill。这篇文章就是给刚接触 Harness 插件开发的人准备的入门实操,我会从一个公式渲染插件出发,完整走一遍从工程初始化、manifest 编写、代码实现到打包发布的全流程,中间该踩的坑也顺手记下来。

1. 先搞清楚:DeepSeek Harness 到底是什么

1.1 它和直接调 API 有什么不同

很多人第一次接触 Harness 时都会问:我直接requests.post到 DeepSeek 的接口不行吗?为什么还需要一个框架?这个问题问到点子上了。裸调 API 解决的是“一问一答”,你把 prompt 发过去,模型把文本吐回来,连接断开,上下文清零。这在聊天场景没问题,但一旦进入“让模型帮我干活”的阶段,问题就暴露了:模型说“我需要读取这个文件”,然后呢?它读不到;模型说“我帮你算好了,结果放在 stdout 里”,然后呢?它没法执行命令;模型说“任务完成了”,但没有人帮它把结果写进仓库。

Harness 做的事,就是把这个“然后呢”补上。它给模型提供了工具调用通道、上下文管理、任务执行链和插件系统。你可以把 Harness 理解成一个给大模型装上手脚的沙箱:模型不再只是输出文字,而是可以请求调用某个工具、拿到工具返回值、继续推理下一步,最终完成一个多步骤任务。插件就是这些“手脚”的可插拔单元。

这里有个概念需要先统一:skill。在 Harness 的语境里,skill 是一个插件暴露给模型的最小能力单元,它包含触发条件、参数声明、执行函数和返回格式。一个插件可以只包含一个 skill,也可以包含多个有依赖关系的 skill。插件本身是打包和分发单位,skill 是运行和执行单位,两者不要搞混。

1.2 插件化设计解决的核心痛点

Harness 选择插件化路线,背后有三个很现实的痛点。

第一个痛点是Prompt 碎片化。同一个项目里,每个人都在维护自己的系统提示词,今天想给模型加一个“渲染公式”的能力,就在 prompt 里塞一段描述;明天想加一个“查询数据库”的能力,再塞一段。结果 prompt 越写越长,互相干扰,模型经常忽略你后加的指令。插件化之后,能力边界是清晰的:公式渲染归渲染插件管,数据库查询归数据库插件管,每个 skill 有独立的描述和注册表,模型按需调用,不需要把业务逻辑全堆在系统提示词里。

第二个痛点是工具集成重复。我在几个项目里都遇到过类似需求:让模型生成一段 Markdown,然后调用本地脚本转 PDF。如果没有统一框架,每个项目都要重新写一遍调用逻辑、错误处理、超时机制。把这些封装成插件后,任何 Harness 工程都可以直接声明引用,开发成本从“每次两小时”降到“引用一行”。

第三个痛点是上下文割裂。裸调 API 时,模型和外部环境的上下文是断开的。模型不知道当前项目里有哪些文件、最近执行过什么命令、数据库里有哪些表。Harness 把这些上下文对象化,插件执行函数可以显式接收项目上下文、任务上下文、用户上下文,模型不再是“失忆打工者”,而是“带着工作台干活的人”。

2. 开发前环境准备:本地工程与调试环境

2.1 基础依赖安装与版本选择

开始写插件之前,先把本地环境收拾利索。我建议用虚拟环境,不要图省事直接装到系统 Python 里,否则后面调试不同版本的插件时,依赖冲突能让你怀疑人生。

python -m venv .venv source .venv/bin/activate pip install --upgrade pip pip install deepseek-harness

不同版本的 Harness 安装名可能略有差异,以你项目 README 里写的为准。装完后输入dsh --help能看到子命令列表,只要命令能正常响应,基本环境就算是通了。

接着需要准备 DeepSeek API 的访问凭证。Harness 默认从环境变量读取密钥,不建议硬编码在插件的代码里,因为插件是要打包分发的,密钥写进去等于裸奔。

export DEEPSEEK_API_KEY="sk-xxxx"

有些版本还支持通过配置文件指定 API base 地址,这个后面部署到内网时会用到,现在先不管。

我的建议版本组合是 Python 3.10 及以上、Harness 0.4.x 以上。Python 3.10 的match语法在写 skill 参数分发时很好用,而 Harness 0.4.x 开始才稳定支持插件热加载和回滚机制,版本太低的话,后面调试流程会走不通。

2.2 多端口 nginx 本地环境配置

插件开发经常需要回调本地的 HTTP 服务,比如公式渲染插件会提供一个本地图片服务,Harness 的 Agent 进程去请求这个服务。开发阶段反复用 IP+端口访问很别扭,建议直接在本地配置一个多站点开发环境,一个域名对应一个插件服务。

我自己常用的方案是“宿主机 + 虚拟机 / 多容器 + nginx 反向代理”。先在宿主机/etc/hosts(Windows 是C:\Windows\System32\drivers\etc\hosts)里加上自定义域名:

127.0.0.1 harness.local 127.0.0.1 math-api.harness.local 192.168.56.101 vm-service.harness.local

然后在 nginx 里配置多个server块,每个域名对应一个本地端口:

server { listen 80; server_name math-api.harness.local; location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }

为什么多走一层 nginx?两个原因。一是统一域名入口后,插件代码里可以用稳定域名来拼 URL,不用管目标服务到底在哪个端口;二是后续把服务和插件拆到不同虚拟机或 Docker 容器时,nginx 只需要换 upstream 地址,插件代码一行不用改。这个习惯我从一开始就养成了,后面省了很多麻烦。

3. 认识 Harness 插件的基本骨架

3.1 插件目录结构与 manifest 文件

一个标准 Harness 插件工程,目录结构大致长这样:

math-render-plugin/ ├── plugin/ │ ├── __init__.py │ ├── manifest.yaml │ ├── skills/ │ │ └── render_math.py │ └── resources/ │ └── templates/ ├── tests/ │ └── test_render_math.py ├── pyproject.toml └── README.md

manifest.yaml是整个插件的身份证,Harness 加载插件时首先读它,而不是读 Python 代码。这个设计很有讲究:Harness 需要在不加载代码的情况下就知道插件的名称、版本、依赖哪些 skill,这样可以提前做依赖分析和冲突检查。

name: math-render version: 0.1.0 description: 将 Markdown 中的 LaTeX 公式渲染为 SVG 或 PNG 图片 author: your-name license: MIT python: requires_python: ">=3.10" skills: - id: render_math name: 渲染数学公式 description: 输入 LaTeX 公式文本,输出渲染后的图片文件路径 parameters: - name: latex type: string required: true description: LaTeX 公式内容,不包括 $$ 定界符 - name: output type: string required: false description: 输出文件路径,默认在临时目录 timeout: 30

写 manifest 最容易出错的地方是parameters的声明和实际函数签名不一致。Harness 在运行时会根据 manifest 里的参数定义来校验模型传来的 JSON,校验通过了才会调用你的函数。如果你在函数里写了一个必选参数scale,但 manifest 没有声明,模型就永远传不进来,测试时会感到莫名其妙。

3.2 skill 的定义与注册机制

看完 manifest,再看 skill 代码。Harness 的插件 API 风格在不同版本之间有些变化,但核心思路是一致的:写一个类,声明为插件,然后里面的方法声明为 skill。

from pathlib import Path from harness import Plugin, Skill, ToolContext, FatalError @Plugin( name="math-render", version="0.1.0", ) class MathRenderPlugin: """数学公式渲染插件。""" @Skill( id="render_math", name="渲染数学公式", description="输入 LaTeX 公式文本,输出渲染后的图片文件路径", ) def render_math( self, ctx: ToolContext, latex: str, output: str | None = None, ) -> str: if not latex.strip(): raise FatalError("latex 参数不能为空") ... return output_path

关于 skill 注册机制,有两点很重要。

第一,ctx参数是框架自动注入的,不需要也不应该由模型传入。ToolContext里带着当前任务的上下文句柄,可以用来读写临时文件、记录日志、查询任务元数据。把ctx放到函数签名第一位是约定俗成的写法,Harness 在调用时会自动跳过它。

第二,每个 skill 在注册时会被编译成一个“工具描述”,随后动态追加到模型消息里。所以你写的description会被模型直接读到,它写得越清楚,模型就越知道什么时候该调用。建议大家用动词开头,写出调用后的效果,比如“渲染数学公式并返回图片路径”,而不是只写“公式渲染”。

4. 手写第一个插件:Markdown 数学公式渲染插件

4.1 需求拆解与方案选型

我为什么选公式渲染插件作为第一个完整案例?因为它既覆盖了插件开发的完整链路,又有很强的实用性。技术博主和文档工程师写 Markdown 时经常要插入数学公式,但很多平台的 Markdown 渲染器不支持 LaTeX,最后只能手动渲染成图再贴进去。这个插件可以让 Harness 里的 AI Agent 直接帮你把公式变成图片,一步到位。

需求可以拆成三点:

  1. 输入:一段 LaTeX 公式,比如\frac{a}{b}。
  2. 动作:在本地把公式渲染成 PNG 或者 SVG。
  3. 输出:返回图片文件的绝对路径,Harness 可以直接引用或上传。

渲染引擎我选了 LaTeX 的简化替代品Matplotlib 的 mathtext。为什么不直接装完整的 TeX Live?一个字:重。完整 TeX Live 动辄几个 GB,为了渲染一个公式没有必要。Matplotlib 内置的 mathtext 支持大部分常用公式语法,渲染效果在博客场景完全够用,而且安装体积小、跨平台稳定。

4.2 代码实现:从入口到渲染

先把渲染函数写出来:

import tempfile from pathlib import Path import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt from harness import Plugin, Skill, ToolContext, FatalError @Plugin( name="math-render", version="0.1.0", ) class MathRenderPlugin: @Skill( id="render_math", name="渲染数学公式", description="将 LaTeX 公式渲染为 PNG 图片,返回图片路径", ) def render_math( self, ctx: ToolContext, latex: str, output: str | None = None, ) -> str: if not latex.strip(): raise FatalError("latex 参数不能为空") if output is None: output = str(ctx.temp_dir / "formula.png") fig = plt.figure(figsize=(0.01, 0.01)) text = fig.text( 0, 0, f"${latex}$", fontsize=16, color="black", ) fig.savefig(output, dpi=300, bbox_inches="tight", pad_inches=0.1) plt.close(fig) return output

这段代码里有几个细节值得展开。

matplotlib.use("Agg")必须在 importpyplot之前调用,否则在无图形界面的服务器上运行时会抛_tkinter.TclError。很多初学者卡在这一步,其实就是在指定后端。

figsize=(0.01, 0.01)看起来很奇怪,这其实是配合bbox_inches="tight"的一个技巧:初始画布无限小,保存时再根据文本实际尺寸自动扩展,最终图片不会有大片留白。

f"${latex}$"外面套了美元符号,是为了让 mathtext 进入数学模式。如果你传入的是\frac{a}{b},matplotlib 会把串中每个反斜杠当作文本转义符,此时把字符串标记为 raw string 更稳妥。上面的写法在普通场景下没问题,但严谨的做法是处理一下:

latex_clean = latex.replace("\\", "\\\\") text = fig.text(0, 0, f"${latex_clean}$", ...)

4.3 注册到 Harness 并测试调用

插件写完后,把它安装到当前 Harness 工程里。如果你是在项目目录下开发的,可以先用--dev模式加载,这样改代码后不用重新打包,热更新直接生效:

dsh plugin install ./math-render-plugin --dev dsh plugin list

然后启动一个交互式任务来验证:

dsh run --prompt "帮我渲染公式 \\frac{a}{b},输出到 /tmp/a_over_b.png"

正常情况下,Harness 会自主判断该调用render_mathskill,然后返回图片路径。如果模型没有调用,先别怪模型,检查一下 manifest 里的description是否写清楚了;如果 Harness 报了参数校验错误,重点排查parameters和函数签名是不是一致。

测试时建议在插件函数里加一行日志:

ctx.log(f"render_math called, latex={latex}, output={output}")

Harness 跑完任务后,控制台会打印这次调用的完整工具会话记录,这个日志可以帮你确认模型到底传了什么参数过来。

5. 让插件进入工作流:Agent 调用与工程化部署

5.1 配置自动调用规则和优先级

插件开发完只是第一步,真正好用要让 Harness 在合适的场景里自动想起来用你的插件。除了靠模型自己根据 tool description 判断,还可以给 skill 增加触发规则。

在 manifest 里新增triggers配置:

skills: - id: render_math ... triggers: - "包含数学公式" - "公式渲染" - "转换 LaTeX"

这些触发词会被 Harness 当成“意图匹配”的参考。模型在分析用户 prompt 时,如果命中触发词,会优先考虑调用这个 skill。注意触发词不是正则,而是语义关键词,所以不需要写太长,反而越短越容易被命中。

如果你同时装了多个公式相关插件,可以在每个 skill 的 manifest 里设置priority:

priority: 10

数字越大优先级越高。Harness 在多个 skill 都能匹配时会选择高优先级的那个。优先级拉不开差距时,还会比较confidence字段,没写的默认是 1.0,这个字段可以理解成你对“该 skill 适用于当前场景”的信心值。

5.2 内网服务器离线部署技巧

热词里经常看到“DeepSeek Harness 附带 skill 怎么部署到内网服务器”,说明不少团队是在隔离环境里使用的。内网部署最大的难题是依赖安装,解决方案是先在一台能联网的机器上把所有依赖拉下来,打包成离线 wheelhouse。

pip download -r requirements.txt -d ./wheelhouse --platform manylinux2014_x86_64 --python-version 310

然后把wheelhouse目录整个拷贝到内网,在内网环境安装:

pip install --no-index --find-links=./wheelhouse -r requirements.txt

需要注意--platform和--python-version这两个参数,它们决定了下载的 wheel 是否匹配目标机器。如果内网服务器是 ARM 架构,要把平台参数改成对应的;如果不确认,可以用pip download -r requirements.txt -d ./wheelhouse不带平台参数,这样下载的是当前机器可用的版本,但换机器后可能装不上。

安装完插件本体后,还需要把 DeepSeek API 访问地址切到内网网关。Harness 通过环境变量或配置文件读取 API base,改成内网地址后,插件不需要做任何代码改动。这也是插件化分层的好处:业务逻辑和运行环境解耦,部署环境切换只需要改配置,不用改代码。

6. 插件调试与常见问题排查实录

6.1 典型报错与解决办法

任何框架都有坑,Harness 也不例外。我把调试过程中真实遇到的典型问题整理成了一张速查表,按出现频率排序:

报错现象可能原因解决办法
manifest.yaml 解析失败YAML 缩进不一致或字段拼错用python -c "import yaml; yaml.safe_load(open('manifest.yaml'))"验证
skill 调用时提示参数缺失manifest 的 parameters 与函数签名不一致逐个参数对照,注意大小写
模型始终不调用插件description 写得太抽象改成“输入 xxx,输出 xxx”的格式
ImportError: No module named 'matplotlib'插件依赖未安装到 Harness 所在环境pip install matplotlib,确认是同一虚拟环境
渲染结果全是乱码LaTeX 字符串被 Python 转义使用 raw string 或转义反斜杠
插件加载后立即崩溃代码顶层有副作用把重操作放进 skill 函数内,不要在 import 时执行
任务超时skill 执行时间超过 manifest 中 timeout合理设置 timeout,长任务改为异步 skill
图片生成成功但路径无法访问临时目录被清理或跨容器不可见指定固定输出目录或使用共享卷

这里我特别想强调manifest.yaml解析问题。YAML 对缩进要求严格,很多人习惯用 Tab,一提交就报错。我的建议是统一用两个空格缩进,并且在.editorconfig里指定:

[*] indent_style = space indent_size = 2

6.2 代码回退与插件版本管理

热词里有“DeepSeek Harness 代码回退”,这其实是插件开发中很容易忽略但非常实用的能力。每次改动插件代码后,如果当前 skill 被 Harness 加载到上下文里,下一次调用可能还在用旧缓存,导致你的修改“好像没生效”。

我的做法是给插件打版本标签,配合git tag管理:

git tag -a v0.1.0 -m "release math-render 0.1.0" git push origin v0.1.0

Harness 侧的操作是:

dsh plugin list dsh plugin rollback math-render --to 0.1.0

这个rollback命令会把当前工程里的插件换回指定的历史版本,并且会保留一份变更记录。如果你改了配置后又后悔,可以通过dsh plugin history math-render查看历史,再决定回退到哪个版本。

刚开始我总觉得版本管理是发布阶段才需要关心的事,后来发现在日常迭代里就用得上。插件和主框架是共生关系,主框架更新后,旧插件可能不兼容,你总得回去翻代码。只有版本号清晰、可回退,这个翻代码的过程才不会变成考古。

6.3 踩坑心得:三个夜间排查经历

第一个经历是图片渲染插件在 Docker 容器里运行时报RuntimeError: main thread is not in main loop。这个问题的根源是 matplotlib 后端选错了,容器里没有 GUI,必须用Agg。我当时花了两个小时才反应过来,因为本机 Mac 上跑得好好的,一打包到 Linux 容器就翻车。后来我把matplotlib.use("Agg")放到了所有 matplotlib 导入之前,并加了平台判断:

import platform if platform.system() == "Linux" or "Docker" in platform.uname().release: import matplotlib matplotlib.use("Agg")

第二个经历是插件执行成功,但模型把返回结果中的路径理解错了,直接在 Markdown 里写了个/tmp/formula.png,没有调用工具。排查后发现问题出在 skill 的description上,我写的是“公式渲染”,模型无法判断什么时候该用。改成“当用户要求生成数学公式图片时调用此技能,返回图片路径”之后,调用率立刻上来了。

第三个经历是内网部署时,插件安装成功,但 Harness 无法访问模型 API。排查到最后发现是环境变量DEEPSEEK_API_BASE少写了一个http://前缀。这种问题最坑,因为报错信息不一定直接告诉你地址格式不对,而是提示“连接超时”。大家在配置内网地址时,务必把自己的配置和案例配置对比一遍。

7. 发布插件:打包、归档与插件市场

7.1 打包规范与校验工具

插件开发完成后,下一件事是打包。Harness 的插件包本质上是一个带元数据的压缩包,但打包方式有标准流程,不要手动 zip。

我的习惯是先写pyproject.toml:

[build-system] requires = ["hatchling"] build-backend = "hatchling.build" [project] name = "math-render-plugin" version = "0.1.0" description = "Render LaTeX formulas from Markdown" requires-python = ">=3.10" dependencies = [ "matplotlib>=3.6", ] [tool.hatch.build.targets.wheel] packages = ["plugin"]

这里有个细节:packages必须包含插件目录,否则打包出来的 wheel 里会缺少manifest.yaml,Harness 安装时会直接报错。你可以在打包后用命令验证:

dsh plugin validate ./math-render-plugin

校验工具会检查 manifest 字段、skill 参数声明、依赖项和目录结构是否完整。

7.2 归档与分享到内部插件市场

如果你只在自己电脑上用,到上一步就结束了。但如果你想把插件分享给团队,或者部署到公司内网,就需要一个归档中心。

Harness 社区里常说的“dsh 插件市场”,本质上是一个静态索引目录或者一个简单的 HTTP 服务。最轻量的做法是用dsh plugin archive命令把插件打成压缩包,传到共享文件服务器,然后在内网机器上通过 URL 安装:

dsh plugin install http://internal-repo.internal/math-render-plugin-0.1.0.hpk

如果团队规模不大,我甚至见过用 NFS 共享目录当插件市场的,Harness 直接扫目录里的.hpk文件就能发现新插件。重点是要维护好versions元数据,方便回滚。

归档时建议每次都生成一个 SHA256 校验值,避免传输过程中文件损坏。拿到插件的人可以先算一遍哈希再安装,尤其是从非官方渠道获取的插件,这一步能挡住很多低级问题。

shasum -a 256 math-render-plugin-0.1.0.hpk

8. 从插件到 Agent:把日常开发工作流也塞进去

开发完公式渲染插件之后,你会发现 Harness 的能力边界完全取决于你写了多少 skill。我后面又做了一个很有意思的小插件:把当前项目的 git diff 整理成周报摘要。这个 skill 内部调用git diff命令,再结合 DeepSeek 的总结能力,输出一份 Markdown 周报。

这个插件的核心逻辑其实也不复杂,关键是用到了 Harness 提供的执行外部命令工具:

from harness import Plugin, Skill, ToolContext import subprocess @Plugin(name="git-weekly", version="0.1.0") class GitWeeklyPlugin: @Skill(id="generate_weekly", name="生成 git 周报") def generate_weekly(self, ctx: ToolContext, since: str) -> str: result = subprocess.run( ["git", "diff", "--stat", since], capture_output=True, text=True, ) return result.stdout

这个插件让我意识到一件事:Harness 插件不只是“给 AI 加技能”,它也可以作为你本地自动化小工具的运行时。你平时写的一堆 Python 脚本、shell 命令,都可以包一层 skill 接口,让模型或你自己在统一的命令行里调用。这套工作流一旦跑顺,效率提升特别明显。

在实际操作中,我的经验是给每个插件都配上完整的 README 和examples目录,说明这个 skill 适合什么场景、不适合什么场景。因为插件一旦发布,使用者可能根本不知道你的实现细节,只能看描述。一个写得好的 README,能减少你回答同事微信私聊的时间。

最后再分享一个我后来才想明白的技巧:插件里的 skill 命名,在描述里不要堆叠太多个花哨的同义词,直接说“渲染 LaTeX 公式为图片”比“将数学公式转换为可视化图像”更容易被模型稳定触发。AI 的工具调用机制天然偏好清晰、直白的文本,你越少让模型“猜”,整个系统就越稳。

这套流程你完整跑一遍之后,再回去看那些“AI 只能在聊天框里输出文字”的说法,应该会有完全不同的感受。从一个公式渲染插件开始,后面不管是接入公司的知识库、自动发周报,还是写代码检查规则,都会发现只是照着同样的骨架再填一遍业务逻辑而已。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询