Files
xinmi/packages/dsh-weknora/README_CN.md
T

165 lines
9.1 KiB
Markdown
Raw Normal View History

# @wxg-prc-cpg/dsh-weknora
[English](./README.md) | 简体中文
[npm](https://www.npmjs.com/package/@wxg-prc-cpg/dsh-weknora)
一个 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(`dsh`)插件,让 harness 里的 Agent 直接用上
[WeKnora](https://github.com/Tencent/WeKnora) 知识库:混合检索、按文档通读、以及 WeKnora 自己带引用的成稿答案。
dsh 本身没有任何检索、向量或知识库能力——它的 `grep`、`glob` 只看工作区文件,`web_search` 只看互联网。这个插件把缺的这一
块换成你自己的文档。
## 安装
```sh
# 从 npm 安装(推荐:装到的是构建产物,用户无需授权执行构建脚本)
dsh plugin --profile web add @wxg-prc-cpg/dsh-weknora
# 或者从本仓库的检出目录安装
dsh plugin --profile web add ./packages/dsh-weknora
```
然后把它指向你的部署。插件自带的配置层会读环境变量,所以最快的起步方式是:
```sh
export WEKNORA_BASE_URL=https://weknora.example.com # 或 http://localhost:8080
export WEKNORA_API_KEY=sk-... # 需要 `retrieve` 能力(用 ask 还需要 `chat`)
export WEKNORA_KNOWLEDGE_BASE_IDS=kb-123,kb-456 # 可选的默认知识库范围
export WEKNORA_RESOURCE_URLS=public # 默认;改成 `handle` 则保留内部文件引用
dsh web
```
超出这个范围的配置,在 profile 自己的 `cordis.patch.yml`(`$DSH_HOME/profiles/<name>/cordis.patch.yml`)里覆盖这一行:
```yaml
- id: weknora
config:
baseUrl: https://weknora.example.com
apiKey: !!js process.env.WEKNORA_API_KEY
knowledgeBaseIds:
- kb-product-docs
agentId: '' # 填 WeKnora 自定义 Agent id,`weknora_ask` 就走 ReAct 流水线
maxResults: 8
maxChunkChars: 1200
requestTimeoutMs: 30000
chatTimeoutMs: 300000
resourceUrls: handle # 默认是 `public`,引用里的图片给可直接加载的直链;`handle` 保留内部引用
toolPrefix: weknora # 重命名工具,例如同时挂两个部署时
tools:
listKnowledgeBases: true
search: true
readDocument: true
ask: true
```
patch 是整块替换该行的 `config`,所以要保留的字段需要一并写出。配置不合法会在插件加载阶段失败,并逐条列出问题,而不是等到
模型第一次调用工具时才报错。
同时挂两个部署,就是两行加两个前缀:
```yaml
- insert:
- id: weknora-internal
name: "@wxg-prc-cpg/dsh-weknora"
config: { baseUrl: https://kb.internal.example.com, toolPrefix: internal_kb }
- id: weknora-public
name: "@wxg-prc-cpg/dsh-weknora"
config: { baseUrl: https://kb.public.example.com, toolPrefix: public_kb }
```
## 工具
| 工具 | WeKnora 接口 | 模型拿到什么 |
|---|---|---|
| `weknora_list_knowledge_bases` | `GET /knowledge-bases` | 知识库名称与 id,用于说明有哪些库,或为后续检索缩小范围 |
| `weknora_search` | `POST /knowledge-search` + `GET /knowledge/search` | 原文片段与排序,每条带 `knowledge_id`、得分、分块序号,外加查询点名的文档 |
| `weknora_read_document` | `GET /chunks/:knowledge_id` + `GET /knowledge/:id` | 单个文档按序拼回的正文,开头给出标题与摘要,支持翻页 |
| `weknora_ask` | `POST /sessions` + `POST /knowledge-chat/:id` 或 `POST /agent-chat/:id` | WeKnora 自己的答案、引用、服务端用过的工具,以及可续聊的 `session_id` |
`weknora_search` 是主力:它把原文交给 Agent 自己推理,Agent 的结论因此是可审计的。它在同一次调用里同时匹配片段内容和
文档名,因为模型常常分不清自己要找的是「哪里讲了这件事」还是「那份叫 X 的文档在哪」。当查询读起来像个标题时,结果里会额外
列出被点名的文档——这样即便某份文档的正文用词与标题完全不同,也依然够得着。
范围不需要配置。没有指定知识库的调用,检索接口会直接拒绝,RAG 接口则会在什么都没检索到的情况下作答,因此当
`knowledgeBaseIds` 为空时,插件会自己解析出该凭据可见的全部知识库并一并使用;这份清单每个进程只解析一次,且由
`weknora_search` 与 `weknora_ask` 共用。让模型先去挑反而更糟:知识库的命名往往糟糕到无从选择。
`weknora_ask` 把整个问题委派给 WeKnora。它适合跨多篇文档、需要综合的宽泛问题,也就是自己检索要来回好几轮的场景;它在服务端
再跑一个模型,所以慢,而且返回的是结论而非支撑结论的证据。配了 `agentId` 时它不会自己填范围:自定义 Agent 会按自己的知识库
选择模式在服务端解析范围,从这里传 id 反而会把它覆盖掉。
`weknora_read_document` 的存在是因为检索返回的是碎片:一旦某个片段看起来对,Agent 通常还需要它的上下文。每条检索结果都带
着这次调用需要的 `knowledge_id`;第一页会先给出文档标题和 WeKnora 生成的摘要,长文档不必翻完才知道自己拿到的是什么。
在 [Code Mode](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/tools/README.md) 下,同样的工具
可以写成 `await tools.weknora_search({ query })`,特别适合多跳检索:一段程序里扇出十个子问题,而不是十次模型往返。
## 权限与数据流
插件只读。它不会写 WeKnora:不入库、不改分块、不删除。四个工具里有三个只需要 `retrieve` 能力的 API Key;`weknora_ask`
额外需要 `chat`,因为它要创建会话并流式取回答案。
Key 通过 `X-API-Key` 发送。如果用的是平台级 API Key,还需要配 `tenantId`,它会作为 `X-Tenant-ID` 选择工作空间。报错信息
只包含 HTTP 状态码和 WeKnora 自己给出的原因,不会带上 Key。
片段内容在进入模型前会按 `maxChunkChars` 截断,避免一份超大文档吃掉整个上下文窗口;截断这件事会明确告知模型
(`truncated: true`),而不是悄悄丢文本。
默认 `resourceUrls: public` 时,WeKnora 会把片段里的 `resource://` 图改写成可直接加载的 http(s) 直链——把这些 Markdown
图片写进回复,dsh 网页端就会把图画出来。限定知识库的 API Key 拿不到直链(WeKnora 会 403),插件会把这次调用改回
handle 模式,之后也一直用 handle。
配置层还会读 `WEKNORA_RESOURCE_URLS`(`public` 或 `handle`)。
## 配置项
| 字段 | 默认值 | 说明 |
|---|---|---|
| `baseUrl` | `http://localhost:8080/api/v1` | 缺少 `/api/v1` 时自动补全 |
| `apiKey` | 未设置 | `X-API-Key`;不设表示部署无鉴权 |
| `tenantId` | 未设置 | `X-Tenant-ID`,平台级 Key 必填 |
| `knowledgeBaseIds` | `[]` | 调用未指定范围时的默认值;留空表示检索该凭据可见的全部知识库 |
| `agentId` | 未设置 | 让 `weknora_ask` 走 ReAct 流水线 |
| `maxResults` | `8` | 同时是 `max_results` 与引用条数的上限 |
| `maxChunkChars` | `1200` | 单个片段的字符预算 |
| `requestTimeoutMs` | `30000` | 检索与读文档 |
| `chatTimeoutMs` | `300000` | `weknora_ask`,要等 WeKnora 自己的模型调用 |
| `resourceUrls` | `public` | `public` 返回可直接加载的文件直链;`handle` 保留内部 `resource://` 引用。限定知识库的 API Key 会自动回退到 `handle`。可用 `WEKNORA_RESOURCE_URLS` 覆盖。 |
| `toolPrefix` | `weknora` | 工具名前缀,需匹配 `^[a-z][a-z0-9_]*$` |
| `tools.*` | 全部 `true` | 少注册几个工具就少占几分 prompt |
## 开发
```sh
npm install
npm test # 先构建,再跑单元测试与契约测试
npm run typecheck
```
端到端检查会把这个包装进一个临时 dsh profile,用 mock 的 WeKnora 后端和一个确定性的 OpenAI 兼容模型启动 headless
profile,并断言 harness 真的调用了工具、并且答案来自工具返回的内容:
```sh
npm run build
node test/e2e/run-in-dsh.mjs # 首次运行会从 npm 装一个 dsh
DSH_BIN=/path/to/dsh node test/e2e/run-in-dsh.mjs # 或者复用已有安装
```
dsh 的会话持久化需要带 zstd 的 Node(≥ 22.15 或 ≥ 24),并且 `PATH` 上要有 pnpm ≥ 10——`dsh plugin add` 通过 pnpm 装进
profile,而它写出的 profile 把设置放在 `pnpm-workspace.yaml` 里。插件自身只要求 Node ≥ 20.11,两者都不需要。
`test/fixtures/api-contract.json` 记录了本插件发出的每一个 WeKnora 调用。`test/contract.test.mjs` 断言插件仍然只发这些
调用,`contract/contract_test.go` 断言 WeKnora 真实的 Go 请求/响应类型仍然接受并提供这些字段——任何一侧改名都会在 CI 挂掉,
而不是在用户的 Agent 里挂掉。
## 兼容性
已针对 dsh `0.1.0-rc.8` 与 WeKnora `0.8.0` 验证。dsh 处于 developer preview 并明确会有破坏性变更;本包因此**没有任何运行
时依赖**,交给 `ctx.tools.register()` 的只是一个普通对象,不锁定任何 harness 包版本。如果 harness 的工具定义契约发生变化,
请在本仓库提 issue。
## 许可
MIT,与 WeKnora 和 dsh 一致。