test: validate external knowledge provider contracts

Add read-only Dify, FastGPT, and RAGFlow probes, a redacted live capability baseline, and focused adapter tests. Document version-specific API and UI behavior, including Dify 0.x/1.x differences and RAGFlow GraphRAG evidence.
This commit is contained in:
mesalogo
2026-09-10 00:33:09 +08:00
parent f3b695214b
commit c1f8ba80ee
10 changed files with 2275 additions and 8 deletions
+3 -2
View File
@@ -1,8 +1,8 @@
# 知识库
知识库已提供本地资料导入、解析、分块、索引、检索、图谱和带来源的知识问答。
将 Dify、FastGPT 和 RAGFlow 中已有知识库接入同一检索范围仍处于规划阶段,不属于
当前已提供能力。
将 Dify、FastGPT 和 RAGFlow 中已有知识库接入同一检索范围已完成 Main 进程适配器和
真实接口验证,但实例管理、绑定、统一检索和界面尚未接通,因此仍不属于当前已提供能力。
## 文档导航
@@ -19,6 +19,7 @@
| [外部知识库逻辑设计](./external-knowledge-logic-design.md) | 状态、不变量、配置优先级和失败规则 |
| [外部知识库 UI 设计](./external-knowledge-ui-design.md) | 知识库页面、外部实例管理和创建向导交互 |
| [外部知识库技术设计](./external-knowledge-technical-design.md) | Provider Adapter、凭据、持久化、IPC 和检索标准化 |
| [外部知识库实测基线](./external-knowledge-probe-baseline.json) | 脱敏记录真实实例的接口状态、字段结构、特色能力和耗时 |
## 术语
@@ -337,7 +337,10 @@ Provider 原始正文不能直接成为用户错误。Adapter 将其映射为稳
实施前仍需技术验证:
- 选定支持的 Dify、FastGPT、RAGFlow 最低版本,形成固定支持矩阵并保存真实响应 fixture。
- 已完成第一轮真实目录与最小检索验证,并确认 Dify `1.17.0` 非空结果结构;仍需确认
FastGPT、RAGFlow 产品版本和 Dify `0.15.8` 非空结果,再选定三者最低版本并形成固定
支持矩阵与去敏 fixture。当前实测详情见
[技术设计 9.0 节](./external-knowledge-technical-design.md#90-真实实例验证基线)。
- 验证各版本检索探测的 API 审计、用量和内容修改行为。
- 确定 Provider 返回 URL 的安全打开策略;未验证前只显示文本定位和远端 ID。
- 依据真实延迟确定默认并发上限和跨知识库上下文配额。
@@ -0,0 +1,910 @@
{
"schemaVersion": 1,
"probedAt": "2026-09-09T16:25:05.512Z",
"mode": "extended-read-only",
"queryCharacters": 3,
"privacy": "No credentials, origins, IDs, names, queries, or content are included.",
"providers": [
{
"provider": "dify",
"profile": "0.x",
"detectedVersion": "0.15.8",
"transport": {
"protocol": "https:",
"tlsVerificationDisabled": true
},
"catalog": {
"count": 3,
"total": 3,
"itemKeys": [
"app_count",
"created_at",
"created_by",
"data_source_type",
"description",
"doc_form",
"document_count",
"embedding_available",
"embedding_model",
"embedding_model_provider",
"external_knowledge_info",
"external_retrieval_model",
"id",
"indexing_technique",
"name",
"permission",
"provider",
"retrieval_model_dict",
"tags",
"updated_at",
"updated_by",
"word_count"
],
"retrievalModelKeys": [
"reranking_enable",
"reranking_mode",
"reranking_model",
"score_threshold",
"score_threshold_enabled",
"search_method",
"top_k",
"weights"
],
"indexingTechniques": [
"high_quality"
],
"metadataSchemaCount": 0,
"multimodalCount": 0,
"pipelineConfiguredCount": 0,
"summaryIndexConfiguredCount": 0
},
"detail": {
"keys": [
"code",
"message",
"status"
]
},
"retrievals": [
{
"mode": "dataset-defaults",
"count": 0,
"itemKeys": [],
"segmentKeys": [],
"documentKeys": [],
"childChunkKeys": [],
"fileKeys": [],
"contentPresent": false,
"childContentPresent": false,
"summaryPresent": false
},
{
"mode": "explicit-current-config",
"count": 0,
"itemKeys": [],
"segmentKeys": [],
"documentKeys": [],
"childChunkKeys": [],
"fileKeys": [],
"contentPresent": false,
"childContentPresent": false,
"summaryPresent": false
}
],
"features": {
"datasetDefaults": true,
"detailSupported": false,
"retrievalOverrideObserved": true,
"retrievalOverrideProbed": true,
"nonEmptyResultObserved": false
},
"operations": [
{
"name": "catalog",
"method": "GET",
"path": "/v1/datasets",
"status": 200,
"durationMs": 288,
"responseKeys": [
"data",
"has_more",
"limit",
"page",
"total"
]
},
{
"name": "detail",
"method": "GET",
"path": "/v1/datasets/{id}",
"status": 405,
"durationMs": 111,
"responseKeys": [
"code",
"message",
"status"
]
},
{
"name": "retrieve-defaults",
"method": "POST",
"path": "/v1/datasets/{id}/retrieve",
"status": 200,
"durationMs": 237,
"responseKeys": [
"query",
"records"
]
},
{
"name": "retrieve-explicit-config",
"method": "POST",
"path": "/v1/datasets/{id}/retrieve",
"status": 200,
"durationMs": 160,
"responseKeys": [
"query",
"records"
]
}
]
},
{
"provider": "dify",
"profile": "1.x",
"detectedVersion": "1.17.0",
"transport": {
"protocol": "https:",
"tlsVerificationDisabled": true
},
"catalog": {
"count": 1,
"total": 1,
"itemKeys": [
"app_count",
"author_name",
"built_in_field_enabled",
"chunk_structure",
"created_at",
"created_by",
"data_source_type",
"description",
"doc_form",
"doc_metadata",
"document_count",
"embedding_available",
"embedding_model",
"embedding_model_provider",
"enable_api",
"external_knowledge_info",
"external_retrieval_model",
"icon_info",
"id",
"indexing_technique",
"is_multimodal",
"is_published",
"maintainer",
"name",
"permission",
"pipeline_id",
"provider",
"retrieval_model_dict",
"runtime_mode",
"summary_index_setting",
"tags",
"total_available_documents",
"total_documents",
"updated_at",
"updated_by",
"word_count"
],
"retrievalModelKeys": [
"reranking_enable",
"reranking_mode",
"reranking_model",
"score_threshold",
"score_threshold_enabled",
"search_method",
"top_k",
"weights"
],
"indexingTechniques": [
"high_quality"
],
"metadataSchemaCount": 0,
"multimodalCount": 0,
"pipelineConfiguredCount": 0,
"summaryIndexConfiguredCount": 1
},
"detail": {
"keys": [
"app_count",
"author_name",
"built_in_field_enabled",
"chunk_structure",
"created_at",
"created_by",
"data_source_type",
"description",
"doc_form",
"doc_metadata",
"document_count",
"embedding_available",
"embedding_model",
"embedding_model_provider",
"enable_api",
"external_knowledge_info",
"external_retrieval_model",
"icon_info",
"id",
"indexing_technique",
"is_multimodal",
"is_published",
"maintainer",
"name",
"permission",
"pipeline_id",
"provider",
"retrieval_model_dict",
"runtime_mode",
"summary_index_setting",
"tags",
"total_available_documents",
"total_documents",
"updated_at",
"updated_by",
"word_count"
]
},
"retrievals": [
{
"mode": "dataset-defaults",
"count": 3,
"itemKeys": [
"child_chunks",
"files",
"score",
"segment",
"summary",
"tsne_position"
],
"segmentKeys": [
"answer",
"completed_at",
"content",
"created_at",
"created_by",
"disabled_at",
"disabled_by",
"document",
"document_id",
"enabled",
"error",
"hit_count",
"id",
"index_node_hash",
"index_node_id",
"indexing_at",
"keywords",
"position",
"sign_content",
"status",
"stopped_at",
"tokens",
"word_count"
],
"documentKeys": [
"data_source_type",
"doc_metadata",
"doc_type",
"id",
"name"
],
"childChunkKeys": [],
"fileKeys": [],
"contentPresent": true,
"childContentPresent": false,
"summaryPresent": false,
"scoreRange": {
"minimum": 0.5038624,
"maximum": 0.6006959
}
},
{
"mode": "explicit-current-config",
"count": 3,
"itemKeys": [
"child_chunks",
"files",
"score",
"segment",
"summary",
"tsne_position"
],
"segmentKeys": [
"answer",
"completed_at",
"content",
"created_at",
"created_by",
"disabled_at",
"disabled_by",
"document",
"document_id",
"enabled",
"error",
"hit_count",
"id",
"index_node_hash",
"index_node_id",
"indexing_at",
"keywords",
"position",
"sign_content",
"status",
"stopped_at",
"tokens",
"word_count"
],
"documentKeys": [
"data_source_type",
"doc_metadata",
"doc_type",
"id",
"name"
],
"childChunkKeys": [],
"fileKeys": [],
"contentPresent": true,
"childContentPresent": false,
"summaryPresent": false,
"scoreRange": {
"minimum": 0.5038624,
"maximum": 0.6006959
}
}
],
"features": {
"datasetDefaults": true,
"detailSupported": true,
"retrievalOverrideObserved": true,
"retrievalOverrideProbed": true,
"nonEmptyResultObserved": true
},
"operations": [
{
"name": "catalog",
"method": "GET",
"path": "/v1/datasets",
"status": 200,
"durationMs": 950,
"responseKeys": [
"data",
"has_more",
"limit",
"page",
"total"
]
},
{
"name": "detail",
"method": "GET",
"path": "/v1/datasets/{id}",
"status": 200,
"durationMs": 616,
"responseKeys": [
"app_count",
"author_name",
"built_in_field_enabled",
"chunk_structure",
"created_at",
"created_by",
"data_source_type",
"description",
"doc_form",
"doc_metadata",
"document_count",
"embedding_available",
"embedding_model",
"embedding_model_provider",
"enable_api",
"external_knowledge_info",
"external_retrieval_model",
"icon_info",
"id",
"indexing_technique",
"is_multimodal",
"is_published",
"maintainer",
"name",
"permission",
"pipeline_id",
"provider",
"retrieval_model_dict",
"runtime_mode",
"summary_index_setting",
"tags",
"total_available_documents",
"total_documents",
"updated_at",
"updated_by",
"word_count"
]
},
{
"name": "retrieve-defaults",
"method": "POST",
"path": "/v1/datasets/{id}/retrieve",
"status": 200,
"durationMs": 2468,
"responseKeys": [
"query",
"records"
]
},
{
"name": "retrieve-explicit-config",
"method": "POST",
"path": "/v1/datasets/{id}/retrieve",
"status": 200,
"durationMs": 801,
"responseKeys": [
"query",
"records"
]
}
]
},
{
"provider": "fastgpt",
"transport": {
"protocol": "https:",
"tlsVerificationDisabled": true
},
"catalog": {
"count": 1,
"envelope": "data-array",
"itemKeys": [
"_id",
"avatar",
"createTime",
"inheritPermission",
"intro",
"name",
"permission",
"private",
"sourceMember",
"tmbId",
"type",
"updateTime",
"vectorModel"
],
"itemTypes": [
"dataset"
]
},
"detail": {
"keys": [
"_id",
"agentModel",
"agentModelId",
"avatar",
"deleteTime",
"inheritPermission",
"intro",
"name",
"parentId",
"permission",
"status",
"teamId",
"tmbId",
"type",
"updateTime",
"vectorModel",
"vectorModelId",
"vlmModel",
"vlmModelId"
],
"vectorModelKeys": [
"avatar",
"charsPointsPrice",
"config",
"isActive",
"isCustom",
"model",
"modelId",
"name",
"provider",
"scope",
"testMode",
"type"
]
},
"retrievals": [
{
"searchMode": "embedding",
"count": 8,
"itemKeys": [
"chunkIndex",
"collectionId",
"datasetId",
"id",
"q",
"score",
"sourceId",
"sourceName",
"updateTime"
],
"contentPresent": true
},
{
"searchMode": "fullTextRecall",
"count": 2,
"itemKeys": [
"chunkIndex",
"collectionId",
"datasetId",
"id",
"q",
"score",
"sourceId",
"sourceName",
"updateTime"
],
"contentPresent": true
},
{
"searchMode": "mixedRecall",
"count": 7,
"itemKeys": [
"chunkIndex",
"collectionId",
"datasetId",
"id",
"q",
"score",
"sourceId",
"sourceName",
"updateTime"
],
"contentPresent": true
},
{
"searchMode": "mixedRecall",
"usingRerank": true,
"count": 9,
"itemKeys": [
"chunkIndex",
"collectionId",
"datasetId",
"id",
"q",
"score",
"sourceId",
"sourceName",
"updateTime"
],
"contentPresent": true
}
],
"features": {
"searchModes": [
"embedding",
"fullTextRecall",
"mixedRecall"
],
"rerankProbed": true,
"extensionQueryProbed": false
},
"operations": [
{
"name": "catalog",
"method": "POST",
"path": "/api/core/dataset/list",
"status": 200,
"durationMs": 707,
"responseKeys": [
"code",
"data",
"message",
"statusText"
]
},
{
"name": "detail",
"method": "GET",
"path": "/api/core/dataset/detail",
"status": 200,
"durationMs": 293,
"responseKeys": [
"code",
"data",
"message",
"statusText"
]
},
{
"name": "retrieve-embedding",
"method": "POST",
"path": "/api/core/dataset/searchTest",
"status": 200,
"durationMs": 797,
"responseKeys": [
"code",
"data",
"message",
"statusText"
]
},
{
"name": "retrieve-fullTextRecall",
"method": "POST",
"path": "/api/core/dataset/searchTest",
"status": 200,
"durationMs": 300,
"responseKeys": [
"code",
"data",
"message",
"statusText"
]
},
{
"name": "retrieve-mixedRecall",
"method": "POST",
"path": "/api/core/dataset/searchTest",
"status": 200,
"durationMs": 1707,
"responseKeys": [
"code",
"data",
"message",
"statusText"
]
},
{
"name": "retrieve-rerank",
"method": "POST",
"path": "/api/core/dataset/searchTest",
"status": 200,
"durationMs": 1727,
"responseKeys": [
"code",
"data",
"message",
"statusText"
]
}
]
},
{
"provider": "ragflow",
"transport": {
"protocol": "http:"
},
"catalog": {
"count": 28,
"total": 28,
"itemKeys": [
"artifact_task_finish_at",
"artifact_task_id",
"avatar",
"chunk_count",
"chunk_method",
"create_date",
"create_time",
"created_by",
"description",
"document_count",
"embedding_model",
"graphrag_task_finish_at",
"graphrag_task_id",
"id",
"language",
"mindmap_task_finish_at",
"mindmap_task_id",
"name",
"nickname",
"pagerank",
"parser_config",
"permission",
"pipeline_id",
"raptor_task_finish_at",
"raptor_task_id",
"similarity_threshold",
"skill_task_finish_at",
"skill_task_id",
"status",
"tenant_avatar",
"tenant_embd_id",
"tenant_id",
"token_num",
"update_date",
"update_time",
"vector_similarity_weight"
],
"parserConfigKeys": [
"auto_keywords",
"auto_questions",
"built_in_metadata",
"children_delimiter",
"chunk_token_num",
"compilation_template_group_id",
"delimiter",
"enable_children",
"enable_metadata",
"ext",
"filename_embd_weight",
"graphrag",
"html4excel",
"image_context_size",
"image_table_context_window",
"layout_recognize",
"llm_id",
"metadata",
"mineru_formula_enable",
"mineru_lang",
"mineru_parse_method",
"mineru_table_enable",
"overlapped_percent",
"pages",
"parent_child",
"raptor",
"table_column_mode",
"table_column_names",
"table_column_roles",
"table_context_size",
"tag_kb_ids",
"task_page_size",
"tenant_llm_id",
"toc_extraction",
"topn_tags"
],
"graphConfiguredCount": 17,
"graphCompletedCount": 15,
"knowledgeCompilationConfiguredCount": 0
},
"detail": {
"keys": [
"artifact_task_finish_at",
"artifact_task_id",
"avatar",
"chunk_count",
"chunk_method",
"create_date",
"create_time",
"created_by",
"description",
"document_count",
"embedding_model",
"graphrag_task_finish_at",
"graphrag_task_id",
"id",
"language",
"mindmap_task_finish_at",
"mindmap_task_id",
"name",
"nickname",
"pagerank",
"parser_config",
"permission",
"pipeline_id",
"raptor_task_finish_at",
"raptor_task_id",
"similarity_threshold",
"skill_task_finish_at",
"skill_task_id",
"status",
"tenant_avatar",
"tenant_embd_id",
"tenant_id",
"token_num",
"update_date",
"update_time",
"vector_similarity_weight"
]
},
"retrievals": [
{
"feature": "baseline",
"count": 0,
"itemKeys": [],
"contentPresent": false
},
{
"feature": "graph",
"count": 1,
"itemKeys": [
"content",
"content_ltks",
"dataset_id",
"document_id",
"document_keyword",
"id",
"image_id",
"important_keywords",
"positions",
"similarity",
"term_similarity",
"vector_similarity"
],
"contentPresent": true,
"scoreRange": {
"minimum": 1,
"maximum": 1
}
},
{
"feature": "knowledge-compilation",
"count": 0,
"itemKeys": [],
"contentPresent": false
}
],
"features": {
"graphRetrievalProbed": true,
"knowledgeCompilationProbed": true,
"metadataConditionProbed": false
},
"operations": [
{
"name": "catalog",
"method": "GET",
"path": "/api/v1/datasets",
"status": 200,
"durationMs": 732,
"responseKeys": [
"code",
"data",
"total_datasets"
]
},
{
"name": "detail",
"method": "GET",
"path": "/api/v1/datasets",
"status": 200,
"durationMs": 157,
"responseKeys": [
"code",
"data",
"total_datasets"
]
},
{
"name": "retrieve-baseline",
"method": "POST",
"path": "/api/v1/retrieval",
"status": 200,
"durationMs": 417,
"responseKeys": [
"code",
"data"
]
},
{
"name": "retrieve-graph",
"method": "POST",
"path": "/api/v1/retrieval",
"status": 200,
"durationMs": 478,
"responseKeys": [
"code",
"data"
]
},
{
"name": "retrieve-knowledge-compilation",
"method": "POST",
"path": "/api/v1/retrieval",
"status": 200,
"durationMs": 331,
"responseKeys": [
"code",
"data"
]
}
]
}
]
}
@@ -358,11 +358,28 @@ knowledge:external-retrieval:test
本节记录 2026-09-03 核对的初始公开接口。实施时应把每个受支持版本的真实响应保存为
去敏 fixture,并以 fixture 和真实实例测试作为发布依据。
### 9.0 真实实例验证基线
2026-09-09 使用本机未入库的测试地址和 API Key 完成第一轮只读验证。验证只记录状态、
字段名、数量和长度,不记录 API Key、知识库名称或片段正文。
| 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 配置证据 |
当前 Node 测试进程设置了 `NODE_TLS_REJECT_UNAUTHORIZED=0`,因此两个 HTTPS 实例的请求
使用了放宽的证书校验。这只能证明接口在当前测试环境可用,不能证明严格 TLS 校验通过。
产品实现不得读取该进程级设置作为隐式默认;实例测试和保存必须按 8.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>` |
@@ -375,6 +392,26 @@ Adapter 只接受 Knowledge Service API Key,不调用应用 `/info`、Chat 或
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
字段,未执行空条件或猜测字段探测;该配置保持禁用,直到所选库详情返回可用字段定义。
### 9.2 FastGPT
| 操作 | 接口 |
@@ -384,10 +421,17 @@ Dify 当前文档限制查询不超过 250 字符。GoodBuddy 不静默截断;
| 检索 | `POST /api/core/dataset/searchTest` |
| 认证 | `Authorization: Bearer <api-key>` |
列表响应同时包含文件夹和知识库,只有 `type = dataset` 可以绑定。检索映射 `datasetId`、
列表响应同时包含文件夹和知识库,只有 `type = dataset` 可以绑定。当前实测列表的
`data` 直接为数组,不是 `data.list`;Adapter 可以仅对已经实测的这两种明确 envelope
做版本化解析,不递归猜测任意嵌套。检索映射 `datasetId`、
`text`、Token `limit`、`similarity`、`searchMode` 和 `usingReRank`。初始版本不发送查询优化
字段,避免调用 FastGPT 配置的 LLM。
响应从 `q`、`a`、`sourceName`、`sourceId`、`collectionId` 和 `score` 构造片段与引用。
当前实测响应的 `data` 包含 `list/duration/limit/searchMode/usingReRank/similarity`;结果项
包含 `q/id/datasetId/collectionId/sourceName/sourceId/chunkIndex/score`,未返回 `a`。
Adapter 以 `q` 为必需片段,`a` 仅在远端实际返回时追加,不能要求 `a` 存在。
当前实例的 embedding、fullTextRecall、mixedRecall 和 mixedRecall + Rerank 均返回 200 和
非空结果,因此这四种界面组合可以启用。查询扩展会调用模型,仍不在初始界面和探测脚本中
启用。
FastGPT 4.15.0 起部署实例的 `/apidoc/devapi` 是接口事实来源,手工文档可能落后。支持矩阵
必须按真实版本测试;不能因为健康页可访问就假定列表和检索 Schema 相同。`searchTest`
@@ -405,17 +449,29 @@ FastGPT 4.15.0 起部署实例的 `/apidoc/devapi` 是接口事实来源,手
检索映射 `question`、`dataset_ids`、`page_size`、`similarity_threshold`、
`vector_similarity_weight`、`knn_top_k`、重排、`use_kg` 和
`include_knowledge_compilation`。不发送已弃用的 `top_k`。响应从 `data.chunks` 和
`data.doc_aggs` 提取片段、文档、位置和原始相似度。
`data.doc_aggs` 提取片段、文档、位置和原始相似度。当前实测非空 chunk 使用
`document_keyword` 表示文档名,并包含 `id/document_id/content/positions/similarity/`
`term_similarity/vector_similarity`;初始 Adapter 应按该字段映射,不假定存在
`document_name`。
图谱规则:
- `parser_config.graphrag` 或等价详情字段作为已有图谱的证据,不写回。
- `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 不生成图谱引用。
当前实例 28 个库中 17 个配置 GraphRAG、15 个存在完成证据。对一个已完成库使用同一查询,
基础检索返回 0 条,`use_kg=true` 返回 1 条且原始相似度为 1,说明图谱开关对实际结果有
影响。当前 28 个库均没有 Knowledge Compilation 配置证据;该请求虽返回 200 和零结果,
界面仍必须禁用开关并显示“当前知识库未配置 Knowledge Compilation”。
## 10. 检索编排
```text
@@ -544,6 +600,39 @@ Adapter 可以保存少量受控事实,例如“目录接口可用”“详情
Mock 或公开文档通过不能代替真实实例验收。
2026-09-09 基线已完成 Dify `0.15.8`/`1.17.0` 目录、详情差异和 1.17 非空检索结构,
FastGPT/RAGFlow 产品版本、严格 TLS、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 读取与
检索端点,不实现或调用远端创建、更新、上传和删除。
### 14.7 CRUD 验收边界
| 对象 | Create | Read | Update | Delete | 验收方式 |
| --- | --- | --- | --- | --- | --- |
| GoodBuddy 外部实例 | 是 | 是 | 是 | 是 | SQLite、凭据存储、IPC 和 Renderer 集成测试 |
| GoodBuddy 外部绑定 | 是 | 是 | 是 | 是 | SQLite、统一知识库列表、检索和删除确认测试 |
| Provider 远端知识库与内容 | 否 | 目录、详情、检索 | 否 | 否 | 探测脚本确认只有只读端点;代码审查确认无写端点 |
FastGPT 等 Provider 的公开 API 确实提供远端知识库、集合和数据 CRUD,但这不属于当前产品
接入。若未来要验证远端 CRUD,必须先修改 PRD,并使用可删除的专用测试实例或命名空间;
不能在本轮三个既有实例上执行破坏性测试。
## 15. 实施顺序
这里的顺序只用于控制代码依赖,不裁剪产品设计:
@@ -258,6 +258,33 @@ Rerank 用量。`
图谱状态”并禁用。Knowledge Compilation 单独显示为 `包含 Knowledge Compilation`,默认
关闭,不把它和旧 GraphRAG 合成同一开关。
### 7.2 实测能力驱动的界面适配
界面不能只按 Provider 名称展示全部开关,必须按实例和当前远端知识库的能力证据处理:
| Provider | 已实测可启用 | 保持隐藏或禁用 | 界面依据 |
| --- | --- | --- | --- |
| 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 列表项应区分三个图谱状态:“未配置”“构建中或未完成”“可用于图谱检索”。不能
仅因 `parser_config.graphrag.use_graphrag = true` 就显示为可用。当前实测中 17 个库配置了
GraphRAG,只有 15 个存在完成证据。
Knowledge Compilation 的请求被服务器接受但当前没有库提供配置证据,因此开关显示为禁用,
说明为“当前知识库未配置 Knowledge Compilation”。返回 200 或零结果都不能改变此状态。
Dify 实例行持续显示探测版本。`0.15.8` 的详情请求返回 405 时显示“此版本从目录读取配置”,
不显示错误状态;`1.17.0` 详情页增加只读能力行:文档可用数、Metadata 字段数、多模态、
摘要索引和 Knowledge Pipeline。没有配置的能力显示“未配置”,不隐藏成未知,也不提供会
写回 Dify 的编辑操作。
Dify 1.x 的 Metadata 条件使用受控字段编辑器:先从 `doc_metadata` 选择字段,再按字段类型
提供比较操作和值输入。当前库字段数为 0 时控件禁用并显示“当前知识库没有 Metadata 字段”。
不得允许自由 JSON,也不得把 `attachment_ids`、Pipeline 或摘要 Prompt 暴露为检索输入。
## 8. 外部知识库详情
选中外部绑定后,现有详情区按来源类型裁剪:
+32 -1
View File
@@ -15,6 +15,36 @@
## 验证证据
2026-09-09 外部知识库适配基础验证:
- 使用本机未入库的测试配置累计执行 107 次只读请求,其中更新后的最终归档脚本执行 19 次;脚本
调试产生的结果只写入批准的临时目录。三套目录、FastGPT 详情和 RAGFlow 详情均成功;
Dify `0.15.8` 详情 GET 返回 405,Dify `1.17.0` 详情 GET 返回 200。验证过程未输出 API
Key、知识库名称和业务正文。
- Dify 响应头确认两个版本为 `0.15.8` 和 `1.17.0`。1.17 默认与显式当前配置检索均返回
3 条非空结果,已验证 `segment/document` 字段;详情新增 Metadata、多模态、摘要索引和
Pipeline 能力字段。FastGPT 与 RAGFlow 响应未提供产品版本。实测还确认
FastGPT 列表 `data` 为直接数组、结果 `a` 可缺失,RAGFlow 文档名字段为
`document_keyword`。详细基线见[外部知识库技术设计 9.0 节](./external-knowledge-technical-design.md#90-真实实例验证基线)。
- 新增 `scripts/external-knowledge-probe.mjs`,可重复生成
不含地址、凭据、远端 ID、名称、查询和正文的能力报告。最终报告归档在
[`external-knowledge-probe-baseline.json`](./external-knowledge-probe-baseline.json)。
- FastGPT 的 embedding、fullTextRecall、mixedRecall 和 mixedRecall + Rerank 均返回非空
结果。RAGFlow 当前 28 个库中 17 个配置 GraphRAG、15 个存在完成证据;已完成库在基础
检索为零结果时,图谱检索返回 1 条结果。当前没有库提供 Knowledge Compilation 配置证据。
- 新增 `src/main/knowledge/external/external-knowledge-client.ts`,固定实现三种 Provider 的
只读目录和检索端点、地址规范化、超时、响应大小限制、HTTP 错误分类和结果标准化。
- `npx vitest run src/main/knowledge/external/external-knowledge-client.test.ts`:1 个测试文件、
5 项测试通过。
- `npx vitest run tests/external-knowledge-probe-report.test.ts src/main/knowledge/external/external-knowledge-client.test.ts`:
2 个测试文件、7 项测试通过;覆盖归档报告脱敏、只读操作和特色能力证据。
- `npx eslint src/main/knowledge/external/external-knowledge-client.ts src/main/knowledge/external/external-knowledge-client.test.ts`:通过。
- `npm run typecheck`:被当前工作区已有的 `src/main/ipc.test.ts:7694` tuple 类型错误阻断;
新增适配器、探测脚本和报告测试的定向测试与 ESLint 已通过。
- 当前 Node 测试环境设置了 `NODE_TLS_REJECT_UNAUTHORIZED=0`,Dify 和 FastGPT 请求实际
使用了放宽的 TLS 校验。产品接入不能继承该进程级默认,保存或测试实例时仍需按技术设计
明确处理传输风险。
2026-09-05 最终验证:
- `npm test`:325 个测试文件、3511 项测试通过;8 个文件、55 项手动或集成测试按既有
@@ -34,7 +64,8 @@
## 剩余工作
- 外部 Dify、FastGPT 和 RAGFlow 知识库接入仍为规划中能力。
- 外部 Dify、FastGPT 和 RAGFlow 已完成 Main 适配基础;实例凭据持久化、绑定迁移、IPC、
Renderer 管理界面、`retrieveMany`、聊天预检索和 MCP 网关仍未接入,当前不能由用户使用。
- 扫描 PDF 的真实 OCR 验收依赖用户已安装并校验的 OCR 模型;本次 DOCX/PDF 回归覆盖
原生文本 PDF,不宣称覆盖扫描件 OCR。
- PDF.js 在测试环境会提示未配置 `standardFontDataUrl`;文本、页码定位与检索结果不受