最近在折腾 DeepSeek Harness 插件时,我做了两款非常贴近日常使用的小插件:一款用来沉淀问答过程中的笔记,一款用来增强会话体验。整体开发周期不长,发布后一周不到就攒了接近 4K Star,说明这种“解决真实痛点、能直接复用”的插件确实有需求。这篇文章就把两款插件的完整开发过程整理成一份可复现的教程,从插件机制、环境准备、核心代码到测试排错都会覆盖到。新手可以通过这篇内容理解插件化开发的基本流程,有后端经验的开发者则可以复用设计思路,快速做出自己的 Harness 插件。
1. 背景与核心概念
1.1 DeepSeek Harness 是什么
先解释一下 Harness 这类工具到底是什么。DeepSeek 本身是提供大模型 API 的服务,我们可以直接调用它的接口完成对话、代码生成、文本总结等任务。但在实际项目里,直接裸调 API 会遇到很多重复劳动:要维护会话上下文、要处理多轮对话的 token 控制、要设计 Prompt 模板、要接入各种工具调用等。
DeepSeek Harness 可以理解为一个基于 DeepSeek 能力封装的开发脚手架,它的定位是把“和 DeepSeek 大模型交互”这件事标准化。开发者不需要每次从零写一套对话循环,而是直接使用 Harness 提供的会话管理、上下文维护、命令分发、插件加载等能力。
这里需要说明的是,Harness 的插件加载接口在不同的版本里可能有差异,本文以常见的插件化设计思路为例,重点演示“一个插件应该包含哪些部分、如何组织代码、如何和 DeepSeek API 配合”。具体注册方式请以你当前使用的 Harness 版本的加载器为准,思路是通用的。
1.2 插件机制解决什么问题
插件机制的核心思想是:核心程序保持稳定和精简,业务能力通过插件按需扩展。这和 VS Code 的扩展机制、Chrome 的扩展生态是同一个套路。
- 主框架负责:会话启动、API 调用、上下文传递、插件扫描加载。
- 插件负责:具体业务逻辑,比如记录笔记、改写 Prompt、生成摘要、统计用量。
- 事件通知负责:在会话的不同阶段把控制权交给插件。
使用插件机制最大的好处是降低定制成本。不同的使用者对 DeepSeek 的需求差异很大:有人需要把对话结果整理成 Markdown 笔记,有人需要做团队知识库沉淀,有人需要接入自己的私有工具。如果这些功能全塞进主程序,项目会变得非常臃肿;而通过插件扩展,每个人只需要开发自己的那一小块逻辑即可。
1.3 为什么先做笔记插件和会话助手插件
这两款插件不是凭空选的,而是我在实际使用中遇到的真实需求。
先说笔记插件。用 DeepSeek 辅助工作时,经常会出现一个场景:对话过程中产生了很有价值的方案、代码片段、结论,但会话结束后这些内容散落在聊天记录里,想再找回来很麻烦。如果能在问答完成后一键把关键内容落盘成 Markdown 文件,并且支持标签和搜索,效率会高很多。
再说会话助手插件。直接面向模型的对话是比较原始的交互方式,用户需要自己记上下文、自己控制指令。如果能给对话加上/remember、/clear、/help这类命令,会话就变得像一个小型产品,使用门槛明显降低。
这两款插件功能边界清晰、代码量适中,非常适合作为 Harness 插件开发的入门实战。
2. 环境准备与项目结构
2.1 运行环境
本文示例使用 Python 开发,建议环境如下:
- Python 3.10 或更高版本。
- 一个可用的 DeepSeek API Key。
openaiPython 库,因为 DeepSeek 提供的 API 兼容 OpenAI 的调用格式。python-dotenv,用于从.env文件加载环境变量。
需要注意,这里没有写死某个具体依赖版本,原因很简单:DeepSeek 的 SDK 和 Harness 框架都在快速迭代,版本需要根据你的项目实际情况调整。本文重点演示配置思路和代码组织方式。
2.2 示例项目结构
先看完整的项目结构:
deepseek-harness-plugins/ ├── README.md ├── requirements.txt ├── .env.example ├── examples/ │ ├── note_demo.py │ └── chat_demo.py ├── plugins/ │ ├── note_plugin/ │ │ ├── manifest.json │ │ └── note_plugin.py │ └── chat_assistant/ │ ├── manifest.json │ └── chat_assistant.py ├── tests/ │ └── test_note_plugin.py └── data/ └── notes/这个结构有几个设计考量:
plugins/是统一的插件目录,每个插件一个子目录。manifest.json描述插件的元信息,包括名称、版本、入口文件、支持的命令。examples/放置独立的运行示例,方便验证。tests/放置单元测试。data/notes/是笔记插件的默认数据目录。
2.3 依赖安装与密钥配置
创建虚拟环境并安装依赖:
python -m venv .venv source .venv/bin/activate # Windows 可以使用 .venv\Scripts\activate pip install openai python-dotenv在项目根目录新建.env文件,内容参考.env.example:
DEEPSEEK_API_KEY=sk-your-api-key-here注意:.env文件不要提交到 Git 仓库,建议把.env.example作为模板提交,里面只放占位符。
3. 插件机制核心概念
3.1 插件生命周期
一个插件在 Harness 中通常会经历几个阶段:
- 加载:Harness 扫描插件目录,读取
manifest.json。 - 初始化:创建插件实例,传入配置对象。
- 注册:插件把命令和事件处理器注册到主框架。
- 运行:有事件触发时,主框架调用插件对应的方法。
- 卸载:会话结束或程序退出时,释放资源、保存状态。
理解生命周期很重要。它决定了你把初始化逻辑放在哪里、把资源释放放在哪里。例如笔记插件在初始化阶段需要创建数据目录、加载索引文件;在卸载阶段需要把索引写回磁盘。
3.2 插件清单与入口约定
每个插件目录下都要有一个manifest.json,下面是一个示例:
{ "name": "note_plugin", "version": "0.1.0", "entry": "note_plugin.py", "commands": ["note", "notes", "search"], "description": "记录 DeepSeek 问答过程中的重要内容,支持标签与检索" }字段含义如下:
name:插件唯一标识,建议使用小写和下划线。version:插件版本号,遵循语义化版本规范。entry:入口文件,Harness 会导入这个文件并查找插件类。commands:插件能够处理的斜杠命令列表。description:插件功能说明,用于帮助信息展示。
不同的 Harness 实现可能要求插件类继承某个基类,也可能只是按约定查找特定属性。这个需要以你实际使用的版本为准。在本文的示例里,我们约定插件类拥有name、version、commands等属性,并提供公开方法即可。
3.3 事件模型
插件要发挥作用,通常需要监听主框架的事件。常见的事件类型如下:
| 事件名 | 触发时机 | 插件可做的事情 |
|---|---|---|
| session_start | 新会话创建时 | 初始化插件状态、加载历史记录 |
| before_chat | 用户输入发送前 | 改写 Prompt、注入上下文 |
| after_chat | 模型回复完成后 | 记录笔记、统计 token 用量 |
| command | 检测到斜杠指令时 | 分发到插件自己的指令处理函数 |
| session_end | 会话结束时 | 持久化数据、释放资源 |
为什么需要事件模型?因为插件往往不只有一个入口。笔记插件既可以在收到/note命令时保存笔记,也可以在每次after_chat后自动记录关键内容。通过事件解耦,插件和主框架互不干扰,后续增加新功能只需要新增一个事件处理器,不用改动主逻辑。
4. 实战一:笔记插件的完整开发
4.1 需求与功能拆分
笔记插件的目标很明确:把对话中的有价值内容沉淀为 Markdown 文件,并支持检索。拆分需求如下:
- 保存笔记:输入标题、内容、标签,生成独立 Markdown 文件。
- 列表查看:按时间倒序输出最近的笔记。
- 关键词搜索:在已保存的笔记内容中查找关键字。
- 数据索引:维护一份
index.json,快速定位笔记元信息而不必扫描全部文件。
4.2 数据模型与存储设计
存储设计采用“Markdown 文件 + JSON 索引”的方式:
- 每条笔记对应一个 Markdown 文件,文件名使用时间戳,避免冲突。
- 文件内容包含标题、创建时间、标签、来源会话和正文。
index.json只存元信息,用于快速列出和搜索,不存正文。
这样设计的好处是:Markdown 文件对人类友好,可以直接查看和编辑;JSON 索引对程序友好,检索效率高。
4.3 核心代码实现
首先定义笔记的数据结构和插件主类。文件路径:plugins/note_plugin/note_plugin.py
import json from datetime import datetime from pathlib import Path from typing import List, Optional class Note: """单条笔记的数据结构""" def __init__(self, note_id: str, title: str, content: str, tags: List[str], source_session: str = "", created_at: Optional[str] = None): self.note_id = note_id self.title = title self.content = content self.tags = tags self.source_session = source_session self.created_at = created_at or datetime.now().strftime("%Y-%m-%d %H:%M:%S") def to_dict(self) -> dict: return { "note_id": self.note_id, "title": self.title, "tags": self.tags, "source_session": self.source_session, "created_at": self.created_at, } def to_markdown(self) -> str: tag_line = " ".join([f"#{tag}" for tag in self.tags]) return ( f"# {self.title}\n\n" f"> 创建时间:{self.created_at}\n" f"> 标签:{tag_line}\n" f"> 来源会话:{self.source_session}\n\n" f"---\n\n" f"{self.content}\n" )接着是插件主类,负责保存、列表、搜索三大功能:
class NotePlugin: name = "note_plugin" version = "0.1.0" commands = ["note", "notes", "search"] def __init__(self, config: dict): self.work_dir = Path(config.get("work_dir", "./data/notes")) self.work_dir.mkdir(parents=True, exist_ok=True) self.index_path = self.work_dir / "index.json" self.index = self._load_index() def _load_index(self) -> List[dict]: if self.index_path.exists(): try: return json.loads(self.index_path.read_text(encoding="utf-8")) except json.JSONDecodeError: # 索引文件损坏时回归为空列表,避免插件启动崩溃 return [] return [] def _save_index(self) -> None: self.index_path.write_text( json.dumps(self.index, ensure_ascii=False, indent=2), encoding="utf-8", ) def save_note(self, title: str, content: str, tags: List[str], source_session: str = "") -> str: note_id = datetime.now().strftime("%Y%m%d%H%M%S") note = Note(note_id=note_id, title=title, content=content, tags=tags, source_session=source_session) file_path = self.work_dir / f"{note_id}.md" file_path.write_text(note.to_markdown(), encoding="utf-8") self.index.append(note.to_dict()) self._save_index() return note_id def list_notes(self, limit: int = 10) -> List[dict]: recent = sorted(self.index, key=lambda x: x["created_at"], reverse=True) return recent[:limit] def search_notes(self, keyword: str) -> List[dict]: results = [] for item in self.index: file_path = self.work_dir / f"{item['note_id']}.md" if not file_path.exists(): continue content = file_path.read_text(encoding="utf-8") if keyword.lower() in content.lower(): results.append(item) return results代码中有几个值得注意的细节:
- 所有文件读写都显式指定
encoding="utf-8",避免在 Windows 或特定环境下出现乱码。 _load_index捕获JSONDecodeError,防止索引文件损坏时整个插件无法启动。save_note使用时间戳作为笔记 ID,保证同一秒内保存多条笔记时基本不会冲突。如果追求更强唯一性,可以改成 UUID。- 搜索时统一转小写,避免大小写导致漏匹配。
4.4 注册与验证
插件的manifest.json已在前文给出。实际运行时,你可以在任意脚本中直接初始化插件并调用方法验证。下面是一个示例:
# 文件路径:examples/note_demo.py from plugins.note_plugin.note_plugin import NotePlugin plugin = NotePlugin({"work_dir": "./data/notes"}) note_id = plugin.save_note( title="DeepSeek 插件开发要点", content="插件需要包含 manifest.json 入口文件,并处理好文件读写编码问题。", tags=["DeepSeek", "插件", "教程"], source_session="demo-session", ) print("保存成功,note_id =", note_id) print("最近笔记:") for item in plugin.list_notes(5): print("-", item["title"], item["created_at"]) print("搜索『插件』结果:") for item in plugin.search_notes("插件"): print("-", item["title"])预期输出大致如下:
保存成功,note_id = 20250301120000 最近笔记: - DeepSeek 插件开发要点 2025-03-01 12:00:00 搜索『插件』结果: - DeepSeek 插件开发要点同时,data/notes/目录下会多出一个 Markdown 文件,打开后就是结构化笔记。
5. 实战二:会话助手插件的完整开发
5.1 需求与功能拆分
会话助手插件的目标是让 DeepSeek 会话更像一个可操控的产品,而不是裸的聊天窗口。功能拆分如下:
/help:列出所有可用指令。/clear:清空当前会话上下文。/remember 内容:把用户输入的零散内容整理成结构化摘要。- 普通对话:正常调用 DeepSeek,并维护上下文。
这套设计有实际价值:普通用户不需要关心上下文机制,只要记住几个命令,就能控制会话行为。
5.2 Prompt 模板与上下文管理
会话助手插件的核心不是 API 调用本身,而是上下文管理。大模型是无状态的,每次调用都需要把历史消息重新传一遍。因此插件内部要维护一个消息队列:
- 用户消息追加到队列。
- 助手回复追加到队列。
- 队列长度超过
max_history时,丢弃最旧的消息。
为什么不是无限制保留?因为 token 是成本,也是输入长度上限。上下文越长,单次请求的 token 消耗越大,响应时间也越长。后面在性能与成本控制部分会再展开。
此外,插件使用了一个系统 Prompt 来约束模型行为。系统 Prompt 的作用是设定角色和规则,和普通用户消息区分开。这里要提醒的是,系统 Prompt 不要塞太多内容,尽量精简,否则会挤占上下文窗口。
5.3 核心代码实现
文件路径:plugins/chat_assistant/chat_assistant.py
import os from typing import Dict, List from openai import OpenAI SYSTEM_PROMPT = ( "你是一个会话助手插件,负责帮助用户更高效地使用 DeepSeek 对话能力。" "当你收到斜杠指令时,按照对应规则执行:\n" "1. /remember 开头:把用户描述的要点整理成 200 字以内的结构化摘要;\n" "2. /clear 开头:清空上下文并回复‘已清空上下文’;\n" "3. /help 开头:列出可用指令;\n" "4. 普通问题:正常回答,并在结尾用一句话总结要点。" ) class ChatAssistantPlugin: name = "chat_assistant" version = "0.2.0" commands = ["remember", "clear", "help"] def __init__(self, config: dict): self.api_key = os.getenv("DEEPSEEK_API_KEY", config.get("api_key", "")) self.base_url = config.get("base_url", "https://api.deepseek.com") self.model = config.get("model", "deepseek-chat") self.max_history = int(config.get("max_history", 20)) self.history: List[Dict[str, str]] = [] self.client = OpenAI(api_key=self.api_key, base_url=self.base_url) def _append_message(self, role: str, content: str) -> None: self.history.append({"role": role, "content": content}) if len(self.history) > self.max_history: self.history = self.history[-self.max_history:] def handle_command(self, command: str, args: str) -> str: if command == "help": return ( "可用指令:\n" "- /remember 内容:把重要内容整理成摘要并保存\n" "- /clear:清空当前上下文\n" "- /help:查看帮助\n" ) if command == "clear": self.history.clear() return "已清空上下文。" if command == "remember": summary = self._summarize(args) return f"已记录摘要:\n{summary}" return "未知指令,输入 /help 查看说明。" def _summarize(self, content: str) -> str: messages = [ {"role": "system", "content": "你负责把用户的零散描述整理成 200 字以内的结构化摘要," "包含要点、结论、待办。"}, {"role": "user", "content": content}, ] resp = self.client.chat.completions.create( model=self.model, messages=messages, temperature=0.3, ) return resp.choices[0].message.content def chat(self, user_input: str) -> str: if user_input.startswith("/"): parts = user_input[1:].split(maxsplit=1) command = parts[0].lower() args = parts[1] if len(parts) > 1 else "" return self.handle_command(command, args) self._append_message("user", user_input) messages = [{"role": "system", "content": SYSTEM_PROMPT}] + self.history resp = self.client.chat.completions.create( model=self.model, messages=messages, temperature=0.7, ) reply = resp.choices[0].message.content self._append_message("assistant", reply) return reply这里有几个关键点:
- DeepSeek 的 API 兼容 OpenAI 格式,所以可以直接用
OpenAI客户端,只需把base_url指向 DeepSeek 的地址。 DEEPSEEK_API_KEY优先从环境变量读取,其次才读配置里的api_key,避免密钥硬编码。- 指令解析用
startswith("/")判断,再按空格切分命令和参数,逻辑简单且不容易误判。 - 普通对话时,系统 Prompt 放在消息列表最前面,历史消息随后,这是常见的 OpenAI 兼容 API 消息排序方式。
5.4 运行与验证
编写一个命令行交互示例,方便直接验证效果。文件路径:examples/chat_demo.py
from plugins.chat_assistant.chat_assistant import ChatAssistantPlugin config = { "max_history": 20, "model": "deepseek-chat", } plugin = ChatAssistantPlugin(config) if __name__ == "__main__": print("会话助手已启动,输入 /help 查看指令,输入 exit 退出。") while True: try: user_input = input("你 > ") if user_input.lower() in {"exit", "quit"}: break reply = plugin.chat(user_input) print("助手 >", reply) except KeyboardInterrupt: break运行方式:
source .venv/bin/activate python examples/chat_demo.py实际交互效果类似:
你 > /help 助手 > 可用指令: - /remember 内容:把重要内容整理成摘要并保存 - /clear:清空当前上下文 - /help:查看帮助 你 > 请用三句话介绍 DeepSeek Harness 插件开发的要点 助手 > DeepSeek Harness 插件开发的核心是把会话管理、上下文维护等通用能力交给主框架,插件只负责具体业务逻辑。开发时需要遵守入口文件约定,在 manifest.json 中声明名称、版本和命令。最后要注意文件读写编码、密钥管理和 token 成本控制。到这里,两款插件都已经是可运行的状态了。接下来看测试与调试。
6. 插件测试与调试
6.1 单元测试思路
插件测试不要等到接入 Harness 之后再补,先把核心逻辑写成可单测的纯函数和类方法。笔记插件的存储与搜索逻辑不依赖网络,非常适合单元测试。文件路径:tests/test_note_plugin.py
import tempfile from pathlib import Path from plugins.note_plugin.note_plugin import NotePlugin def test_save_and_search_note(): with tempfile.TemporaryDirectory() as tmp_dir: plugin = NotePlugin({"work_dir": tmp_dir}) note_id = plugin.save_note( title="测试笔记", content="本文包含 DeepSeek 插件开发要点", tags=["DeepSeek", "插件"], source_session="demo", ) assert Path(tmp_dir, f"{note_id}.md").exists() assert len(plugin.list_notes()) == 1 results = plugin.search_notes("插件") assert len(results) == 1 assert results[0]["note_id"] == note_id使用tempfile.TemporaryDirectory可以避免测试污染真实数据目录。运行测试:
pip install pytest pytest tests/ -v对于会话助手插件,由于它依赖真实 API 调用,单元测试里不应该真的去请求接口。更推荐的方式是 mock 掉client.chat.completions.create,只验证消息组装和指令分发逻辑。这样可以保证离线环境下测试也能跑通。
6.2 本地调试技巧
本地调试时我有几个习惯:
- 先用固定的一小段输入验证流程,不要一开始就压测长对话。
- 在关键方法入口打印入参,例如
save_note的 title、tags。 - 遇到 API 报错先看 HTTP 状态码,401 通常是密钥问题,429 通常是限流。
- 使用
python -m pdb或 IDE 断点调试,不要在代码里堆大量print后不清理。
调试和业务代码分离很重要,临时调试日志在提交流程前要统一清理或改成正式日志。
6.3 日志与错误追踪
插件运行在 Harness 环境里时,日志是排查问题的第一手资料。建议在项目入口统一配置 Python 的 logging:
import logging logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", ) logger = logging.getLogger("harness-plugin")在插件内部,像“保存笔记成功”“上下文已清空”“API 请求失败,重试中”这类关键动作都应该记录日志。日志级别这样划分:
- DEBUG:调试细节,如请求参数、耗时。
- INFO:关键流程步骤,如插件加载成功、笔记保存成功。
- WARNING:可恢复的问题,如索引文件损坏已重建。
- ERROR:无法继续执行的错误,如 API Key 无效。
不建议在生产环境把日志级别调到 DEBUG,否则日志量会非常大,而且可能把敏感信息打出来。
7. 常见问题与排查思路
把开发过程中最容易遇到的问题整理成一张表,方便快速对照:
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 插件加载后没有生效 | manifest.json 的 entry 路径错误或入口代码抛异常 | 检查入口文件位置,确认插件目录在 Harness 扫描路径内,查看启动日志 |
| 启动时提示 api_key 为空 | 环境变量未设置或初始化顺序太早 | 确认.env已加载,检查os.getenv("DEEPSEEK_API_KEY")的变量名是否一致 |
| 请求返回 401 错误 | API Key 无效或账户无对应模型权限 | 核对 Key,去 DeepSeek 控制台检查额度与模型权限 |
| 请求返回 429 限流 | 并发过高或触发速率限制 | 增加指数退避重试,降低并发,缩减max_tokens |
| 笔记文件写入后乱码 | 文件读写未指定编码 | 所有write_text/read_text统一指定encoding="utf-8" |
| 搜索查不到刚保存的笔记 | 索引没有刷新或搜索时大小写不匹配 | 确认save_note里调用了_save_index,搜索统一转小写 |
| 会话历史太长导致请求失败 | max_history设置过大或消息体超限 | 按 token 估算裁剪历史,只保留最近 N 轮;必要时对历史做摘要压缩 |
| 两个插件的命令冲突 | 多个插件都监听了同一个 command 事件 | 在注册中心统一管理命令,按插件优先级或命名空间分发 |
排查问题时建议按这个顺序:先看日志有没有异常,再确认配置和环境变量,最后才是业务代码逻辑。大多数“插件没生效”的问题,其实都出在加载路径、入口文件和初始化顺序上。
8. 最佳实践与工程建议
8.1 安全与密钥管理
插件开发中,安全是必须优先考虑的问题。最重要的一条是:API Key 绝不能硬编码到插件源码或 manifest 配置里。
推荐做法:
- 密钥放在
.env或系统环境变量中。 - 插件初始化时通过
os.getenv读取。 .env文件加入.gitignore。- 如果插件有上传到开源仓库的需求,提供
.env.example模板即可。
另外,插件权限要遵循最小化原则。笔记插件只需要本地文件读写权限,不需要让它去访问网络上的任意地址;会话助手插件只需要调用 DeepSeek API 的权限,不要给插件配置超级管理权限。把权限边界划清楚,一旦某个插件有安全漏洞,损失也能控制住。
8.2 配置管理与版本隔离
每个插件都应该有独立的配置命名空间,避免多个插件之间互相覆盖配置。例如笔记插件的配置统一以note_前缀开头,会话助手插件的配置统一以chat_前缀开头。
配置优先级建议是:环境变量 > 用户显式配置 > 插件默认值。在代码里的体现就是os.getenv(...)优先,其次读取config.get(...),兜底用默认值。
版本管理上,插件的主类属性要维护version,manifest 里也要有版本号。发布新版本时,至少要标注破坏性变更,否则依赖该插件的 Harness 主程序升级后可能行为不一致。建议遵循语义化版本规范:主版本号变化表示不兼容,次版本号变化表示新增功能,补丁号变化表示修复问题。
8.3 性能与成本控制
调用 DeepSeek API 是按 token 计费的,所以性能优化的核心是减少不必要的 token 消耗。
几个有效的策略:
- 设置
max_tokens上限,避免模型偶尔输出超长内容造成成本飙升。 - 压缩历史消息。当会话时长超过阈值时,把早期对话用摘要代替,而不是一直堆积原始消息。
- 对于
/remember这类指令,使用较低的温度参数(例如 0.3),让输出更稳定。 - 对于重复性查询,可以在插件层加缓存,命中缓存时直接返回,不调用 API。
- 并发请求要控制数量,避免触发限流后反而因为重试浪费更多时间。
日志里建议记录每次请求的 token 用量。很多 SDK 会返回usage字段,把它解析出来写入日志,月底复盘成本时非常有帮助。
8.4 开源维护与增长经验
回到标题提到的 Star 增长,我的实际感受是:工具类插件能在开源社区跑起来,靠的不是复杂炫技,而是“别人拿到就能用、用完感觉值”。
具体可以归纳成几点:
- README 要直接给出效果演示,最好有终端截图或 GIF,让读者 30 秒内知道这个插件解决什么问题。
- 安装方式要简单到“复制一行命令”。
- 每个命令给出输入输出示例,降低试用门槛。
- 提前写好 Issue 模板,明确要求提交 Bug 时附带版本、日志、复现步骤。
- 发布版本时写 CHANGELOG,让用户知道每次更新的影响范围。
这些偏工程运营的细节,往往比代码本身更能决定一个开源插件有没有人用。质量和文档是增长的基础。
9. 总结与下一步
这篇文章从概念、环境、核心机制到完整代码,把 DeepSeek Harness 插件的开发流程梳理了一遍。笔记插件展示了文件存储、索引维护和搜索的实现思路;会话助手插件展示了上下文管理、指令分发和 Prompt 约束的落地方式。两个插件合在一起,刚好覆盖插件开发最常用的两件事:数据沉淀和交互增强。
如果你想继续深入,建议按这几个方向进阶:
- 给插件增加 Function Calling 能力,让 DeepSeek 能调用外部工具,比如查数据库、发邮件。
- 把笔记存储从本地文件迁移到 SQLite 或向量数据库,支持语义检索。
- 为会话助手增加多会话管理,不同 session 互相隔离。
- 建立一套插件评测集,每次改动后用固定问题集回归,防止效果回退。
动手做插件最好的方式,就是从自己使用中最不舒服的点开始。把那个痛点写成一个最小可运行版本,再逐步优化,这比空想架构有用得多。如果本文对你有帮助,可以收藏备用,也欢迎在评论区聊聊你正在做的 Harness 插件。