From 491858be4a7d3000a9da3b75221c5fc4640edbac Mon Sep 17 00:00:00 2001 From: "zeyu.fz" Date: Mon, 24 Aug 2026 18:55:28 +0800 Subject: [PATCH] =?UTF-8?q?docs(readme):=20=E6=9B=B4=E6=96=B0=20Bailian=20?= =?UTF-8?q?KB=20dsh=20=E6=8F=92=E4=BB=B6=E7=9A=84=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E5=92=8C=E6=B7=BB=E5=8A=A0=E8=BF=90=E8=A1=8C=E6=97=B6=E8=AF=B4?= =?UTF-8?q?=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 增补详细的 runtime-behavior.md,说明插件的内部运行逻辑和设计取舍 - 完善 packages/bailian-kb-dsh 的 README,添加英文版及使用要求说明 - 细化配置项说明,示例及环境变量配置方式展示 - 增加 LICENSE 文件,明确 Apache 2.0 许可证 - 说明插件安装、配置、和卸载的详细步骤 - 规范 README 和文档多语言版本的同步更新及说明管理 - 说明服务发现、缓存刷新及代理行为的设计和技术细节 - 细化 Web UI 配置页功能介绍和操作指导 --- docs/agents/dsh-plugin.md | 13 +- docs/kb-dsh/runtime-behavior.md | 127 +++++++++++++++ packages/bailian-kb-dsh/LICENSE | 202 +++++++++++++++++++++++ packages/bailian-kb-dsh/README.md | 229 +++++++++++++-------------- packages/bailian-kb-dsh/README.zh.md | 158 ++++++++++++++++++ 5 files changed, 606 insertions(+), 123 deletions(-) create mode 100644 docs/kb-dsh/runtime-behavior.md create mode 100644 packages/bailian-kb-dsh/LICENSE create mode 100644 packages/bailian-kb-dsh/README.zh.md diff --git a/docs/agents/dsh-plugin.md b/docs/agents/dsh-plugin.md index c68b407..b5f9535 100644 --- a/docs/agents/dsh-plugin.md +++ b/docs/agents/dsh-plugin.md @@ -57,13 +57,19 @@ - [ ] `skills/bailian-kb/` 留在**包内**,不要挪到仓库顶层 `skills/`:`.github/workflows/publish-skills.yml` 把 `skills/**` 全量对账到 OSS registry,`bl skill init` 会装给所有 `bl` 用户,而这个 skill 讲的 `kb_search` / `kb_chat` 原生工具只在 dsh 里存在 - [ ] skill 只有一个手写 `SKILL.md`,**不带 `reference/`**:它不是 CLI,没有义务维护一份 `bl` 参数手册。`bl` 命令的 flag 详情交给 `bl <命令> --help`(权威的 `bl` reference 由 `tools/generate-reference.ts` 写到 `skills/bailian-cli/reference/`,与本包无关)。SKILL.md 里写到的 `bl` 命令/flag 修改时手动核对 `packages/commands/src/commands/`,不要锚版本号 -### E. 依赖与测试约定 +### E. 文档(README 是 npm 主页,双语) + +- [ ] 用户可见行为变了(工具参数、配置字段、环境变量、设置页、安装/卸载命令)→ `README.md` 与 `README.zh.md` **一起改**;两份互为镜像,头部互链 +- [ ] README 只写用户要知道的事;"为什么这么设计"(上下文注入载体、缓存布局、刷新触发点、桥接路由)写进 [../kb-dsh/runtime-behavior.md](../kb-dsh/runtime-behavior.md),不要回流 README +- [ ] 包根保留 `LICENSE`(npm 无条件打包),与 `package.json` 的 `license` 一致 + +### F. 依赖与测试约定 - [ ] `@deepseek-ai/dsh-*` 同时列在 `peerDependencies`(运行时由 dsh 安装闭包提供)和 `devDependencies`(本地类型检查)——升级时两处同步 - [ ] 测试从 `vite-plus/test` 导入(仓库统一约定),不要用 `vitest` - [ ] 忽略的 catch 绑定与 mock 签名参数用 `_` 前缀(根 `vite.config.ts` 已为本包放开 `no-unused-vars` 的对应 pattern) -### F. 改完跑 +### G. 改完跑 ```sh pnpm --filter bailian-kb-dsh run build # tsc 出 dist/ + web 半隔离检查 + tsdown 出 client.js @@ -114,5 +120,6 @@ node tools/release/publish-kb-dsh.mjs --dry-run --channel dsh-beta ## 相关文档 - 设计与实现计划归档:[docs/kb-dsh/](../kb-dsh/) -- 包内实现说明:[packages/bailian-kb-dsh/README.md](../../packages/bailian-kb-dsh/README.md) +- 运行时行为与设计取舍(内部):[docs/kb-dsh/runtime-behavior.md](../kb-dsh/runtime-behavior.md) +- 用户面说明:[packages/bailian-kb-dsh/README.md](../../packages/bailian-kb-dsh/README.md) / [README.zh.md](../../packages/bailian-kb-dsh/README.zh.md) - skill 文案与路由约定:[skill-change.md](skill-change.md) diff --git a/docs/kb-dsh/runtime-behavior.md b/docs/kb-dsh/runtime-behavior.md new file mode 100644 index 0000000..a59020f --- /dev/null +++ b/docs/kb-dsh/runtime-behavior.md @@ -0,0 +1,127 @@ +# bailian-kb-dsh 运行时行为(内部说明) + +面向维护者:记录 `packages/bailian-kb-dsh` 里那些**为什么这么做**的选择。用户面文档在 [packages/bailian-kb-dsh/README.md](../../packages/bailian-kb-dsh/README.md)(中文版 `README.zh.md`),改动清单在 [docs/agents/dsh-plugin.md](../agents/dsh-plugin.md)。 + +## Bundle 声明与配置分层 + +`package.json` 的 `dsh.bundle.patch` 指向 `cordis.patch.yml`,向 profile 插入插件行: + +```yaml +- insert: + - id: tool-bailian-kb + name: "bailian-kb-dsh" + config: + workspaceId: !!js process.env.BAILIAN_WORKSPACE_ID +``` + +`workspaceId` 只是解析链的一层,不是唯一来源:Config 同时注册为 `bailian-kb` settings namespace,patch entry 作 base 层,设置页 / 设置文档的用户层叠在其上;都未设置时 per-call 回退到 `BAILIAN_WORKSPACE_ID` credential。同样的回退覆盖 `defaultRetrieveAgentId`(`BAILIAN_DEFAULT_RETRIEVE_AGENT_ID`)、`defaultChatAgentId`(`BAILIAN_DEFAULT_CHAT_AGENT_ID`)与 API key(`DASHSCOPE_API_KEY`,无 settings 面)。 + +settings 注册是**手写**的,没有用 `installSettingsSection`:需要两个它不带的东西 —— `expose` opt-in(设置页从浏览器改这个 section)和凭据迁移要写入的 scope handle。所有值每次调用经 source thunk 读取,因此设置改动无需重启或重注册工具。 + +### 四个值的解析链 + +| 值 | 1️⃣ settings 用户层(设置页可编辑、回显) | 2️⃣ entry config(patch 或用户覆盖,作 base 层) | 3️⃣ credential(`~/.dsh/.credentials.yaml` / env) | 4️⃣ 都缺失时 | +| ----------------------------------- | ---------------------------------------- | ----------------------------------------------- | ------------------------------------------------- | ----------------------------------------------------------------- | +| `DASHSCOPE_API_KEY` | —(无 settings 面) | —(无 config 面) | ✅ | 工具调用报错并引导配置 | +| `BAILIAN_WORKSPACE_ID` | ✅ `workspaceId` | ✅ `workspaceId` | ✅ | 工具调用报错并引导配置 | +| `BAILIAN_DEFAULT_RETRIEVE_AGENT_ID` | ✅ `defaultRetrieveAgentId` | ✅ `defaultRetrieveAgentId` | ✅ | 该 scene 只有一个已部署服务时取它;否则省略 `agent_id` 的调用报错 | +| `BAILIAN_DEFAULT_CHAT_AGENT_ID` | ✅ `defaultChatAgentId` | ✅ `defaultChatAgentId` | ✅ | 同上 | + +"唯一服务即默认"这一层是 2C 部署的零配置路径:只有一个服务时没有可选项,逼用户在设置里点一次名字买不到任何东西。注意 `agent_id` 在两个工具的 schema 中**恒必填**,模型路径不会触发默认服务回退;回退保留是为程序化调用与 credential 热切换。 + +行为参数(`endpointHost` / `agentVersion` / `chatTimeoutMs`)只在 config/settings 层(设置文档可改,实时生效)。 + +解析后的 workspaceId 还经 `shellEnv` 注册导出为 `BAILIAN_WORKSPACE_ID`,否则 settings 文档里的值对 bash 子进程(`bl knowledge …`)不可见。 + +### 用户覆盖 + +用户 patch 层在本 bundle 之上,按 id 覆盖时**替换整个 config(无 deep-merge)**: + +```yaml +# ~/.dsh/cordis.patch.yml 或 profile 的 cordis.patch.yml +- id: tool-bailian-kb + config: + defaultRetrieveAgentId: aid-search-service + defaultChatAgentId: aid-chat-service + chatTimeoutMs: 600000 +``` + +禁用:`- id: tool-bailian-kb` + `disabled: true`。 + +## Web UI 配置页 + +装进 profile 后,Settings 左侧导航出现"百炼知识库"页(`settings.section` 槽位): + +- **DashScope API Key** — write-only,`type=password` 遮罩输入草稿,仅显示 configured / 来自环境变量 徽标;写 `~/.dsh/.credentials.yaml` +- **Bailian Workspace ID / 默认检索服务 ID / 默认对话服务 ID** — 回显:读写 `bailian-kb` settings 用户层,预填当前解析值;清空保存 = 移除用户层,回退 entry config → credential。两个服务 ID 可从服务缓存里选 +- **自动获取** — 按钮调 Host 桥接路由 `/bailian-kb/autofill`:Host 在宿主机拉起浏览器登录百炼控制台(不经 `bl` 命令),回调落到本机 loopback 端口后直接把 API 密钥写入凭据存储、工作空间 ID 写入 settings,明文 key 不过浏览器;面板轮询到完成后自动刷新(无需再次点击)。登录 URL 始终请求签发新 key,因此每次都与当前账号配对,切换账号直接点一次即可 +- **检索服务缓存** — 经 `/bailian-kb/services` 读缓存诊断(上次拉取时间、各 scene 条数、是否截断)并提供强制刷新按钮:面板存在的意义就是"开发者认为缓存不对"的那一刻 + +桥接路由(`/bailian-kb/settings`、`/bailian-kb/services`、`/bailian-kb/autofill`)而不是 settings wire:wire 需要 apiproxy 白名单,而 composition 不给树外 namespace 授权。GET 和 POST 共用一次 exact-route 注册 —— webServer map 按 (kind, path) 建键,同路径注册两次会抛 "duplicate route"。 + +首次接入 seed:启动时若 API key / workspaceId 从未被设置过(settings、credential、env 均无值),自动从 `~/.bailian/config.json` 采纳一次;`seededFields` 字段(settings 文档内,面板不可编辑)记账已消费 / 已由用户管理的字段,用户主动清空的值永不会被重新填回。 + +降级:远程浏览器(非 loopback,settings RPC 不可达)或未组合 settings 服务时,ID 字段退回旧的 write-only credential 控件,页面顶部显示提示。 + +## 检索服务缓存与上下文注入 + +模型要判断"该不该检索",靠的是看到本 workspace 部署了哪些检索服务。插件内部经 `/api/v1/indices/rag/app/list` 拉取该清单并缓存,**不对模型暴露服务发现工具**(`kb_service_list` 不会回归:它会把"先 list 再 search"的额外一轮重新引入);管理面仍用 bl。 + +### 载体:上下文消息,不是工具描述 + +两个工具的 **description 保持静态**(不含任何服务 id)。清单经 `agent/pre-step` 注入为一条带 source 的 `UserMessage`(`{ kind: 'plugin', plugin: 'tool-bailian-kb/services', form: 'catalog' }`),而不是烘进 tool description。两个原因: + +1. **插件加载是每进程一次,不是每会话一次。** 描述在 `apply()` 时定型,长驻宿主里 TTL 只会被评估一次,用户在控制台新建的服务要等重启才能被感知; +2. **重注册工具会废掉 prompt 前缀缓存**(从第一个变化的 schema token 起)。走上下文消息则让 schema 永久稳定。 + +**变化抑制是正确性要求,不是优化**:`pre-step` 每个"步"(= 一次模型请求)触发一次,一轮里调 5 次工具就触发 6 次。只有清单内容变化时才重发,且判定叠加**可见性**(`session.surface.nodes`)—— 压缩把清单消息裁掉后会自动重新注入,否则模型会静默失去清单。 + +### 清单内容策略 + +| 情形 | 注入内容 | +| ------------------------ | ----------------------------------------------------- | +| 配了默认服务 | 只列该服务 + "另有 N 个" 提示 | +| 未配默认,deployed ≤ 10 | 全量 `agent_id` + 名称 | +| 未配默认,deployed > 10 | 按 `modify_time` 倒序取 10 条,**显式标明截断**与总数 | +| 0 个 / 拉取失败 / 无缓存 | 不注入(工具仍可用) | + +英文框架 + 服务名原样保留;空 scene 整节省略;截断必须告知(静默截断会让模型把清单当全集,进而断言"没有对应知识库")。 + +### 缓存与刷新 + +落点:`${DSH_HOME:-~/.dsh}/cache/bailian-kb/services-.json`(临时文件 + `rename()` 原子发布,目录 `0o700`)。按 workspace 分文件是必需的:api key 只能访问自己的 workspace,而"自动获取"按钮就是为了切账号。 + +存:`agent_id` / `agent_name` / `scene` / `status` / `modify_time`,预留 `description`(待列表接口返回)。**不存 `pipeline_list`** —— 它不稳定携带 `pipeline_name`,做不了知识库标签。 + +| 刷新触发点 | 模型何时看见 | +| ---------------------------------------------------------------------------------- | ------------------------------------ | +| pre-step 间隔调度(超 TTL 30 分钟,后台异步,**不阻塞**) | 下一步 | +| 控制台登录成功(`/bailian-kb/autofill` 回调) | 下一步 | +| agent 跑了 `bl knowledge service create/deploy/delete/copy`(`tools/result` 观察) | 下一步 | +| 面板强制刷新(`POST /bailian-kb/services`) | 下一步 | +| 调用撞 4xx(agent_id 已失效) | **本步**,刷新后的列表追加进错误消息 | +| workspaceId / apiKey 变更 | 下一步 | + +`tools/result` 的匹配是在序列化参数里找命令串,而不是认某个具体工具名:agent 可能用 bash、终端工具或 run_code 跑 `bl`。宽匹配是故意的 —— 误判只多花一次 list 请求,漏判则退回 TTL。 + +刷新失败只 warn,保留旧文档;并发刷新共享一个请求(pre-step 每步都会检查)。pre-step 监听器**永不抛异常** —— 抛出会使用户当前这一步失败。未组合 `agents` 的 headless 装配只是没有清单,工具照常可用。 + +## 错误语义 + +- HTTP 错误:4xx 时刷新服务缓存并把当前可用服务追加进错误消息(这两个接口上 `agent_id` 是唯一的调用方标识符,所以 4xx 大多是 id 已失效);5xx 与刷新本身失败则原错误透传; +- 凭证缺失:指向 `~/.dsh/.env` / `.credentials.yaml` 配置方式与控制台取 key 页面; +- chat 超时:说明服务端多轮检索特性,建议重试或改用 `kb_search`; +- 服务端错误体截断至 500 字符进入错误信息(优先 `code: message`)。 + +## 管理面 skill + +`skills/bailian-kb/SKILL.md` 随包分发,插件通过 `ctx.inject(['skills'])` 在 skills 服务可用时以 `source: 'bundled'` 运行时注册;无 skills 服务的组合(headless 最小装配)不受影响。文件的 YAML frontmatter 是 name / description 的**单一事实源**,注册时会被剥离(`SkillDefinition.content` 契约上是已去元数据的正文,而 runtime 注册路径不做任何解析)。 + +内容:bl CLI 安装 / 鉴权 / workspace 解析、建库 → 上传 → 部署工作流、服务清单的行为语义。**skill 不承担"该不该检索"的引导**(那是工具描述与上下文清单的事:skill 正文要模型先决定加载才能读到,是二阶决策);它反过来承担一件工具做不到的事:**引导 agent 在 `service create` 时把服务名写清楚**。无 desc 时服务名是唯一语义来源,管理面的动作直接决定检索面的效果。 + +## 已知限制的成因 + +- kb_chat 执行期无进展显示(缓冲式消费 SSE)。 +- `top_k` 是客户端截断:请求体不含该参数,服务端返回条数由检索服务配置决定,截断只影响进入模型上下文的量。 +- **服务画像的质量上限取决于服务名**:`service list` 接口当前不返回描述字段,所以模型只能靠 `agent_name` 判断一个服务能查什么。名字模糊的部署引导能力接近于零。列表接口补齐描述字段后只需改三处(`api-types` 补字段名 → `services.ts` 解析 → `buildServiceCatalog` 追加并截断到 200 字符),缓存已预留 `description` 键,无需迁移。 +- 拉取每个 scene 最多 2 页,超出时标 `truncated` 并在清单里告知。 diff --git a/packages/bailian-kb-dsh/LICENSE b/packages/bailian-kb-dsh/LICENSE new file mode 100644 index 0000000..9eb125c --- /dev/null +++ b/packages/bailian-kb-dsh/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2026 Aliyun Model Studio (DashScope) AI Platform + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/bailian-kb-dsh/README.md b/packages/bailian-kb-dsh/README.md index c3cde7e..9f93271 100644 --- a/packages/bailian-kb-dsh/README.md +++ b/packages/bailian-kb-dsh/README.md @@ -1,81 +1,80 @@ -# bailian-kb-dsh +
-百炼知识库的 dsh 插件包(同时是 dsh bundle):在 `ctx.tools` 注册两个检索模型工具(kb_search、kb_chat),并在 skills 服务可用时注册管理面 skill。服务发现通过 bl CLI(bailian-cli)完成。 +# Bailian Knowledge Base for DeepSeek Harness -## 安装(dsh 用户) +**Knowledge-base retrieval tools for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness), backed by Aliyun Model Studio (Bailian).** + +[![npm version](https://img.shields.io/npm/v/bailian-kb-dsh?color=0969da&label=npm)](https://www.npmjs.com/package/bailian-kb-dsh) +[![Node.js](https://img.shields.io/badge/node-%3E%3D22.12-brightgreen)](https://nodejs.org) +[![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6)](https://www.typescriptlang.org) +[![License](https://img.shields.io/badge/license-Apache%202.0-blue)](LICENSE) + +[Bailian Console](https://bailian.console.aliyun.com/) · [中文文档](README.zh.md) · [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) · [API Documentation](https://help.aliyun.com/zh/model-studio/) + +
+ +## What is this? + +`bailian-kb-dsh` is a plugin (and dsh bundle) that gives a DeepSeek Harness agent access to knowledge bases hosted on [Aliyun Model Studio](https://bailian.console.aliyun.com/) (Bailian). It registers two model-facing tools — `kb_search` for raw evidence and `kb_chat` for grounded answers — and ships a settings page plus a management skill for the [`bl` CLI](https://www.npmjs.com/package/bailian-cli). + +Retrieval happens through **retrieval services** you deploy on Bailian: a service binds one or more knowledge bases to an embedding/rerank configuration and is addressed by its `agent_id`. The plugin keeps the list of deployed services in front of the model, so it can decide whether the question is answerable from your knowledge at all. + +## Features + +- **Two model-facing tools** — `kb_search` returns scored chunks with source references; `kb_chat` returns a complete grounded answer +- **Service awareness** — the workspace's deployed retrieval services are injected into the conversation, so the model knows what it can look up instead of guessing service ids +- **Low-friction setup** — sign in to the Bailian console from the settings page to fill in the API key and workspace id; an existing `bl` CLI login is adopted automatically +- **Settings page** — a "Bailian KB" section in the web UI for credentials, default services, and service-cache inspection +- **Management skill** — bundled `bailian-kb` skill teaching the agent the `bl` CLI workflow for creating knowledge bases, ingesting documents, and deploying services + +## Requirements + +- DeepSeek Harness with its plugin runtime (`@deepseek-ai/dsh-*`), Node.js >= 22.12 +- An Aliyun Model Studio account: a **workspace id** and a **DashScope API key** ([get one](https://bailian.console.aliyun.com/?tab=app#/api-key)) +- At least one **deployed** retrieval or Q&A service in that workspace — create one in the [console](https://bailian.console.aliyun.com/) or with `bl knowledge service create` / `bl knowledge service deploy` + +## Installation ```sh -dsh plugin --profile web add bailian-kb-dsh # 本地开发用绝对/相对路径 +dsh plugin --profile web add bailian-kb-dsh ``` -安装后 CLI 自动把 bundle 加入 profile 的层栈,无需手改 YAML。配置写入 `~/.dsh/.env`: +The CLI adds the bundle to the profile's layer stack; no manual YAML editing required. To remove it: ```sh -BAILIAN_WORKSPACE_ID=ws-xxx # 必填:百炼工作空间 id -DASHSCOPE_API_KEY=sk-xxx # 必填:也可放 ~/.dsh/.credentials.yaml,或用设置页的“自动获取” +dsh plugin --profile web remove bailian-kb-dsh ``` -验证:`dsh --profile web --dump-config` 应能看到 `tool-bailian-kb` row。缺 `BAILIAN_WORKSPACE_ID` 时加载期直接报错(fail loud),不会静默跳过。卸载:`dsh plugin --profile web remove bailian-kb-dsh`。 +Verify the plugin is composed — `dsh --profile web --dump-config` should list a `tool-bailian-kb` row. -## 开发 +## Configuration -依赖 dsh 的运行时包(`@deepseek-ai/dsh-tools` 等)以 peerDependencies 声明、由 dsh 安装闭包在运行时提供。 +### Option 1 — Settings page (recommended) + +After installation, **Settings → Bailian KB** appears in the web UI: + +- **Fetch from console login** — opens the Bailian console in a browser on the host machine; when you finish signing in, the API key and workspace id of that account are stored on the host (the key never travels to the browser). Each login requests a freshly issued key, so switching accounts is one click. +- **API key** — write-only: the stored value is never echoed back, only reported as configured or not. +- **Workspace id / default retrieval service / default Q&A service** — editable with echo; the service ids can be picked from the cached service list. Clearing a value falls back to the layers below. +- **Retrieval service cache** — shows when the injected service list was last fetched, how many services it holds, and offers a manual refresh for a service you just created. + +If you have already run `bl auth login`, the API key and workspace id are adopted once from `~/.bailian/config.json` at startup. A value you deliberately clear is never re-filled. + +### Option 2 — Environment and credential files ```sh -pnpm --filter bailian-kb-dsh run build # tsc 出 dist/ + tsdown 出 dist/web/client.js -pnpm --filter bailian-kb-dsh run typecheck # node 半 + web 半两套 tsconfig -pnpm --filter bailian-kb-dsh run test +# ~/.dsh/.env, or the credential store at ~/.dsh/.credentials.yaml +DASHSCOPE_API_KEY=sk-xxx # required +BAILIAN_WORKSPACE_ID=ws-xxx # required +BAILIAN_DEFAULT_RETRIEVE_AGENT_ID=aid-xxx # optional +BAILIAN_DEFAULT_CHAT_AGENT_ID=aid-xxx # optional ``` -本地联调(patch 文件受 HMR 监听): +### Option 3 — Profile patch -```sh -dsh plugin --profile dev add <本仓库>/packages/bailian-kb-dsh -``` - -## Bundle 声明 - -`package.json` 的 `dsh.bundle.patch` 指向 [`cordis.patch.yml`](cordis.patch.yml),向 profile 插入插件行: +The bundle inserts its own entry into the profile; you can override it by id in `~/.dsh/cordis.patch.yml` or the profile's patch file. An override **replaces the whole config object** (no deep merge): ```yaml -- insert: - - id: tool-bailian-kb - name: "bailian-kb-dsh" - config: - workspaceId: !!js process.env.BAILIAN_WORKSPACE_ID -``` - -`workspaceId` 只是解析链的一层,不是唯一来源:Config 同时注册为 `bailian-kb` settings namespace,patch entry 作 base 层,设置页/设置文档的用户层叠在其上;都未设置时 per-call 回退到 `BAILIAN_WORKSPACE_ID` credential。同样回退覆盖 `defaultRetrieveAgentId`(`BAILIAN_DEFAULT_RETRIEVE_AGENT_ID`)、`defaultChatAgentId`(`BAILIAN_DEFAULT_CHAT_AGENT_ID`)与 API key(`DASHSCOPE_API_KEY`,无 settings 面)。 - -### 四个值的解析链 - -| 值 | 1️⃣ settings 用户层(设置页可编辑、回显) | 2️⃣ entry config(本 patch 或用户覆盖,作 base 层) | 3️⃣ credential(`~/.dsh/.credentials.yaml` / env) | 4️⃣ 都缺失时 | -| ----------------------------------- | ---------------------------------------- | -------------------------------------------------- | ------------------------------------------------- | ---------------------------------------------------------------------------------- | -| `DASHSCOPE_API_KEY` | —(无 settings 面) | —(无 config 面) | ✅ | 工具调用报错并引导配置 | -| `BAILIAN_WORKSPACE_ID` | ✅ `workspaceId` | ✅ `workspaceId` | ✅ | 工具调用报错并引导配置 | -| `BAILIAN_DEFAULT_RETRIEVE_AGENT_ID` | ✅ `defaultRetrieveAgentId` | ✅ `defaultRetrieveAgentId` | ✅ | `kb_search` 无 `agent_id` 的**程序化**调用报错并附配置指引(模型侧 schema 恒必填) | -| `BAILIAN_DEFAULT_CHAT_AGENT_ID` | ✅ `defaultChatAgentId` | ✅ `defaultChatAgentId` | ✅ | `kb_chat` 无 `agent_id` 的**程序化**调用报错并附配置指引(模型侧 schema 恒必填) | - -行为参数(`endpointHost`/`agentVersion`/`chatTimeoutMs`)在 config/settings 层(设置文档可改,实时生效)。 - -### Web UI 配置页 - -装进 profile 后,Settings 左侧导航出现“百炼知识库”页(`settings.section` 槽位): - -- **DashScope API Key** — write-only,`type=password` 遮罩输入草稿,仅显示 configured/来自环境变量 徽标;写 `~/.dsh/.credentials.yaml` -- **Bailian Workspace ID / 默认检索服务 ID / 默认对话服务 ID** — 回显:读写 `bailian-kb` settings 用户层,预填当前解析值;清空保存 = 移除用户层,回退 entry config → credential -- **自动获取** — 按钮调 Host 桥接路由 `/bailian-kb/autofill`:Host 在宿主机拉起浏览器登录百炼控制台(不经 `bl` 命令),回调落到本机 loopback 端口后直接把 API 密钥写入凭据存储、工作空间 ID 写入 settings,明文 key 不过浏览器;面板轮询到完成后自动刷新(无需再次点击)。登录 URL 始终请求签发新 key,因此每次都与当前账号配对,切换账号直接点一次即可。 - -首次接入 seed:启动时若 API key / workspaceId 从未被设置过(settings、credential、env 均无值),自动从 `~/.bailian/config.json` 采纳一次;`seededFields` 字段(settings 文档内,面板不可编辑)记账已消费/已由用户管理的字段,用户主动清空的值永不会被重新填回。 - -降级:远程浏览器(非 loopback,settings RPC 不可达)或未组合 settings 服务时,ID 字段退回旧的 write-only credential 控件,页面顶部显示提示。 - -### 用户覆盖 - -用户 patch 层在本 bundle 之上,按 id 覆盖时**替换整个 config(无 deep-merge)**: - -```yaml -# ~/.dsh/cordis.patch.yml 或 profile 的 cordis.patch.yml - id: tool-bailian-kb config: defaultRetrieveAgentId: aid-search-service @@ -83,87 +82,77 @@ dsh plugin --profile dev add <本仓库>/packages/bailian-kb-dsh chatTimeoutMs: 600000 ``` -禁用:`- id: tool-bailian-kb` + `disabled: true`。 +Disable the plugin with `- id: tool-bailian-kb` plus `disabled: true`. -## Config +### Config fields -Config 同时注册为 `bailian-kb` settings namespace(`installSettingsSection`):profile patch 的 entry config 作为 base 层,用户在设置页/设置文档的修改叠在其上且实时生效(所有值每次调用经 source thunk 读取,无需重启或重注册工具)。 +The config doubles as the `bailian-kb` settings section, so edits in the settings page or settings document apply to the next call without a restart. -| 字段 | 类型 | 默认 | 语义 | -| ------------------------ | ------- | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `workspaceId` | string? | — | 百炼工作空间 id;API host 为 workspace 子域名 `https://.`。未设置时每次调用回退 `BAILIAN_WORKSPACE_ID` credential | -| `endpointHost` | string | `cn-beijing.maas.aliyuncs.com` | host 后缀,其他 region/私有化时替换 | -| `defaultRetrieveAgentId` | string? | — | 默认检索服务;`kb_search` 的 `agent_id` 参数 schema **恒必填**(模型永远显式传),此默认仅作用于省略 `agent_id` 的程序化调用,每次调用运行时解析(settings/config → credential) | -| `defaultChatAgentId` | string? | — | 默认对话服务;`kb_chat` 的 `agent_id` 参数 schema **恒必填**(模型永远显式传),此默认仅作用于省略 `agent_id` 的程序化调用,每次调用运行时解析(settings/config → credential) | -| `agentVersion` | string? | — | `beta`(草稿调试)或已发布版本号;不暴露给模型 | -| `chatTimeoutMs` | number | 300000 | kb_chat 超时;服务端是分钟级 agentic loop | +| Field | Type | Default | Meaning | +| ------------------------ | ------- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------- | +| `workspaceId` | string? | — | Bailian workspace id; the API host is the workspace subdomain `https://.` | +| `endpointHost` | string | `cn-beijing.maas.aliyuncs.com` | Host suffix; replace for another region or a private deployment | +| `defaultRetrieveAgentId` | string? | — | Service `kb_search` falls back to when the caller omits `agent_id` | +| `defaultChatAgentId` | string? | — | Service `kb_chat` falls back to when the caller omits `agent_id` | +| `agentVersion` | string? | — | `beta` (draft) or a published version number; defaults to the latest published version. Not exposed to the model | +| `chatTimeoutMs` | number | `300000` | `kb_chat` timeout — the server side is a minutes-scale agentic loop | -凭证与回退链:`DASHSCOPE_API_KEY` 只走 `ctx.credentials` 引用(write-only,每次调用重新解析,热更换生效);`workspaceId`/`defaultRetrieveAgentId`/`defaultChatAgentId` 先取 settings 解析值(用户层 > entry config),缺失时回退同名 credential(`BAILIAN_WORKSPACE_ID`/`BAILIAN_DEFAULT_RETRIEVE_AGENT_ID`/`BAILIAN_DEFAULT_CHAT_AGENT_ID`),都没有时报错并附配置指引。注意:`agent_id` 在两个工具的 schema 中恒必填,模型路径不会触发默认服务回退;回退保留是为程序化调用与 credential 热切换。 +### Resolution order -## 工具 +| Value | Settings layer (settings page) | Entry config (profile patch) | Credential store / env | +| ------------------------- | ------------------------------ | ---------------------------- | ----------------------------------- | +| `DASHSCOPE_API_KEY` | write-only control | — | `DASHSCOPE_API_KEY` | +| workspace id | ✅ `workspaceId` | ✅ `workspaceId` | `BAILIAN_WORKSPACE_ID` | +| default retrieval service | ✅ `defaultRetrieveAgentId` | ✅ `defaultRetrieveAgentId` | `BAILIAN_DEFAULT_RETRIEVE_AGENT_ID` | +| default Q&A service | ✅ `defaultChatAgentId` | ✅ `defaultChatAgentId` | `BAILIAN_DEFAULT_CHAT_AGENT_ID` | -| 工具 | 参数 | 返回 | -| ----------- | --------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | -| `kb_search` | `query`、`agent_id`(**必填**;程序化省略时回退 defaultRetrieveAgentId)、`top_k?`(默认 5,**客户端截断**——服务端无此参数)、`images?` | chunks(text/score/来源)+ total | -| `kb_chat` | `message`、`agent_id`(**必填**;程序化省略时回退 defaultChatAgentId) | 完整答案(内部消费 SSE 流缓冲返回)+ request_id | +Every value is re-read per call, so a rotated key or a switched workspace takes effect immediately. The API key and workspace id are mandatory: without them the tools fail with a message pointing at these configuration paths. Default services are optional — when the workspace has exactly one deployed service for a scene, that one is used. -两个工具的 **description 保持静态**(不含任何服务 id);可用服务清单由下述服务缓存经 `agent/pre-step` 注入为上下文消息。 +## Tools -## 检索服务缓存与上下文注入 +| Tool | Parameters | Returns | +| ----------- | ---------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | +| `kb_search` | `query`, `agent_id` (required), `top_k?` (default 5, applied client-side), `images?` (image URLs for multimodal) | Scored chunks with source references, plus a total | +| `kb_chat` | `message`, `agent_id` (required) | The complete answer plus a `request_id` | -模型要判断"该不该检索",靠的是看到本 workspace 部署了哪些检索服务。插件内部经 `/api/v1/indices/rag/app/list` 拉取该清单并缓存,**不对模型暴露服务发现工具**(`kb_service_list` 不会回归:它会把"先 list 再 search"的额外一轮重新引入);管理面仍用 bl。 +`agent_id` is required in both schemas: the schema cannot tell the model whether this deployment pins a default, and discovering a missing default at call time wastes a round-trip. The configured default is still honoured for programmatic calls that omit it. -### 载体:上下文消息,不是工具描述 +The list of deployed services — id, name, and scene — is injected into the conversation as a context message, refreshed periodically and whenever a `bl knowledge service` command changes the inventory. Long lists are truncated with the total stated, so the model never mistakes a partial list for the full inventory. -清单经 `agent/pre-step` 注入为一条带 source 的 `UserMessage`(`{ kind: 'plugin', plugin: 'tool-bailian-kb/services', form: 'catalog' }`),而不是烘进 tool description。两个原因: +## Errors -1. **插件加载是每进程一次,不是每会话一次。** 描述在 `apply()` 时定型,长驻宿主里 TTL 只会被评估一次,用户在控制台新建的服务要等重启才能被感知; -2. **重注册工具会废掉 prompt 前缀缓存**(从第一个变化的 schema token 起)。走上下文消息则让 schema 永久稳定。 +- **HTTP 4xx** — most often an `agent_id` that no longer exists, so the service list is refreshed and appended to the error message for immediate recovery +- **HTTP 5xx** — passed through unchanged +- **Missing credentials** — the message names the configuration paths (`~/.dsh/.env`, `~/.dsh/.credentials.yaml`, the settings page) and links to the console key page +- **`kb_chat` timeout** — the message explains the server-side multi-turn retrieval and suggests retrying or switching to `kb_search` -**变化抑制是正确性要求,不是优化**:`pre-step` 每个“步”(= 一次模型请求)触发一次,一轮里调 5 次工具就触发 6 次。只有清单内容变化时才重发,且判定叠加**可见性**(`session.surface.nodes`)——压缩把清单消息裁掉后会自动重新注入,否则模型会静默失去清单。 +## Known limitations -### 清单内容策略 +- `kb_chat` buffers the server stream, so there is no progress output while it runs. +- `top_k` is a client-side cut: the request body carries no such parameter, and how many chunks the server returns is decided by the service configuration. +- **Service names carry the routing signal.** The service list API does not return a description field yet, so the model judges what a service covers from its name alone. Name your services after their content (`Product docs retrieval`, not `Service 1`). +- At most two pages per scene are fetched; beyond that the injected list is marked as truncated. -| 情形 | 注入内容 | -| ------------------------ | ----------------------------------------------------- | -| 配了默认服务 | 只列该服务 + "另有 N 个" 提示 | -| 未配默认,deployed ≤ 10 | 全量 `agent_id` + 名称 | -| 未配默认,deployed > 10 | 按 `modify_time` 倒序取 10 条,**显式标明截断**与总数 | -| 0 个 / 拉取失败 / 无缓存 | 不注入(工具仍可用) | +## Development -英文框架 + 服务名原样保留;空 scene 整节省略;截断必须告知(静默截断会让模型把清单当全集,进而断言"没有对应知识库")。 +```sh +pnpm --filter bailian-kb-dsh run build # tsc → dist/ (node half) + tsdown → dist/web/client.js (browser half) +pnpm --filter bailian-kb-dsh run typecheck # node and web tsconfigs +pnpm --filter bailian-kb-dsh run test +``` -### 缓存与刷新 +For local integration, add the working copy to a dev profile (the patch file is watched by HMR): -落点:`${DSH_HOME:-~/.dsh}/cache/bailian-kb/services-.json`(临时文件 + `rename()` 原子发布,目录 `0o700`)。按 workspace 分文件是必需的:api key 只能访问自己的 workspace,而"自动获取"按钮就是为了切账号。 +```sh +dsh plugin --profile dev add /packages/bailian-kb-dsh +``` -存:`agent_id` / `agent_name` / `scene` / `status` / `modify_time`,预留 `description`(待列表接口返回)。**不存 `pipeline_list`**——它不稳定携带 `pipeline_name`,做不了知识库标签。 +Internal design notes — context injection strategy, service cache layout, refresh triggers — live in [docs/kb-dsh/runtime-behavior.md](https://github.com/modelstudioai/cli/blob/main/docs/kb-dsh/runtime-behavior.md); the maintenance checklist is [docs/agents/dsh-plugin.md](https://github.com/modelstudioai/cli/blob/main/docs/agents/dsh-plugin.md). -| 刷新触发点 | 模型何时看见 | -| --------------------------------------------------------- | ------------------------------------ | -| pre-step 间隔调度(超 TTL 30 分钟,后台异步,**不阻塞**) | 下一步 | -| 控制台登录成功(`/bailian-kb/autofill` 回调) | 下一步 | -| 调用撞 4xx(agent_id 已失效) | **本步**,刷新后的列表追加进错误消息 | -| workspaceId / apiKey 变更 | 下一步 | +## Contributing -刷新失败只 warn,保留旧文档;并发刷新共享一个请求(pre-step 每步都会检查)。pre-step 监听器**永不抛异常**——抛出会使用户当前这一步失败。未组合 `agents` 的 headless 装配只是没有清单,工具照常可用。 +Bug reports, feature requests, and PRs are welcome. See [CONTRIBUTING.md](https://github.com/modelstudioai/cli/blob/main/CONTRIBUTING.md) for developer setup and the contribution workflow. -## 错误语义 +## License -- HTTP 错误:4xx 时刷新服务缓存并把当前可用服务追加进错误消息(这两个接口上 `agent_id` 是唯一的调用方标识符,所以 4xx 大多是 id 已失效);5xx 与刷新本身失败则原错误透传; -- 凭证缺失:指向 `~/.dsh/.env` / `.credentials.yaml` 配置方式与控制台取 key 页面; -- chat 超时:说明服务端多轮检索特性,建议重试或改用 `kb_search`; -- 服务端错误体截断至 500 字符进入错误信息(优先 `code: message`)。 - -## 管理面 skill - -`skills/bailian-kb/SKILL.md` 随包分发,插件通过 `ctx.inject(['skills'])` 在 skills 服务可用时以 `source: 'bundled'` 运行时注册;无 skills 服务的组合(headless 最小装配)不受影响。文件的 YAML frontmatter 是 name / description 的**单一事实源**,注册时会被剥离(`SkillDefinition.content` 契约上是已去元数据的正文,而 runtime 注册路径不做任何解析)。 - -内容:bl CLI 安装/鉴权/workspace 解析、建库→上传→部署工作流、服务清单的行为语义。**skill 不承担"该不该检索"的引导**(那是工具描述与上下文清单的事:skill 正文要模型先决定加载才能读到,是二阶决策);它反过来承担一件工具做不到的事:**引导 agent 在 `service create` 时把服务名写清楚**。无 desc 时服务名是唯一语义来源,管理面的动作直接决定检索面的效果。 - -## Known Limitations - -- kb_chat 执行期无进展显示(缓冲式)。 -- `top_k` 是客户端截断:请求体不含该参数,服务端返回条数由检索服务配置决定,截断只影响进入模型上下文的量。 -- **服务画像的质量上限取决于服务名**:`service list` 接口当前不返回描述字段,所以模型只能靠 `agent_name` 判断一个服务能查什么。名字模糊的部署引导能力接近于零。列表接口补齐描述字段后只需改三处(`api-types` 补字段名 → `services.ts` 解析 → `buildServiceCatalog` 追加并截断到 200 字符),缓存已预留 `description` 键,无需迁移。 -- 拉取每个 scene 最多 2 页,超出时标 `truncated` 并在清单里告知。 +[Apache 2.0](LICENSE) diff --git a/packages/bailian-kb-dsh/README.zh.md b/packages/bailian-kb-dsh/README.zh.md new file mode 100644 index 0000000..9ab976b --- /dev/null +++ b/packages/bailian-kb-dsh/README.zh.md @@ -0,0 +1,158 @@ +
+ +# 百炼知识库 for DeepSeek Harness + +**基于阿里云百炼(Aliyun Model Studio)的知识库检索工具,供 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 使用。** + +[![npm version](https://img.shields.io/npm/v/bailian-kb-dsh?color=0969da&label=npm)](https://www.npmjs.com/package/bailian-kb-dsh) +[![Node.js](https://img.shields.io/badge/node-%3E%3D22.12-brightgreen)](https://nodejs.org) +[![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6)](https://www.typescriptlang.org) +[![License](https://img.shields.io/badge/license-Apache%202.0-blue)](LICENSE) + +[百炼控制台](https://bailian.console.aliyun.com/) · [English](README.md) · [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) · [API 文档](https://help.aliyun.com/zh/model-studio/) + +
+ +## 这是什么? + +`bailian-kb-dsh` 是一个 DeepSeek Harness 插件(同时是 dsh bundle),让 agent 能检索托管在[阿里云百炼](https://bailian.console.aliyun.com/)上的知识库。它注册两个面向模型的工具 —— `kb_search` 取原始证据、`kb_chat` 出成品答案 —— 并附带一个设置页和一份面向 [`bl` CLI](https://www.npmjs.com/package/bailian-cli) 的管理 skill。 + +检索经由你在百炼上部署的**检索服务**完成:一个服务把一个或多个知识库绑定到指定的向量 / 排序配置上,通过 `agent_id` 寻址。插件会把已部署服务的清单持续呈现给模型,让它能判断用户的问题是否落在你的知识范围内。 + +## 功能特性 + +- **两个面向模型的工具** — `kb_search` 返回带分数和来源的知识切片;`kb_chat` 返回基于知识的完整答案 +- **服务感知** — 工作空间里已部署的检索服务会注入到会话上下文,模型据此知道自己能查什么,不必猜 `agent_id` +- **低门槛配置** — 在设置页登录百炼控制台即可自动填入 API 密钥与工作空间 ID;已有的 `bl` CLI 登录会被自动采纳 +- **设置页** — Web UI 中的"百炼知识库"页,管理凭据、默认服务,并可查看服务缓存状态 +- **管理 skill** — 随包分发的 `bailian-kb` skill,教 agent 用 `bl` CLI 完成建库、文档导入、服务部署 + +## 环境要求 + +- DeepSeek Harness 及其插件运行时(`@deepseek-ai/dsh-*`),Node.js >= 22.12 +- 阿里云百炼账号:一个**工作空间 ID** 和一个 **DashScope API 密钥**([去获取](https://bailian.console.aliyun.com/?tab=app#/api-key)) +- 该工作空间下至少有一个**已部署**的检索或问答服务 —— 可在[控制台](https://bailian.console.aliyun.com/)创建,或用 `bl knowledge service create` / `bl knowledge service deploy` + +## 安装 + +```sh +dsh plugin --profile web add bailian-kb-dsh +``` + +CLI 会自动把 bundle 加入 profile 的层栈,无需手改 YAML。卸载: + +```sh +dsh plugin --profile web remove bailian-kb-dsh +``` + +验证插件已装配:`dsh --profile web --dump-config` 应能看到 `tool-bailian-kb` row。 + +## 配置 + +### 方式一 — 设置页(推荐) + +安装后,Web UI 的 **Settings → 百炼知识库** 页出现: + +- **自动获取** — 在宿主机浏览器中拉起百炼控制台登录;登录完成后,该账号的 API 密钥与工作空间 ID 直接落到宿主机(明文密钥不经过浏览器)。每次登录都会请求签发新密钥,因此切换账号点一次即可。 +- **API 密钥** — 只写不回显:存下的值不会再次显示,只显示"已配置 / 未配置"。 +- **工作空间 ID / 默认检索服务 / 默认对话服务** — 可编辑且回显;两个服务 ID 可从缓存的服务清单里选。清空保存则回退到下层来源。 +- **检索服务缓存** — 展示注入清单的上次拉取时间、各场景服务条数、是否被截断,并提供手动刷新(刚新建完服务想立刻生效时用)。 + +如果此前已运行过 `bl auth login`,启动时会从 `~/.bailian/config.json` 一次性采纳 API 密钥与工作空间 ID。被你主动清空的值不会被重新填回。 + +### 方式二 — 环境变量与凭据文件 + +```sh +# ~/.dsh/.env,或凭据存储 ~/.dsh/.credentials.yaml +DASHSCOPE_API_KEY=sk-xxx # 必填 +BAILIAN_WORKSPACE_ID=ws-xxx # 必填 +BAILIAN_DEFAULT_RETRIEVE_AGENT_ID=aid-xxx # 选填 +BAILIAN_DEFAULT_CHAT_AGENT_ID=aid-xxx # 选填 +``` + +### 方式三 — Profile patch + +bundle 会向 profile 插入自己的 entry,你可以在 `~/.dsh/cordis.patch.yml` 或 profile 的 patch 文件里按 id 覆盖。覆盖时**替换整个 config 对象(无 deep-merge)**: + +```yaml +- id: tool-bailian-kb + config: + defaultRetrieveAgentId: aid-search-service + defaultChatAgentId: aid-chat-service + chatTimeoutMs: 600000 +``` + +禁用插件:`- id: tool-bailian-kb` 加 `disabled: true`。 + +### 配置字段 + +Config 同时注册为 `bailian-kb` settings section,因此在设置页或设置文档里的修改会在下一次调用生效,无需重启。 + +| 字段 | 类型 | 默认值 | 语义 | +| ------------------------ | ------- | ------------------------------ | --------------------------------------------------------------------------------- | +| `workspaceId` | string? | — | 百炼工作空间 ID;API host 为工作空间子域名 `https://.` | +| `endpointHost` | string | `cn-beijing.maas.aliyuncs.com` | host 后缀,其他 region 或私有化部署时替换 | +| `defaultRetrieveAgentId` | string? | — | 调用方省略 `agent_id` 时 `kb_search` 使用的服务 | +| `defaultChatAgentId` | string? | — | 调用方省略 `agent_id` 时 `kb_chat` 使用的服务 | +| `agentVersion` | string? | — | `beta`(草稿调试)或已发布版本号;默认调用最新发布版本。不暴露给模型 | +| `chatTimeoutMs` | number | `300000` | `kb_chat` 超时时间 —— 服务端是分钟级的多轮检索循环 | + +### 解析优先级 + +| 值 | settings 用户层(设置页) | entry config(profile patch) | 凭据存储 / 环境变量 | +| ------------------- | --------------------------- | ----------------------------- | ----------------------------------- | +| `DASHSCOPE_API_KEY` | 只写控件 | — | `DASHSCOPE_API_KEY` | +| 工作空间 ID | ✅ `workspaceId` | ✅ `workspaceId` | `BAILIAN_WORKSPACE_ID` | +| 默认检索服务 | ✅ `defaultRetrieveAgentId` | ✅ `defaultRetrieveAgentId` | `BAILIAN_DEFAULT_RETRIEVE_AGENT_ID` | +| 默认对话服务 | ✅ `defaultChatAgentId` | ✅ `defaultChatAgentId` | `BAILIAN_DEFAULT_CHAT_AGENT_ID` | + +所有值每次调用重新解析,因此轮换密钥或切换工作空间即时生效。API 密钥与工作空间 ID 是必填项:缺失时工具调用会报错并指出上述配置路径。默认服务是选填的 —— 当某个场景下工作空间只有一个已部署服务时,直接用它。 + +## 工具 + +| 工具 | 参数 | 返回 | +| ----------- | ---------------------------------------------------------------------------------------- | ------------------------------ | +| `kb_search` | `query`、`agent_id`(必填)、`top_k?`(默认 5,客户端截断)、`images?`(多模态图片 URL) | 带来源引用的评分切片,以及总数 | +| `kb_chat` | `message`、`agent_id`(必填) | 完整答案,以及 `request_id` | + +两个工具的 schema 中 `agent_id` 均为必填:schema 无法告诉模型这套部署是否配了默认服务,而"调用时才发现没有默认值"会白费一轮。配置的默认服务仍对省略该参数的程序化调用生效。 + +已部署服务的清单(ID、名称、场景)以上下文消息的形式注入会话,周期性刷新,`bl knowledge service` 命令改动服务清单时也会刷新。清单过长时会截断并注明总数,避免模型把部分清单当成全部。 + +## 错误处理 + +- **HTTP 4xx** — 多数情况是 `agent_id` 已失效,因此会刷新服务清单并追加到错误信息里,便于立即纠正 +- **HTTP 5xx** — 原样透传 +- **凭据缺失** — 错误信息指出配置路径(`~/.dsh/.env`、`~/.dsh/.credentials.yaml`、设置页)并给出控制台取密钥的链接 +- **`kb_chat` 超时** — 错误信息说明服务端多轮检索的特性,建议重试或改用 `kb_search` + +## 已知限制 + +- `kb_chat` 会缓冲服务端流式输出,执行期间没有进展显示。 +- `top_k` 是客户端截断:请求体不含该参数,服务端返回多少切片由检索服务配置决定。 +- **服务名承载了路由信号。** 服务列表接口目前不返回描述字段,模型只能靠服务名判断一个服务能查什么。请按内容命名(`产品文档检索`,而不是`检索服务1`)。 +- 每个场景最多拉取两页,超出时注入的清单会标明已截断。 + +## 开发 + +```sh +pnpm --filter bailian-kb-dsh run build # tsc 出 dist/(node 半)+ tsdown 出 dist/web/client.js(浏览器半) +pnpm --filter bailian-kb-dsh run typecheck # node 与 web 两套 tsconfig +pnpm --filter bailian-kb-dsh run test +``` + +本地联调时把工作副本装进 dev profile(patch 文件受 HMR 监听): + +```sh +dsh plugin --profile dev add <本仓库>/packages/bailian-kb-dsh +``` + +内部设计说明(上下文注入策略、服务缓存布局、刷新触发点)见 [docs/kb-dsh/runtime-behavior.md](https://github.com/modelstudioai/cli/blob/main/docs/kb-dsh/runtime-behavior.md);维护清单见 [docs/agents/dsh-plugin.md](https://github.com/modelstudioai/cli/blob/main/docs/agents/dsh-plugin.md)。 + +## 参与贡献 + +欢迎提交 Bug 报告、功能建议和 PR。开发环境搭建与贡献流程见 [CONTRIBUTING.md](https://github.com/modelstudioai/cli/blob/main/CONTRIBUTING.md)。 + +## 许可证 + +[Apache 2.0](LICENSE)