feat: add external knowledge retrieval and cross-project activity

Connect Dify, FastGPT and RAGFlow through read-only bindings, encrypted local credentials, shared retrieval and persistent citations. Configuration and removal affect GoodBuddy only.

Show active conversations across projects, retain older live work, and improve project dialog scrolling. Queue concurrent questions and route managed SSH OpenCode questions through the Agent. Preserve recoverable read/search errors and partial search results.

Document the Electron UI automation standard, real model and external knowledge validation, and remaining acceptance limits.

Validation: earlier full regression passed 4081 tests with 66 skipped; subsequent focused remote-question checks and real Linux evidence are recorded in feature progress. Precommit typecheck and lint passed; latest project dialog and shared UI suites passed 58 tests. The full suite was not rerun for the final dialog changes.

Release note: Use external knowledge in conversations, find active work across projects, and answer concurrent local or managed SSH OpenCode questions. Managed SSH questions require the updated Agent adapter.
This commit is contained in:
mesalogo
2026-09-13 13:10:01 +08:00
parent b84250f62a
commit 3b560c9a5e
102 changed files with 6716 additions and 1484 deletions
+11 -5
View File
@@ -1,8 +1,12 @@
# 知识库
知识库已提供本地资料导入、解析、分块、索引、检索、图谱和带来源的知识问答。
将 Dify、FastGPT 和 RAGFlow 中已有知识库接入同一检索范围已完成 Main 进程适配器和
真实接口验证,但实例管理、绑定、统一检索和界面尚未接通,因此仍不属于当前已提供能力。
当前工作区已接入 Dify、FastGPT 和 RAGFlow 的实例管理、外部绑定、检索配置、统一检索、
聊天引用与界面。2026-09-13 已验证三家 Provider 的生产服务、本机 HTTP MCP 和生产 IPC
短答案生成,并从完整 App 输入区完成 Dify 检索、真实回答及引用点击。外部实例弹窗已修正
Portal 字段样式,并完成浅深主题和键盘检查。当前 UI 修正后的聚焦测试、全量回归及相关检查已通过;
FastGPT/RAGFlow 完整 App 与专属配置、真实远程预检索仍待验收。验证只覆盖有依据的短答案,
不代表广泛问答质量,详见[实施进度](./progress.md)。
## 文档导航
@@ -10,7 +14,7 @@
| --- | --- |
| [知识库检索与分块增强 PRD](./prd.md) | 检索、分块、诊断和产品验收 |
| [User Stories](./user-stories.md) | 知识使用者、维护者和调试者场景 |
| [UI 设计](./ui-design.md) | 当前本地知识库的信息架构、首次使用、状态与高级功能分层 |
| [UI 设计](./ui-design.md) | 本地知识库的信息架构、首次使用、状态与外部界面分工 |
| [实施进度](./progress.md) | 已验证实现事实、验证命令与剩余外部知识库工作 |
| [本地文本向量技术设计](./local-embedding-technical-design.md) | 本地与兼容向量连接、模型包、索引和进程边界 |
| [知识检索评估](./retrieval-evaluation.md) | 检索质量评估方法、数据集、指标和门槛 |
@@ -18,10 +22,12 @@
| [外部知识库 User Stories](./external-knowledge-user-stories.md) | 实例配置、添加、使用和故障恢复场景 |
| [外部知识库逻辑设计](./external-knowledge-logic-design.md) | 状态、不变量、配置优先级和失败规则 |
| [外部知识库 UI 设计](./external-knowledge-ui-design.md) | 知识库页面、外部实例管理和创建向导交互 |
| [外部知识库技术设计](./external-knowledge-technical-design.md) | Provider Adapter、凭据、持久化、IPC 和检索标准化 |
| [外部知识库技术设计](./external-knowledge-technical-design.md) | 当前 Client、Service、Store、共享契约、IPC 和检索标准化 |
| [外部知识库实测基线](./external-knowledge-probe-baseline.json) | 脱敏记录真实实例的接口状态、字段结构、特色能力和耗时 |
| [2026-09-10 基础复测](./external-knowledge-probe-2026-09-10-strict.json) | 开启证书校验的目录、详情和基础检索结果 |
| [2026-09-10 扩展复测](./external-knowledge-probe-2026-09-10-extended.json) | 配置覆盖、重排、图谱与知识编译开关的请求结果及结构 |
| [2026-09-13 产品验证摘要](./external-knowledge-validation-2026-09-13.json) | 生产服务与本机 HTTP MCP、Dify 桌面、凭据重开、调用计数、最终自动检查状态及远程阻断的脱敏证据 |
| [2026-09-13 UI 与真实生成复验](./external-knowledge-follow-up-2026-09-13.json) | 后续 UI 修正、三家生产 IPC 生成、Dify 完整 App 回答与引用、视觉 fixture 及本轮请求计数 |
## 术语
@@ -34,4 +40,4 @@
或进入本地索引;实际用于回答的有界片段会随引用保存在本地会话中。
本目录尚未拆出统一逻辑设计;当前本地知识库状态规则由 PRD、User Stories 和实现契约
共同约束。外部知识库继续使用独立逻辑设计,直至进入实施阶段。
定义。外部知识库的实例、绑定和失败规则由独立逻辑设计维护。
@@ -0,0 +1,106 @@
{
"date": "2026-09-13",
"kind": "sanitized-ui-and-real-generation-follow-up",
"sources": ["kb-real-generation/report.json", "kb-full-app-live/report.json", "external-visual-0913/results.json", "goodbuddy-binding-selector-fix-full.log", "goodbuddy-binding-selector-fix-full.result.json"],
"automatedChecks": {
"status": "passed-after-ui-follow-up",
"authoritativeCounts": "progress.md",
"focusedWholeAppAndTwoAgentTests": { "status": "passed", "evidence": "owner handoff" },
"fullSuite": {
"command": "npm test -- --maxWorkers=1 --reporter=verbose",
"status": "passed",
"exitCode": 0,
"evidence": ["goodbuddy-binding-selector-fix-full.log", "goodbuddy-binding-selector-fix-full.result.json"]
},
"typecheck": { "command": "npm run typecheck", "status": "passed", "evidence": "owner handoff" },
"lint": { "command": "npm run lint", "status": "passed", "evidence": "owner handoff" },
"workingTreeDiffCheck": { "status": "passed", "evidence": "owner handoff" },
"stagedDiffCheck": { "status": "passed", "evidence": "owner handoff" },
"sourceStability": {
"scope": "App test, two Agent tests and ExternalKnowledge stable throughout final run",
"evidence": "owner handoff"
},
"lastTestAdjustment": "App test changed from obsolete native select interaction to actual accessible rich menu interaction; no product source change",
"agentFailureResolution": "resolved in concurrent owner's latest code; no edits by this docs task",
"furtherSourceChanges": "none planned per owner handoff"
},
"model": {
"configuration": "saved default model settings; credential resolved in Main using safeStorage",
"transport": "http",
"originalSettingsUnchangedInFullAppRun": true,
"redaction": "No endpoints, private profile identifiers, keys, business text, answer prose or content hashes retained"
},
"productionIpcGeneration": {
"realElectronPreloadIpcAndModelRuntime": true,
"fullAppRenderer": false,
"referenceDisplay": "isolated validation DOM receiving real preload events",
"persistence": "production task_events; text, single-library scope and external locators verified",
"scenarios": [
{ "provider": "dify", "mode": "always", "passed": true },
{ "provider": "fastgpt", "mode": "always", "passed": false, "groundedQuote": true, "citationMarker": true, "failure": "initial raw-string evidence assertion did not account for serialization" },
{ "provider": "ragflow", "mode": "always", "passed": false, "groundedQuote": true, "citationMarker": true, "failure": "initial raw-string evidence assertion did not account for serialization" },
{ "provider": "dify", "mode": "auto", "passed": true, "transport": "ModelToolProvider to KnowledgeMcpGateway in process; not HTTP MCP", "tool": "knowledge_search" },
{ "provider": "fastgpt", "mode": "always", "passed": true, "evidenceCheck": "decoded provider body and embedded evidence JSON" },
{ "provider": "ragflow", "mode": "always", "passed": true, "evidenceCheck": "decoded provider body and embedded evidence JSON" }
],
"finalScenarioChecks": ["nonempty short answer", "grounded quote", "citation marker", "reference schema", "text and references persisted"],
"requests": { "model": 7, "kbTotal": 12, "kbRetrieval": 9, "kbCatalog": 3 },
"retrievalBreakdown": { "bindingVerification": 3, "forcedPreflight": 5, "autoScoped": 1 },
"runtimeModelRetries": 0,
"strictHttpsProviders": ["dify", "fastgpt"],
"httpProviders": ["ragflow"]
},
"fullApp": {
"provider": "dify",
"mode": "always",
"workMode": "ask",
"currentProductionMainPreloadRenderer": true,
"instanceAndPositiveBindingCreatedInUi": true,
"composerSelectionAndSendClicked": true,
"noFabricatedMessagesReferencesOrEvents": true,
"mainOnlyKbCredentialSubstitution": true,
"generationDone": true,
"answerPersisted": true,
"singleSelectedLibrary": true,
"liveEvidencePresentInModelInput": true,
"groundedShortAnswerWithCitation": true,
"citationClicked": true,
"citationSnippetDocumentAndChunkMatch": true,
"citationFocusInside": true,
"strictDifyHttps": true,
"modelHttp": true,
"requests": { "model": 1, "kbTotal": 5, "kbRetrieval": 3, "kbCatalog": 1, "kbDetail": 1 },
"setupVerificationRetrievals": 2,
"composerRetrievals": 1,
"toolExecutions": 0,
"portalEditor": { "themes": ["light", "dark"], "widthPx": 560, "sharedTypography": true, "withinViewport": true, "bodyOverflow": 0 },
"isolatedProfileAndBuildRemoved": true
},
"visualFixture": {
"boundary": "current source components and styles in real Electron with real body portal; fake local IPC data only",
"themes": ["light", "dark"],
"viewportSizes": [[1280, 800], [960, 720], [720, 640], [640, 420]],
"screenshots": 56,
"checksPassed": 138,
"errors": [],
"keyboardMethod": "native Chromium actions via Electron sendInputEvent and insertText",
"coverage": ["password reveal layout", "switch keyboard toggle", "sticky footer visibility", "focus wrap and restoration", "instance menu arrows and disabled items", "tabs", "credential replacement and clear confirmation", "selected hover", "empty state"]
},
"followUpRequestAccounting": {
"modelRequests": 8,
"kbRequests": 17,
"kbRetrievalRequests": 12,
"kbCatalogRequests": 4,
"kbDetailRequests": 1,
"boundary": "this follow-up only; app HTTP dispatches, not upstream proxy internals or provider embedding/reranking usage",
"cumulativeReportFieldsExcluded": "real-generation report cumulative fields omit prior desktop retrievals; not used"
},
"acceptanceBounds": {
"groundedShortAnswerPath": "passed for three providers through production IPC and Dify through full App composer",
"broadAnswerQuality": "not evaluated",
"fastgptRagflowFullAppAndProviderSpecificUi": "pending",
"linuxRemoteForcedPreflight": "still blocked by prior saved SSH credential decryption failure",
"fullRegressionAfterUiFollowUp": "passed",
"authoritativeAutomatedTestCounts": "progress.md"
}
}
@@ -4,345 +4,167 @@
| 项目 | 内容 |
| --- | --- |
| 状态 | 设计稿 |
| 版本 | 0.1 |
| 日期 | 2026-09-03 |
| 状态 | 当前实现规则;三家短答案、Dify 完整 App 与当前 UI 自动回归已通过,远程待验收 |
| 版本 | 0.2 |
| 日期 | 2026-09-13 |
| 关联需求 | [外部知识库接入 PRD](./external-knowledge-prd.md) |
| 关联场景 | [外部知识库 User Stories](./external-knowledge-user-stories.md) |
| 技术契约 | [技术设计](./external-knowledge-technical-design.md) |
## 1. 不变量
### EK-I-01 单一知识库身份
本地知识库和外部知识库都使用 GoodBuddy `knowledgeBaseId` 参与聊天范围、检索模式、
工具授权和活动记录。Provider、实例 ID 和远端知识库 ID 不能替代该授权单位。
对应:`EK-FR-10`、`EK-FR-11`、`EK-US-C1`。
### EK-I-02 外部绑定只读
GoodBuddy 对外部实例只执行连接测试、目录读取、详情读取和检索。添加、编辑、移除
均只修改本机记录,不调用远端创建、更新、上传或删除接口。
对应:`EK-FR-15`、`EK-US-A3`。
### EK-I-03 凭据不离开 Main
API Key 明文只在 Main 内完成解密并短暂进入请求头。Renderer、Preload 返回值、Runtime、
Provider 配置、知识库元数据、诊断、日志和错误只能获得凭据状态。
对应:`EK-FR-02`、`EK-FR-11`。
### EK-I-04 Provider 不能定义界面
远端响应只提供数据和能力证据。可显示字段、字段类型、范围、默认值和请求映射均来自
GoodBuddy 内置、版本化的 Provider Adapter。
对应:`EK-FR-09`、`EK-US-D1`。
### EK-I-05 无静默切换
请求固定使用绑定保存的实例、远端知识库和 Provider 配置。失败时不切换实例、公共云
地址、认证方式或其他知识库。
对应:`EK-FR-14`、`EK-FR-16`、`EK-US-C4`。
### EK-I-06 零结果与失败分离
成功响应经过过滤后没有片段才是零结果。超时、认证、权限、限流、目标不存在、服务错误
和响应无效均为失败,不能折叠成零结果。
对应:`EK-FR-14`、`EK-US-D4`。
### EK-I-07 外部证据不可信
Provider 返回的名称、内容、元数据、URL 和错误均视为不可信输入,经过长度、数量、类型
和 URL 校验后才能显示或进入 Runtime。内容中的指令不能改变系统或工具权限。
对应:`EK-FR-12`。
### EK-I-08 Provider 配置不跨类型复用
Dify、FastGPT 和 RAGFlow 使用各自的判别配置。切换知识库类型或实例 Provider 时,旧类型
配置不参与新请求,也不能按同名字段自动迁移。
对应:`EK-FR-06`、`EK-FR-09`。
| 稳定 ID | 规则 | 对应需求 |
| --- | --- | --- |
| `EK-I-01` 单一知识库身份 | 本地与外部都以 GoodBuddy `knowledgeBaseId` 参与聊天范围与工具授权,实例和远端 ID 不扩大选中范围 | `EK-FR-10`、`EK-FR-11` |
| `EK-I-02` 外部绑定只读 | 添加、配置和移除仅修改本机记录;请求限于目录、详情、检索 | `EK-FR-15` |
| `EK-I-03` 凭据边界 | 新密钥在输入表单和保存 IPC 中短暂存在;加密后只在 Main 解密使用,摘要与引用不返回密钥 | `EK-FR-02`、`EK-FR-11` |
| `EK-I-04` 本地定义界面 | Provider 只返回数据,控件和配置由本地共享契约及组件定义 | `EK-FR-09` |
| `EK-I-05` 无静默切换 | 请求使用保存的实例、目标和配置;失败不更换实例、公共云地址或其他知识库 | `EK-FR-14`、`EK-FR-16` |
| `EK-I-06` 零结果与失败分离 | 合法成功响应无片段是零命中;请求或响应失败保留失败信息 | `EK-FR-14` |
| `EK-I-07` 外部证据不可信 | 标准化片段按已有知识证据边界进入 Runtime,不改变工具权限 | `EK-FR-12` |
| `EK-I-08` 配置不跨类型复用 | Provider 配置必须与实例一致,类型切换不迁移同名字段 | `EK-FR-06`、`EK-FR-09` |
## 2. 对象关系
```text
ExternalKnowledgeInstance 1 ─── n ExternalKnowledgeBinding
│
│ 1:1
▼
KnowledgeBase
```
一个实例可以有零个或多个绑定;一个绑定对应一个 GoodBuddy 知识库和一个远端目标。
同一实例与远端 ID 的组合唯一,不同实例可以使用相同 ID。本地知识库没有外部绑定。
类型由绑定关系推导,旧本地知识库不需要改成另一套对象。
- 实例可以没有绑定。
- 外部知识库只能绑定一个实例和一个远端知识库 ID。
- 同一实例的同一远端知识库默认只允许存在一个绑定,避免重复选择和重复检索。
- 允许不同实例绑定同名或相同远端 ID,因为实例边界不同。
- 本地知识库不存在 `ExternalKnowledgeBinding`。
GoodBuddy 显示名称与 `remoteName` 分开保存。刷新详情仅更新当前视图,不自动改写本地
名称或持久化远端名称;重新保存绑定时才写入当前表单使用的远端名称。
## 3. 实例状态
## 3. 实例状态与连接测试
实例状态由启停、凭据和最近探测三个维度组成,不能用一个布尔值代替。
### 3.1 持久状态
| 维度 | 值 |
| 状态维度 | 当前值与行为 |
| --- | --- |
| 启停 | `enabled`、`disabled` |
| 凭据 | `configured`、`missing`、`unavailable` |
| 探测 | `untested`、`catalog-ready`、`healthy`、`list-restricted`、`auth-failed`、`unreachable`、`incompatible`、`failed` |
| 启停 | `enabled` 为 false 时 Main 拒绝目录和检索,并取消当前实例请求 |
| 凭据 | `configured`、`missing`、`unavailable`;列表尝试解密判断可用性 |
| 最近探测 | `untested`、`catalog-ready`、`healthy`、`list-restricted`、`auth-failed`、`unreachable`、`failed`;其中 `healthy` 在类型中保留,当前操作没有写入它 |
`unavailable` 表示系统安全存储当前无法解密或保存凭据,与用户尚未配置的 `missing` 分开。
实例先保存为 `untested`,不要求保存前完成网络或认证测试。保存、替换或清除凭据后
重置探测状态。保存后的“测试连接”只尝试目录读取:成功写入 `catalog-ready`;403
写入 `list-restricted`;401 写入 `auth-failed`;网络或超时写入 `unreachable`;其他错误
写入 `failed`。时间和错误码随本次结果保存。
### 3.2 派生可用性
探测结果是最近一次观察,不是 Main 的永久禁用条件。Main 请求检查实例是否存在、启用
和凭据能否解密;即使最近为 `auth-failed` 或 `failed`,仍可重新请求。UI 会据该状态
显示不可用并禁用聊天勾选,用户可编辑或重新测试实例。
| 条件 | 目录读取 | 已绑定知识库检索 | 页面状态 |
| --- | --- | --- | --- |
| disabled | 禁止 | 禁止 | 已停用 |
| missing / unavailable | 禁止 | 禁止 | 缺少凭据 / 凭据不可用 |
| auth-failed | 禁止 | 禁止 | 认证失效 |
| unreachable | 可重试 | 可重试 | 无法连接 |
| incompatible | 禁止 | 禁止 | 接口不兼容 |
| list-restricted | 禁止 | 允许按 ID 验证与检索 | 需手工指定知识库 |
| catalog-ready | 允许 | 创建绑定时验证 | 目录可用,检索待验证 |
| healthy | 允许 | 允许 | 正常 |
| untested | 允许测试 | 已有绑定可尝试检索 | 未测试 |
绑定创建另做目标检索验证,成功零命中可保存。保存实例不代表目录可用,目录成功也不
代表绑定检索或整个产品验收通过。
保存新实例要求至少完成服务可达和认证验证。目录受限可以保存;检索接口尚未通过时,
实例不能用于创建绑定。
## 4. 目录与手工 ID
## 4. 连接测试状态机
目录每页最多 100 项,UI 当前请求 30 项,页码上限 500;当前没有“总目录最多 500 项”
的累计限制。Dify/RAGFlow 由远端分页,搜索过滤当前页;FastGPT 按 `parentId` 读取
当前父级,在返回结果内搜索和分页。文件夹只能进入浏览,不能作为目录绑定目标。
```text
idle
└─ test → connecting
├─ network failure → unreachable
└─ connected → authenticating
├─ 401 → auth-failed
├─ incompatible response → incompatible
└─ authenticated → listing
├─ 403 / unsupported → list-restricted
└─ success
├─ no target → catalog-ready
└─ target selected → probing-retrieval
├─ success / empty → healthy
└─ failure → failed
```
目录加载、空结果、搜索无结果和失败分别展示;失败提供重试,用户可主动切换手工 ID。
已经绑定的目录项禁用。手工 ID 不依赖详情接口一定可用,但保存前仍必须通过实际检索。
详情失败不阻断基础检索,例如旧 Dify 的详情 405。
检索探测需要一个目标知识库。新增实例尚未选择目标时,目录成功即可把实例保存为“目录可用,
检索待绑定验证”;创建绑定时补做真实检索。手工模式必须先填写目标 ID,再执行检索探测。
检索请求正常完成但返回零条结果时,说明接口和目标可访问,可继续创建绑定;失败状态按错误
分类处理。
Renderer 丢弃已失效的目录、详情和测试响应,不把切换前的结果写回当前表单。它没有
通过 IPC 取消已发出的目录请求,Main 请求仍受超时和服务生命周期管理。
每次测试拥有 request ID 和取消信号。用户修改 Provider、地址、凭据或实例后,旧 request ID
的结果作废。
## 5. 创建与编辑
## 5. 目录读取规则
流程为选择 `local | dify | fastgpt | ragflow`,外部类型再选择实例、目录项或手工 ID、
显示名称、配置与测试查询。表单测试成功后允许保存;服务保存前再次执行同一查询验证,
检查实例、绑定和唯一性是否在等待期间改变,再写入数据库。
1. 只有选定、启用且凭据可用的实例可以读取目录。
2. Provider 列表接口按适配器定义分页,Main 最多向 Renderer 返回 500 个目录项。
3. FastGPT 列表中的文件夹和知识库必须按类型区分;文件夹不可作为绑定目标。
4. 列表搜索优先使用 Provider 支持的服务端过滤,不支持时只过滤已经有界加载的当前结果。
5. 下拉列表以远端 ID 为值,以远端名称为标签;重名时追加短 ID 或父级名称。
6. 已经绑定的目标显示“已添加”并禁止重复添加。
7. 远端列表项名称变化不自动修改 GoodBuddy 显示名称,只更新远端名称快照。
8. 刷新失败时可以保留上次成功目录供查看,但不得用旧目录完成新绑定,直到目标重新验证。
## 6. 创建向导状态
### 6.1 类型
```text
local | dify | fastgpt | ragflow
```
`local` 使用现有字段和验证。外部类型使用以下步骤状态:
```text
provider selected
→ instance selected
→ catalog loading / manual ID
→ remote target selected
→ configuration valid
→ retrieval verified
→ binding created
```
前置字段变化时,后续状态按下表失效:
| 变化 | 必须清除或重新验证 |
| 表单变化 | 当前失效规则 |
| --- | --- |
| 类型 | 实例、远端目标、全部 Provider 配置、测试结果 |
| 实例 | 远端目标、能力快照、Provider 配置、测试结果 |
| 远端目标 | 远端详情、能力适用性、测试结果 |
| 通用或 Provider 配置 | 测试结果 |
| 显示名称 | 不清除测试结果 |
| Provider 类型 | 重新挂载绑定表单,清空旧类型状态 |
| 实例 | 清空目标、详情、目录位置和搜索,恢复该 Provider 默认配置,清除测试结果 |
| 远端目标、测试查询、通用或 Provider 参数 | 清除测试结果,需要重新测试 |
| 显示名称 | 保留测试结果 |
| 已保存实例摘要变化 | 旧测试结果不再视为有效 |
| 更换现有绑定实例或远端目标 | 需要重新测试和重新绑定确认 |
## 7. 配置模型
成功新建后仅把新库加入当前对话选择,不重新启用已取消的其他知识库。当前表单保存期间
禁用字段和操作;generation 检查用于避免旧响应更新 UI,不构成可取消的保存事务。
### 7.1 通用配置
## 6. 配置与优先级
| 字段 | 范围 | 默认值 | 语义 |
| --- | --- | --- | --- |
| `resultLimit` | 1 至 20 | 6 | Provider 结果标准化后最多保留的片段数 |
| `requestTimeoutMs` | 1,000 至 60,000 | 15,000 | 单次远端请求总时限 |
| `maxSnippetCharacters` | 500 至 8,000 | 4,000 | 单片段进入 IPC 和 Runtime 前的字符上限 |
当前配置的精确字段、数值范围和默认值由[技术设计第 3 节](./external-knowledge-technical-design.md#3-共享契约)
对应的共享 Schema 定义。恢复默认只重置本地表单,保存成功后才影响该绑定后续请求。
Provider 请求仍使用自己的候选数量或 Token 预算。`resultLimit` 只定义 GoodBuddy 最终保留
上限,不改写 Provider 字段的单位。
Dify `useDatasetDefaults=true` 时省略整个覆盖对象,使用远端当时配置;远端管理员修改
默认值会影响后续结果。关闭后按完整覆盖对象发送,不与远端默认逐字段合并。
FastGPT 使用本地保存的模式、Token 预算、相似度和重排开关,不开放查询优化。
### 7.2 Dify 配置
RAGFlow 图谱与知识编译分别保存且默认关闭。图谱完成时间有效才允许开启图谱;非空
知识编译模板组 ID 才允许开启知识编译。服务在发送启用请求前重新读详情确认;仅配置
GraphRAG 或请求返回 200 都不能替代对应能力证据。证据不足时 UI 仍允许关闭已保存的开关。
| 字段 | 初始范围 | 说明 |
当前没有自动版本识别、Adapter 版本矩阵或配置迁移状态。共享契约拒绝未知输入与
Provider 错配,不能据此承诺所有部署版本均支持每个显式参数。
## 7. 绑定可用性
绑定只持久化配置和 `lastVerifiedAt`,没有独立 `binding_status`、最后成功检索时间或
远端缺失状态字段。详情与列表当前按实例推导:
| 条件 | UI 状态 |
| --- | --- |
| 实例不存在 | `temporarily-unavailable` |
| 实例停用 | `instance-disabled` |
| 凭据未配置、无法解密或最近认证失败 | `credential-error` |
| 最近探测失败或不可达 | `temporarily-unavailable` |
| 其余且绑定有验证时间 | `ready` |
| 其余且无验证时间 | `untested` |
聊天选择器保留已选 ID,但不可用行禁用勾选。检索时的 401、404、超时等通过本次失败
报告,不自动写成持久 `remote-missing/config-invalid` 状态。`ready` 表示当前可尝试使用,
不保证下一次请求成功。
## 8. 多库检索与失败
请求使用已冻结的 GoodBuddy ID 集合,`retrieveMany` 去重后并行检索各库。本地与外部
共享入口,外部库不触发本地 Embedding。预算按各库结果的 rank 轮转分配,先各库第 1 条、
再各库第 2 条,同 rank 按输入库顺序处理;不先耗尽整个库,也不比较 Provider 原始分数。
当前没有每库最低字符配额。精确预算见
[技术设计第 7 节](./external-knowledge-technical-design.md#7-检索编排)。
成功零命中返回空 results 且无 failure。单库错误返回 `diagnostics.failure`,其他库
仍可返回结果;上层据此区分部分失败与全部失败。MCP 在有失败且没有任何片段时返回
工具错误,即使另有成功零命中的库;预检索则保留降级诊断。用户取消或服务关闭会向上
传播取消,不把整次取消解释为成功零命中。
客户端错误码包括 `NETWORK/TIMEOUT/CANCELLED/AUTH/FORBIDDEN/NOT_FOUND/RATE_LIMITED/`
`INCOMPATIBLE/INVALID_RESPONSE/SERVER`,均以 `EXTERNAL_KB_` 为前缀。服务另报告凭据
不可用、停用、配置变化、配置错配、能力不可用、重复绑定和实例被引用。远端错误正文
不直接成为 UI 错误;部分本地校验错误仍是 Zod、TypeError 或 RangeError,不能承诺
所有失败都已有独立本地化诊断。
## 9. 删除与引用
停用和清除凭据保留绑定,UI 清除凭据需要确认。有绑定的实例不能直接删除;管理界面
列出受影响知识库并确认后,依次移除本地库再删实例。若中途失败,已成功移除的本地
记录保持删除,刷新列表后显示当前状态。任何一步均不发送远端内容删除请求。
历史引用保存当时的片段与外部定位,移除实例或绑定后仍能阅读。当前不重新读取远端
片段,不打开来源 URL,不查询实例是否已删除;因此没有自动“连接已移除”标签。
App 与 Main 引用处理共用 `knowledgeReferenceKey`,键包含远端库、文档和片段 ID、
定位及正文,避免外部结果缺少本地 ID 时被错误合并。弹窗只显示优先选中的一个定位。
早期 Dify 历史引用保存和重载检查使用测试会话;后续完整 App 已从实际输入区生成真实
短答案并点击引用验证。两轮证据分别保留,不把测试会话当成模型回答。
## 10. 需求追踪与完整性
| 需求 | 规则 | 场景 |
| --- | --- | --- |
| `useDatasetDefaults` | boolean,默认 true | 开启时不发送 `retrieval_model` 覆盖 |
| `searchMethod` | keyword / semantic / full-text / hybrid | 对应 Dataset Retrieve 检索方式 |
| `providerTopK` | 1 至适配器上限 | Dify 返回候选数量 |
| `scoreThresholdEnabled` | boolean | 是否向 Dify 发送分数阈值 |
| `scoreThreshold` | 0 至 1 | 开启阈值后生效 |
| `rerankingMode` | weighted-score / reranking-model | 覆盖检索配置时必填 |
| `rerankingProvider`、`rerankingModel` | 受支持的远端标识 | 选择模型重排时必填 |
| `keywordWeight`、`vectorWeight` | 0 至 1,总和为 1 | 加权混合模式时生效 |
| `embeddingProvider`、`embeddingModel` | 受支持的远端标识 | Dify 要求时从知识库详情选择或填写 |
| `EK-FR-01` 至 `EK-FR-05` | 实例状态、保存后测试、目录与手工 ID | `EK-US-A1`、`EK-US-A2`、`EK-US-B1`、`EK-US-B2` |
| `EK-FR-06` 至 `EK-FR-10` | 对象关系、创建编辑、配置与优先级 | `EK-US-B3`、`EK-US-B4`、`EK-US-D1`、`EK-US-D3` |
| `EK-FR-11` 至 `EK-FR-14` | 范围、可用性、结果与失败、引用 | `EK-US-C1` 至 `EK-US-C4`、`EK-US-D2`、`EK-US-D4` |
| `EK-FR-15`、`EK-FR-16` | 只读生命周期、凭据与请求边界 | `EK-US-A3`、`EK-US-C2` |
`useDatasetDefaults` 开启时,其余覆盖字段隐藏且不发送。关闭后必须收集当前 Dify 接口
构造 `retrieval_model` 所需的完整字段,缺少 Provider 或模型标识时不能保存。
### 7.3 FastGPT 配置
| 字段 | 初始范围 | 说明 |
| --- | --- | --- |
| `searchMode` | embedding / fullTextRecall / mixedRecall | 搜索方式 |
| `tokenLimit` | 适配器限制,最高 20,000 | Provider 返回内容预算,不等同结果条数 |
| `similarity` | 0 至 1 | 最低相关度 |
| `usingRerank` | boolean | 使用 FastGPT 重排 |
初始版本不开放 FastGPT 查询优化字段,避免把查询发送给 FastGPT 配置的 LLM。检索和重排
仍可能产生远端 Embedding、Rerank 用量及 API 审计记录,测试与保存配置时必须说明。
### 7.4 RAGFlow 配置
| 字段 | 初始范围 | 说明 |
| --- | --- | --- |
| `similarityThreshold` | 0 至 1 | 最低综合相似度 |
| `vectorSimilarityWeight` | 0 至 1 | 向量分数权重 |
| `knnTopK` | 1 至适配器上限 | KNN 候选数量,不使用已弃用 `top_k` |
| `rerankCandidatesCount` | 0 或正整数 | 重排候选数量,实例支持时显示 |
| `rerankId` | 可选受限字符串 | 远端重排模型 ID |
| `useKg` | boolean,默认 false | 仅在已有图谱证据成立时允许开启 |
| `includeKnowledgeCompilation` | boolean,默认 false | 实例支持时显式发送,禁止依赖服务端默认 true |
| `metadataCondition` | 后续受控编辑器 | 初始版本不接受任意 JSON 文本 |
`parser_config.graphrag.use_graphrag` 描述构建状态,不是检索参数。只有列表、详情或受控
探测提供已有图谱的证据时,界面才允许开启 `useKg`。Knowledge Compilation 是另一项
检索能力,必须与旧 GraphRAG 分别显示和保存。
## 8. 配置优先级
生效请求由以下信息组成:
```text
GoodBuddy 固定安全上限
∩ 当前 Adapter 支持矩阵
∩ 当前绑定保存的通用配置和 Provider 配置
= 本次请求配置
```
- 固定安全上限不可被实例或用户扩大。
- 显式保存 Provider 参数时,远端默认值只用于创建表单的建议值。Dify
`useDatasetDefaults=true` 是例外:每次请求省略 `retrieval_model`,沿用远端当时的配置;
远端管理员修改配置后,后续检索随之变化。本地不保存一份默认参数快照。
- Provider 增加新字段不会自动启用。
- 当前实例版本离开 Adapter 支持矩阵,或 Adapter 新版本无法解析已保存配置时,绑定标记
配置失效;用户确认前不重写配置。
- 服务端忽略未知字段不能视为成功。Adapter 只发送已知且受支持的字段。
## 9. 绑定状态
| 状态 | 条件 | 可进入聊天范围 |
| --- | --- | --- |
| `ready` | 实例可用,目标最近验证成功,配置有效 | 是 |
| `untested` | 迁移或新增后尚未完成真实检索 | 否 |
| `instance-disabled` | 实例停用 | 否 |
| `credential-error` | 凭据缺失、不可用或认证失败 | 否 |
| `remote-missing` | 远端目标返回不存在 | 否 |
| `config-invalid` | 已保存配置不再受支持 | 否 |
| `temporarily-unavailable` | 超时、限流或服务错误 | 是,但本次显示失败并允许重试 |
短暂故障不立即永久禁用绑定;认证失败、目标不存在和配置失效会改变持久状态。
## 10. 多知识库检索
1. 读取请求冻结的 GoodBuddy 知识库 ID 集合。
2. 本地库进入本地检索,外部库按实例和绑定构造独立请求。
3. 同一请求最多选择现有契约允许的 20 个知识库,并遵循全局并发上限。
4. 每个库单独记录成功、零结果、失败、耗时和截断。
5. 成功结果按知识库保留来源,不比较不同 Provider 的原始分数。
6. 上下文拼装使用现有总字符预算,在各成功知识库间采用确定性配额。
7. 全部失败时返回检索失败;至少一个成功时返回证据和部分失败诊断。
初始版本不对跨 Provider 结果运行统一学习型重排。后续如增加,必须保留原始 Provider
分数和重排来源,且单独定义评估数据。
## 11. 错误分类
| 稳定错误码 | 用户状态 | 持久影响 |
| --- | --- | --- |
| `EXTERNAL_KB_NETWORK` | 无法连接实例 | 无 |
| `EXTERNAL_KB_TIMEOUT` | 检索超时 | 无 |
| `EXTERNAL_KB_AUTH` | 认证失效 | 实例标记认证失败 |
| `EXTERNAL_KB_FORBIDDEN` | 没有访问权限 | 目录或绑定按操作标记受限 |
| `EXTERNAL_KB_NOT_FOUND` | 远端知识库不存在 | 绑定标记目标不存在 |
| `EXTERNAL_KB_RATE_LIMITED` | 请求过于频繁 | 无,可显示建议等待 |
| `EXTERNAL_KB_INCOMPATIBLE` | 接口或配置不兼容 | 实例或绑定标记不兼容 |
| `EXTERNAL_KB_INVALID_RESPONSE` | 返回内容无法读取 | 无,记录脱敏诊断 |
| `EXTERNAL_KB_SERVER` | 外部服务处理失败 | 无 |
| `EXTERNAL_KB_CANCELLED` | 已取消 | 无 |
Provider 原始正文不能直接成为用户错误。Adapter 将其映射为稳定错误码和有界短消息。
## 12. 删除规则
| 操作 | 无绑定 | 有绑定 |
| --- | --- | --- |
| 停用实例 | 直接停用 | 允许;绑定变为不可用 |
| 清除凭据 | 直接清除 | 需要确认;绑定保留但不可用 |
| 删除实例 | 直接删除 | 阻止直接删除,要求同时移除本地绑定 |
| 移除绑定 | 删除本地绑定 | 不影响实例和远端知识库 |
删除确认不得使用“删除远端知识库”等文案。
## 13. 需求追踪
| 需求 | 主要逻辑 | User Story |
| --- | --- | --- |
| `EK-FR-01` 至 `EK-FR-03` | 实例状态、连接测试状态机 | `EK-US-A1`、`EK-US-A2` |
| `EK-FR-04`、`EK-FR-05` | 目录读取规则、手工目标验证 | `EK-US-B1`、`EK-US-B2` |
| `EK-FR-06`、`EK-FR-07`、`EK-FR-08`、`EK-FR-09` | 创建向导状态、配置模型和优先级 | `EK-US-B3`、`EK-US-B4`、`EK-US-D1`、`EK-US-D3` |
| `EK-FR-10` 至 `EK-FR-13` | 单一身份、多知识库检索和引用规则 | `EK-US-C1` 至 `EK-US-C3`、`EK-US-D2` |
| `EK-FR-14` | 绑定状态、错误分类和部分失败 | `EK-US-C4`、`EK-US-D4` |
| `EK-FR-15` | 对象关系和删除规则 | `EK-US-A3` |
| `EK-FR-16` | 无静默切换、请求冻结和传输风险确认 | `EK-US-C2` |
## 14. 逻辑完整性
已确定:
- 产品入口、对象关系、只读边界和凭据边界。
- 目录读取与手工 ID 的切换条件。
- 三种 Provider 的初始配置范围和 RAGFlow 图谱语义。
- 多知识库部分失败、删除和版本变化规则。
实施前仍需技术验证:
- 已完成第一轮真实目录与最小检索验证,并确认 Dify `1.17.0` 非空结果结构;仍需确认
FastGPT、RAGFlow 产品版本和 Dify `0.15.8` 非空结果,再选定三者最低版本并形成固定
支持矩阵与去敏 fixture。当前实测详情见
[技术设计 9.0 节](./external-knowledge-technical-design.md#90-真实实例验证基线)。
- 验证各版本检索探测的 API 审计、用量和内容修改行为。
- 确定 Provider 返回 URL 的安全打开策略;未验证前只显示文本定位和远端 ID。
- 依据真实延迟确定默认并发上限和跨知识库上下文配额。
三家真实 Provider 的生产服务、本机 HTTP MCP、生产 IPC `always` 短答案,以及 Dify
`auto` 与完整 App 输入区回答、引用已有证据,详见[实施进度](./progress.md)。`auto` 本轮
使用进程内工具调用,不覆盖远程 ACP。FastGPT/RAGFlow 完整桌面、复杂问答和远程
forced-preflight 仍待完成;远程已确认网络可达,但已有 SSH 凭据在验证程序内无法解密。
后续 Portal 与控件修正后的聚焦测试、全量回归及相关检查已通过,精确结果由实施进度维护。
原场景中的保存前分项测试、按地址
传输确认、取消交互、版本能力状态和完整诊断尚不等同当前实现,不能把对应 User Story
全部标为通过。是否调整这些验收要求由功能 owner 确认,本轮不增加新的状态框架。
@@ -4,14 +4,38 @@
| 项目 | 内容 |
| --- | --- |
| 状态 | 设计稿 |
| 版本 | 0.1 |
| 日期 | 2026-09-03 |
| 状态 | 三家短答案、Dify 完整 App 与当前 UI 自动回归已通过;剩余产品验收待完成 |
| 版本 | 0.2 |
| 日期 | 2026-09-13 |
| 适用产品 | GoodBuddy 桌面端 |
| 关联逻辑 | [外部知识库逻辑设计](./external-knowledge-logic-design.md) |
| 关联界面 | [外部知识库 UI 设计](./external-knowledge-ui-design.md) |
| 技术设计 | [外部知识库技术设计](./external-knowledge-technical-design.md) |
## 当前交付范围
当前工作区已提供实例管理、加密凭据保存、目录与手工 ID、外部绑定、Provider 检索参数、
统一知识库列表、聊天预检索与按需工具检索、外部引用和本地移除的实现。参数与恢复默认
遵循下文只读产品决策。2026-09-13 已验证三家 Provider 的生产服务与本机 HTTP MCP,
以及 Dify 桌面的实例保存、绑定、参数更新、重启和历史引用。后续已修正外部弹窗的字段
样式与布局,并验证三家通过生产 IPC 生成有依据的短答案、Dify 按需检索,以及从完整 App
输入区发送 Dify 问题并点击真实答案引用。该证据不代表广泛问答质量。
FastGPT/RAGFlow 完整桌面、远程预检索仍待验收;当前 UI 修改后的聚焦测试、全量回归与
相关检查已通过,精确结果见[实施进度](./progress.md)。
本文的功能需求和产品验收仍是验收目标。以下差异不能仅凭实现存在就标为满足:
| 原需求 | 当前实现与待核对差异 |
| --- | --- |
| `EK-FR-02`、`EK-FR-03` | 实例先保存为未测试,再在列表执行目录测试;目标检索在绑定时验证。没有分项草稿探测、版本或完整能力快照 |
| `EK-FR-04` | 已有目录搜索和分页;Dify/RAGFlow 搜索过滤当前页,FastGPT 搜索当前父级响应,不能保证全目录搜索 |
| `EK-FR-09` | 内置严格配置已实现,没有按探测版本维护支持矩阵;Dify 高级只读能力展示、Metadata 编辑和 RAGFlow Rerank 未提供 |
| `EK-FR-13`、`EK-FR-14` | 已显示片段、原始评分、总耗时和失败;没有完整的 Provider 耗时、裁剪前数量与截断诊断,也不持久化远端目标失效状态 |
| `EK-FR-16` | Main 固定端点及数据说明已接入;当前没有单独接入全局内网兼容模式和按地址传输确认流程 |
这些差异由功能 owner 在验收时核对。2026-09-09、2026-09-10 真实接口探测只作为历史
兼容性资料,不作为本轮桌面产品验证结果。
## 1. 背景
不少企业已经在内网部署 Dify、FastGPT 或 RAGFlow,并在其中维护制度、产品、研发
@@ -42,6 +66,11 @@ GoodBuddy 负责。
10. API Key 在用户输入表单和受限 IPC 中短暂存在,由 Main 进程加密保存并用于请求;
保存后不回传 Renderer,也不进入 Runtime、日志、普通元数据和检索引用。
第一版按只读接入交付:参数修改和恢复默认只更新 GoodBuddy 中当前绑定的客户端配置,
在后续检索请求中使用,不修改远端知识库默认配置,也不影响其他客户端。远端文档、
索引、图谱构建、模型和 Pipeline 设置由用户在对应平台维护。检索请求仍可能产生远端
Embedding、重排用量和审计记录。
## 3. 目标
### 3.1 用户目标
@@ -93,22 +122,23 @@ GoodBuddy 负责。
1. 用户在知识库页面点击“管理外部实例”。
2. 页面列出实例名称、Provider、服务地址、状态和已绑定知识库数量。
3. 用户添加实例,填写名称、Provider、服务地址和 API Key。
4. “测试连接”分别验证服务可达、认证、知识库列表和检索能力。
5. 用户保存后,API Key 被加密保存,列表只显示凭据已配置。
4. 用户先保存实例,API Key 被加密保存,列表显示凭据状态和“未测试”。
5. 在已保存实例上“测试连接”读取目录并记录结果;检索能力在绑定目标时验证。
列表接口不可用但已知远端知识库 ID 可检索时,实例可以保存为“需手工指定知识库”。
服务不可达或认证失败时不得保存为可用实例;用户可以保留表单并修正。
目录 403 显示“需手工指定知识库”;不可达或认证失败保留实例和失败状态,用户可编辑
后重试。保存本身不代表可用。原分项测试验收目标见 `EK-FR-03`,当前实现差异见上表。
### 6.2 添加外部知识库
1. 用户点击现有“新建知识库”。
2. 在“知识库类型”中选择“本地”、Dify、FastGPT 或 RAGFlow。
3. 选择外部类型后,左侧选择同 Provider 的外部实例。
4. GoodBuddy 自动读取远端知识库列表,并填入可搜索下拉框。
4. GoodBuddy 自动读取远端知识库列表,提供搜索、分页和目标选择。
5. 列表权限不可用时,用户可以手工填写远端知识库 ID。
6. 用户选择一个远端知识库,填写 GoodBuddy 显示名称。
7. 右侧展示通用配置和当前 Provider 的专属配置。
8. 用户填写测试查询并执行一次有界检索;请求成功后创建本地绑定,零结果不算失败。
8. 用户填写测试查询并执行有界检索;成功后可添加,零结果不算失败。保存时服务会再次
执行该查询验证目标和配置,之后创建本地绑定。
添加完成后沿用现有知识库规则,把新绑定加入当前聊天选择,但不会重新启用用户此前
取消的其他知识库。
@@ -222,13 +252,20 @@ Provider 的远端创建、更新、上传或删除接口。
| --- | --- | --- | --- |
| Dify | 知识库列表与详情 | Dataset Retrieve | 使用知识库默认配置,或完整覆盖检索方式、Top K、Score 阈值及 Rerank 模式 |
| FastGPT | 知识库层级列表与详情 | Dataset Search Test | 搜索模式、Token 上限、相似度、Rerank |
| RAGFlow | Dataset 列表与详情 | Retrieval | 相似度、向量权重、KNN Top K、Rerank、图谱检索、Knowledge Compilation |
| RAGFlow | Dataset 列表与详情 | Retrieval | 相似度、向量权重、KNN Top K、图谱检索、Knowledge Compilation;当前未提供 Rerank 配置 |
“首批专属配置”表示适配器允许表达的字段,不保证每个实例版本都支持。具体显隐由能力
探测和[逻辑设计](./external-knowledge-logic-design.md)共同决定。
## 9. 产品验收
以下为验收清单,尚未整体通过。第 2 项已有三家生产服务绑定与检索证据,Dify 另有桌面
操作证据;其他 Provider 的桌面、受限目录与高级配置等仍需补验。第 12 项确认流程当前
未实现。早期历史引用检查使用标记为测试的会话;后续已从完整 App 输入区生成 Dify
真实短答案并点击引用,三家另有生产 IPC 短答案证据。完整 App 尚未覆盖 FastGPT/RAGFlow,
也未评估广泛问答质量;本机 MCP 成功不替代被 SSH 凭据解密阻断的真实远程预检索。
详细结果以实施进度为准。
1. 用户可以在知识库页面完成三种 Provider 的实例添加、连接测试、编辑和删除。
2. 每种 Provider 至少通过一个真实自部署实例完成列表读取、绑定和检索。
3. 列表权限关闭时,可以通过手工 ID 添加并验证一个可检索知识库。
@@ -4,721 +4,309 @@
| 项目 | 内容 |
| --- | --- |
| 状态 | 设计稿 |
| 版本 | 0.1 |
| 日期 | 2026-09-03 |
| 状态 | 三家生产 IPC 真实生成、Dify 完整 App 与当前 UI 自动回归已通过;远程待验收 |
| 版本 | 0.2 |
| 日期 | 2026-09-13 |
| 产品需求 | [外部知识库接入 PRD](./external-knowledge-prd.md) |
| 逻辑设计 | [外部知识库逻辑设计](./external-knowledge-logic-design.md) |
| UI 设计 | [外部知识库 UI 设计](./external-knowledge-ui-design.md) |
| 行为规则 | [外部知识库逻辑设计](./external-knowledge-logic-design.md) |
| 界面 | [外部知识库 UI 设计](./external-knowledge-ui-design.md) |
| 验证状态 | [实施进度](./progress.md) |
## 1. 设计约束
## 1. 实现边界
1. 外部知识库扩展现有知识库领域,不建立独立检索工具、聊天范围或 Runtime 权限模型。
2. 外部绑定使用远端检索,不落地远端文档、分块、向量和图谱。
3. 所有网络、凭据、Provider 响应校验和结果标准化都在 Main 完成。
4. Renderer 只能通过受限 IPC 管理实例、读取目录、保存绑定和发起测试。
5. Dify、FastGPT、RAGFlow 使用独立 Adapter 和判别配置,不通过任意 JSON 请求模板实现。
6. 首次实现只调用无内容修改语义的列表、详情和检索接口。
7. Provider API 变化通过真实 fixture、Adapter 版本和能力快照处理,不通过猜测字段兼容。
外部知识库复用 GoodBuddy 知识库 ID、聊天范围和检索入口。网络、凭据、响应读取和
标准化在 Main 完成,Renderer 通过明确的 Preload 方法调用 IPC。远端只执行目录、详情和
检索请求,不创建、修改、上传或删除远端内容,不调用外部 App、Chat、Workflow 或 Agent。
## 2. 现有代码边界
本地 `file | directory | url` 仍表示需要导入和索引的资料来源。外部绑定独立存储,
不加入该枚举,也不生成本地文档、分块、向量或图谱。实际用于回答的片段随会话引用保存。
当前实现中的主要扩展点:
## 2. 当前模块
| 边界 | 现有位置 | 需要的变化 |
| --- | --- | --- |
| 共享知识契约 | `src/shared/knowledge-contracts.ts`、`src/shared/contracts.ts` | 增加实例、绑定、Provider 配置和外部引用判别联合 |
| IPC 名称 | `src/shared/ipc-channels.ts` | 增加实例和目录操作通道 |
| Preload | `src/preload/index.ts` | 暴露受限、类型化的外部知识库 API |
| Main IPC | `src/main/ipc.ts` | 校验输入、调用服务、投影脱敏结果 |
| 知识服务 | `src/main/knowledge/knowledge-service.ts` | 按知识库来源分派本地或外部检索 |
| 数据库 | `src/main/knowledge/knowledge-database.ts` | 增加实例、绑定和迁移 |
| 内部类型 | `src/main/knowledge/types.ts` | 增加 Provider、实例、绑定和标准化结果类型 |
| 知识页面 | `src/renderer/src/KnowledgeWorkspace.tsx` | 实例管理、创建向导类型和外部详情 |
| 检索测试 | `src/renderer/src/KnowledgeRetrievalWorkbench.tsx` | 支持本地与外部诊断判别联合 |
| 聊天预检索 | `src/main/ipc.ts` | 统一调用 Provider-aware 检索编排 |
| Agent 知识工具 | `src/main/agent/knowledge-mcp-gateway.ts` | 与预检索复用同一编排和引用标准化 |
| 文件 | 职责 |
| --- | --- |
| `src/main/knowledge/external/external-knowledge-client.ts` | 一个 `ExternalKnowledgeClient` 内按 Provider 分支处理固定端点、地址规范化、分页、HTTP 错误、超时、取消、响应上限和结果映射;同文件定义 `ExternalKnowledgeError` |
| `src/main/knowledge/external/external-knowledge-service.ts` | 实例保存与测试、凭据加解密、目录与详情、检索测试、创建或更新绑定、请求生命周期与配置变化检查 |
| `src/main/knowledge/external/external-knowledge-store.ts` | 在已有 SQLite 连接上读写实例 JSON 和绑定 JSON |
| `src/main/knowledge/knowledge-database.ts` | v12 迁移、知识库与绑定事务、唯一约束及删除外键 |
| `src/main/knowledge/knowledge-service.ts` | 本地与外部检索分派、混合检索和总字符预算;阻止外部绑定进入本地维护操作 |
| `src/shared/external-knowledge-contracts.ts` | Provider 配置、实例与绑定输入、目录、测试结果和外部引用定位契约 |
| `src/shared/knowledge-contracts.ts`、`contracts.ts`、`assistant-contracts.ts` | 现有知识结果、列表和会话引用增加外部字段 |
| `src/shared/knowledge-reference.ts` | 预检索与 MCP 共用的引用转换和去重键 |
| `src/main/ipc.ts`、`src/preload/index.ts`、`src/shared/ipc-channels.ts` | 可信发送方检查、Zod 输入解析、类型化跨进程调用 |
| `src/renderer/src/ExternalKnowledge.tsx` | 实例 Modal、绑定表单、Provider 配置、外部详情与检索结果 |
| `KnowledgeWorkspace.tsx`、`App.tsx`、`KnowledgeCitationDialog.tsx` | 统一列表、聊天选择、刷新及历史引用显示 |
外部 Provider 不是 `KnowledgeSourceType`。现有 `file | directory | url` 表示会进入本地
文档、分块和索引的资料来源;把远端绑定塞入该枚举会错误触发同步、分块和来源打开逻辑。
`ExternalKnowledge.tsx` 的字段改用全局 `.field`,修正 body Portal 脱离知识库祖先后
丢失 scoped 控件样式的问题。CSS 提供 560px 紧凑弹窗和粘附底部操作区;实例选择复用
既有 picker 样式并实现菜单键盘行为。没有新增 Main 模块,具体控件规则由
[UI 设计](./external-knowledge-ui-design.md#当前实现与验收边界)维护。
## 3. 模块设计
当前没有独立 Provider 文件、Adapter Registry、Normalizer、版本化能力清单或配置迁移框架。
本文件按现有三个 Main 模块描述实现,不把旧草案中的拆分方案列为待建基础设施。
建议增加:
## 3. 共享契约
```text
src/main/knowledge/external/
├─ external-knowledge-service.ts
├─ external-knowledge-store.ts
├─ external-knowledge-errors.ts
├─ external-knowledge-http.ts
├─ external-knowledge-registry.ts
├─ external-knowledge-normalizer.ts
└─ providers/
├─ dify-adapter.ts
├─ fastgpt-adapter.ts
└─ ragflow-adapter.ts
```
### 3.1 实例与绑定
职责:
实例输入包含 `id?`、`name`、`provider`、`baseUrl`、`enabled` 和
`credential: keep | replace | clear`。实例摘要返回启停、凭据状态、`probeStatus`、绑定数、
最近测试时间和错误码,不返回凭据 envelope。当前没有 `detectedVersion`、`adapterVersion`
或 `transportSecurity` 字段。
- `ExternalKnowledgeService`:实例、目录、绑定、测试和检索编排。
- `ExternalKnowledgeStore`:实例和绑定持久化,凭据加解密。
- `ExternalKnowledgeHttp`:超时、取消、大小限制、地址和响应读取。
- `ExternalKnowledgeRegistry`:按 Provider 返回内置 Adapter,不接受运行时插件注册。
- `ExternalKnowledgeNormalizer`:统一结果边界、截断和引用。
- Adapter:构造固定端点与请求,解析 Provider 响应,声明静态配置和能力条件。
绑定保存 `knowledgeBaseId`、`instanceId`、`provider`、`remoteKnowledgeBaseId`、
`remoteName`、`commonConfig`、`providerConfig` 和 `lastVerifiedAt`。GoodBuddy 名称和描述
保留在知识库记录。创建和更新输入都要求用户填写 `testQuery`;服务保存前重新执行检索,
因此先点击“测试检索”再保存会产生两次检索请求。成功零命中允许保存。
`KnowledgeService` 继续作为聊天、检索测试和 MCP 网关的统一入口。它根据
`KnowledgeBase.kind` 调用现有本地检索或 `ExternalKnowledgeService.retrieve`。
### 3.2 配置范围
## 4. Provider Adapter
| 配置 | 当前共享契约 |
| --- | --- |
| 通用 | `resultLimit` 1 至 20,默认 6;`requestTimeoutMs` 1,000 至 60,000,默认 15,000;`maxSnippetCharacters` 100 至 8,000,默认 4,000 |
| Dify | 默认分支仅包含 `provider` 和 `useDatasetDefaults: true`;覆盖分支使用严格的 `retrievalModel` 对象,包含检索方式、Top K、阈值开关和重排开关,按启用条件校验阈值、重排模型或权重 |
| FastGPT | `searchMode`、`tokenLimit`(1 至 30,000)、`similarity`、`usingRerank`;不发送查询优化参数 |
| RAGFlow | `similarityThreshold`、`vectorSimilarityWeight`、`knnTopK`(1 至 2,048)、`useKg`、`includeKnowledgeCompilation`;当前无 Rerank、Metadata 编辑字段 |
```ts
type ExternalKnowledgeProvider = 'dify' | 'fastgpt' | 'ragflow'
Provider 必须与实例一致。Dify 加权配置还在客户端检查权重和为 1。共享查询输入上限为
4,000 字符,Dify 客户端另按 Unicode 码点拒绝超过 250 字符的查询,不静默压缩。
interface ExternalKnowledgeAdapter<PersistedConfig, CapabilitySnapshot> {
readonly provider: ExternalKnowledgeProvider
readonly configVersion: number
### 3.3 结果与引用
normalizeBaseUrl(input: string): URL
testConnection(context: AdapterContext): Promise<ConnectionTestResult>
listKnowledgeBases(
context: AdapterContext,
input: CatalogPageInput
): Promise<CatalogPage>
getKnowledgeBase(
context: AdapterContext,
remoteKnowledgeBaseId: string
): Promise<RemoteKnowledgeBaseSummary>
probeRetrieval(
context: AdapterContext,
input: ProviderRetrieveInput<PersistedConfig>
): Promise<ProviderRetrieveResult>
retrieve(
context: AdapterContext,
input: ProviderRetrieveInput<PersistedConfig>
): Promise<ProviderRetrieveResult>
parseCapabilities(input: unknown): CapabilitySnapshot
validateConfig(
input: unknown,
capabilities: CapabilitySnapshot
): PersistedConfig
}
```
现有 `KnowledgeRetrievalResult` 的本地 `documentId/sourceId/chunkId` 改为可选,增加
`external?: ExternalKnowledgeLocator`,没有另建一套 `UnifiedKnowledgeRetrievalResult`。
外部定位包含 Provider、实例 ID、远端库 ID,以及实际返回的文档 ID、片段 ID、位置和评分。
FastGPT 数组评分存为 `providerScores: {type, value, index?}[]`,保留顺序与类型;数值评分
存为 `providerScore`,不选取数组首项冒充总分。
`probeRetrieval` 可以复用 `retrieve`,但必须使用用户在创建向导中明确输入的测试查询。
不得生成会泄露组织信息的默认问题,不得调用 Provider 的 App 或模型问答接口,也不得
修改远端知识内容。Provider 自身记录 API 审计或计算检索用量不属于内容修改,必须由界面
提前说明。
外部结果沿用旧结果容器的空 `channels` 和 `scores.fusedScore = 0`。该零值是结构占位,
不表示质量;`toKnowledgeReference` 不把它写成引用总分。外部结果没有统一 `relevance`,
不同 Provider 的原始分数不参与跨库混排。引用去重键包含外部定位和片段,不伪造本地 ID。
Adapter 返回数据对象,不返回 React 组件、HTML 或远端 Schema。Renderer 中的配置定义
由共享的 Provider 判别联合和本地字段描述生成。
## 4. SQLite 与凭据
## 5. 共享契约
### 5.1 实例
```ts
type CredentialMutation =
| { action: 'keep' }
| { action: 'replace'; value: string }
| { action: 'clear' }
interface ExternalKnowledgeInstanceSummary {
id: string
name: string
provider: 'dify' | 'fastgpt' | 'ragflow'
baseUrl: string
enabled: boolean
credentialStatus: 'configured' | 'missing' | 'unavailable'
probeStatus:
| 'untested'
| 'catalog-ready'
| 'healthy'
| 'list-restricted'
| 'auth-failed'
| 'unreachable'
| 'incompatible'
| 'failed'
detectedVersion?: string
bindingCount: number
lastTestedAt?: string
lastErrorCode?: string
}
```
Renderer 输入使用 `CredentialMutation`,因此新密钥会在输入控件和该次 IPC 中短暂存在。
Main 返回值永远没有 `credential`、`apiKey`、`authorization` 或加密 envelope。
### 5.2 绑定
```ts
interface ExternalKnowledgeBinding {
knowledgeBaseId: string
instanceId: string
remoteKnowledgeBaseId: string
remoteName: string
remoteDescription?: string
commonConfig: ExternalKnowledgeCommonConfig
providerConfig: DifyConfig | FastGptConfig | RagflowConfig
providerConfigVersion: number
adapterVersion: number
lastVerifiedAt?: string
}
```
`providerConfig` 必须带 `provider` 判别字段,且与实例 Provider 一致。`remoteName` 是用于
显示和变化检测的快照,不是授权标识。
### 5.3 标准化结果
现有 `KnowledgeRetrievalResult` 假定结果映射到本地 `documentId/sourceId/chunkId`。需要改为
引用定位的判别联合,同时保持现有调用方可按统一字段读取片段:
```ts
type KnowledgeReferenceLocator =
| {
kind: 'local'
documentId: string
sourceId: string
chunkId: string
parentChunkId?: string
}
| {
kind: 'external'
provider: ExternalKnowledgeProvider
instanceId: string
remoteKnowledgeBaseId: string
remoteDocumentId?: string
remoteChunkId?: string
providerScore?: number
location?: string
sourceUrl?: string
metadata?: Record<string, string | number | boolean | null>
}
interface UnifiedKnowledgeRetrievalResult {
knowledgeBaseId: string
documentTitle: string
sourceDisplayName: string
snippet: string
relevance?: number
rank: number
locator: KnowledgeReferenceLocator
}
```
`remoteDocumentId` 和 `remoteChunkId` 只保存 Provider 实际返回的标识,缺失时不得从标题、
序号或内容哈希推导。`providerScore` 保留原始值,但不改名为统一 `relevance`。只有经过
单独定义和验证的归一化算法才能填写跨 Provider `relevance`。初始跨库拼装按结果配额和
稳定 Provider 顺序工作,不按原始分数混排。
所有共享 Schema 使用严格对象并限制:
- 查询不超过 4,000 字符;同时遵守 Provider 更小限制,例如 Dify 当前公开接口的
250 字符限制,超出时在发送前明确失败,不静默截断查询。
- 单次目录页不超过 100 项,总目录最多 500 项。
- 标准化结果最多 20 条,单片段最多 8,000 字符。
- 标题、ID、定位、URL 和元数据键值均设独立上限。
- Provider 原始响应总字节数设固定上限,超限立即取消读取。
## 6. 持久化
### 6.1 数据库变化
保留现有 `knowledge_bases` 表,增加:
```sql
ALTER TABLE knowledge_bases
ADD COLUMN kind TEXT NOT NULL DEFAULT 'local'
CHECK (kind IN ('local', 'external'));
```
增加实例表:
数据库 v12 增加两张表:
```sql
CREATE TABLE external_knowledge_instances (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
provider TEXT NOT NULL CHECK (provider IN ('dify', 'fastgpt', 'ragflow')),
base_url TEXT NOT NULL,
enabled INTEGER NOT NULL CHECK (enabled IN (0, 1)),
credential_envelope_json TEXT,
probe_status TEXT NOT NULL,
detected_version TEXT,
capabilities_json TEXT NOT NULL,
transport_security TEXT NOT NULL,
transport_risk_accepted_url TEXT,
transport_risk_accepted_at TEXT,
last_tested_at TEXT,
last_error_code TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
value_json TEXT NOT NULL
);
```
增加绑定表:
```sql
CREATE TABLE external_knowledge_bindings (
knowledge_base_id TEXT PRIMARY KEY
REFERENCES knowledge_bases(id) ON DELETE CASCADE,
instance_id TEXT NOT NULL
REFERENCES external_knowledge_instances(id) ON DELETE RESTRICT,
remote_knowledge_base_id TEXT NOT NULL,
remote_name TEXT NOT NULL,
remote_description TEXT,
common_config_json TEXT NOT NULL,
provider_config_json TEXT NOT NULL,
provider_config_version INTEGER NOT NULL,
adapter_version INTEGER NOT NULL,
binding_status TEXT NOT NULL,
last_verified_at TEXT,
last_success_at TEXT,
last_error_code TEXT,
UNIQUE(instance_id, remote_knowledge_base_id)
knowledge_base_id TEXT PRIMARY KEY REFERENCES knowledge_bases(id) ON DELETE CASCADE,
instance_id TEXT NOT NULL REFERENCES external_knowledge_instances(id) ON DELETE RESTRICT,
remote_id TEXT NOT NULL,
value_json TEXT NOT NULL,
UNIQUE(instance_id, remote_id)
);
```
已有知识库迁移为 `kind = 'local'`。外部知识库仍保留现有名称、描述和创建时间,但本地
`storageMode`、图谱、分块和本地检索设置不进入外部请求;后续可以在数据库重构时再移除
这些历史必填默认值,本功能不为此扩大迁移范围。
没有向 `knowledge_bases` 增加 `kind` 列;IPC 快照根据是否存在绑定投影
`KnowledgeLibrary.kind` 和 `external`。旧本地数据保留,知识库与绑定在同一事务中保存。
外部库沿用本地表所需的默认字段,但这些字段不进入 Provider 请求。
### 6.2 凭据
Main 启动把已有 `secureCipher` 注入 `KnowledgeService`。服务复用
`SettingsCredentialCipher` 和现有加密函数,把 envelope 放入实例 JSON;`replace` 要求
安全存储可用,`keep` 保留已有 envelope,`clear` 移除凭据。列表会尝试解密以区分
`configured/missing/unavailable`,请求前再次解密,没有独立明文凭据缓存。
复用 `SettingsCredentialCipher` 和 Electron `safeStorage`:
## 5. IPC 与生命周期
- `replace`:Main 加密新值后在一个事务中替换 envelope。
- `keep`:不读取、不重写现有 envelope。
- `clear`:确认后删除 envelope,并把实例凭据状态设为缺失。
- 安全存储不可用时拒绝新增或替换,不降级为明文。
- 解密只发生在发起请求前;请求结束后不缓存明文。
Preload 暴露实例 `List/Save/Test/SetEnabled/Delete`、目录 `List/Get`、绑定
`Create/Update` 和 `externalRetrievalTest`。通道使用 `knowledge:external-*` 前缀,
准确名称以 `ipc-channels.ts` 为准。移除绑定复用 `deleteLibrary`,没有独立 remove 通道。
实例导出、诊断和数据库普通 JSON 视图均排除 `credential_envelope_json`。
Handler 调用 `assertTrustedSender` 并解析共享输入;检索按实例 ID 在 Main 读取地址与凭据。
实例先保存为 `untested`,列表上的连接测试调用目录请求并更新摘要,不测试未保存草稿。
绑定检索测试是单独操作,不会把实例探测状态自动升级为 `healthy`。
## 7. IPC 设计
服务按实例记录 AbortController。保存实例、停用、删除以及服务关闭会取消相应请求;
调用方取消信号与总超时合并。异步测试或保存完成前比较实例配置,变化时返回
`EXTERNAL_KB_CONFIG_CHANGED`,绑定更新还检查保存期间绑定是否改变。共享 IPC 没有
`requestId` 或取消方法;Renderer 用局部 generation/active 标记丢弃旧响应,这与中止
Main 请求是两种行为。
建议增加独立通道:
删除仍被引用的实例返回 `EXTERNAL_KB_INSTANCE_IN_USE`。UI 确认后逐个调用本地
`deleteLibrary`,再删除实例;这是顺序操作,发生中途失败会刷新已移除的绑定,不是跨调用事务。
```text
knowledge:external-instances:list
knowledge:external-instances:test
knowledge:external-instances:save
knowledge:external-instances:set-enabled
knowledge:external-instances:delete
knowledge:external-catalog:list
knowledge:external-catalog:get
knowledge:external-bindings:create
knowledge:external-bindings:update
knowledge:external-bindings:remove
knowledge:external-retrieval:test
```
## 6. HTTP 边界
所有 Handler 必须:
客户端只接受 HTTP(S),拒绝地址内的用户名、密码、查询与片段;按 Provider 规范化 API
路径,仅拼接内置端点。请求使用 Bearer 认证,`redirect: 'error'`,无自动重试或公共云回退。
默认超时 15 秒,单个响应默认上限 5,000,000 字节;响应头和流式读取均检查上限,取消
期间终止读取。解析后只投影需要的有界字段,最多返回 20 条、每片段 8,000 字符。
1. `assertTrustedSender`。
2. 用共享 Zod Schema 解析严格输入。
3. 根据实例 ID 在 Main 读取地址和凭据,不接受 Renderer 在检索请求中重复传入。
4. 使用稳定错误码返回脱敏错误。
5. 对网络操作支持 AbortSignal、总超时和响应大小限制。
当前使用 `fetcher ?? fetch`,没有单独连接全局内网兼容模式、证书例外或按地址保存传输
确认。2026-09-13 当前生产服务在真实 Electron 中对两个 Dify 和 FastGPT HTTPS 配置完成
严格证书校验,RAGFlow 使用 HTTP。此证据覆盖这些配置的请求,不表示旧 PRD 的地址确认
或证书例外流程已实现;相关产品差异仍由功能 owner 核对。
目录和测试请求返回 `requestId`。Renderer 仍需校验当前类型、实例和目标与响应关联,避免
旧响应覆盖新表单。
## 7. 检索编排
## 8. HTTP 边界
`KnowledgeService.retrieve` 查询本地绑定,存在绑定时调用
`ExternalKnowledgeService.testRetrieval`,否则执行现有本地路径。外部结果附加
`diagnostics.external`,不运行本地向量、全文或图谱检索。
### 8.1 服务地址
`retrieveMany` 对传入知识库 ID 去重并读取知识库,使用 `Promise.all` 逐库检索;只有选中
需要向量的本地库时才准备本地查询 Embedding。当前没有额外的 Provider 并发队列。单库
失败写入 `response.diagnostics.failure` 并保留空结果,成功零命中没有 failure;调用方
取消仍向上传播。当前返回 `{knowledgeBaseId, response}[]`,没有独立的 Outcome 状态联合。
- 只接受 `http:` 和 `https:`。
- 拒绝用户名、密码、查询和片段。
- Adapter 负责规范化 Provider 所需 API 根路径,避免用户填写完整操作端点。
- 请求只能拼接 Adapter 内置相对路径,远端目录项或错误不能改变目标 URL。
- 重定向默认拒绝;若某 Provider 的受支持部署确需重定向,只允许同源且不得跨跳转发送
Authorization。
- 当前全局“内网兼容模式”决定是否允许 HTTP 和非标准 HTTPS 证书;操作开始后冻结该值。
- HTTP 或实际使用证书例外时,Main 返回 `transportSecurity` 风险状态。用户必须针对规范化
后的当前地址明确确认,才能保存实例或发送 API Key;地址变化立即使旧确认失效。
- 不进行公共云回退,不把自部署地址替换成 Provider 官方地址。
服务测试先按绑定的结果数与单片段限制裁剪,并限制该次片段总量为 48,000 字符。
`retrieveMany` 展平各库结果并按库内 rank 稳定排序,再轮转分配 48,000 字符的片段预算:
先处理各库第 1 条,再处理各库第 2 条,同 rank 保持输入库顺序。不会先耗完整个库再轮到
下一个库;仍没有固定的每库最低字符配额。关联的本地上下文 groups 随相应结果使用另一份
48,000 字符预算。二次裁剪会标记 `context.truncated`,但第一次标准化裁剪不提供完整的
远端原始条数与截断诊断。
用户明确配置内网地址属于产品用途,因此不能采用禁止私网地址的通用 SSRF 规则。安全边界
依靠显式实例配置、Main-only 请求、固定端点、无任意方法和无远端 URL 跟随。
聊天预检索、检索测试相关 IPC 和 `KnowledgeMcpGateway` 已使用统一检索入口。引用转换
复用 `knowledge-reference.ts`,聊天与 MCP 仍按现有授权范围和各自输出预算处理结果。
MCP 在总预算裁剪后按库内 rank 交错选择结果,再应用工具条数和字节上限;有失败且
没有任何片段时返回工具错误。预检索只在全部库失败时报告整体失败,成功零命中伴随其他
库失败时保留降级诊断。这两种上层呈现并不完全相同。
### 8.2 请求限制
## 8. 引用显示与历史数据
- 默认超时 15 秒,用户可在 1 至 60 秒内调整。
- 目录、详情和检索分别设置响应字节上限。
- JSON 解析后仍执行深度、数组数量、字符串长度和未知字段策略。
- 默认不自动重试。用户重试或上层下一次检索会产生新请求。
- 取消、应用关闭和实例删除立即终止相关请求。
- 日志只记录 Provider、实例 ID、操作、状态码、稳定错误码、耗时和字节数。
会话契约和数据库保存外部定位及有界片段。外部引用弹窗读取保存的片段,显示 Provider、
实例 ID、远端库和实际返回的定位及评分;不请求本地 `referenceContext`,不显示本地
`openSource` 操作。当前没有远端来源打开或引用刷新请求,`sourceUrl` 是可选契约字段,
三家现有映射没有填充它。
App 的 `source-references` 事件使用共享 `knowledgeReferenceKey` 去重传入引用,再合并
尚未被替代的旧引用,最多保留 20 条。键包含 Provider、实例、远端库、远端文档和片段 ID、
定位与片段正文;外部结果缺少本地 ID 时也不会仅因本地 ID 相同而被合并。引用弹窗按
`context.locator ?? external.location ?? locator` 选择一个定位,只显示一次。
移除绑定后历史片段仍可阅读;当前弹窗不查询实例状态,也不生成“连接已移除”标记。
实例名称没有作为独立字段存入外部 locator,不能承诺删除实例后仍显示其名称。
2026-09-13 Dify 桌面报告已验证实时搜索引用通过生产 `conversations.saveLocal` 保存、
重载并打开弹窗。会话由验证程序标记为测试记录,没有模型生成答案;最终引用去重与定位
修正后的回归状态单独记录在[实施进度](./progress.md)。
后续 `kb-full-app-live` 则通过完整 App 输入区真实发送并保存模型答案,再点击引用。
引用片段、远端文档和片段 ID 与真实检索记录一致,无伪造会话或事件;此证据覆盖 Dify
单库短答案。三家生产 IPC 生成检查还验证了 `task_events` 中的文本、引用和选中库范围。
## 9. Provider 映射
本节依据 2026-09-10 核对的官方文档、官方源码和真实实例响应。实施时应把每个受支持版本的真实响应保存为
去敏 fixture,并以 fixture 和真实实例测试作为发布依据。
官方来源与版本边界:
- Dify:[Knowledge API 与密钥范围](https://docs.dify.ai/en/api-reference/guides/knowledge)、
[列表](https://docs.dify.ai/en/api-reference/knowledge-bases/list-knowledge-bases)、
[详情](https://docs.dify.ai/en/api-reference/knowledge-bases/get-knowledge-base)、
[检索](https://docs.dify.ai/en/api-reference/knowledge-bases/retrieve-chunks-from-a-knowledge-base-test-retrieval)。
缺省配置顺序另对照[官方实现](https://github.com/langgenius/dify/blob/main/api/services/hit_testing_service.py)。
- FastGPT:[API 说明](https://doc.fastgpt.io/zh-CN/openapi/intro)和
[已停止维护的手工接口页](https://doc.fastgpt.io/zh-CN/openapi/dataset)。当前契约对照
[生成文档 Schema](https://github.com/labring/FastGPT/blob/main/packages/global/openapi/core/dataset/api.ts)、
[检索处理器](https://github.com/labring/FastGPT/blob/main/projects/app/src/pages/api/core/dataset/searchTest.ts)和
[结果项 Schema](https://github.com/labring/FastGPT/blob/main/packages/global/core/dataset/type.ts)。
云端 `/apidoc/` 本次只取得应用壳,未读取渲染后的部署 Schema。
- RAGFlow:[官网 HTTP API](https://ragflow.io/docs/dev/http_api_reference)正文未能有效提取,
采用[官方当前文档原文](https://github.com/infiniflow/ragflow/blob/main/docs/references/http_api_reference.md),并与
[v0.24.0 固定版本](https://github.com/infiniflow/ragflow/blob/v0.24.0/docs/references/http_api_reference.md)比较。
`main` 分支文档与源码会变化,不是部署版本凭据。FastGPT、RAGFlow 的实际产品版本仍未确认。
本节区分当前客户端行为与历史接口探测。历史响应和官方文档只说明当时观察到的接口,
不证明当前桌面已完成验收。
### 9.0 真实实例验证基线
2026-09-09 使用本机未入库的测试地址和 API Key 完成第一轮只读验证。验证只记录状态、
字段名、数量和长度,不记录 API Key、知识库名称或片段正文。
| 历史日期 | 证据与限制 |
| --- | --- |
| 2026-09-09 | [基线报告](./external-knowledge-probe-baseline.json):累计 107 次只读请求,最终归档脚本 19 次;当时 HTTPS 放宽证书校验 |
| 2026-09-10 | [基础报告](./external-knowledge-probe-2026-09-10-strict.json)与[扩展报告](./external-knowledge-probe-2026-09-10-extended.json):共 33 次只读请求,其中检索 17 次;HTTPS 显式开启证书校验,RAGFlow 为 HTTP |
| Provider | 实例证据 | 目录实测 | 检索实测 | 当前结论 |
| --- | --- | --- | --- | --- |
| Dify `0.x` | 响应头 `x-version: 0.15.8` | 列表返回 200 和 3 项;详情 GET 返回 405 | 默认配置和显式当前配置均返回 200,结果为空 | 从目录项读取配置,手工 ID 通过检索验证;非空结果仍未验证 |
| Dify `1.x` | 响应头 `x-version: 1.17.0` | 列表返回 200 和 1 项;详情 GET 返回 200 | 默认配置和显式当前配置均返回相同的 3 条非空结果 | 详情和 `records[].segment/document` 已验证;新增摘要索引、元数据、多模态和 Pipeline 能力字段 |
| FastGPT | 响应未提供版本,只能确认当前托管实例 | 基址包含 `/api`;列表和详情均返回 200,列表 `data` 直接为数组,本次 1 个 Dataset | embedding 8 条、fullTextRecall 2 条、mixedRecall 7 条、mixedRecall + Rerank 9 条 | 三种检索模式和 Rerank 均已实测;当前结果包含 `q`,未包含 `a` |
| RAGFlow | 响应未提供产品版本,Server 为 nginx,不能据此推断版本 | `GET /api/v1/datasets` 返回 200,共 28 项;17 项配置 GraphRAG,15 项有完成证据 | 选定已完成图谱的库,基础检索 0 条,`use_kg=true` 返回 1 条,Knowledge Compilation 0 条 | 图谱检索已产生区别于基础检索的结果;当前没有库具备 Knowledge Compilation 配置证据 |
历史结果包含 Dify 0.15.8 详情 405、检索零命中,Dify 1.17.0 详情成功及 3 条非空结果;
FastGPT 三种模式和重排非空;RAGFlow 图谱开关产生不同结果,但 Knowledge Compilation
没有配置证据。FastGPT/RAGFlow 产品版本、Dify 0.15.8 非空结构及真实错误矩阵仍未确认。
逐项数量、脚本命令和当时测试结果由[实施进度](./progress.md#验证证据)维护。
2026-09-09 的 Node 测试进程设置了 `NODE_TLS_REJECT_UNAUTHORIZED=0`,因此当时 HTTPS 实例的请求
使用了放宽的证书校验。这只能证明接口在当前测试环境可用,不能证明严格 TLS 校验通过。
产品实现不得读取该进程级设置作为隐式默认;实例测试和保存必须按 8.1 节处理传输风险。
2026-09-10 复测显式设置 `NODE_TLS_REJECT_UNAUTHORIZED=1`,两个 Dify 实例和 FastGPT
均通过证书校验;RAGFlow 配置为 HTTP,不涉及 TLS。基础 14 次、扩展 19 次,共 33 次只读
请求。每个实例从目录选择一个库检索,未遍历全部库;未调用聊天或生成端点,检索仍可能
使用远端 Embedding、Rerank 并记录用量。两份报告与逐项结果见[实施进度](./progress.md)。
2026-09-13 新增当前产品证据:[生产验证摘要](./external-knowledge-validation-2026-09-13.json)
记录真实 Electron safeStorage 持久化重开、生产服务与本机 HTTP MCP,以及 Dify 桌面。
服务结果与 MCP 引用数分别为 Dify 配置 1 的 0/0、Dify 配置 2 的 2/2、FastGPT 的 2/2、
RAGFlow 的 1/1。报告配置编号不是版本识别,历史版本差异不据此重新认证。
### 9.1 Dify
| 操作 | 接口 |
| --- | --- |
| 列表 | `GET /v1/datasets` |
| 详情 | `GET /v1/datasets/{dataset_id}`,`0.15.8` 不支持、`1.17.0` 支持 |
| 检索 | `POST /v1/datasets/{dataset_id}/retrieve` |
| 认证 | `Authorization: Bearer <knowledge-api-key>` |
固定使用 `GET /v1/datasets`、`GET /v1/datasets/{id}` 和
`POST /v1/datasets/{id}/retrieve`。默认省略整个 `retrieval_model`,远端优先使用知识库
当时配置,未保存时由远端选默认;覆盖时发送完整的本地 `retrievalModel`,不逐字段合并。
结果从 `records[].segment`、文档和分数取值。
Adapter 只接受 Knowledge Service API Key,不调用应用 `/info`、Chat 或 Workflow。默认不
发送 `retrieval_model`,沿用远端知识库配置。用户选择覆盖时,Adapter 必须一次收集并验证
当前接口要求的 `search_method`、`reranking_enable`、`top_k`、`score_threshold_enabled`。
开启重排时再验证 `reranking_mode`,以及该模式所需的 Provider、模型或权重;缺少必填
标识时不构造请求。显式对象不会与知识库配置逐字段合并。省略整个对象时,服务端优先使用
知识库配置,未保存配置时才使用内置默认。响应从
`records[].segment`、`score`、文档名称和元数据提取标准化引用。
客户端把详情 405 分类为不兼容,绑定表单允许详情失败后继续检索验证,不实现版本探测或
自动遍历目录的详情回退。Scoped Key 可尝试手工 ID,但当前产品的真实受限密钥验收仍待完成。
Metadata、多模态、摘要索引、子块、附件和 Pipeline 没有进入当前简化目录或结果映射。
当前官方文档说明限定知识库范围的 Key 不能调用列表,但可访问授权 ID 的详情和检索。
列表 403 不足以认定密钥无效;本轮测试配置均能列目录,尚未验证 Scoped Key。
Dify 当前文档限制查询不超过 250 字符。GoodBuddy 不静默截断;超出时检索测试和聊天
诊断返回 Provider 限制,并允许后续单独设计查询压缩策略。
两个实测版本共同返回 `retrieval_model_dict`,键包括搜索方式、Top K、阈值、Rerank 和
权重。显式原样复用该配置均返回 200,证明覆盖 envelope 可用,但 UI 编辑后的每种组合仍
需单独契约测试。
版本差异:
| 能力 | `0.15.8` | `1.17.0` | Adapter 和 UI 规则 |
| --- | --- | --- | --- |
| Dataset 详情 | GET 返回 405 | GET 返回 200 | 能力探测后调用;旧版从目录项读取配置,不能把 405 视为实例故障 |
| 非空检索结构 | 本次未命中 | 默认和显式配置均返回 3 条 | 1.17 已验证 `records[].segment.content`、`segment.document.id/name`、位置和分数映射 |
| 元数据 | 无 `doc_metadata` | 字段存在,当前库定义数为 0 | 有实际字段定义时才显示受控元数据过滤器;不提供自由 JSON |
| 多模态与附件 | 未返回能力字段 | `is_multimodal` 和 `files` 字段可用,当前库非多模态且结果无附件 | 只显示远端状态;仅在结果实际带 `files` 时显示附件引用 |
| 父子分块 | 未验证 | 返回 `child_chunks` 数组,本次为空 | 仅在实际非空时显示子块证据,不补造父子关系 |
| 摘要索引 | 未返回能力字段 | 当前库配置了 `summary_index_setting`,本次结果 `summary` 为空 | 概览显示只读状态;仅在结果实际返回摘要时展示 |
| Knowledge Pipeline | 未返回能力字段 | `pipeline_id/runtime_mode` 存在,当前库未配置 Pipeline | 只读显示,不提供运行或编辑 Pipeline 的操作 |
Dify 1.x 官方 RetrievalModel 还声明 `metadata_filtering_conditions`,并支持
`keyword_search/semantic_search/full_text_search/hybrid_search`。当前实测库没有 Metadata
字段,未执行空条件或猜测字段探测;该配置保持禁用,直到所选库详情返回可用字段定义。
官方来源:[Knowledge API](https://docs.dify.ai/en/api-reference/guides/knowledge)、
[列表](https://docs.dify.ai/en/api-reference/knowledge-bases/list-knowledge-bases)、
[详情](https://docs.dify.ai/en/api-reference/knowledge-bases/get-knowledge-base)、
[检索](https://docs.dify.ai/en/api-reference/knowledge-bases/retrieve-chunks-from-a-knowledge-base-test-retrieval)、
[缺省配置实现](https://github.com/langgenius/dify/blob/main/api/services/hit_testing_service.py)。
### 9.2 FastGPT
| 操作 | 接口 |
| --- | --- |
| 列表 | `POST /api/core/dataset/list` |
| 详情 | `GET /api/core/dataset/detail?id=...` |
| 检索 | `POST /api/core/dataset/searchTest` |
| 认证 | `Authorization: Bearer <api-key>` |
固定使用 `POST /api/core/dataset/list`、`GET /api/core/dataset/detail?id=...` 和
`POST /api/core/dataset/searchTest`。列表 `data[]` 保留 Dataset 与文件夹,按 `parentId`
读取层级,在当前父级响应内搜索和分页;文件夹只用于浏览。
列表响应同时包含文件夹和知识库,只有 `type = dataset` 可以绑定。根列表请求发送
`parentId: null`;文件夹需按父 ID 继续读取。当前实测列表为 `data[]`,检索为
`data.list[]`,这是两个不同端点的结构。旧手工文档还展示检索 `data[]`,探测脚本接受
该旧结构并单独记录 envelope;当前客户端只接受检索 `data.list[]`,旧结构尚未经过真实
部署验证。不递归猜测任意嵌套。检索映射 `datasetId`、
`text`、Token `limit`、`similarity`、`searchMode` 和 `usingReRank`。初始版本不发送查询优化
字段,避免调用 FastGPT 配置的 LLM。
当前实测响应的 `data` 包含 `list/duration/limit/searchMode/usingReRank/similarity`;结果项
包含 `q/id/datasetId/collectionId/sourceName/sourceId/chunkIndex/score`,未返回 `a`。
Adapter 以 `q` 为必需片段,`a` 仅在远端实际返回时追加,不能要求 `a` 存在。
2026-09-10 实测 `score` 为 `{type, value, index}[]`,与当前官方结果 Schema 一致;
旧手工文档中的数值分数不能作为当前统一映射依据。应保留评分类型或按明确规则选分,
不能默认取第一项作为总分。当前客户端仅提取数值分数,数组评分的映射仍待实现。
当前实例的 embedding、fullTextRecall、mixedRecall 和 mixedRecall + Rerank 均返回 200 和
非空结果,因此这四种界面组合可以启用。查询扩展会调用模型,仍不在初始界面和探测脚本中
启用。
检索接受业务码 200 的 `data.list[]` 与旧手工文档的 `data[]`,不递归猜测嵌套结构。
`q` 必需,`a` 仅在存在时追加;评分数组保留类型、值和可选 index,数值评分独立处理。
当前客户端已通过 FastGPT 真实服务与本机 HTTP MCP,均返回 2 条。报告确认结果契约与
引用映射有效,没有逐项归档评分包装,故旧包装和全部评分变体仍以定向测试证据为限。
FastGPT 4.15.0 起应优先查阅部署实例的自动生成文档,`/apidoc/devapi` 与
`/apidoc/systemopenapi` 的认证范围不同,Dev API 中出现不等于允许 API Key 调用。支持矩阵
必须按真实版本测试;不能因为健康页可访问就假定列表和检索 Schema 相同。`searchTest`
可能记录 `SEARCH_TEST` 等远端审计并更新 API Key 用量;Embedding 和 Rerank 也可能产生
费用。GoodBuddy 将这些行为作为检索成本显示,不宣称该接口没有副作用。
请求映射 `datasetId/text/limit/similarity/searchMode/usingReRank`,不发送查询扩展。
Embedding、Rerank 和远端 API 审计仍可能产生用量;不调用 FastGPT 生成最终回答。
官方来源:[API 说明](https://doc.fastgpt.io/zh-CN/openapi/intro)、
[旧手工接口页](https://doc.fastgpt.io/zh-CN/openapi/dataset)、
[生成 Schema](https://github.com/labring/FastGPT/blob/main/packages/global/openapi/core/dataset/api.ts)、
[检索处理器](https://github.com/labring/FastGPT/blob/main/projects/app/src/pages/api/core/dataset/searchTest.ts)、
[结果 Schema](https://github.com/labring/FastGPT/blob/main/packages/global/core/dataset/type.ts)。
4.15.0 起应核对部署实例生成文档;历史检查只取得云端 `/apidoc/` 应用壳。
### 9.3 RAGFlow
| 操作 | 接口 |
| --- | --- |
| 列表/详情 | `GET /api/v1/datasets` |
| 检索 | `POST /api/v1/retrieval` |
| 认证 | `Authorization: Bearer <api-key>` |
目录和按 ID 详情均使用 `GET /api/v1/datasets`,检索使用 `POST /api/v1/retrieval`。
请求发送 `question`、单个 `dataset_ids`、`page: 1`、`page_size: 20`、阈值、向量权重、
`knn_top_k` 及显式的图谱、知识编译布尔值。HTTP 成功且业务码为 0 才读取 `data.chunks`。
文档名映射 `document_keyword`,位置保存返回的 `positions`,分数取原始 `similarity`。
检索映射 `question`、`dataset_ids`、`page_size`、`similarity_threshold`、
`vector_similarity_weight`、`knn_top_k`、重排、`use_kg` 和
`include_knowledge_compilation`。当前文档以 `knn_top_k` 取代 `top_k`;v0.24.0 文档只声明
`top_k`,因此当前客户端不发送 `top_k` 的行为不能作为旧版本兼容承诺。
`knn_top_k` 控制向量候选数量,`page_size` 控制返回数量;请求成功不能单独证明未知参数
已生效。本轮未验证候选数参数的效果,也未确定新字段首次发布版本。
HTTP 成功且业务 `code === 0` 才能解析结果。响应从 `data.chunks` 和
`data.doc_aggs` 提取片段、文档、位置和原始相似度。当前实测非空 chunk 使用
`document_keyword` 表示文档名,并包含 `id/document_id/content/positions/similarity/`
`term_similarity/vector_similarity`;初始 Adapter 应按该字段映射,不假定存在
`document_name`。
`graphEnabled` 根据有效非零 `graphrag_task_finish_at` 判断;仅配置
`parser_config.graphrag.use_graphrag` 不算完成。知识编译依据非空
`compilation_template_group_id`。启用任一能力时,服务在检索前重新读取详情确认。
当前不保存完整能力快照,不区分图谱未配置与构建中,也不检测 Provider 版本。
图谱规则:
官方来源:[HTTP API](https://ragflow.io/docs/dev/http_api_reference)、
[当前文档原文](https://github.com/infiniflow/ragflow/blob/main/docs/references/http_api_reference.md)、
[v0.24.0 文档](https://github.com/infiniflow/ragflow/blob/v0.24.0/docs/references/http_api_reference.md)。
旧文档只声明 `top_k`,当前客户端发送 `knn_top_k`;请求成功不证明候选数参数生效,
也不是旧部署版本兼容承诺。
- `parser_config.graphrag.use_graphrag = true` 只表示库配置了 GraphRAG;单独出现该配置不
表示图谱已经构建完成。
- `graphrag_task_finish_at` 为有效非零时间才作为图谱完成证据,不写回。
- `use_kg` 是检索时开关,只在已有图谱和受支持版本均确认后发送。
- Knowledge Compilation 与旧 GraphRAG 分别建模。
- `include_knowledge_compilation` 使用本地保存的显式布尔值,初始默认 false;即使服务端
默认 true,也不依赖字段省略获得隐式行为。
- `compilation_template_group_id` 或后续版本的等价受控字段作为 Knowledge Compilation 已
配置证据;仅仅请求返回 200 不能启用界面开关。
- Provider 没有返回图节点或路径证据时,GoodBuddy 不生成图谱引用。
## 10. 验证与验收
2026-09-09 实例 28 个库中 17 个配置 GraphRAG、15 个存在完成证据。对一个已完成库使用同一查询,
基础检索返回 0 条,`use_kg=true` 返回 1 条且原始相似度为 1,说明图谱开关对实际结果有
影响。当时 28 个库均没有 Knowledge Compilation 配置证据;该请求虽返回 200 和零结果,
界面仍必须禁用开关并显示“当前知识库未配置 Knowledge Compilation”。
2026-09-10 目录增至 29 项,18 项配置图谱、16 项有完成证据;所选库基础检索 2 条、
图谱检索 3 条。Knowledge Compilation 开关请求返回 2 条,目录仍无配置证据,不能据此
认定编译内容参与了检索。
三家 Provider 已通过生产 `KnowledgeService` 与本机 HTTP MCP,真实 Electron
safeStorage 加解密和持久化重开成功。Dify 桌面已完成绑定、参数更新、重启、三档窗口宽度
及历史引用检查。验证方法、原始报告边界和调用计数见[实施进度](./progress.md)及
[脱敏摘要](./external-knowledge-validation-2026-09-13.json)。本轮文档只读取报告,没有
另发 Provider 或模型请求,也未改动历史探测 JSON。
## 10. 检索编排
当前 Portal 与控件修改后的聚焦测试、全量回归、类型检查、lint、工作区及暂存区 diff
检查均已通过。精确计数、运行命令、耗时、执行期间文件稳定性和原失败修正记录统一见
[实施进度](./progress.md#2026-09-13-当前工作区实现与分组验证)。
```text
retrieveMany(knowledgeBaseIds, query, requestContext)
├─ load and authorize GoodBuddy knowledge bases
├─ partition local and external bindings
├─ local: existing retrieval path
├─ external: bounded concurrent adapter requests
├─ validate and normalize each response
├─ allocate deterministic context budget
└─ return evidence plus per-library diagnostics
```
后续真实生成使用已保存的默认模型,凭据在 Main 通过 safeStorage 解密。三家 Provider
的 `always` 及 Dify `auto` 已产生有依据和引用的短答案;Dify `auto` 经进程内
`ModelToolProvider → KnowledgeMcpGateway` 执行,不能当成本机 HTTP MCP 或远程 ACP
验收。FastGPT/RAGFlow 首轮因直接比较序列化字符串产生验证断言失败,解码请求和嵌入
证据 JSON 后重跑通过;报告保留初始结果。完整 App 输入区的发送、真实答案及引用点击
仅覆盖 Dify。Dify/FastGPT 使用严格 HTTPS,RAGFlow 和模型使用已配置的 HTTP。
预检索和 `knowledge_search` 工具必须调用同一个 `retrieveMany`。如果只修改聊天预检索而
未修改 `KnowledgeMcpGateway`,`auto` 和 `always` 会产生不同能力,这是阻断发布的问题。
请求精确增量与视觉 fixture 边界见[后续验证摘要](./external-knowledge-follow-up-2026-09-13.json)
及[实施进度](./progress.md#2026-09-13-ui-修正与真实生成复验)。不公开模型端点、私有配置
标识、凭据和答案正文。这些短答案检查没有评估广泛问答质量。
### 10.1 上下文预算
FastGPT/RAGFlow 完整桌面专属操作、复杂或混合问答、删除和真实错误矩阵仍需补验。
Linux Host 的两条网络路径可达,但验证程序无法解密已有 SSH 凭据,在认证前返回
`E_SAFE_STORAGE_DECRYPT_FAILED`,真实远程 forced-preflight 未执行。本机 HTTP MCP
成功不覆盖该远程路径。剩余 Provider 版本和高级参数效果也不能由基础检索成功推断。
- 每个知识库先保留不超过其 `resultLimit` 的结果。
- 先给每个成功知识库一个最小有界配额,再按稳定顺序分配剩余字符。
- 不用不同 Provider 原始分数决定跨库优先级。
- 片段截断在 Main 完成并在引用中标记。
- 相同实例的多个绑定仍按绑定分别请求,除非 Provider 明确支持且 Adapter 实现等价的多
Dataset 请求;合并不得改变每个绑定的配置和诊断。
原设计的分项探测 UI、最低版本矩阵、详细诊断、地址确认和取消交互未全部落地。它们是
待核对的产品差异,不能以补建 Adapter Registry 或版本框架代替验收决策。
### 10.2 失败结果
`scripts/external-knowledge-probe.mjs` 仍可只读探测列表、详情、检索;`--extended` 增加
Dify 配置覆盖、FastGPT 重排和 RAGFlow 两种开关。报告区分成功零命中与失败,不输出
凭据或业务正文。脚本不打包到桌面或 Agent,脚本执行本身不覆盖 UI、IPC、存储和引用链路。
返回结构包含每个知识库的:
## 11. 需求追踪
```ts
interface KnowledgeRetrievalOutcome {
knowledgeBaseId: string
status: 'success' | 'empty' | 'failed' | 'cancelled'
durationMs: number
resultCount: number
errorCode?: ExternalKnowledgeErrorCode
truncated: boolean
}
```
至少一个 `success` 或 `empty` 表示检索编排完成;全部 `failed/cancelled` 时上层显示整体失败。
## 11. 引用与来源打开
外部结果不能进入依赖本地数据库的 `referenceContext` 和 `openSource` 路径。
- 引用详情直接使用请求时保存的标准化片段和定位。
- Main 可以按远端片段 ID 提供一次只读重新读取,但初始版本不要求。
- Provider 返回 URL 只作为不可信元数据保存。没有独立 URL 规则和真实实例验证前,
Renderer 不显示打开链接操作。
- 活动和会话持久化保存标准化引用,其中包含实际用于回答的有界远端片段;这属于引用
留存,不是批量同步或本地索引。删除会话或对应活动时按现有生命周期删除该副本。
- 实例删除后,历史引用仍显示 Provider、知识库、文档名称和已保存片段,但标记连接已移除。
## 12. 支持矩阵与版本变化
每个 Adapter 维护明确的最低版本、已验证版本和 `adapterVersion`。连接测试优先读取可靠的
版本接口或响应头;无法识别版本时,只允许经真实请求验证过的基础列表和检索字段,高级字段
保持不可用。
Adapter 可以保存少量受控事实,例如“目录接口可用”“详情表明已构建图谱”,但不尝试从
远端拼出完整 capability manifest,也不计算 capability fingerprint。
- `adapterVersion` 相同且实例仍在支持矩阵内:解析已保存配置并执行。
- Adapter 升级:使用明确的配置迁移或把绑定标记为 `config-invalid`,不猜测字段迁移。
- 实例版本离开支持矩阵:停止发送高级字段,并要求重新测试和确认。
- 新能力出现:保持默认关闭,用户主动配置后才发送。
- RAGFlow `use_kg` 还要求当前远端知识库存在已构建图谱的证据。
## 13. 安全与隐私
- API Key 在用户输入和保存 IPC 中短暂存在,随后使用系统安全存储加密;保存后不返回
Renderer,也不进入 Runtime、日志和普通元数据。
- 请求不附带完整会话、附件、本地知识库证据、系统提示、模型配置或其他 Provider 结果。
- Provider 结果使用与本地知识证据相同的不可信上下文标记。
- Adapter 拒绝响应中的原型污染键、过深对象、超长数组和非有限数值。
- 远端错误正文只用于 Main 内映射,日志与 UI 使用稳定错误码和短消息。
- 外部知识库不能扩大当前对话显式选择的 GoodBuddy 知识库 ID。
- Ask 与 Execute 对知识检索均为只读,Execute 不获得额外 Provider 权限。
- Provider 配置中的模型 ID、背景文本和 Metadata 条件按独立长度限制验证,不能携带凭据。
- HTTP 或证书例外需要按当前规范化地址确认凭据传输风险,地址变化后重新确认。
## 14. 测试
### 14.1 契约测试
- 每个 Provider 的列表、详情、检索成功和错误 fixture。
- 未知字段、缺字段、错误类型、超长内容、非有限分数和超大响应。
- Dify 查询长度、FastGPT 文件夹过滤、RAGFlow 图谱和 Knowledge Compilation 字段。
- Provider 配置判别联合拒绝类型错配和未知字段。
### 14.2 安全测试
- 保存完成后的 Renderer 快照、IPC 返回值、日志、错误、活动和导出中不存在明文 API Key;
`replace` 输入只在当前表单和单次 IPC 请求中短暂存在。
- `keep/replace/clear` 凭据语义和安全存储不可用路径。
- 地址中的凭据、查询、片段、危险 scheme、跨源重定向和远端 URL 注入被拒绝。
- 取消、超时、响应大小上限和应用关闭释放请求。
### 14.3 集成测试
- 实例保存、目录读取、手工 ID、绑定创建、编辑和本地移除。
- 同一远端目标重复绑定被拒绝。
- 删除实例的引用保护和级联本地移除确认。
- 本地与外部 `retrieveMany` 部分成功、全部失败和确定性上下文预算。
- 聊天 `always` 与 Agent `knowledge_search` 使用同一 Provider-aware 编排。
- 历史引用在实例或绑定移除后仍可读。
### 14.4 UI 测试
- 类型切换、异步旧响应、目录搜索、刷新和手工模式。
- 两栏与窄窗口堆叠、键盘、焦点恢复和离开保护。
- Provider 字段显隐、失效配置、RAGFlow 图谱说明和恢复默认。
- 外部详情不出现导入、同步、分块和本地图谱操作。
- 错误、零结果、部分失败和 Provider 引用显示。
### 14.5 真实实例验收
每个 Provider 至少选择一个明确版本的自部署实例,记录:
- 版本、部署方式和认证范围。
- 列表与检索的请求/响应 fixture,移除凭据和业务正文。
- 默认与专属配置是否真实生效。
- 401、403、404、429、超时和取消行为。
- 查询、片段、引用和诊断的实际上限。
Mock 或公开文档通过不能代替真实实例验收。
2026-09-09 基线已完成 Dify `0.15.8`/`1.17.0` 目录、详情差异和 1.17 非空检索结构,
2026-09-10 补充验证了 HTTPS 实例的严格 TLS。FastGPT/RAGFlow 产品版本、真实实例
401/403/404/429、超时和取消尚未完成,因此不满足本节完整验收条件。
### 14.6 可重复探测脚本
仓库提供 `node scripts/external-knowledge-probe.mjs`。默认从进程环境变量读取
`GOODBUDDY_<PROVIDER>_BASE_URL/API_KEY`,本地开发也兼容当前两行格式的 `.env.dify`、
`.env.fastgpt` 和 `.env.ragflow`;Dify 文件还支持多个 `标签/URL/API Key` 三行配置块,用于
同一次报告比较多个版本。输出不包含地址、凭据、远端 ID、名称、查询和正文。
```text
node scripts/external-knowledge-probe.mjs
node scripts/external-knowledge-probe.mjs --provider=ragflow --extended
node scripts/external-knowledge-probe.mjs --extended --output=<report.json>
```
默认模式覆盖目录、详情和基础检索。`--extended` 额外执行 Dify 当前配置覆盖、FastGPT
Rerank、RAGFlow GraphRAG 和 Knowledge Compilation 检索。脚本只有固定 GET/POST 读取与
检索端点,不实现或调用远端创建、更新、上传和删除。
三家报告均将请求成功与命中数量分开:HTTP 必须成功,FastGPT 业务 `code` 必须为 `200`、
RAGFlow 必须为 `0`,并分别校验 Dify `records`、FastGPT `data` 或 `data.list`、RAGFlow
`data.chunks` 数组结构,才记录检索 `success: true` 和 `count`。成功的空数组
记录 `count: 0`;HTTP、业务、响应结构、JSON 解析或网络失败记录 `success: false` 与
固定失败类别,不用零命中代替失败。目录失败时不继续调用详情和检索。
`features.searchModes` 只包含基础检索实际成功的模式;`rerankProbed` 表示实际尝试,
`rerankRequestSucceeded` 表示请求成功,结果 `rerankEnabled` 单独记录远端实际返回的
`usingReRank` 布尔值。FastGPT 另记录检索 envelope、分数类型和分数数组项结构;
operation 保留 HTTP 状态、安全业务码及白名单网络错误码,不记录远端错误消息。
历史基线保留原始证据,不追填未观测的成功字段。
`tests/external-knowledge-probe-fastgpt.test.ts` 和 `tests/external-knowledge-probe-outcomes.test.ts`
使用内存 fetch fixture 运行真实 CLI,覆盖错误、成功零命中和非空结果,不连接外部知识服务。
### 14.7 CRUD 验收边界
| 对象 | Create | Read | Update | Delete | 验收方式 |
| --- | --- | --- | --- | --- | --- |
| GoodBuddy 外部实例 | 是 | 是 | 是 | 是 | SQLite、凭据存储、IPC 和 Renderer 集成测试 |
| GoodBuddy 外部绑定 | 是 | 是 | 是 | 是 | SQLite、统一知识库列表、检索和删除确认测试 |
| Provider 远端知识库与内容 | 否 | 目录、详情、检索 | 否 | 否 | 探测脚本确认只有只读端点;代码审查确认无写端点 |
FastGPT 等 Provider 的公开 API 确实提供远端知识库、集合和数据 CRUD,但这不属于当前产品
接入。若未来要验证远端 CRUD,必须先修改 PRD,并使用可删除的专用测试实例或命名空间;
不能在本轮三个既有实例上执行破坏性测试。
## 15. 实施顺序
这里的顺序只用于控制代码依赖,不裁剪产品设计:
1. 建立共享判别契约、数据库迁移和凭据存储。
2. 实现 HTTP 边界、错误映射和 Adapter Registry。
3. 逐个实现三种 Provider,并用 fixture 完成契约测试。
4. 实现实例管理和目录 IPC。
5. 扩展创建向导、统一列表和外部详情。
6. 扩展检索测试、引用和 `retrieveMany`。
7. 接入聊天预检索与 `KnowledgeMcpGateway`。
8. 完成真实实例、安全、响应式和跨平台验收。
## 16. 发布条件
- 三种 Provider 的受支持版本和限制已在产品界面或发布说明中可查。
- 真实实例列表、绑定、检索和错误矩阵均通过。
- 没有写入远端知识内容的代码路径。
- API Key 泄漏测试、地址边界和响应大小测试通过。
- 本地知识库创建、同步、检索、图谱和引用回归测试通过。
- `auto`、`always`、检索测试和 MCP 知识工具得到一致的外部知识能力。
## 17. 需求追踪
| 需求 | 技术落点 | 验证 |
| 需求 | 当前实现落点 | 待验收重点 |
| --- | --- | --- |
| `EK-FR-01` 至 `EK-FR-03` | 实例契约、Store、实例 IPC、凭据和测试状态 | 实例集成测试、安全测试 |
| `EK-FR-04`、`EK-FR-05` | Adapter 目录与详情接口、目录 IPC | Provider fixture、手工 ID 集成测试 |
| `EK-FR-06` 至 `EK-FR-09` | Provider 判别联合、Adapter 配置版本、Renderer 本地字段定义 | 契约测试、UI 字段显隐测试 |
| `EK-FR-10`、`EK-FR-11` | `KnowledgeBase.kind`、绑定表、统一 `retrieveMany` | 本地与外部混合检索测试 |
| `EK-FR-12`、`EK-FR-13` | 引用定位判别联合、结果标准化、检索测试 IPC | 引用持久化与 UI 测试 |
| `EK-FR-14` | 稳定错误码、逐库 Outcome、取消和超时 | 错误 fixture、部分失败测试 |
| `EK-FR-15` | 外键限制、绑定本地删除、无远端写接口 | 删除生命周期测试 |
| `EK-FR-16` | Main-only HTTP、固定端点、传输风险确认 | 地址、证书和凭据泄漏测试 |
| `EK-FR-01` 至 `EK-FR-05` | 实例 Service/Store、目录 Client、IPC 与管理表单 | 服务已验证;补充 FastGPT/RAGFlow 桌面和受限目录手工 ID |
| `EK-FR-06` 至 `EK-FR-10` | 共享 Provider 配置、绑定事务、统一列表与两栏表单 | Dify 参数与布局已验证;其他 Provider 专属参数及未提供字段待核对 |
| `EK-FR-11` 至 `EK-FR-14` | `retrieveMany`、预检索、MCP、共享引用与 failure 字段 | 三家 IPC 短答案与 Dify 完整输入区引用已验证;混合失败、广泛问答与远程待补 |
| `EK-FR-15` | 唯一约束、外键保护、顺序本地删除 | 中途失败刷新、远端内容不变 |
| `EK-FR-16` | Main 固定端点、有界请求与数据说明 | 当前桌面传输行为与 PRD 差异 |
@@ -4,13 +4,90 @@
| 项目 | 内容 |
| --- | --- |
| 状态 | 设计稿 |
| 版本 | 0.1 |
| 日期 | 2026-09-03 |
| 状态 | Portal 与控件已修正,Dify 完整 App 真实问答及当前自动回归已通过 |
| 版本 | 0.2 |
| 日期 | 2026-09-13 |
| 产品需求 | [外部知识库接入 PRD](./external-knowledge-prd.md) |
| 行为规则 | [外部知识库逻辑设计](./external-knowledge-logic-design.md) |
| 全局规范 | [GoodBuddy UI 设计系统](../../../UI-DESIGN.md) |
## 当前实现与验收边界
`KnowledgeWorkspace.tsx` 已接入 `ExternalKnowledge.tsx`,不再处于只有界面草案的阶段。
当前操作与控件如下;后续编号章节保留目标布局、文案和验收要求,其中超出本节的内容
仍待核对,不能作为已实现或已通过桌面验收的描述。
| 区域 | 当前实现 |
| --- | --- |
| 页面入口与列表 | 标题区有“管理外部实例”和新建操作;本地和外部同一列表,按显示名、远端名、Provider、实例名搜索,原生 Select 筛选来源 |
| 实例管理 | 560px 紧凑 Modal 内切换实例列表、编辑表单和删除确认;标题随状态变化,行内提供测试、编辑、启停和移除,显示地址 origin、凭据、探测状态、绑定数与本地化测试时间 |
| 实例保存与测试 | 新建直接显示密码输入;编辑才提供 keep/replace/clear,清除需要确认。眼睛图标切换显隐并提供可访问名称和按下状态。保存后回列表测试目录,连接说明默认折叠;HTTP 地址下显示传输说明 |
| 创建类型 | 共享 `SegmentedControl` 选择本地、Dify、FastGPT、RAGFlow;本地继续使用现有创建向导,外部挂载独立绑定表单 |
| 来源选择 | 实例使用复用既有 picker 样式的富信息菜单,条目显示名称、地址 origin 与凭据状态;目录使用搜索框、目标按钮、刷新和分页,FastGPT 文件夹可进入与返回根目录;共享分段控件切换手工 ID |
| 配置 | 常规窗口来源与参数两栏,宽度不超过 959px 时单栏;参数使用数字输入、原生 Select 与共享滑动 Switch 样式,没有 Slider 或自由 JSON 编辑器 |
| 测试与保存 | 用户填写查询,测试成功后可保存,零命中允许添加;保存会再次发出检索请求。查询、目标或参数改变会使测试失效,更换现有绑定目标要求确认 |
| 外部详情 | 共享 `PageTabs` 提供概览、检索配置和检索测试;概览显示远端 ID、推导状态和绑定验证时间,可刷新详情、用于对话、重新绑定、管理实例或移除 |
| 参数编辑 | 复用绑定表单,保存按钮为“保存检索参数”;恢复默认只重置表单。配置页签访问后保持挂载,切换详情页签保留草稿 |
| 检索结果 | 外部组件显示总耗时、返回数量、片段、定位、实际 ID 和原始评分,包括 FastGPT 评分数组;不显示本地全文、向量、图谱通道或本地上下文操作 |
| 聊天与引用 | 统一选择器显示 Provider、实例与状态;外部引用打开保存片段,显示实例 ID 和远端定位,不请求本地上下文或打开原始来源 |
原 `knowledge-field` 的控件样式依赖知识库工作区祖先,实例 Modal 经 `createPortal` 挂到
body 后丢失该作用域。当前 `Field` 使用全局 `.field` 和标签 span,密码也复用同一字段
结构,确保 Portal 内的字体、输入边框、背景和焦点样式与应用一致。弹窗宽度为
`min(560px, 100%)`,内容可滚动;操作区粘附底部,范围徽标靠左、按钮靠右,滚动留白
避免键盘焦点被底部操作区遮住。通用参数、Provider 参数和认证使用 fieldset/legend 分组。
Switch 按标签文字在左、滑动开关在右排列,保持 `role="switch"`。实例菜单支持方向键
打开与移动、Home/End、跳过禁用项、Enter 选择和 Escape 返回触发按钮;目录空状态使用
共享 `EmptyState`,无搜索结果可清空查询,无目录可切换手工 ID。已选目标悬停不覆盖
选中背景,保存和测试分别显示对应忙碌文案。
保存、删除等成功信息及实例管理中的异步错误使用应用通知;绑定测试、详情刷新和删除
中途失败保留就地恢复所需的错误。实例有绑定时,确认后依次移除本地绑定再删实例;中途
失败刷新已完成的移除,远端内容不变。
当前已实现过期响应丢弃、Modal 焦点管理、`PageTabs`、标记了 `role="switch"` 的开关和
窄窗口 CSS。2026-09-13 Dify 已在当前生产 BrowserWindow、Main、Preload、Renderer 和
IPC 上完成实例保存、目录选择、检索后绑定、参数更新、重启及历史引用检查,详见
[脱敏验证摘要](./external-knowledge-validation-2026-09-13.json)。验证程序仅在 Main 的实例
保存 Handler 边界替入已有密钥,未将真实密钥放入 Renderer。
1440、900、700 窗口宽度对应报告 viewport 1439、899、699,均无 body 横向溢出。
宽屏配置两栏、窄屏单栏,窄屏列表可见;原生 ArrowRight 可以激活检索页签。已保存并
重开的参数为返回 2 条、超时 15 秒、单片段 200 字符。生产搜索返回两条引用,验证程序
保存带测试标记的会话后重载,引用弹窗显示 Provider、片段和快照说明,焦点在弹窗内,
没有本地上下文或打开来源操作。此检查没有生成模型答案。
上述早期检查没有生成答案。后续完整 App 已从实际输入区选择 Dify 知识库并点击发送,
得到已保存的真实模型短答案,再点击引用核对片段及远端文档/片段 ID;没有伪造消息或
引用。浅深主题均检查了真实 Portal 编辑器的共享字体、背景、560px 宽度与无横向溢出。
另有真实 Electron 视觉 fixture 覆盖浅深主题、四档尺寸及原生键盘操作;其 IPC 是假数据,
只用于界面、焦点和布局检查。三家生成的生产 IPC 验证中,引用展示使用隔离 DOM;只有
Dify 完成了完整 App 的发送与点击引用。计数与方法边界见
[实施进度](./progress.md#2026-09-13-ui-修正与真实生成复验)和
[后续摘要](./external-knowledge-follow-up-2026-09-13.json)。当前 UI 修正后的聚焦测试、
全量回归、类型检查、lint 与工作区/暂存区 diff 检查均已通过。App 测试已按实际可访问
富信息菜单交互替换旧 Select 操作,精确计数由实施进度维护。FastGPT/RAGFlow 完整桌面专属操作和
广泛问答质量仍待验收。
与下文目标设计相比,当前还存在以下边界:
- 没有保存前按地址传输风险确认,也没有接入独立证书例外设置;只有传输说明。
- 没有目录或检索的 IPC 取消入口;丢弃旧响应不等于中止网络请求。测试期间绑定表单整体
禁用,不能把其“取消”按钮描述为正在进行的请求可随时取消。
- Dify 版本、Metadata、摘要、多模态和 Pipeline 状态没有进入当前界面;RAGFlow 只接收
图谱完成与知识编译可用布尔证据,没有三态构建展示或版本支持矩阵。
- 列表和概览没有持久化的最近成功检索时间;概览显示 `lastVerifiedAt`。当前也没有自动
`remote-missing/config-invalid` 状态、完整裁剪诊断或历史引用“连接已移除”标签。
- 没有覆盖所有关闭、类型切换和手工模式切换的未保存草稿离开确认;实例列表使用可换行
列表而非表格,未单设 720px 表格转换规则。
## 目标设计与待验收要求
以下保留既有产品布局和交互目标。当前事实以上节为准;历史 Provider 探测用于解释能力
边界,不作为下述界面已经呈现或已通过产品验证的证据。
## 1. 信息架构
外部知识库不增加左侧主导航和知识库一级页签。现有知识库页面结构保持:
@@ -82,7 +159,7 @@ RAGFlow
## 4. 管理外部实例
点击“管理外部实例”打开大尺寸 Modal。Modal 使用标题、可滚动内容和固定操作区,遵循
点击“管理外部实例”打开最大 560px 的紧凑 Modal。Modal 使用标题、可滚动内容和粘附底部操作区,遵循
全局焦点陷阱、Escape、背景隔离和焦点恢复规则。
```text
@@ -237,6 +314,14 @@ Rerank 用量。`
右侧配置固定分为“通用配置”和“{Provider} 配置”,不创建嵌套卡片。高级字段可以使用
一个折叠区,但核心检索方式、阈值和 RAGFlow 图谱检索保持直接可见。
第一版遵循 [PRD 产品决策](./external-knowledge-prd.md#2-产品决策)的只读范围。面板固定说明:
`参数仅保存在 GoodBuddy,用于此绑定的后续检索,不会修改远端知识库设置。`
编辑页保存按钮使用“保存检索参数”;恢复默认仅重置当前表单,保存后作用于当前绑定。
远端索引、模型、图谱构建和 Pipeline 配置只展示状态,不提供编辑或执行操作。
Dify 的“使用知识库默认配置”开启时说明:`每次检索沿用远端当前设置,远端设置变化会影响结果。`
关闭后,客户端显式检索参数随请求发送,不写回远端默认配置。
控件选择:
- 开关:图谱检索、Score 阈值启用、Rerank、使用 Dify 知识库默认配置。
@@ -267,11 +352,11 @@ Rerank 用量。`
| Dify `0.15.8` | 使用知识库默认配置;展示 `retrieval_model_dict` 中已出现的检索方式、Top K、阈值和 Rerank 配置 | Metadata、多模态、摘要和 Pipeline 状态 | 目录项能力;该版本详情 GET 返回 405,不能把详情失败显示成实例不可用 |
| Dify `1.17.0` | 默认配置、当前完整配置;详情中的文档统计、摘要索引、多模态、Metadata 和 Pipeline 只读状态 | 当前库没有字段定义时禁用 Metadata 条件;远端 Pipeline 和摘要配置不可编辑 | 详情 GET 和 3 条非空检索结果已实测;`child_chunks/files/summary` 仅按结果实际存在显示 |
| FastGPT 当前实例 | embedding、全文召回、混合召回;Rerank | 查询扩展、扩展模型和背景文本 | 三种模式和 Rerank 均有 200 非空实测;查询扩展会调用模型,初始版本不启用 |
| RAGFlow 当前实例 | 相似度、向量权重、KNN Top K;具备完成证据时启用图谱检索 | 没有独立配置证据时禁用 Knowledge Compilation;Metadata 条件仍隐藏 | `graphrag_task_finish_at` 有效才启用图谱;`compilation_template_group_id` 有效才启用 Knowledge Compilation |
| RAGFlow 当前实例 | 基础检索;具备完成证据时启用图谱检索 | 没有独立配置证据时禁用 Knowledge Compilation;Metadata 条件仍隐藏 | 相似度、向量权重和候选数请求已被接受,但未单独验证参数效果;候选数参数需按部署版本适配,详见技术设计;`graphrag_task_finish_at` 有效才启用图谱;`compilation_template_group_id` 有效才启用 Knowledge Compilation |
RAGFlow 列表项应区分三个图谱状态:“未配置”“构建中或未完成”“可用于图谱检索”。不能
仅因 `parser_config.graphrag.use_graphrag = true` 就显示为可用。当前实测中 17 个库配置了
GraphRAG,只有 15 个存在完成证据。
仅因 `parser_config.graphrag.use_graphrag = true` 就显示为可用。实测数量见
[实施进度](./progress.md#验证证据),界面按当前远端知识库的配置和完成状态判断。
Knowledge Compilation 的请求被服务器接受但当前没有库提供配置证据,因此开关显示为禁用,
说明为“当前知识库未配置 Knowledge Compilation”。返回 200 或零结果都不能改变此状态。
@@ -0,0 +1,96 @@
{
"date": "2026-09-13",
"kind": "sanitized-validation-summary",
"scopeNote": "Historical evidence before subsequent portal/control fixes and real-generation follow-up; not final regression evidence for the newer UI",
"followUpEvidence": "external-knowledge-follow-up-2026-09-13.json",
"sources": [
"kb-strict-validation/report.json",
"kb-desktop-ui-validation-20260913/report.json",
"goodbuddy-native-controls-fix-full.log",
"goodbuddy-native-controls-fix-full.result.json"
],
"automatedChecks": {
"status": "passed-before-subsequent-ui-follow-up",
"authoritativeCounts": "progress.md",
"fullApp": { "status": "passed", "evidence": "owner handoff" },
"fullSuite": {
"command": "npm test -- --maxWorkers=1 --reporter=verbose",
"status": "passed",
"exitCode": 0,
"evidence": ["goodbuddy-native-controls-fix-full.log", "goodbuddy-native-controls-fix-full.result.json"]
},
"typecheck": { "command": "npm run typecheck", "status": "passed", "evidence": "owner handoff after final code edits" },
"lint": { "command": "npm run lint", "status": "passed", "evidence": "owner handoff after final code edits" },
"diffCheck": { "status": "passed", "evidence": "owner handoff after final code edits" },
"sourceStability": {
"files": ["src/renderer/src/App.tsx", "src/renderer/src/App.test.tsx"],
"hashesStableThroughoutFullRun": true,
"evidence": "owner handoff"
},
"priorFailures": "resolved; exact intermediate and final counts are maintained only in progress.md"
},
"strictService": {
"boundary": "Production KnowledgeService/client and local HTTP MCP in real Electron; no interactive renderer or authenticated Linux execution",
"electronVersion": "43.2.0",
"safeStorageAvailable": true,
"safeStorageRoundTrip": true,
"retrievalHttpAttempts": 12,
"retrievalHttpResponses": 12,
"profiles": [
{ "provider": "dify", "profile": 1, "scheme": "https:", "strictTls": true, "catalogPassed": true, "detailHttpStatus": 405, "bindingPassed": true, "persistedCredentialReopened": true, "responseSchemaValid": true, "retrievalResults": 0, "mcpToolSuccess": true, "mcpReferences": 0, "citationMappingValid": true, "retrievalHttpAttempts": 3 },
{ "provider": "dify", "profile": 2, "scheme": "https:", "strictTls": true, "catalogPassed": true, "detailHttpStatus": 200, "bindingPassed": true, "persistedCredentialReopened": true, "responseSchemaValid": true, "retrievalResults": 2, "mcpToolSuccess": true, "mcpReferences": 2, "citationMappingValid": true, "retrievalHttpAttempts": 3 },
{ "provider": "fastgpt", "profile": 1, "scheme": "https:", "strictTls": true, "catalogPassed": true, "detailHttpStatus": 200, "bindingPassed": true, "persistedCredentialReopened": true, "responseSchemaValid": true, "retrievalResults": 2, "mcpToolSuccess": true, "mcpReferences": 2, "citationMappingValid": true, "retrievalHttpAttempts": 3 },
{ "provider": "ragflow", "profile": 1, "scheme": "http:", "strictTls": null, "catalogPassed": true, "detailHttpStatus": 200, "bindingPassed": true, "persistedCredentialReopened": true, "responseSchemaValid": true, "retrievalResults": 1, "mcpToolSuccess": true, "mcpReferences": 1, "citationMappingValid": true, "retrievalHttpAttempts": 3 }
]
},
"desktop": {
"provider": "dify",
"phase": "completed",
"boundary": "Production electron-vite Main, preload, BrowserWindow, renderer and IPC; existing provider key substituted in Main at instance-save handler boundary",
"callsByKind": { "catalog": 4, "detail": 4, "retrieval": 6 },
"totalReadOnlyCalls": 14,
"allHttpStatus200": true,
"checks": [
"Instance saved through production IPC",
"Catalog selected and binding saved after live retrieval",
"Persisted binding reopened in isolated desktop profile",
"Local parameters saved through production binding update",
"Native ArrowRight moved focus and activated retrieval tab",
"Live search references persisted and reopened in production citation dialog"
],
"measuredViewportWidths": [1439, 899, 699],
"bodyOverflowAtMeasuredWidths": [0, 0, 0],
"configColumnsAtMeasuredWidths": { "1439": 2, "699": 1 },
"mobileListVisible": true,
"savedCommonConfig": { "resultLimit": 2, "requestTimeoutMs": 15000, "maxSnippetCharacters": 200 },
"resultCount": 2,
"localContextButtons": false,
"window": { "sandbox": true, "contextIsolation": true, "nodeIntegration": false },
"encryptedCredential": { "scheme": "electron-safe-storage", "decryptMatches": true, "plaintextAbsent": true },
"citationPersisted": { "count": 1, "referenceCount": 2 },
"citationDialog": { "snapshot": true, "provider": true, "snippet": true, "openSource": false, "focusInside": true },
"citationMethod": "Production knowledge.search returned live references; harness saved a labeled test transcript through conversations.saveLocal and opened it after reload. No model answer generated",
"isolatedProfileAndBuildRemoved": true
},
"remote": {
"lanTcpConnected": true,
"vpnTcpConnected": true,
"savedCredentialSource": "unreadable",
"hostKeyState": "verified",
"blocker": "E_SAFE_STORAGE_DECRYPT_FAILED",
"forcedPreflightLive": "blocked-before-authenticated-ssh",
"authenticatedSshAttempts": 0
},
"sessionAccounting": {
"source": "Strict report historical bounds plus desktop report; confirmed by owner handoff",
"unsurfacedFirstPassRetrievalMinimum": 0,
"unsurfacedFirstPassRetrievalMaximum": 12,
"earlierConfirmedRetrievalCalls": 12,
"strictRetrievalCalls": 12,
"desktopRetrievalCalls": 6,
"totalRetrievalMinimum": 30,
"totalRetrievalMaximum": 42,
"generatedModelAnswers": 0,
"limitation": "First pass retained no diagnostics, so its exact retrieval count cannot be reconstructed. Provider-side embedding, reranking and audit usage is not counted as generated answers"
}
}
+4
View File
@@ -13,6 +13,10 @@
## 1. 背景
本文负责本地检索与分块增强。当前工作区另已接入外部实例、绑定和统一检索,其产品范围见
[外部知识库 PRD](./external-knowledge-prd.md),实现与待验收状态见[实施进度](./progress.md)。
外部绑定不进入下述本地分块、向量索引和图谱维护流程。
GoodBuddy 已具备本地多知识库、文件与目录同步、网页导入、SQLite FTS5、
OpenAI 兼容向量模型、RRF 混合检索、知识图谱、任务状态和来源引用。现有实现
优先建立了本地数据主权、安全边界和跨 Runtime 工具授权,但用户仍难以稳定
+168 -5
View File
@@ -2,7 +2,8 @@
## 已验证实现
截至 2026-09-05:
截至 2026-09-05 的本地知识库验证记录如下。外部知识库当前实现和验证范围见后文
2026-09-13 记录。
- 知识库选择按 Conversation 持久化,新对话默认空范围。
- 创建流程默认只展示名称和描述,高级存储与图谱设置折叠。
@@ -15,6 +16,156 @@
## 验证证据
### 2026-09-13 当前工作区实现与分组验证
三家 Provider 已通过生产服务、本机 HTTP MCP 与生产 IPC 的短答案生成检查,Dify 另已
从完整 App 输入区完成真实回答和引用点击。后续 UI 修正后的聚焦测试、全量回归、类型
检查、lint、工作区及暂存区 diff 检查均已通过;下表区分早期结果与最终结果。
| 需求范围 | 当前代码落点与行为 |
| --- | --- |
| `EK-FR-01` 至 `EK-FR-05` | `ExternalKnowledgeService`、`ExternalKnowledgeStore`、数据库 v12 迁移和类型化 IPC 已接入;支持加密保存实例、启停、目录分页、详情和手工 ID 检索验证 |
| `EK-FR-06` 至 `EK-FR-10` | `ExternalKnowledge.tsx` 与 `KnowledgeWorkspace.tsx` 已提供四种创建类型、两栏绑定表单、Provider 配置、统一列表和外部详情;参数只保存在本地绑定 |
| `EK-FR-11` 至 `EK-FR-14` | `KnowledgeService.retrieve` 按绑定分派;`retrieveMany` 已供聊天预检索和 MCP 搜索使用;外部定位、逐库失败和历史引用已接入共享契约与会话持久化 |
| `EK-FR-15`、`EK-FR-16` | 实例引用保护、本地移除确认、固定只读端点和有界请求已实现;传输与交互的实际限制见技术、UI 设计,不能视为原草案全部验收项已满足 |
实施代理与 owner 回报的测试记录如下,本轮文档核对没有重新执行这些测试:
| 分组 | 回报结果 | 证据边界 |
| --- | --- | --- |
| Client | 35 项测试通过 | 包含 FastGPT 响应包装、数组评分及取消等客户端契约修正 |
| Service 组 | 96 项测试通过 | 服务及关联集成分组结果,不单独推断每个文件的测试数量 |
| UI 组 | 107 项测试通过 | 组件及关联界面分组结果,不能替代 Electron 桌面操作验收 |
| 后续集成组 | 11 个 suite、403 项通过 | 后于上述分组;不与早期计数相加 |
| 中间全量测试 | 4,021 项通过、4 项失败、66 项跳过 | 历史结果;原失败已修正,最终回归通过 |
| 中间 App + Citation | 216 项通过、2 项失败 | 历史结果;原生控件测试交互已修正 |
| UI 复验前完整 App 测试 | 215 项通过 | owner 回报,覆盖整个 App 测试文件 |
| UI 复验前全量回归 | 347 个文件通过、9 个文件跳过;4,039 项通过、66 项跳过 | `npm test -- --maxWorkers=1 --reporter=verbose`;退出码 0,Vitest 耗时 677.40 秒 |
| UI 复验前类型检查 | 通过 | 当时最后代码修改后执行 `npm run typecheck`,owner 回报 |
| UI 复验前 lint | 通过 | 当时最后代码修改后执行 `npm run lint`,owner 回报 |
| UI 复验前 diff 检查 | 通过 | 当时最后代码修改后检查通过,owner 回报 |
| 最终聚焦测试 | 245 项通过 | 完整 App 与两个 Agent 测试文件,owner 回报 |
| 当前 UI 修正后最终全量回归 | 349 个文件通过、9 个文件跳过;4,081 项通过、66 项跳过 | `npm test -- --maxWorkers=1 --reporter=verbose`;退出码 0,Vitest 耗时 598.33 秒 |
| 最终类型检查与 lint | 通过 | `npm run typecheck`、`npm run lint`,owner 回报 |
| 最终工作区与暂存区 diff 检查 | 通过 | owner 回报;本轮另检查文档 diff |
各次测试覆盖有重叠,不合并为去重后的全仓总数。后续源码已确认 `retrieveMany` 按 rank
跨库轮转分配预算;App 引用事件使用共享 `knowledgeReferenceKey`,保留远端 ID 不同的
引用并去除重复项;引用弹窗的定位只显示一次。
UI 复验前全量证据来自临时目录中的 `goodbuddy-native-controls-fix-full.log` 和
`goodbuddy-native-controls-fix-full.result.json`,日志汇总与结果文件退出码已核对。
owner 确认 `App.tsx`、`App.test.tsx` 的哈希在整轮运行期间保持不变,最后代码修改后的
类型检查、lint 和 diff 检查均通过。之后用户要求重新检查 UI 并执行真实模型与知识库验证,
外部界面又有修改,因此该通过记录不自动覆盖当前源码。
原失败涉及 Preload 测试顺序沿用旧计数、原生选项控件的延迟焦点交互,以及并发导航;
相应修正已完成,并发导航由协作代理处理。引用去重与重复定位修正也已纳入最终回归。
精确自动测试计数仅在本文维护,其他设计文档和脱敏摘要引用本记录。
当前最终全量证据为 `goodbuddy-binding-selector-fix-full.log` 和
`goodbuddy-binding-selector-fix-full.result.json`,日志汇总和退出码已核对。最后一项 App
测试修正把旧原生 Select 操作改为实际富信息菜单的可访问交互,没有修改产品源码。
其他 Agent 测试失败由并发 owner 在其最新代码中解决,本轮未修改这些文件。
owner 确认 App 测试、两个 Agent 测试和 `ExternalKnowledge` 在整轮执行期间保持稳定,
不再追加源码修改。当前通过结果已覆盖后续 UI 版本,早期结果继续作为历史记录保留。
### 2026-09-13 生产服务与桌面验证
已读取脱敏的 `kb-strict-validation/report.json` 和
`kb-desktop-ui-validation-20260913/report.json`,并在
[产品验证摘要](./external-knowledge-validation-2026-09-13.json)归档必要字段。报告检查
当前生产实现,与下述 2026-09-09、2026-09-10 历史探测分开计证。
严格验证在真实 Electron 43.2.0 中运行生产 `KnowledgeService`、客户端和本机 HTTP MCP。
四个配置均完成绑定保存、凭据持久化后重开、响应 Schema 和引用映射检查;每个配置执行
绑定、服务检索、HTTP MCP 三次检索,共 12 次请求与 12 次响应。
| 配置编号 | 传输 | 目录 / 详情 | 服务结果 / MCP 引用 | 结论范围 |
| --- | --- | --- | --- | --- |
| Dify 1 | HTTPS,严格证书校验 | 成功 / HTTP 405 | 0 / 0 | 详情不兼容仍可绑定与检索,零命中是成功;没有非空证据 |
| Dify 2 | HTTPS,严格证书校验 | 成功 / 成功 | 2 / 2 | 非空服务检索与本机 HTTP MCP 成功 |
| FastGPT | HTTPS,严格证书校验 | 成功 / 成功 | 2 / 2 | 非空服务检索与本机 HTTP MCP 成功,未覆盖桌面 Provider 专属操作 |
| RAGFlow | HTTP,不涉及 TLS | 成功 / 成功 | 1 / 1 | 非空服务检索与本机 HTTP MCP 成功,未覆盖桌面图谱等专属操作 |
这些编号来自本轮报告,不由历史版本号推断。`safeStorage` 可用且真实加解密往返成功,
四个配置保存的凭据均在重开后可用。HTTPS 结论只覆盖表中三个 HTTPS 配置。
Dify 桌面验证使用当前 Main、Preload、Renderer、真实 BrowserWindow 和生产 IPC。
实例保存时,验证程序仅在 Main 的保存 Handler 边界替入已有测试密钥。已验证目录选择、
真实检索后绑定、本地参数保存和隔离桌面配置重启;保存参数为返回 2 条、超时 15 秒、
单片段 200 字符。14 次只读请求均为 HTTP 200,其中目录 4 次、详情 4 次、检索 6 次。
窗口按 1440、900、700 宽度检查,报告实际 viewport 为 1439、899、699,均无 body 横向
溢出;宽屏参数两栏、窄屏单栏及窄屏列表可见。原生 ArrowRight 切换并激活检索页签。
桌面凭据记录采用 `electron-safe-storage`,解密匹配且明文未写入被检查的凭据记录;
BrowserWindow 保持 sandbox、contextIsolation 开启和 nodeIntegration 关闭。
生产 `knowledge.search` 返回两条真实引用,验证程序通过 `conversations.saveLocal`
保存带测试标记的会话后重载,再打开生产引用弹窗。已检查片段、Provider、快照说明和
弹窗内焦点,未出现本地上下文或打开来源按钮。这里验证的是历史引用保存与阅读,
没有生成模型答案,也未证明模型会在真实回答中使用或引用证据。隔离配置和构建已清理。
截至上述早期验证,已知检索请求 30 次,上界 42 次:未保留诊断的首轮为 0 至 12 次、此前已确认
12 次、严格验证 12 次、桌面 6 次。首轮准确数量无法恢复。模型生成调用为 0;Provider
侧 Embedding、重排及审计用量不据此记为零。该计数不合并 9 月 9 日、10 日的历史探测。
Linux 测试 Host 的 LAN 与 VPN TCP 均可达,但验证程序读取已有 SSH 凭据时发生
`E_SAFE_STORAGE_DECRYPT_FAILED`,在认证 SSH 之前阻断。因此真实远程 forced-preflight
未执行;本机 HTTP MCP 成功不能替代远程生产路径验收。
### 2026-09-13 UI 修正与真实生成复验
用户指出原界面不符合要求,并要求真实模型与知识库一起验证。当前 `ExternalKnowledge.tsx`
与 CSS 已修正 Portal 移出工作区后丢失字段作用域的问题,改用共享 `.field`;实例弹窗收为
560px,密码使用眼睛图标,新建直接填凭据、编辑才选择保留或替换或清除。开关、粘附底部
操作区、带地址和凭据状态的实例菜单、EmptyState 与键盘行为均已更新。实现细节见
[UI 设计](./external-knowledge-ui-design.md#当前实现与验收边界)。
本轮读取 `kb-real-generation/report.json`、`kb-full-app-live/report.json` 和
`external-visual-0913/results.json`,脱敏摘要归档在
[UI 与真实生成复验](./external-knowledge-follow-up-2026-09-13.json)。摘要不保存模型端点、
私有配置标识、密钥、业务正文、生成答案或内容哈希。
| 验证层 | 实际覆盖 | 结果与边界 |
| --- | --- | --- |
| 生产 IPC 与真实模型 | 真实 Electron、Preload、已注册 IPC 和 ModelAgentRuntime;Dify/FastGPT/RAGFlow 的 `always`,以及 Dify `auto` | 最终场景均有非空短答案、依据匹配、引用标记、单库范围与文本/外部引用持久化证据;Renderer 是隔离验证 DOM,不是完整 App |
| 完整 App | 真实 Main、Preload、Renderer;从界面创建 Dify 实例及非空绑定,在输入区选择知识库并点击发送,Ask + `always` | 真实模型生成并保存答案,检索片段进入模型请求,点击引用后片段和远端文档/片段 ID 匹配,焦点在弹窗内;无伪造会话、引用或 Agent 事件 |
| 视觉与键盘 fixture | 当前组件和样式、真实 body Portal 与 Electron 原生键盘;IPC 为本地假数据 | 56 张截图、138 项检查通过,errors 为空;覆盖浅深主题及 1280×800、960×720、720×640、640×420,不作为真实 Provider 业务调用证据 |
生产 IPC 首轮 FastGPT/RAGFlow 已有非空、依据匹配和引用,但验证程序直接在序列化请求
字符串内寻找原片段,导致 `retrievedSnippetInBody=false`、`passed=false`。改为解码 Provider
请求及其中的证据 JSON 后,两家重跑均通过。归档保留首轮失败和修正后结果,不把首轮
改写为通过,也不把这次断言修正记成产品检索修复。Dify `auto` 通过进程内
`ModelToolProvider → KnowledgeMcpGateway` 执行 `knowledge_search`,不是 HTTP MCP 会话。
模型使用用户已保存的默认配置,凭据在 Main 用 safeStorage 解密。完整 App 检查确认原
模型设置未改变;Dify/FastGPT 请求使用严格 HTTPS,RAGFlow 和所用模型保留已配置的 HTTP。
完整 App 中的测试知识库密钥仅在 Main 保存 IPC 边界替入,报告没有公开实际密钥。
完整 App 另检查浅深主题的 Portal 编辑器:宽度 560px、字体与字号继承共享控件、背景
随主题变化,窗口内无横向溢出。视觉 fixture 检查了密码显隐前后布局、原生 Tab/Space、
开关、底部保存按钮、焦点循环与恢复、菜单跳过禁用项、页签、凭据替换和清除确认。
| 本轮请求 | 模型 | KB 全部 | KB 检索 | 目录 | 详情 |
| --- | --- | --- | --- | --- | --- |
| 生产 IPC 生成报告 | 7 | 12 | 9 | 3 | 0 |
| 完整 App 报告 | 1 | 5 | 3 | 1 | 1 |
| 合计 | 8 | 17 | 12 | 4 | 1 |
计数是本轮应用实际发出的 HTTP 请求,包含绑定验证和断言修正后的重跑;不推断上游代理
内部请求或 Provider Embedding、重排用量。生产 IPC 的检索含绑定验证 3 次、强制预检索
5 次、按需检索 1 次;完整 App 含创建验证 2 次、输入区发送触发 1 次。本轮采用精确增量,
不沿用生成报告中漏计早期桌面请求的累计字段。
“生成未经验证”已在上述短答案范围内解决。检查证明选中知识库的真实片段进入模型并
产生带依据和引用的短答案,不证明长文、多库冲突、复杂推理或广泛问答质量。完整 App
仍只覆盖 Dify;FastGPT/RAGFlow 完整桌面与专属参数、Linux 远程路径继续待验收。
当前 UI 修改后的聚焦测试、全量回归及相关检查已通过,精确结果见前文自动测试记录;
早期通过记录保持原时间范围。
### 历史验证记录
2026-09-10 官方 API 核对与真实实例复测:
- 官方资料链接与版本差异见[技术设计第 9 节](./external-knowledge-technical-design.md#9-provider-映射)。
@@ -37,9 +188,12 @@
- 修正探测脚本将 Dify/RAGFlow 失败记成零命中的问题,增加实际调用与成功标记,统一
FastGPT 地址规范化并记录分数结构、响应中的重排状态。未修改产品客户端;旧 FastGPT
检索数组包装、数组评分映射及上一轮发现的取消分类问题仍待处理。
检索数组包装、数组评分映射及上一轮发现的取消分类问题在当时仍待处理;当前客户端已修正,
当前服务和本机 HTTP MCP 的真实 Provider 复验见 2026-09-13 记录,旧包装等特定响应变体
仍不能仅凭成功计数认定覆盖。
- 三组探测测试共 43 项通过,包含两份新报告的脱敏与只读操作检查;`npm run typecheck`、`npm run lint` 通过。
`npm test` 先后达到 60 秒和 240 秒执行时限,均未输出汇总,全量回归尚未完成。
`npm test` 在该历史轮次先后达到 60 秒和 240 秒执行时限,均未输出汇总;当前最终全量
已通过,见 2026-09-13 记录。
- 本轮未验证真实 401/403/404/429、超时、取消、Scoped Key、分页、元数据过滤或候选数
参数效果。探测脚本不打包到桌面或 GoodBuddy Agent,本次未改变远程 Runtime 生产路径。
@@ -92,8 +246,17 @@
## 剩余工作
- 外部 Dify、FastGPT 和 RAGFlow 已完成 Main 适配基础;实例凭据持久化、绑定迁移、IPC、
Renderer 管理界面、`retrieveMany`、聊天预检索和 MCP 网关仍未接入,当前不能由用户使用。
- 完成 FastGPT/RAGFlow 桌面专属配置及实际参数效果检查;补充混合库、真实聊天
`always`/`auto` 的多库与复杂问答、停用和本地删除、未覆盖的主题及键盘路径。三家生产 IPC
短答案、Dify 完整输入区真实回答与引用,以及已检查的主题/尺寸/键盘不再列为首次待验收。
- 解决验证程序对已有 SSH 凭据的解密问题后,执行真实 Linux forced-preflight;网络已可达,
当前阻断点是认证前凭据读取。
- 三家服务已完成真实检索与本机 HTTP MCP。FastGPT/RAGFlow 部署版本、Dify 1 非空结果、
Scoped Key 和真实错误矩阵等仍需补证;旧探测不证明当前桌面配置效果。
- 原设计中的分项探测、版本能力展示、地址确认、请求取消交互和详细诊断与当前实现有差异,
具体范围见[技术设计](./external-knowledge-technical-design.md)和
[UI 设计](./external-knowledge-ui-design.md#当前实现与验收边界)。这些差异需由功能 owner 核对验收,
本轮仅改文档,不补产品代码或扩大架构。
- 扫描 PDF 的真实 OCR 验收依赖用户已安装并校验的 OCR 模型;本次 DOCX/PDF 回归覆盖
原生文本 PDF,不宣称覆盖扫描件 OCR。
- PDF.js 在测试环境会提示未配置 `standardFontDataUrl`;文本、页码定位与检索结果不受
+7 -3
View File
@@ -8,6 +8,10 @@
## 2. 信息架构
当前列表同时容纳本地库与外部绑定,页面增加“管理外部实例”和来源筛选。以下详情结构、
资料导入和索引状态适用于本地库;外部创建、配置、检索测试及引用的当前实现与待验收项见
[外部知识库 UI 设计](./external-knowledge-ui-design.md)。
```text
知识库
├─ 知识库列表
@@ -23,9 +27,9 @@
## 3. 首次使用
没有知识库时,页面空状态显示唯一的“新建知识库”主操作。创建表单默认只展示名称和
描述;存储方式与知识图谱放入折叠的“高级设置”。创建完成后进入“文档与来源”,用户
可以导入文件、文件夹或 URL。
没有知识库时,页面空状态显示“新建知识库”主操作。创建时先选择类型;本地类型默认
展示名称和描述,存储方式与知识图谱放入折叠的“高级设置”。本地库创建完成后进入
“文档与来源”,用户可以导入文件、文件夹或 URL。
文档处理完成后,标题区“用于当前对话”可用。该操作把当前知识库加入当前
Conversation 的检索范围,返回对话并聚焦输入框。