容错显示

This commit is contained in:
董宏凯 committed 2026-05-18 17:41:51 +08:00
1 parent 836dd73446
commit 5be5531722
17 files changed
+433 -76

No files matched your search

+188
View File
@@ -0,0 +1,188 @@
# 素材专家检索 —— 内网接口对接说明
本文档说明 AI 智能写作中「素材收集专家」的检索机制、内网知识库接口的对接逻辑、
涉及的代码文件,以及常见错误的排查方式。
---
## 1. 检索来源切换
素材专家支持多种检索来源,通过**后端配置文件**统一切换,**无需改动任何代码**。
配置位置:`offline-backend-20260512/backend/config.yaml`
```yaml
ai_writing:
# 检索来源开关:internet=互联网检索(默认)/ intranet=内网知识库 / tavily / demo
search_provider: internet
# 以下两项仅在 search_provider=intranet 时生效
intranet_search_url: http://10.106.57.180:7863/knowledge_base/search_docs_es
intranet_knowledge_base: samples
```
| `search_provider` 取值 | 含义 |
|---|---|
| `internet` | 互联网检索(DuckDuckGo),失败时回退到 demo 演示素材。**当前默认值** |
| `intranet` | 内网知识库检索(调用 `search_docs_es` 接口) |
| `tavily` | Tavily 互联网检索 |
| `demo` | 内置演示素材,仅用于本地调试 |
> 内网接口联通后,把 `search_provider` 改为 `intranet` 即可切换。
> 配置文件支持热加载(`get_app_config()` 按 mtime 自动重载),改完一般无需重启后端。
---
## 2. 内网接口规格
- **地址**:`http://10.106.57.180:7863/knowledge_base/search_docs_es`
- **方法**:`POST`
- **请求体**:
```json
{
"query": "检索关键词",
"knowledge_base_name": "samples",
"ts": "yes"
}
```
- **响应体**:一个数组,每个元素结构如下:
```json
[
{
"page_content": "正文片段",
"metadata": {
"m_title": "标题",
"content": "内容",
"recUuid": "原文id",
"m_publish":"发布时间",
"source1": "来源"
}
}
]
```
我们需要的字段都在 `metadata` 里。
### 字段映射
后端把内网响应映射为统一的 `SearchResult` 结构:
| 内网响应字段 | SearchResult 字段 | 说明 |
|--------------------------|--------------------|------|
| `metadata.m_title` | `title` | 标题,缺省为「(无标题)」 |
| `metadata.content` | `content` | 内容,截断到 500 字 |
| `metadata.source1` | `source` | 来源,缺省为「内网知识库」 |
| `metadata.m_publish` | `published_at` | 发布时间 |
| `metadata.recUuid` | `recUuid` | 原文 id,用于前端拼接「查看原文」链接 |
| — | `url` | 固定为空,原文地址由前端依据 `recUuid` 拼接 |
| — | `relevance_score` | 接口不返回分值,按返回顺序赋递减分以保留 ES 排序 |
| — | `source_type` | 固定为 `"intranet"` |
---
## 3. 对接逻辑与涉及文件
### 后端
| 文件 | 作用 |
|------|------|
| `packages/harness/deerflow/agents/ai_writing/search/intranet_stub.py` | **内网检索适配器** `IntranetSearchProvider`。发起 POST 请求、解析响应、映射为 `SearchResult` |
| `packages/harness/deerflow/agents/ai_writing/search/__init__.py` | `get_search_provider()` 工厂函数,依据 `search_provider` 配置返回对应 provider |
| `packages/harness/deerflow/agents/ai_writing/search/base.py` | `SearchResult` 数据结构 + `BaseSearchProvider` 抽象基类 |
| `packages/harness/deerflow/agents/ai_writing/nodes/researcher.py` | 素材收集专家节点,调用 provider 检索并把结果合并、排序为素材包 |
| `app/gateway/routers/ai_writing.py` | `_ai_writing_config()` 读取 `config.yaml` 的 `ai_writing` 段,经 `_make_config()` 注入运行时 |
| `config.yaml` | `ai_writing` 段,检索来源开关与内网接口地址 |
调用链:
```
ai_writing.py(_make_config 注入 ai_writing_config)
→ researcher_node
→ get_search_provider(ai_writing_config) # 依据 search_provider 选择
→ IntranetSearchProvider.search() # POST search_docs_es
→ SearchResult[] # 字段映射
```
### 前端
| 文件 | 作用 |
|------|------|
| `src/core/config/index.ts` | `INTRANET_SOURCE_URL_BASE` 原文详情页地址前缀 + `buildIntranetSourceUrl()` 构造函数 |
| `src/open-canvas/components/ai-writing/MaterialItem.tsx` | 素材卡片,`recUuid` 非空时显示「查看原文」按钮 |
| `src/open-canvas/components/ai-writing/CompletedInterventionCard.tsx` | 历史回显的素材卡,「原文」链接同样基于 `recUuid` |
| `src/open-canvas/api/ai-writing.ts` | `normalizeMaterial()` 把后端 snake_case 字段转为前端 camelCase(含 `recUuid`) |
「查看原文」链接拼接规则:
```
INTRANET_SOURCE_URL_BASE + recUuid
= https://chlib.b.uat.4.cn/knowlibrary/#/knowledge/mid_detail?recUuid={recUuid}
```
- 仅当素材的 `recUuid` **非空**时才显示「查看原文」按钮。
- 点击直接 `window.open(...)` 新标签页打开,不做站内跳转。
---
## 4. 错误处理与排查
### 4.1 内网检索返回空 / 调不通
**现象**:切到 `intranet` 后,素材专家时间线出现琥珀色告警「ES 波动导致检索异常」,
下方显示真实报错原因;若完全无结果,再提示「未检索到任何素材」。
`IntranetSearchProvider.search()` 对所有异常(连接失败、超时、非 2xx、JSON 解析失败)
都做了兜底:**捕获后记录 warning 日志、把原因收集到 `provider.errors`,并返回空列表**,
不会让整个写作流程崩溃。`researcher_node` 在检索结束后取出 `provider.errors`,
以非致命的 `notice` 事件推送到前端时间线(标题「ES 波动导致检索异常」+ 真实原因),
**接口报错不影响写作流程继续**。
> 容错相关代码:
> - 检索异常收集:`intranet_stub.py` / `duckduckgo_search.py` 的 `errors` 列表
> - 告警事件发出:`researcher.py` 的 `_notice()` 与 `progress_events`
> - 模型调用异常:`researcher.py`(检索词/排序)、`writer_outline.py` / `writer_draft.py` /
> `editor.py` 均会发出「模型错误」事件(致命错误用 `error`,可兜底的用 `notice`)
> - 前端展示:`TimelineStep.tsx` 渲染 `notice`(琥珀)/ `error`(红色)的标题 + 真实原因
排查步骤:
1. **确认网络可达**:在后端所在机器执行
`curl -X POST http://10.106.57.180:7863/knowledge_base/search_docs_es -H "Content-Type: application/json" -d "{\"query\":\"测试\",\"knowledge_base_name\":\"samples\",\"ts\":\"yes\"}"`
2. **查看后端日志**:搜索关键字 `intranet search failed`,会打印具体异常(连接超时 / DNS / 401 等)。
3. **确认配置**:`config.yaml` 的 `intranet_search_url`、`intranet_knowledge_base` 是否正确。
空结果时前端已有兜底:素材确认卡会弹出「未检索到任何素材,是否在无素材的情况下继续写作」,
用户可以选择「重新搜索」或「无数据继续写作」。
### 4.2 字段缺失 / 响应结构变化
`search()` 对每个字段都用 `or` 兜底(如 `m_title` 缺失时回退为「(无标题)」),
单条数据字段缺失不会导致整批失败。
若内网接口改为「数组外包一层 `data`/`results`」的结构,适配器已兼容;
若改成其它结构,需修改 `intranet_stub.py` 中解析 `items` 的逻辑。
### 4.3 「查看原文」按钮不显示
**原因**:该素材的 `recUuid` 为空。这是预期行为 —— 只有内网知识库素材才带 `recUuid`,
互联网检索(`internet`/`tavily`/`demo`)的素材没有 `recUuid`,不显示「查看原文」。
如需让互联网素材也能跳转,需另行调整 `MaterialItem.tsx` 的显示条件。
### 4.4 原文链接打不开 / 地址不对
「查看原文」地址前缀写在 `src/core/config/index.ts` 的 `INTRANET_SOURCE_URL_BASE`。
若内网原文详情页地址有变,只改这一个常量即可。
---
## 5. 联调上线步骤
1. 确认后端机器能访问 `10.106.57.180:7863`。
2. 把 `config.yaml` 的 `ai_writing.search_provider` 改为 `intranet`。
3. 后端配置热加载生效(或重启后端)。
4. 进入 AI 智能写作页面,发起一次写作,观察素材收集专家是否返回内网素材。
5. 校验素材卡片的标题/来源/时间显示正确,「查看原文」能跳到内网详情页。
6. 如检索为空,按 [4.1](#41-内网检索返回空--调不通) 排查。