sync: 鍚屾灞€鍩熺綉鏈€鏂颁唬鐮?(source master @ b69568e4)
This commit is contained in:
1 parent
4f7f07504a
commit
8bc8b0e082
1651 files changed
+278599
-11416
No files matched your search
@@ -0,0 +1,161 @@
|
||||
# 智能体回答评价与用户反馈评分规范
|
||||
|
||||
## 1. 定位
|
||||
|
||||
本功能采集用户对**单条智能体回答**的直接评价,并聚合为智能体的“用户评价分”。
|
||||
|
||||
它回答的是:
|
||||
|
||||
> 用户是否认可这个智能体已经给出的回答,以及负面体验主要集中在哪些方面?
|
||||
|
||||
它不等同于完整的智能体能力评测。工具选择、Skill 增益、执行轨迹、事实基准、成本、稳定性和多智能体协作仍需独立的离线评测或运行指标,不能用本分数替代。
|
||||
|
||||
## 2. 单条回答的评价入口
|
||||
|
||||
每条可评价的最终 AI 回答提供两种选择:
|
||||
|
||||
- `回答优秀`:记为正向评价 `rating = 1`。
|
||||
- `回答不佳`:记为负向评价 `rating = -1`,且必须至少选择一个原因。
|
||||
|
||||
负向原因采用固定枚举:
|
||||
|
||||
| 枚举 | 展示文案 | 统计归属 |
|
||||
|---|---|---|
|
||||
| `incorrect_or_incomplete` | 不正确或不完整 | 正确性、完整性、证据支撑 |
|
||||
| `unmet_expectations` | 与期望不符 | 相关性、可执行性 |
|
||||
| `slow_or_problematic` | 速度慢或存在问题 | 性能/可靠性问题分布 |
|
||||
| `style_or_tone` | 风格或语气 | 表达体验问题分布 |
|
||||
| `safety_or_legal` | 安全或法律疑虑 | 安全合规问题分布 |
|
||||
| `other` | 其他 | 其他问题分布 |
|
||||
|
||||
用户可以补充自由文本详情。正向评价不携带负向原因;同一用户对同一条回答重复提交时更新原评价,不重复计数。
|
||||
|
||||
回答所属智能体必须由服务端根据 `run`、线程和消息记录确定,不能接受前端自行指定的归属。
|
||||
|
||||
## 3. 汇总指标
|
||||
|
||||
设:
|
||||
|
||||
- `P`:正向评价数;
|
||||
- `Q`:负向评价数;
|
||||
- `N = P + Q`:有效评价样本数;
|
||||
- 贝叶斯先验:`alpha = 8`、`beta = 2`,即 10 个等效样本、80 分的中性偏正先验。
|
||||
|
||||
### 3.1 原始任务成功率
|
||||
|
||||
当 `N > 0` 时:
|
||||
|
||||
```text
|
||||
task_success_rate = 100 × P / N
|
||||
```
|
||||
|
||||
这是没有平滑的真实好评率,必须与样本数一起展示。
|
||||
|
||||
### 3.2 用户评价分
|
||||
|
||||
当 `N > 0` 时:
|
||||
|
||||
```text
|
||||
score = 100 × (P + alpha) / (N + alpha + beta)
|
||||
= 100 × (P + 8) / (N + 10)
|
||||
```
|
||||
|
||||
该分数使用贝叶斯平滑,避免只有一两条反馈时直接出现 0 分或 100 分。`N = 0` 时返回 `null`,界面展示“暂无评价”,不得把先验 80 分当作真实成绩。
|
||||
|
||||
### 3.3 置信度
|
||||
|
||||
```text
|
||||
confidence = N / (N + 10)
|
||||
```
|
||||
|
||||
置信度只表示样本量相对先验的充分程度,不表示回答事实正确的概率。
|
||||
|
||||
### 3.4 分数档位
|
||||
|
||||
| 分数 | 档位 | 含义 |
|
||||
|---:|---|---|
|
||||
| `>= 90` | `excellent` | 优秀 |
|
||||
| `>= 80` | `good` | 良好 |
|
||||
| `>= 70` | `needs_attention` | 需要关注 |
|
||||
| `< 70` | `poor` | 较差 |
|
||||
| 无样本 | `insufficient_data` | 数据不足 |
|
||||
|
||||
## 4. 回答质量五维诊断
|
||||
|
||||
回答质量采用以下权重:
|
||||
|
||||
| 维度 | 权重 |
|
||||
|---|---:|
|
||||
| 正确性 `correctness` | 30% |
|
||||
| 完整性 `completeness` | 20% |
|
||||
| 证据支撑 `evidence_grounding` | 20% |
|
||||
| 相关性 `relevance` | 15% |
|
||||
| 可执行性 `actionability` | 15% |
|
||||
|
||||
对任一维度 `d`:
|
||||
|
||||
- 每条正向评价都是该维度的一条正向证据,记为 `S_d = P`;
|
||||
- 只有映射到该维度的负向原因才构成该维度的负向证据,记为 `F_d`;
|
||||
- `E_d = S_d + F_d` 为该维度证据数。
|
||||
|
||||
当 `E_d > 0` 时:
|
||||
|
||||
```text
|
||||
dimension_score_d = 100 × (S_d + 8) / (E_d + 10)
|
||||
```
|
||||
|
||||
没有证据时维度分返回 `null`。不能因为用户选择了“风格或语气”,就虚构一次“正确性”失败。
|
||||
|
||||
五维任务质量分按有证据的维度加权,并对可用权重重新归一化:
|
||||
|
||||
```text
|
||||
task_score = Σ(weight_d × dimension_score_d) / Σ(weight_d for available dimensions)
|
||||
```
|
||||
|
||||
性能、表达、安全和“其他”原因保持为独立问题分布,不混入五维任务质量分,但仍会影响总的用户评价分。
|
||||
|
||||
## 5. 展示要求
|
||||
|
||||
智能体详情至少同时展示:
|
||||
|
||||
- 用户评价分和档位;
|
||||
- 有效样本数;
|
||||
- 原始好评率;
|
||||
- 置信度或明确的低样本提示;
|
||||
- 五维质量诊断;
|
||||
- 各类负向原因数量。
|
||||
|
||||
禁止只展示一个没有样本数和解释的总分。前端文案必须使用“用户评价分”或“回答评价”,不得命名为“智能体综合能力分”。
|
||||
|
||||
## 6. API 约定
|
||||
|
||||
消息级接口:
|
||||
|
||||
```text
|
||||
GET /api/threads/{thread_id}/messages/{message_id}/feedback-context
|
||||
PUT /api/threads/{thread_id}/messages/{message_id}/feedback
|
||||
DELETE /api/threads/{thread_id}/messages/{message_id}/feedback
|
||||
```
|
||||
|
||||
智能体聚合接口:
|
||||
|
||||
```text
|
||||
GET /api/agents/{agent_id}/feedback-score
|
||||
```
|
||||
|
||||
该聚合接口仅允许 `system_role=admin` 的管理员访问;正常鉴权模式下未登录返回 401、普通用户返回 403,关闭鉴权的部署沿用项目统一放行语义。消息级评价接口仍按线程所有权和当前用户隔离。
|
||||
|
||||
原有运行级反馈接口继续兼容,但新界面应优先使用消息级接口,避免前端猜测 `run_id`。
|
||||
|
||||
## 7. 后续完整评测体系
|
||||
|
||||
用户评价分适合生产环境的持续反馈闭环。上线准入和版本对比还应逐步补充:
|
||||
|
||||
- 规则校验与 Ground Truth;
|
||||
- 正确性、引用和幻觉率测试集;
|
||||
- Skill/Tool 选择准确率与调用成功率;
|
||||
- 轨迹效率、异常恢复率和无效步骤率;
|
||||
- Sub-agent 委派、信息保留和交接质量;
|
||||
- Token、延迟、成本、稳定性与回归通过率。
|
||||
|
||||
完整评测应按任务类型和智能体版本保存测试集、运行轨迹及评分,不应把在线赞踩直接解释为上述工程指标。
|
||||
@@ -1,5 +1,36 @@
|
||||
# 登录鉴权扩展接口文档(auth_login)
|
||||
|
||||
## 账号、注册与单点总开关(2026-10-05)
|
||||
|
||||
管理员设置 `GET/PUT /api/system-settings/authentication`,请求和响应是完整对象:
|
||||
|
||||
```json
|
||||
{"password_login_enabled":false,"registration_enabled":false,"sso_enabled":true,"single_session_enabled":false}
|
||||
```
|
||||
|
||||
密码登录页面、自助注册与同账号单处登录默认关闭,外部系统免登默认开启,已有管理员保存的设置不变。至少保留一种登录方式;自助注册要求密码登录开启。无个人密码的存量用户使用原共享口令,未配置则使用 `123ewq`,因此关闭外部系统免登无需先给管理员设个人密码。未登录页面通过 `GET /api/v1/auth/options` 读取同四字段和 `needs_setup`(当前没有管理员),响应禁止缓存。这里只公开开关,不公开共享密码、个人密码或上游密钥。
|
||||
|
||||
| 接口 | 请求 | 成功响应 |
|
||||
| --- | --- | --- |
|
||||
| `POST /api/v1/auth/login/local` | form-urlencoded `username`、`password`;用户名或邮箱 | `UsernameLoginResponse` + HttpOnly Cookie |
|
||||
| `POST /api/v1/auth/register` | JSON `{username,password}`;兼容 `{email,password}`,两种账号字段只传一项 | `201` + `UsernameLoginResponse`,自动登录普通用户 |
|
||||
| `POST /api/v1/auth/initialize` | JSON `{email,password}`;只允许系统没有管理员时 | `201` + `UsernameLoginResponse`,创建首任管理员 |
|
||||
| `GET /api/v1/auth/me` | 当前会话 | `id,email,system_role,needs_setup,has_password,requires_current_password,uses_default_password` |
|
||||
| `POST /api/v1/auth/change-password` | JSON `{current_password,new_password}` | `message,access_token,token_type,expires_in` + 更新 Cookie |
|
||||
| `PUT /api/v1/auth/users/{id}/login-password` | 管理员 JSON `{password}`;也可用 `/reset-password` 别名 | 用户列表项;重置自己时附 `access_token,token_type,expires_in` |
|
||||
|
||||
`UsernameLoginResponse` 为 `access_token,token_type,expires_in,user_id,email,system_role,needs_setup,created,username`。前端收到改密/本人重置成功响应后必须同步替换本地 Bearer,否则会被旧版本 token 拒绝。新增 `auth_usernames` 表用数据库唯一键保护并发新建账号的用户名;新旧域名注册同名也只能成功一次。ORM 自动创建该表,历史用户在新建前按邮箱 local-part 检查。用户名单点在初次查询后发生并发注册/改密时,会再次校验最终账号凭据后才签发会话。
|
||||
|
||||
新密码为 8–255 字符,拒绝常见弱密码和首尾空格;所有用户都必须提供 `current_password`,无个人凭据时用原共享口令(未配置则 `123ewq`)首次设置,不能留空。用户名接受字母、数字、`.`、`_`、`-`,首尾须为字母或数字,也接受有效邮箱。用户名不区分大小写;已存在的相同邮箱 local-part 不得通过换域名重复注册或覆盖。重复返回 `409`,校验失败 `422`,关闭方式或越权返回 `403`,错误密码 `401`(改密码的错误当前密码为 `400`),登录限速 `429`。新注册沿用现有黑名单语义默认放行;已有封禁账号仍被拦截。
|
||||
|
||||
新密码与「登录口令」统一在 `password_hash`、`login_password` 保存同一版本化散列;两种登录入口接受同一密码。历史明文个人口令优先于旧 `password_hash`,可直接用于密码登录、URL 口令登录或验证改密,修改后即改存散列。管理员列表仅对历史明文保留旧兼容,任何散列均不返回,新增 `has_login_password` 表示是否已设置。管理员设置/重置不回显密码;已有密码不得清空。复制新登录链接时使用本次输入,之后需重新输入或重置。修改/重置原子递增 `token_version`,此前全部会话与旧密码失效;并发过期修改返回 `409`,普通审批不能覆写凭据或复活旧会话。
|
||||
|
||||
`sso_enabled=false` 后,用户名直登、上游 token、OAuth 及 `/api/parallel-agents/auth/token` 都拒绝登录。并行面板兼容入口现复用标准用户名登录的密码、限速和封禁校验,可附 `?password=`。关闭自助注册不影响已启用的单点对外部身份自动建普通用户。用户名含 `admin` 不再授予或提升管理员权限;历史已经是管理员的账号不变。首任管理员初始化使用独立单例 claim 与用户插入的同一数据库事务,跨工作进程并发也最多成功一次。部署无需人工增加列,ORM 启动自动创建 `auth_initialization` 表;管理员恢复仍使用 `python -m app.gateway.auth.reset_admin`,同样同步两套凭据。
|
||||
|
||||
启用 `password_login_enabled` 后,用户名/并行面板登录也必须验证个人密码或原共享口令(未配置 `auth_login.username_login.password` 时为 `123ewq`);个人凭据存在时共享口令不能替代。JWT 的 `auth_method=password/passphrase/sso` 仅由实际完成校验的登录路径显式写入。Gateway 与 LangGraph 同时拒绝缺少标记的历史/免密会话,要求重新登录;已验证的上游 SSO 仍受独立 `sso_enabled` 控制。开发环境 `DEER_FLOW_AUTH_DISABLED` 也不能绕过已开启的密码登录或单处登录限制。关闭密码登录时保留旧用户名单点及共享口令门行为;`create_access_token` 不会自动给未经校验的调用添加可信标记。
|
||||
|
||||
「单点登录(同账号仅一处在线)」由独立 `single_session_enabled` 控制,默认关闭;`sso_enabled` 仅表示既有外部单点/免登入口,两个功能分开。开启单处登录后,每次成功的账号密码、用户名口令、上游 token、并行面板、注册或初始化登录,都在数据库内原子递增该账号 `token_version` 后签新 JWT,旧会话立即返回 401,前端轮询 `/me` 后退出旧页面。失败登录不会踢人,关闭时允许多处会话并存;修改/重置密码仍始终撤销旧会话。并发登录通过同一行的原子递增保证最终仅最高版本会话有效;事务还比较已验证的密码/口令与权限快照,避免并发重置密码后旧凭据借新登录版本恢复。
|
||||
|
||||
在原有登录之上新增两种自定义登录模式,均由 `config.yaml` 的 `auth_login` 段控制。
|
||||
|
||||
## 配置
|
||||
@@ -33,7 +64,7 @@ auth_login:
|
||||
|
||||
## 功能一:用户名直登 + 口令门
|
||||
|
||||
在原 `POST /api/v1/auth/login/username` 上叠加一个共享口令校验。`require_password=false`(默认)时行为完全不变(免密直登 + 自动注册)。
|
||||
此前提为系统设置的 `sso_enabled=true`。账号已设置个人密码/口令时,始终校验该凭据;未设置个人凭据时,开启账号密码登录或 `require_password` 任一开关都会要求共享口令(未配置则为 `123ewq`)。
|
||||
|
||||
### 请求
|
||||
|
||||
@@ -44,8 +75,8 @@ Content-Type: application/json
|
||||
{ "username": "alice" }
|
||||
```
|
||||
|
||||
- `?password=`:仅当 `require_password=true` 时需要。值需等于 `username_login.password`;该项留空时默认口令为 `123ewq`。
|
||||
- 口令门是**单一共享口令**,只决定"放不放行",不影响登录成的是哪个账号。
|
||||
- `?password=`:账号已有个人密码/口令时必须匹配个人凭据;否则仅当 `require_password=true` 时需要匹配共享口令。
|
||||
- 共享口令仅适用于没有个人凭据的账号,不影响目标账号选择。
|
||||
- 校验失败按 IP 计入登录限速(5 次/5 分钟锁定),与本地密码登录共用同一限速器。
|
||||
|
||||
### 响应
|
||||
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
# 商业情报监测接口、技能与业务链说明
|
||||
|
||||
## 组成
|
||||
|
||||
本功能全部使用项目级自定义资源:
|
||||
|
||||
- 自定义任务详情技能:`fs-task-detail-info`
|
||||
- 自定义接口技能:`fs-timeline-info`、`fs-insight-info`、`fs-target-info`、`fs-connection-info`
|
||||
- 当前判断独立接口技能:`fs-current-judgment-info`
|
||||
- 自定义摘要技能:`fs-brief-info`
|
||||
- 自定义编排技能:`fs-collect`
|
||||
- 自定义业务链:`biz-intel-chain`
|
||||
- 自定义页面助理:`biz-detail-assistant`,显示名“商业QB助理”
|
||||
|
||||
这些资源不写入内置技能或内置智能体目录。接口技能脚本第一行均为对应生产接口地址。
|
||||
|
||||
## 本地模拟接口
|
||||
|
||||
```powershell
|
||||
cd F:\react01\deeflow-code\deerflow-server\offline-backend-20260512\backend
|
||||
./devtools/mock_monitor_api/start-mock.ps1
|
||||
```
|
||||
|
||||
新开一个 PowerShell,设置:
|
||||
|
||||
```powershell
|
||||
$env:MONITOR_API_BASE_URL = "http://127.0.0.1:18080/consumer"
|
||||
$env:MONITOR_AUTHORIZATION = "admin"
|
||||
```
|
||||
|
||||
若要让定时业务链也访问模拟接口,需要在**启动 DeerFlow Gateway 的同一环境**中设置以上
|
||||
两个环境变量,并保持模拟服务运行;只在另一个终端设置不会改变已经启动的 Gateway 进程。
|
||||
|
||||
模拟接口校验 `Authorization: admin`,提供任务详情、时间节点、热点/威胁/战机、实体及
|
||||
打击/塑势衔接任务。任务名作为严格关键词时先返回空数组,用来验证技能会逐步扩大时间、
|
||||
移除关键词并最终命中。
|
||||
|
||||
## 采集命令
|
||||
|
||||
只采集和转换,不写入:
|
||||
|
||||
```powershell
|
||||
python skills/custom/fs-collect/scripts/fs_collect.py `
|
||||
--group source --task-id 3 --dry-run
|
||||
```
|
||||
|
||||
采集并原子合并到态势详情:
|
||||
|
||||
```powershell
|
||||
python skills/custom/fs-collect/scripts/fs_collect.py `
|
||||
--group source --task-id 003
|
||||
```
|
||||
|
||||
当前判断写入后生成情报摘要:
|
||||
|
||||
```powershell
|
||||
python skills/custom/fs-collect/scripts/fs_collect.py `
|
||||
--group judgment --task-id 003
|
||||
|
||||
python skills/custom/fs-collect/scripts/fs_collect.py `
|
||||
--group summary --task-id 003
|
||||
```
|
||||
|
||||
脚本逐次输出接口方法、地址、Authorization 状态、查询参数或请求体、原始返回和放宽原因;
|
||||
最后一行输出机器可读 JSON。
|
||||
|
||||
## 关键字段
|
||||
|
||||
- 时间节点:页面字段外保留 `recUuid` 和全部原始时间字段。
|
||||
- 三类事件:保留接口 `id`,并复制为 `eventId` 供实体查询和追溯。
|
||||
- 当前判断:独立调用 `eventMonitorByTime`,固定 `discriminant=威胁`,保留 `id/eventId`。
|
||||
- 重要目标:保留 `eid/etype/text/imagePath/num/label/reason/score`。
|
||||
- 衔接任务:标识字段是 `taskId`,不生成 `id`;类型只有 `打击/塑势`。
|
||||
- 情报摘要:`sourceRefs` 保留所依据的事件 id、eid、recUuid、taskId 和判断 id。
|
||||
|
||||
## 自定义智能体与业务链注册
|
||||
|
||||
Gateway 启动后执行:
|
||||
|
||||
```powershell
|
||||
python scripts/setup_force_situation_chain.py
|
||||
python scripts/setup_biz_detail_assistant.py
|
||||
```
|
||||
|
||||
业务链安装脚本默认只更新配置,不立即触发真实接口。需要立即执行时显式追加
|
||||
`--trigger`。链路阶段为:接口数据采集 → 当前判断威胁接口采集 → 情报摘要与校验。
|
||||
|
||||
## 测试
|
||||
|
||||
```powershell
|
||||
python -m pytest tests/test_force_situation_monitor_skills.py -q
|
||||
```
|
||||
@@ -49,7 +49,6 @@ models:
|
||||
use: deerflow.models.openai_codex_provider:CodexChatModel
|
||||
model: gpt-5.4
|
||||
supports_thinking: true
|
||||
supports_reasoning_effort: true
|
||||
|
||||
- name: claude-sonnet-4.6
|
||||
display_name: Claude Sonnet 4.6 (Claude Code OAuth)
|
||||
@@ -59,6 +58,15 @@ models:
|
||||
supports_thinking: true
|
||||
```
|
||||
|
||||
Whether a model exposes the per-chat reasoning-effort selector is managed by
|
||||
an administrator in **Model Management** and persisted in the
|
||||
`model_capabilities` database table. For upgrade compatibility, a legacy
|
||||
`supports_reasoning_effort: true` value is imported once at startup only when
|
||||
that model has no database row. Existing database values (including `false`)
|
||||
are never overwritten; after that first import the database is authoritative.
|
||||
Remove the legacy YAML field when convenient. Model Management also removes it
|
||||
the next time the model list is saved, and new configurations should not add it.
|
||||
|
||||
**Auth behavior for CLI-backed providers**:
|
||||
- `CodexChatModel` loads Codex CLI auth from `~/.codex/auth.json`
|
||||
- The Codex Responses endpoint currently rejects `max_tokens` and `max_output_tokens`, so `CodexChatModel` does not expose a request-level token cap
|
||||
@@ -123,6 +131,27 @@ models:
|
||||
|
||||
If your OpenRouter key lives in a different environment variable name, point `api_key` at that variable explicitly (for example `api_key: $OPENROUTER_API_KEY`).
|
||||
|
||||
**vLLM (per-model, do not change other entries)**:
|
||||
|
||||
`use` is per model. SiliconFlow / DeepSeek entries should stay on `langchain_openai:ChatOpenAI`. For a self-hosted vLLM reasoning model (thinking may arrive as `reasoning` on newer vLLM, or `reasoning_content` on older builds), set only that one entry to `VllmChatModel`:
|
||||
|
||||
```yaml
|
||||
models:
|
||||
- name: qwen3.6
|
||||
display_name: qwen3.6
|
||||
use: deerflow.models.vllm_provider:VllmChatModel
|
||||
model: qwen3.6
|
||||
api_key: $VLLM_API_KEY
|
||||
base_url: http://127.0.0.1:8001/v1
|
||||
supports_thinking: true
|
||||
when_thinking_enabled:
|
||||
extra_body:
|
||||
chat_template_kwargs:
|
||||
enable_thinking: true
|
||||
```
|
||||
|
||||
The default `langchain_openai:ChatOpenAI` path also maps both thinking field names onto the UI's `reasoning_content`. Use `VllmChatModel` when follow-up turns must echo vLLM's original `reasoning` field.
|
||||
|
||||
**Thinking Models**:
|
||||
Some models support "thinking" mode for complex reasoning:
|
||||
|
||||
@@ -270,21 +299,21 @@ skills:
|
||||
# Host path (optional, default: ../skills)
|
||||
path: /custom/path/to/skills
|
||||
|
||||
# Container mount path (default: /mnt/skills)
|
||||
container_path: /mnt/skills
|
||||
# Optional system-prompt routing rule for the es_query skill
|
||||
es_query_routing:
|
||||
enabled: true
|
||||
prompt: |
|
||||
当用户的问题需要基于 Elasticsearch Query DSL 灵活构造、调整或执行查询时,优先使用 `es_query` 技能。
|
||||
执行前先读取该技能的 SKILL.md,并严格遵循其中的流程和参数。
|
||||
```
|
||||
|
||||
`skills.es_query_routing.enabled` controls whether the routing rule is injected
|
||||
into the overall agent system prompt. Edit `prompt` to tune the matching boundary
|
||||
or invocation requirements. When disabled (the schema default), the block is not
|
||||
injected. Custom agents receive it only when their explicit skill allowlist
|
||||
contains `es_query`.
|
||||
# Container mount path (default: /mnt/skills)
|
||||
container_path: /mnt/skills
|
||||
# Optional system-prompt routing rule for the es_query skill
|
||||
es_query_routing:
|
||||
enabled: true
|
||||
prompt: |
|
||||
当用户的问题需要基于 Elasticsearch Query DSL 灵活构造、调整或执行查询时,优先使用 `es_query` 技能。
|
||||
执行前先读取该技能的 SKILL.md,并严格遵循其中的流程和参数。
|
||||
```
|
||||
|
||||
`skills.es_query_routing.enabled` controls whether the routing rule is injected
|
||||
into the overall agent system prompt. Edit `prompt` to tune the matching boundary
|
||||
or invocation requirements. When disabled (the schema default), the block is not
|
||||
injected. Custom agents receive it only when their explicit skill allowlist
|
||||
contains `es_query`.
|
||||
|
||||
**How Skills Work**:
|
||||
- Skills are stored in `deer-flow/skills/{public,custom}/`
|
||||
|
||||
@@ -6,11 +6,14 @@ DeerFlow 后端提供了完整的文件上传功能,支持多文件上传,
|
||||
|
||||
## 功能特性
|
||||
|
||||
- ✅ 支持多文件同时上传
|
||||
- ✅ 可选地转换文档为 Markdown(PDF、PPT、Excel、Word)
|
||||
- ✅ 文件存储在线程隔离的目录中
|
||||
- ✅ Agent 自动感知已上传的文件
|
||||
- ✅ 支持文件列表查询和删除
|
||||
- ✅ 支持多文件同时上传,默认单条消息最多 20 个
|
||||
- ✅ 上传策略持久化到数据库,管理员可在「设置 → 上传设置」实时调整
|
||||
- ✅ 按文件类别设置格式与单文件上限,并校验扩展名和文件真实签名
|
||||
- ✅ 可选地转换文档为 Markdown(PDF、PPT、Excel、Word)
|
||||
- ✅ 文件存储在线程隔离的目录中
|
||||
- ✅ Agent 自动感知已上传的文件
|
||||
- ✅ 大文件采用流式统计和头部/中段/尾部采样,不把全文直接塞入模型上下文
|
||||
- ✅ 支持文件列表查询和删除
|
||||
|
||||
## API 端点
|
||||
|
||||
@@ -22,7 +25,15 @@ POST /api/threads/{thread_id}/uploads
|
||||
**请求体:** `multipart/form-data`
|
||||
- `files`: 一个或多个文件
|
||||
|
||||
网关会在应用层限制上传规模,默认最多 10 个文件、单文件 50 MiB、单次请求总计 100 MiB。可通过 `config.yaml` 的 `uploads.max_files`、`uploads.max_file_size`、`uploads.max_total_size` 调整;前端会读取同一组限制并在选择文件时提示,超过限制时后端返回 `413 Payload Too Large`。
|
||||
网关每次上传都会读取数据库中的全局上传策略;前端也读取同一策略并在选择文件时预校验。内置默认值如下:
|
||||
|
||||
| 类别 | 默认格式 | 默认单文件上限 |
|
||||
|---|---|---:|
|
||||
| 文档/文本 | `.txt`、`.md`、`.doc`、`.docx`、`.ppt`、`.pptx`、`.pdf` | 10 MiB |
|
||||
| 图片 | `.jpg`、`.jpeg`、`.png` | 10 MiB |
|
||||
| 表格 | `.xls`、`.xlsx`、`.csv` | 5 MiB |
|
||||
|
||||
单条消息默认最多 20 个文件、附件合计默认不超过 100 MiB。管理员可在「设置 → 上传设置」调整数量、总量、各类别格式和大小,以及大文件采样阈值与输出字符预算。服务端始终执行最终校验;超过限制返回 `413 Payload Too Large`,不支持或内容签名不匹配返回 `415 Unsupported Media Type`。整批文件先写入暂存目录并全部验证,任一文件失败时不会留下半批结果。
|
||||
|
||||
**响应:**
|
||||
```json
|
||||
@@ -35,10 +46,10 @@ POST /api/threads/{thread_id}/uploads
|
||||
"path": ".deer-flow/threads/{thread_id}/user-data/uploads/document.pdf",
|
||||
"virtual_path": "/mnt/user-data/uploads/document.pdf",
|
||||
"artifact_url": "/api/threads/{thread_id}/artifacts/mnt/user-data/uploads/document.pdf",
|
||||
"markdown_file": "document.md",
|
||||
"markdown_path": ".deer-flow/threads/{thread_id}/user-data/uploads/document.md",
|
||||
"markdown_virtual_path": "/mnt/user-data/uploads/document.md",
|
||||
"markdown_artifact_url": "/api/threads/{thread_id}/artifacts/mnt/user-data/uploads/document.md"
|
||||
"markdown_file": "document.pdf.md",
|
||||
"markdown_path": ".deer-flow/threads/{thread_id}/user-data/uploads/document.pdf.md",
|
||||
"markdown_virtual_path": "/mnt/user-data/uploads/document.pdf.md",
|
||||
"markdown_artifact_url": "/api/threads/{thread_id}/artifacts/mnt/user-data/uploads/document.pdf.md"
|
||||
}
|
||||
],
|
||||
"message": "Successfully uploaded 1 file(s)"
|
||||
@@ -50,23 +61,62 @@ POST /api/threads/{thread_id}/uploads
|
||||
- `virtual_path`: Agent 在沙箱中使用的虚拟路径
|
||||
- `artifact_url`: 前端通过 HTTP 访问文件的 URL
|
||||
|
||||
### 2. 查询上传限制
|
||||
```
|
||||
GET /api/threads/{thread_id}/uploads/limits
|
||||
```
|
||||
|
||||
返回网关当前生效的上传限制,供前端在用户选择文件前提示和拦截。
|
||||
### 2. 查询和管理上传策略
|
||||
|
||||
所有已登录用户都可读取当前策略:
|
||||
|
||||
```
|
||||
GET /api/upload-policy
|
||||
```
|
||||
|
||||
管理员原子替换或恢复默认策略:
|
||||
|
||||
```
|
||||
PUT /api/upload-policy
|
||||
POST /api/upload-policy/reset
|
||||
```
|
||||
|
||||
策略首次启动时会将内置默认值写入数据库表 `upload_policy`,后续修改带递增 `revision` 和更新人/更新时间审计信息。各 Gateway worker 在读取或修改策略时同步刷新 harness 运行时副本。
|
||||
|
||||
兼容旧前端的线程级限制查询仍保留:
|
||||
|
||||
```
|
||||
GET /api/threads/{thread_id}/uploads/limits
|
||||
```
|
||||
|
||||
返回网关当前生效的分类限制、总量限制、大文件策略和策略版本。
|
||||
|
||||
**响应:**
|
||||
```json
|
||||
{
|
||||
"max_files": 10,
|
||||
"max_file_size": 52428800,
|
||||
"max_total_size": 104857600
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 列出已上传文件
|
||||
"max_files": 20,
|
||||
"max_file_size": 10485760,
|
||||
"max_total_size": 104857600,
|
||||
"max_files_per_message": 20,
|
||||
"max_total_bytes": 104857600,
|
||||
"groups": {
|
||||
"document": {"max_bytes": 10485760, "extensions": [".txt", ".md", ".doc", ".docx", ".ppt", ".pptx", ".pdf"]},
|
||||
"image": {"max_bytes": 10485760, "extensions": [".jpg", ".jpeg", ".png"]},
|
||||
"spreadsheet": {"max_bytes": 5242880, "extensions": [".xls", ".xlsx", ".csv"]}
|
||||
},
|
||||
"large_file": {
|
||||
"threshold_bytes": 1048576,
|
||||
"max_result_chars": 12000,
|
||||
"hard_max_result_chars": 32000
|
||||
},
|
||||
"revision": 1
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 有界文本预览
|
||||
|
||||
```
|
||||
GET /api/threads/{thread_id}/artifact-preview?path=/mnt/user-data/uploads/example.txt&max_chars=12000
|
||||
```
|
||||
|
||||
小文件返回有界完整文本;大文件返回流式统计、开头/中段/结尾的确定性分块采样、截断标记和警告。浏览器预览不会调用无界的 `response.text()`。
|
||||
|
||||
### 4. 列出已上传文件
|
||||
```
|
||||
GET /api/threads/{thread_id}/uploads/list
|
||||
```
|
||||
@@ -89,7 +139,7 @@ GET /api/threads/{thread_id}/uploads/list
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 删除文件
|
||||
### 5. 删除文件
|
||||
```
|
||||
DELETE /api/threads/{thread_id}/uploads/{filename}
|
||||
```
|
||||
@@ -111,7 +161,7 @@ DELETE /api/threads/{thread_id}/uploads/{filename}
|
||||
- Word (`.doc`, `.docx`)
|
||||
- OpenDocument (`.odt`, `.ods`, `.odp`)
|
||||
|
||||
转换后的 Markdown 文件会保存在同一目录下,文件名为原文件名 + `.md` 扩展名。
|
||||
转换后的 Markdown 文件会保存在同一目录下,文件名为完整原文件名再追加 `.md`,例如 `report.docx.md`。这样 `report.docx` 和 `report.pdf` 不会同时覆盖同一个 `report.md`。前端预览仍兼容升级前的旧侧车名称。
|
||||
|
||||
通用部署可以关闭自动转换,以避免在网关主机上对不受信任的 Office/PDF 上传执行解析。本仓库当前 `config.yaml` 和纯 DeerFlow 离线部署包默认设置为 `uploads.auto_convert_documents: true`,用于受信任的内网部署。
|
||||
|
||||
@@ -121,19 +171,13 @@ DELETE /api/threads/{thread_id}/uploads/{filename}
|
||||
|
||||
Agent 在每次请求时会自动收到已上传文件的列表,格式如下:
|
||||
|
||||
```xml
|
||||
<uploaded_files>
|
||||
The following files have been uploaded and are available for use:
|
||||
|
||||
- document.pdf (1.2 MB)
|
||||
Path: /mnt/user-data/uploads/document.pdf
|
||||
|
||||
- document.md (45.3 KB)
|
||||
Path: /mnt/user-data/uploads/document.md
|
||||
|
||||
You can read these files using the `read_file` tool with the paths shown above.
|
||||
</uploaded_files>
|
||||
```
|
||||
```text
|
||||
<uploaded_files>
|
||||
{经过 JSON 转义和字符预算裁剪的文件元数据、统计与采样摘要}
|
||||
</uploaded_files>
|
||||
```
|
||||
|
||||
当前轮文件可附带短摘要;历史轮次只注入元数据;自动生成的 Markdown 侧车不会作为第二个独立附件重复注入。整个 `<uploaded_files>` 块受数据库策略中的常规/硬字符预算约束。
|
||||
|
||||
### 使用上传的文件
|
||||
|
||||
@@ -144,8 +188,10 @@ Agent 在沙箱中运行,使用虚拟路径访问文件。Agent 可以直接
|
||||
read_file(path="/mnt/user-data/uploads/document.pdf")
|
||||
|
||||
# 读取转换后的 Markdown(推荐)
|
||||
read_file(path="/mnt/user-data/uploads/document.md")
|
||||
```
|
||||
read_file(path="/mnt/user-data/uploads/document.pdf.md")
|
||||
```
|
||||
|
||||
当文本达到大文件阈值时,`read_file` 不会一次性返回全文,而会逐块读取并产生:总字节数/行数、空行和乱码统计、最长行、内容哈希,以及开头/中段/结尾样本。需要进一步核实时应使用明确的行区间或有界 `grep`。超长单行也按固定大小片段处理,内存占用不会随单行长度无限增长。
|
||||
|
||||
**路径映射关系:**
|
||||
- Agent 使用:`/mnt/user-data/uploads/document.pdf`(虚拟路径)
|
||||
@@ -217,18 +263,20 @@ backend/.deer-flow/threads/
|
||||
└── user-data/
|
||||
└── uploads/
|
||||
├── document.pdf # 原始文件
|
||||
├── document.md # 转换后的 Markdown
|
||||
├── presentation.pptx
|
||||
├── presentation.md
|
||||
├── document.pdf.md # 转换后的 Markdown
|
||||
├── presentation.pptx
|
||||
├── presentation.pptx.md
|
||||
└── ...
|
||||
```
|
||||
|
||||
## 限制
|
||||
|
||||
- 最大文件大小:100MB(可在 nginx.conf 中配置 `client_max_body_size`)
|
||||
- 文件名安全性:系统会自动验证文件路径,防止目录遍历攻击
|
||||
- 线程隔离:每个线程的上传文件相互隔离,无法跨线程访问
|
||||
- 自动文档转换默认关闭;如需启用,需在 `config.yaml` 中显式设置 `uploads.auto_convert_documents: true`
|
||||
- 数据库策略可配置范围带服务端硬上限:文件数最多 100、单文件最多 100 MiB、单次总量最多 200 MiB;反向代理的 `client_max_body_size` 还必须不低于所需请求大小
|
||||
- 文件名安全性:系统会自动验证文件路径,防止目录遍历攻击
|
||||
- 文件真实性:PDF、JPG、PNG、OLE 与 OOXML 会校验签名/容器结构,OOXML 同时限制条目数、解压总量和压缩比
|
||||
- 线程隔离:每个线程的上传文件相互隔离,无法跨线程访问
|
||||
- 自动文档转换由 `uploads.auto_convert_documents` 控制;转换在事件循环外执行、限制并发,提取的 Markdown 还有固定字符上限
|
||||
- 模型安全硬边界:单次文件摘要/读取结果最多 32,000 字符,图片还会校验像素数并生成受限尺寸派生图
|
||||
|
||||
## 技术实现
|
||||
|
||||
@@ -314,9 +362,7 @@ async function listFiles(threadId: string) {
|
||||
|
||||
### 扩展功能建议
|
||||
|
||||
1. **文件预览**:添加预览端点,支持在浏览器中直接查看文件
|
||||
2. **批量删除**:支持一次删除多个文件
|
||||
3. **文件搜索**:支持按文件名或类型搜索
|
||||
4. **版本控制**:保留文件的多个版本
|
||||
5. **压缩包支持**:自动解压 zip 文件
|
||||
6. **图片 OCR**:对上传的图片进行 OCR 识别
|
||||
1. **批量删除**:支持一次删除多个文件
|
||||
2. **文件搜索**:支持按文件名或类型搜索
|
||||
3. **版本控制**:保留文件的多个版本
|
||||
4. **图片 OCR**:对上传的图片进行 OCR 识别
|
||||
+502
@@ -0,0 +1,502 @@
|
||||
# 知识空间优先检索、Wiki 扩展检索与全库摘要实现方案
|
||||
|
||||
> 状态:P0/P1 与普通 Wiki 的 P2 主链路已落地;助手关系图扩展和 P3 待实施
|
||||
> 适用范围:普通 WeKnora/LLMWiki 知识库、助手知识库、通用问答与智能体问答
|
||||
> 更新日期:2026-09-16
|
||||
|
||||
## 1. 结论
|
||||
|
||||
本次改造不应只是“向量搜不到时再补一个 LIKE”。需要同时修复检索范围、索引可用性判断、召回策略和全库认知四个层面:
|
||||
|
||||
1. **勾选任一知识空间后,本轮进入严格知识空间模式**:只允许检索已选普通/助手知识空间,不调用 `web_search`、`web_fetch`、图片搜索、外部检索技能或检索型 MCP;非检索工具仍可使用。知识空间没有结果时必须明确报告原因,不能偷偷转向互联网。
|
||||
2. **把普通知识库和助手知识库统一成一个检索入口**:预检索和智能体二次检索必须覆盖同一批已选空间,使用同一套权限、诊断、引用和排序协议。
|
||||
3. **保留最后一次成功索引继续服务**:一次增量同步失败或取消,不能让已经完整生成的向量立刻整体失效。
|
||||
4. **默认采用混合召回**:向量、关键词/全文、标题/别名精确命中并行执行,经 RRF 融合;向量不可用或低置信时关键词结果仍可回答,但响应必须附带 `fallback_reason`,以便继续修复向量根因。
|
||||
5. **利用 Wiki 图做有界扩展**:在首批命中页面上沿 `InLinks`、`OutLinks`、实体关系和目录父子关系扩展 1 跳,确有多跳意图或首轮低置信时再扩展第 2 跳,然后统一重排,而不是把整张图塞进上下文。
|
||||
6. **为每个知识空间维护“全库摘要”**:系统持续生成草稿,用户拥有可编辑的发布版本;系统更新不能覆盖人工修改。回答“这个库是干什么的/都讲了什么”时优先读取该摘要,并附代表性页面引用。
|
||||
|
||||
优先落地顺序是:**P0 可观测性与索引保活 → P1 严格范围与混合召回 → P2 Wiki 图扩展 → P3 全库摘要维护 → P4 重排与质量评测**。
|
||||
|
||||
## 2. 已确认的现状与问题
|
||||
|
||||
### 2.1 当前请求链路
|
||||
|
||||
当前前端已经把两类选择写入线程 metadata 和运行 context:
|
||||
|
||||
- `llmwiki_knowledge_base_ids`
|
||||
- `assistant_knowledge_base_ids`
|
||||
|
||||
Gateway 的 `app/gateway/llmwiki_rag.py::build_llmwiki_rag_context` 会在模型调用前做一次检索,并把最多 8 条资料放进隐藏提示和引用列表。普通知识库走 `WikiRetrievalService`,助手知识库走 `AssistantKnowledgeRepository.search_pages`。
|
||||
|
||||
但现有实现存在四个断层:
|
||||
|
||||
| 断层 | 当前行为 | 影响 |
|
||||
|---|---|---|
|
||||
| 工具范围 | `services.py` 只在未选择普通知识库时排除 `llmwiki_search`,选择知识空间时没有排除 Web/外部检索 | 模型仍可能越过已选空间调用互联网或其他检索 |
|
||||
| 二次检索 | `llmwiki_search_tool.py` 只读取 `llmwiki_knowledge_base_ids` | 助手知识库只参与一次预检索,智能体无法围绕它继续分解查询 |
|
||||
| 普通 Wiki 召回 | 完整索引只做向量;远端回退按用户原句查询 Wiki 页面列表 | 同义表达、宽泛问题、精确专名都可能漏召回 |
|
||||
| Wiki 图 | 本地镜像只保存 `parent_slug`,`normalize.py` 没有保留 `InLinks` / `OutLinks` | 数据虽然 Wiki 化,检索仍是孤立页面向量,没有利用页面关系扩展 |
|
||||
|
||||
### 2.2 已确认的向量漏检机制
|
||||
|
||||
普通 Wiki 只有同时满足以下条件才会进入本地向量检索:
|
||||
|
||||
```text
|
||||
sync state == idle
|
||||
AND last_success_at 非空
|
||||
AND stored embedding_fingerprint == 当前编码器 fingerprint
|
||||
AND ready_page_count == local_page_count
|
||||
AND failed_page_count == 0
|
||||
AND(空库 OR vector_count > 0)
|
||||
```
|
||||
|
||||
对应代码是 `local_index/retrieval.py::is_fully_vectorized`。这个判断把“最新同步是否成功”和“最后一个成功索引是否仍可服务”混成了一个状态。
|
||||
|
||||
2026-09-11 本地 SQLite 快照及 2026-09-07 后端日志已经出现以下实际情况:
|
||||
|
||||
| 库状态样例 | 页面/向量 | 当前结果 |
|
||||
|---|---:|---|
|
||||
| `对话沉淀` | 124 页 / 281 向量 | 最新状态 `failed: Sync cancelled`,已有向量被整体排除,退回 Wiki API |
|
||||
| `111` | 13 页 / 36 向量 | 同样因同步取消不使用已有向量 |
|
||||
| `admin的知识库` | 0 页 / 0 向量 | 远端知识库映射持续返回 404,确实不可检索 |
|
||||
| 两个空/源库 | 0 页 / 0 向量 | Wiki 页面和向量都不存在,属于真实空结果 |
|
||||
| 三个验收/图片库 | 19 页 / 101 向量 | `idle` 且指纹一致,可走本地向量 |
|
||||
|
||||
这证明目前至少存在三类“搜不到”,必须分别呈现:
|
||||
|
||||
1. **数据真空**:没有可检索正文页或向量;
|
||||
2. **索引存在但被状态门禁禁用**:最后一次增量同步失败,旧成功快照也不能服务;
|
||||
3. **映射失效**:DeerFlow 本地记录指向已删除或无权访问的 WeKnora 库。
|
||||
|
||||
还存在一个独立的召回截断:`LocalWikiIndexConfig.min_similarity` 默认是 `0.35`,`search.py` 在此阈值之前直接丢弃候选。`keyword_boost_enabled` 默认关闭;即使开启,它也只给已超过向量阈值的候选加分,并不是关键词兜底。因此,只要所有向量分数低于 0.35,结果就是 0。
|
||||
|
||||
助手知识库已有关键词回退,但只有当前编码器指纹、维度和来源的 `last_import_job_id` 同时匹配时才会走向量。当前快照中也存在 active 来源为 0 条向量的情况。现有返回协议没有说明某个来源究竟是“无向量、指纹不符、最新导入不匹配”还是“向量低于阈值”。
|
||||
|
||||
### 2.3 “Wiki 已生成”不等于“可向量检索”
|
||||
|
||||
完整链路实际是:
|
||||
|
||||
```text
|
||||
原始文件存在
|
||||
→ WeKnora Wiki 异步生成完成
|
||||
→ DeerFlow 成功拉取所有 Wiki 正文页
|
||||
→ 正文成功切段
|
||||
→ 使用当前编码器生成向量
|
||||
→ 指纹/维度与查询编码器一致
|
||||
→ 最后成功索引快照被激活
|
||||
→ 查询分数超过阈值
|
||||
```
|
||||
|
||||
任一步失败都可能出现“页面在 Wiki 管理里看得到,但聊天搜不到”。后续 UI 和接口必须展示每一层的数量与状态,不能只显示一个模糊的“已 Wiki 化”。
|
||||
|
||||
## 3. 外部开源方案调研与选型
|
||||
|
||||
### 3.1 可借鉴的项目
|
||||
|
||||
1. **腾讯 WeKnora(优先复用)**
|
||||
|
||||
当前上游已经把 Wiki 页面组织成有向图,页面包含 `InLinks` / `OutLinks`,并提供 `/wiki/graph`、`/wiki/search`、页面编辑、版本历史和回滚接口。上游说明还明确区分 `summary`、`entity`、`concept`、`index` 等页面类型。参考:[WeKnora Wiki 官方文档](https://github.com/Tencent/WeKnora/blob/main/website-docs/03-features/14-wiki.md)、[WeKnora 官方仓库](https://github.com/Tencent/WeKnora)。
|
||||
|
||||
2. **HippoRAG / HippoRAG 2(Wiki 扩展检索算法参考)**
|
||||
|
||||
HippoRAG 将稠密召回、知识图和 Personalized PageRank 组合起来,目标就是跨文档关联与多跳检索。这与“先命中 Wiki 页面,再沿关联页面扩展”的需求最接近。参考:[HippoRAG 官方仓库](https://github.com/OSU-NLP-Group/HippoRAG)、[NeurIPS 2024 论文](https://proceedings.neurips.cc/paper_files/paper/2024/file/6ddc001d07ca4f319af96a3024f6dbd1-Paper-Conference.pdf)。
|
||||
|
||||
3. **Microsoft GraphRAG(全库摘要参考)**
|
||||
|
||||
GraphRAG 的知识模型包含实体、关系、层级社区和 Community Report;Global Search 基于社区报告做全局问题汇总。这适合借鉴到“这个知识库整体讲什么”,但完整 GraphRAG 索引成本较高,不建议当前直接替换现有 WeKnora/本地向量链路。参考:[官方索引数据流](https://github.com/microsoft/graphrag/blob/main/docs/index/default_dataflow.md)、[官方 Global Search 示例](https://microsoft.github.io/graphrag/examples_notebooks/global_search/)。
|
||||
|
||||
4. **LightRAG(备选实现参考)**
|
||||
|
||||
LightRAG 使用图结构与向量的双层检索,并强调增量更新。它可作为独立图检索服务的备选,但当前系统已经有 WeKnora Wiki 图和助手库实体关系,再引入一套完整存储会造成双份索引、权限和一致性成本。参考:[LightRAG 官方仓库](https://github.com/HKUDS/LightRAG)。
|
||||
|
||||
### 3.2 选型结论
|
||||
|
||||
第一阶段**不整体引入 GraphRAG、HippoRAG 或 LightRAG 运行时**,而是:
|
||||
|
||||
- 复用 WeKnora 已生成的 Wiki 页面、链接图和现有 DeerFlow 助手知识图;
|
||||
- 采用 HippoRAG 的“检索种子 + PPR/图扩散”思想实现轻量有界扩展;
|
||||
- 采用 GraphRAG 的“分层摘要/全局摘要”思想维护全库摘要;
|
||||
- 保留一个实验适配器,后续用离线评测决定是否接入完整框架。
|
||||
|
||||
这样能避免重复抽取同一批实体关系,也符合当前离线部署和 Harness/App 分层约束。
|
||||
|
||||
## 4. 目标行为
|
||||
|
||||
### 4.1 严格知识空间模式
|
||||
|
||||
只要本轮 `llmwiki_knowledge_base_ids` 或 `assistant_knowledge_base_ids` 至少有一个有效 ID,就设置:
|
||||
|
||||
```json
|
||||
{
|
||||
"retrieval_scope": "selected_knowledge_spaces",
|
||||
"external_retrieval_allowed": false
|
||||
}
|
||||
```
|
||||
|
||||
必须保证:
|
||||
|
||||
- 允许:统一知识空间检索、读取命中 Wiki 全文、文件读写、计算、制图等非外部检索能力;
|
||||
- 禁止:`web_search`、`web_fetch`、`image_search`、其他公网/内网检索工具、外部检索技能、检索型 MCP;
|
||||
- 子智能体继承同一限制;
|
||||
- 即使 0 命中也不能自动 Web fallback;
|
||||
- 用户需要联网时,应先取消知识空间选择,或未来显式切换“知识空间 + 联网”模式,不能由模型自行决定。
|
||||
|
||||
只靠提示词不能作为保证。短期在 Gateway 合并 `excluded_tools`;中期给工具、技能和 MCP 增加 `retrieval_channel` 能力标签,在工具装配层统一 fail-closed 过滤,并在调用网关再次校验。
|
||||
|
||||
### 4.2 统一检索工具
|
||||
|
||||
用 `knowledge_space_search` 替代仅支持普通库的 `llmwiki_search`:
|
||||
|
||||
```json
|
||||
{
|
||||
"query": "聚焦后的检索问题",
|
||||
"intent": "fact | overview | multi_hop | compare",
|
||||
"selected_spaces": [
|
||||
{"kind": "llmwiki", "id": "..."},
|
||||
{"kind": "assistant", "id": "..."}
|
||||
],
|
||||
"top_k": 8,
|
||||
"expand_wiki": "auto"
|
||||
}
|
||||
```
|
||||
|
||||
工具内部从 runtime context 获取白名单,模型不能通过参数扩大范围。预检索也调用同一个服务,避免两套算法和返回结构再次漂移。
|
||||
|
||||
统一结果至少包含:
|
||||
|
||||
```json
|
||||
{
|
||||
"results": [],
|
||||
"mode": "hybrid_graph",
|
||||
"diagnostics": {
|
||||
"vector_candidates": 0,
|
||||
"lexical_candidates": 7,
|
||||
"graph_candidates": 11,
|
||||
"vector_status": "threshold_filtered",
|
||||
"top_raw_vector_score": 0.31,
|
||||
"threshold": 0.35,
|
||||
"fallback_reason": "VECTOR_BELOW_THRESHOLD"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 混合检索与 Wiki 扩展算法
|
||||
|
||||
### 5.1 查询路由
|
||||
|
||||
先做轻量意图识别,不调用外部检索:
|
||||
|
||||
- `overview`:“这个知识库做什么”“全部讲了什么”“有哪些主题”;
|
||||
- `fact`:单事实、定义、专名、编号;
|
||||
- `multi_hop`:关系链、原因链、跨页面综合;
|
||||
- `compare`:多个对象/方案比较。
|
||||
|
||||
同时生成 1~4 个聚焦查询,包括原句、去停用词版本、实体/别名版本。保留原句用于引用追踪。
|
||||
|
||||
### 5.2 第一阶段召回
|
||||
|
||||
在每个已选空间内并行执行:
|
||||
|
||||
1. **向量召回**:页面正文 section 的 BGE-M3 向量;先取 raw top-N,再判断低置信,不能先丢掉诊断信息;
|
||||
2. **关键词/全文召回**:标题、别名、heading、正文、实体名;中文使用分词或 CJK 2/3-gram,支持精确短语;
|
||||
3. **结构命中**:slug、目录路径、实体 ID、来源文档名精确匹配;
|
||||
4. **概览命中**:直接读取已发布的全库摘要,不把“知识库干什么”送去做普通相似度查询。
|
||||
|
||||
推荐默认并行混合,而不是只有 0 条时才执行关键词。向量擅长同义表达,关键词擅长专名、编号和缩写;两者用 Reciprocal Rank Fusion 合并,避免直接比较不可校准的原始分数:
|
||||
|
||||
```text
|
||||
score_rrf(d) = Σ 1 / (k + rank_channel(d))
|
||||
```
|
||||
|
||||
标题/别名精确命中可作为独立 rank channel,不直接覆盖正文相关性。
|
||||
|
||||
### 5.3 Wiki 图扩展
|
||||
|
||||
同步时把 WeKnora `GET /wiki/graph` 或页面 `InLinks` / `OutLinks` 落到本地边表。助手库复用现有 `assistant_knowledge_entities`、`assistant_knowledge_relations` 及包内双向链接。
|
||||
|
||||
建议的节点与边:
|
||||
|
||||
| 节点 | 边 | 默认权重方向 |
|
||||
|---|---|---:|
|
||||
| Wiki 页面 | 显式 `[[wiki-link]]` / InLinks / OutLinks | 1.0 |
|
||||
| 页面 ↔ 实体 | 页面提及或实体贡献 | 0.9 |
|
||||
| 实体 ↔ 实体 | 已抽取关系 | 0.8 |
|
||||
| 页面 ↔ 来源文档 | `source_refs` / `chunk_refs` | 0.7 |
|
||||
| 页面 ↔ 目录父子 | `parent_slug` / folder | 0.4 |
|
||||
|
||||
在线流程:
|
||||
|
||||
1. 取混合召回 top 10~20 作为个性化种子;
|
||||
2. `fact` 默认扩 1 跳,`multi_hop` 或低置信再扩 2 跳;
|
||||
3. 每个种子的邻居上限、总节点上限和每库配额必须固定;
|
||||
4. 用有界 PPR 或衰减传播得到图分;
|
||||
5. 将向量 rank、关键词 rank、图 rank 用 RRF 融合;
|
||||
6. 可选本地 reranker 对 top 30 重排;
|
||||
7. 用 MMR/去重限制同一页面和同一主题占满上下文;
|
||||
8. 最终只返回有正文且能追溯到原始页面/文档的 6~10 条证据。
|
||||
|
||||
图扩展只扩大候选池,不直接把关联页面当作事实证据。最终引用必须对应真实正文片段。
|
||||
|
||||
### 5.4 索引快照保活
|
||||
|
||||
同步状态拆成两组:
|
||||
|
||||
- `serving_snapshot_*`:当前对查询可见的最后成功版本;
|
||||
- `sync_attempt_*`:最新一次同步任务的状态、错误和进度。
|
||||
|
||||
新同步在 staging revision 中完成,只有页面、向量、指纹和维度校验全部通过后才原子切换 serving revision。同步失败时继续使用旧 revision,同时返回 `index_stale=true` 和失败原因。只有从未成功生成过 serving snapshot 时才退回 Wiki API/关键词。
|
||||
|
||||
## 6. 全库摘要的持续维护与人工编辑
|
||||
|
||||
### 6.1 产品语义
|
||||
|
||||
每个知识空间维护两份摘要:
|
||||
|
||||
- **系统草稿**:随 Wiki 页面变化持续重算;
|
||||
- **人工发布版**:用户可编辑,是聊天回答和知识库详情默认展示的摘要。
|
||||
|
||||
如果从未人工编辑,发布版自动跟随系统草稿;一旦人工编辑,后续系统更新只产生“待合并草稿”和差异提示,不能覆盖用户文字。用户可以逐段接受、全部替换、恢复历史版本。
|
||||
|
||||
### 6.2 摘要结构
|
||||
|
||||
摘要不是一段无来源的模型结论,建议存储结构化字段:
|
||||
|
||||
```json
|
||||
{
|
||||
"purpose": "这个知识空间解决什么问题",
|
||||
"scope": ["覆盖范围"],
|
||||
"key_topics": [{"name": "主题", "page_slugs": ["..."]}],
|
||||
"key_entities": [{"name": "实体", "page_slugs": ["..."]}],
|
||||
"time_coverage": "资料时间范围或未知",
|
||||
"source_coverage": {"documents": 12, "wiki_pages": 63, "eligible_pages": 58},
|
||||
"limitations": ["缺口、失败页面、过期风险"],
|
||||
"summary_markdown": "面向用户的可编辑摘要"
|
||||
}
|
||||
```
|
||||
|
||||
`source_coverage` 和 `limitations` 由系统生成且不可由摘要模型伪造。
|
||||
|
||||
### 6.3 生成策略
|
||||
|
||||
借鉴 GraphRAG 的分层摘要,但复用现有 Wiki 分类:
|
||||
|
||||
```text
|
||||
页面摘要(增量复用)
|
||||
→ 按目录 / 实体社区 / 来源文档聚类
|
||||
→ 主题摘要
|
||||
→ 全库摘要草稿
|
||||
→ 事实覆盖与引用校验
|
||||
```
|
||||
|
||||
触发条件:文件导入完成、Wiki 页面创建/编辑/删除、索引 serving revision 切换。采用 30~120 秒 debounce,同一知识库合并任务;只重算受影响主题,最后合并全库草稿。
|
||||
|
||||
### 6.4 数据模型
|
||||
|
||||
新增:
|
||||
|
||||
```text
|
||||
knowledge_space_profiles
|
||||
kind, space_id, serving_revision
|
||||
generated_json, generated_markdown, generated_at
|
||||
published_markdown, published_version, edited_by, edited_at
|
||||
source_revision, status, last_error
|
||||
|
||||
knowledge_space_profile_revisions
|
||||
profile_id, version, source(system|user|merge|revert)
|
||||
content_markdown, content_json, created_by, created_at
|
||||
```
|
||||
|
||||
唯一键为 `(kind, space_id)`;更新使用 `published_version` 乐观锁。
|
||||
|
||||
### 6.5 API 与界面
|
||||
|
||||
建议统一接口:
|
||||
|
||||
- `GET /api/knowledge-spaces/{kind}/{id}/profile`
|
||||
- `PATCH /api/knowledge-spaces/{kind}/{id}/profile`:人工编辑;
|
||||
- `POST /api/knowledge-spaces/{kind}/{id}/profile/regenerate`:重算系统草稿;
|
||||
- `POST /api/knowledge-spaces/{kind}/{id}/profile/accept-generated`:接受系统草稿;
|
||||
- `GET /api/knowledge-spaces/{kind}/{id}/profile/revisions`
|
||||
- `POST /api/knowledge-spaces/{kind}/{id}/profile/revert/{version}`
|
||||
|
||||
普通/助手知识库详情增加“知识库摘要”页签:显示覆盖数量、生成时间、是否落后于当前 Wiki、编辑、差异、重算和历史。问答遇到 `overview` 意图时读取发布摘要,再补充 3~5 个代表性正文引用。
|
||||
|
||||
## 7. 检索健康诊断
|
||||
|
||||
### 7.1 健康接口
|
||||
|
||||
新增:
|
||||
|
||||
```text
|
||||
GET /api/knowledge-spaces/{kind}/{id}/retrieval-health
|
||||
POST /api/knowledge-spaces/{kind}/{id}/retrieval-health/reindex
|
||||
POST /api/knowledge-spaces/{kind}/{id}/retrieval-health/resync
|
||||
```
|
||||
|
||||
返回至少包括:
|
||||
|
||||
- 远端文档数、Wiki 页面数、正文可检索页面数;
|
||||
- active/draft/archived、summary/index/body 页面分布;
|
||||
- 已切段页数、向量数、失败数;
|
||||
- serving revision 与最新 sync attempt;
|
||||
- 当前/已存 embedding model、fingerprint、dimensions;
|
||||
- 空正文数、失效映射/权限状态、最后错误;
|
||||
- 最后成功同步/向量化/摘要更新时间;
|
||||
- 关键词索引和 Wiki 图边数量。
|
||||
|
||||
### 7.2 每次查询追踪
|
||||
|
||||
每次检索生成 `retrieval_trace_id`,记录:
|
||||
|
||||
```text
|
||||
选中空间 → 权限通过数 → 查询变体 → 每通道候选数
|
||||
→ 向量 top raw score / threshold → 图扩展数 → rerank 数
|
||||
→ 最终引用数 → 实际模式 / fallback_reason → 各阶段耗时
|
||||
```
|
||||
|
||||
0 结果必须归类为稳定错误码,例如:
|
||||
|
||||
- `SPACE_EMPTY`
|
||||
- `REMOTE_MAPPING_NOT_FOUND`
|
||||
- `WIKI_NOT_FINALIZED`
|
||||
- `NO_SERVING_VECTOR_SNAPSHOT`
|
||||
- `EMBEDDING_FINGERPRINT_MISMATCH`
|
||||
- `EMBEDDING_DIMENSION_MISMATCH`
|
||||
- `VECTOR_BELOW_THRESHOLD`
|
||||
- `NO_ELIGIBLE_BODY_PAGE`
|
||||
- `KEYWORD_NO_MATCH`
|
||||
- `RETRIEVAL_PROVIDER_UNAVAILABLE`
|
||||
|
||||
前端不得把 `UNAVAILABLE` 显示成“库内没有相关资料”。管理员可以展开诊断并直接触发重同步/重建索引。
|
||||
|
||||
## 8. 后端改造位置
|
||||
|
||||
| 模块 | 改造 |
|
||||
|---|---|
|
||||
| `app/gateway/services.py` | 识别严格知识空间模式;统一过滤外部检索能力;策略传给子智能体 |
|
||||
| `app/gateway/llmwiki_rag.py` | 改为调用统一 `KnowledgeSpaceRetrievalService`;附诊断和 trace id |
|
||||
| `tools/builtins/llmwiki_search_tool.py` | 迁移为同时支持普通/助手库的 `knowledge_space_search`,保留兼容别名 |
|
||||
| `local_index/retrieval.py` | serving snapshot 保活、普通库关键词召回、模式与 fallback 原因 |
|
||||
| `local_index/search.py` | 返回 raw top score;阈值校准;向量与关键词并行融合 |
|
||||
| `local_index/normalize.py` | 保留 `in_links`、`out_links`、folder/关系字段 |
|
||||
| `persistence/llmwiki_index/*` | serving/staging revision、Wiki 图节点边、全文索引 |
|
||||
| `persistence/assistant_knowledge/*` | 统一诊断协议;复用助手实体关系做图扩展 |
|
||||
| 新 `knowledge_space_retrieval/` | 查询路由、hybrid/RRF、图扩展、上下文组装、trace |
|
||||
| 新 `knowledge_space_profiles/` | 全库摘要任务、版本、人工编辑与 diff |
|
||||
|
||||
Harness 不导入 `app.*`;Gateway 负责用户和权限解析,把授权后的空间描述传给 Harness 服务。
|
||||
|
||||
## 9. 前端改造
|
||||
|
||||
1. 选择知识空间后,在选择器旁明确显示“仅检索已选知识空间,不联网”;
|
||||
2. 检索过程显示“向量 / 关键词 / Wiki 关联扩展 / 重排”阶段及耗时;
|
||||
3. 0 命中时展示具体状态和修复入口,不只显示空卡片;
|
||||
4. 引用卡片标注 `向量命中`、`关键词命中`、`Wiki 关联扩展`,但不能暴露内部敏感路径;
|
||||
5. 普通/助手知识库共用“知识库摘要”编辑、差异和历史组件;
|
||||
6. 管理页面增加“检索健康”抽屉,普通用户看到简化状态,管理员看到完整诊断。
|
||||
|
||||
## 10. 性能设计
|
||||
|
||||
- 空间列表继续只读 metadata/聚合统计,不扫描 Wiki 正文和向量;
|
||||
- 多空间权限校验改成批量查询,不按 ID 串行;
|
||||
- 本地向量矩阵按 `(space_id, serving_revision, fingerprint)` 缓存;
|
||||
- 查询向量按 `(fingerprint, normalized_query)` 短 TTL 缓存;
|
||||
- 关键词索引增量维护,禁止在线全表 `%LIKE%` 扫描大库;SQLite 可用 FTS5/CJK n-gram 辅助表,PostgreSQL/高斯/MySQL 通过统一适配层接各自全文索引;
|
||||
- Wiki 图常驻压缩邻接表,在线只取有界邻居;
|
||||
- 页面全文和引用详情批量加载,避免 N+1 远端请求;
|
||||
- reranker 只处理融合后的 top 20~30,超时则降级到 RRF,不阻断回答;
|
||||
- 摘要生成完全后台化,聊天只读 serving profile。
|
||||
|
||||
建议验收预算(需用生产规模压测校准):热缓存本地检索 p95 ≤ 2 秒,图扩展自身 p95 ≤ 200 ms;远端回退必须有独立超时,不能拖住整个模型首 token。
|
||||
|
||||
## 11. 分阶段实施
|
||||
|
||||
### 当前落地状态(2026-09-16)
|
||||
|
||||
- 已落地 P0/P1 主链路:失败/取消同步保留最后成功向量快照、向量 raw-score 诊断、普通 Wiki
|
||||
关键词并行召回 + RRF、低分回退原因、普通/助手二次检索统一、选库后的 Web/MCP/技能/子智能体
|
||||
硬隔离,以及选库后的 4 个推荐问题。
|
||||
- 已落地逐次查询追踪与同链路预览:`POST /api/llmwiki/retrieval-debug` 复用问答预检索,知识空间
|
||||
「检索调试」页展示最终证据、通道候选数、阈值、降级和零结果原因;正常问答在 SSE `metadata`
|
||||
与 `/api/threads/{thread_id}/reference-batches.retrieval_traces` 暴露相同 trace,F12 可直接核对。
|
||||
后续轮次省略选库字段时沿用线程选择,显式空列表仍清空范围。
|
||||
- 编码器已按部署配置切换为 SiliconFlow `BAAI/bge-m3`(1024 维),密钥只从环境变量读取;
|
||||
供应商切换产生新 fingerprint,旧库将重建,期间由关键词/Wiki API 保底。
|
||||
- 已落地 P2 普通知识库与助手知识库图扩展:普通库同步库内入链/出链、正文 wikilink 与 provider
|
||||
graph;助手库沿导入实体关系并通过 `entity.wiki_page_id` 回到 Wiki 正文。两者都以混合召回页为
|
||||
种子,默认 1 跳,显式多跳意图或低置信时最多 2 跳,并执行候选/种子/每库/最终上下文硬上限、
|
||||
衰减传播排序、真实正文校验、授权二次过滤和可审计路径 trace。P3 可编辑全库摘要仍未实施,
|
||||
不能把当前 overview 代表页聚合误称为“持续维护摘要”。
|
||||
|
||||
### P0:诊断与正确性保底
|
||||
|
||||
- 建 retrieval health/trace;
|
||||
- 拆分 serving snapshot 与 sync attempt;
|
||||
- 修复失效远端映射和同步取消导致的整库失效;
|
||||
- 记录 raw top score、阈值和每库候选数。
|
||||
|
||||
验收:现有 124 页/281 向量的库即使新同步失败,仍从上一次成功 snapshot 返回结果,并标记 stale。
|
||||
|
||||
### P1:严格范围与混合召回
|
||||
|
||||
- 勾选知识空间后禁止所有外部检索通道;
|
||||
- 合并普通/助手知识工具;
|
||||
- 向量、关键词、标题/别名召回 + RRF;
|
||||
- 0 结果返回明确 fallback reason。
|
||||
|
||||
验收:测试 spy 证明选中空间的整轮运行中 Web/外部检索调用次数为 0;向量低于阈值时关键词能召回精确专名。
|
||||
|
||||
### P2:Wiki 图扩展
|
||||
|
||||
- 已同步 WeKnora InLinks/OutLinks、正文 wikilink 和 provider graph;
|
||||
- 已实现普通 Wiki 1~2 跳有界扩展、衰减传播、稳定融合、去重、每库配额与 trace;
|
||||
- 已接入助手实体关系:只沿所选助手库内 active 的关系、两端实体和页面扩展,并阻断跨库追边;
|
||||
- 后续继续以多跳评测集校准传播权重和配额。
|
||||
|
||||
验收:准备只能通过关联页找到答案的 1 跳/2 跳数据集;结果必须命中且引用真实正文。
|
||||
|
||||
### P3:全库摘要与编辑
|
||||
|
||||
- profile 表、后台生成、事件触发;
|
||||
- 人工发布版、diff、历史、回滚;
|
||||
- overview 查询优先走 profile。
|
||||
|
||||
验收:新增/编辑/删除 Wiki 后系统草稿更新;人工摘要不被覆盖;“知识库干什么”稳定返回摘要和代表性引用。
|
||||
|
||||
### P4:重排与质量评测
|
||||
|
||||
- 可选 BGE reranker;
|
||||
- 建中文问答集、专名集、概览集、多跳集和负样本;
|
||||
- 评估 Recall@K、MRR、nDCG、citation precision、0 命中率和 p95。
|
||||
|
||||
只有离线评测显著优于 RRF 基线,才默认开启 reranker 或引入完整外部 GraphRAG/HippoRAG 服务。
|
||||
|
||||
## 12. 必测用例
|
||||
|
||||
1. 普通库、助手库、二者混选时都严格限定到所选 ID;
|
||||
2. 选择知识空间后直接/技能/MCP/子智能体均不能调用外部检索;
|
||||
3. 同步取消但存在 serving snapshot 时继续向量检索;
|
||||
4. 指纹或维度不匹配时返回关键词结果和明确诊断;
|
||||
5. 所有向量低于阈值时返回 raw top score,不误报为空库;
|
||||
6. 中文专名、英文缩写、编号、标题别名可被关键词召回;
|
||||
7. summary/index 页面不作为普通事实的唯一证据;
|
||||
8. Wiki 1 跳/2 跳扩展不越出已选知识空间;
|
||||
9. 多知识空间有每库配额,单个大库不会吞掉全部 top-K;
|
||||
10. 空库仍可进入管理页、编辑摘要说明和上传文件;
|
||||
11. overview 问题不依赖与问句的向量相似度;
|
||||
12. 人工编辑摘要后自动更新只生成待合并草稿;
|
||||
13. 远端 404、超时、权限错误与真实 0 命中分别显示;
|
||||
14. 引用可打开对应 Wiki 页面或源文档,且正文与答案事实一致。
|
||||
|
||||
## 13. 完成定义
|
||||
|
||||
以下条件全部满足才算完成,而不是“接口能返回 200”:
|
||||
|
||||
- 勾选知识空间后,外部检索调用为 0;
|
||||
- 普通和助手知识空间共享一套检索语义;
|
||||
- 每个 0 结果都有可解释原因;
|
||||
- 旧成功索引不因新同步失败而下线;
|
||||
- 向量、关键词和 Wiki 图扩展均有独立指标;
|
||||
- “这个知识库干什么”读取可持续维护、可人工编辑的全库摘要;
|
||||
- 摘要、混合检索和图扩展都有可追溯引用;
|
||||
- 离线质量集和性能压测达到约定门槛后再发布。
|
||||
@@ -78,6 +78,8 @@ WeKnora 前端不做任何修改。DeerFlow 原“知识库资源管理”入口
|
||||
- 原模式:保留现有 LLMWiki iframe 页面;
|
||||
- WeKnora 模式:渲染 DeerFlow 原生 React 页面,提供个人库、公共库、直接发布和检索。点击卡片进入独立知识库路由,内容区复刻 WeKnora 的详情页结构,同时保留 DeerFlow 的导航、登录和权限壳。Wiki 库按 **Wiki → 文档 → 图谱** 排列并默认打开 Wiki;标准库不显示 Wiki 标签,按 **文档 → 图谱** 排列。Wiki 视图包含页面目录、搜索、页面正文和引用摘要;文档以卡片网格展示,点击后从右侧抽屉查看元数据、原文件与真实解析分块;图谱视图渲染 WeKnora Wiki 页面节点和边。新建知识库可选择“Wiki 知识库”或“标准知识库”,详情页支持文件、网页 URL、手工 Markdown 三种导入方式。问答选择器沿用 WeKnora 的紧凑搜索/分组/多选交互,颜色、间距和组件沿用 DeerFlow 设计系统;智能体创建和编辑页使用同一数据源的内嵌绑定面板。
|
||||
|
||||
Wiki 知识库可选填“参考目录”,支持逐行录入或上传 `.txt/.md`。参考目录只帮助生成模型理解用户期望的信息组织方式,不是目录白名单,也不是必须逐项套用的固定模板。WeKnora 可以根据原始资料新增、合并、拆分、改名、调整或省略目录;Gateway 不会为参考目录预建文件夹,也不会在生成后移动页面、删除目录或清理参考目录之外的有效结构。`directory_paths` 与兼容字段 `parent_directories` 均遵循这一语义。未填写时沿用原有自动 Wiki 生成方式;编辑时清空只表示不再向后续生成提供该参考,不撤销已有页面或目录。
|
||||
|
||||
问答检索命中以 DeerFlow 来源卡片展示。“查看原文”调用 DeerFlow 的 `/api/llmwiki/sources/context`,由服务端在重新校验 DeerFlow 用户权限后向 WeKnora 读取命中切片及相邻上下文,不向浏览器暴露 WeKnora 原始资源 ID。
|
||||
|
||||
## 主要接口
|
||||
|
||||
+400
@@ -0,0 +1,400 @@
|
||||
# 普通知识库智能推理检索模式设计
|
||||
|
||||
> 状态:已实现,与当前代码一致
|
||||
>
|
||||
> 适用范围:通用问答、普通智能体问答中的 WeKnora 普通知识库
|
||||
>
|
||||
> 更新日期:2026-10-18
|
||||
>
|
||||
> WeKnora 行为核查基线:v0.7.2,commit 3d5d8bfcdfeeea266b292b71cea616847af28d0f
|
||||
|
||||
## 1. 目标
|
||||
|
||||
系统在原有经典检索之外增加“知识库智能检索”模式。经典模式完整保留。新模式的全局开关默认关闭,只有管理员能在设置页开启;关闭时新对话默认经典检索,开启后新对话默认智能检索,经典检索位于候选位置。已有线程继续按自身 metadata 恢复模式,不被全局开关批量改写。
|
||||
开关和本功能新增配置只保存在数据库中,不写 `system_settings.json`、`config.yaml` 或其他配置文件。智能模式在一次用户提问内执行模型与工具的多轮循环。
|
||||
用户只需直接提问,不需要在问题中说明先检索什么、再检索什么。每个新问题先执行原始文档向量/混合检索取得直接相关的分片、标题和实体,再用这些线索执行 Wiki 正则检索。Wiki 有命中时,模型只选择与回答有关的页面深读;模型未选择时,中间件读取排序第一的页面作为最低证据基线。长 Wiki 页面会使用 `next_cursor` 连续读取到 `complete=true`。之后只有在 Wiki 暴露出首轮 RAG 没有覆盖且回答确实需要的线索时,才继续改写查询、做原始切片正则核验或读取文档上下文。证据足够就停止检索并回答;Wiki 与原始文档冲突时,以原始文档为准。
|
||||
检索结果是回答的背景资料,不替代模型原有的理解、归纳、写作和推理能力。模型可以使用稳定通用知识解释术语与背景,但必须把库内事实、基于材料的推断和一般知识区分清楚,不能用模型记忆编造知识库专属事实。
|
||||
最终回答不要求带 [1]、[2] 等编号。
|
||||
|
||||
## 2. 与原 WeKnora 逻辑的对应关系
|
||||
|
||||
本实现对齐 WeKnora 智能推理的搜索、深读和证据缺口循环。按当前验收要求,在 WeKnora 混合检索策略之上增加确定性的证据基线:先搜索原始分片,再结合原始命中的标题和实体搜索 Wiki,并至少深读一个相关 Wiki 页面。中间件会推迟越序的 Wiki 调用并补齐缺少的阶段,但不会为了走流程而读取全部 Wiki 命中页。基线完成后,后续轮次由模型根据问题类型和真实证据缺口决定。后续补强路径如下:
|
||||
|
||||
| 问题类型 | 固定首轮后的补强面 | 深读动作 |
|
||||
|---|---|---|
|
||||
| 概念、主题、关系、整体脉络 | Wiki 正则搜索 | 读取命中 Wiki 页正文 |
|
||||
| 数字、日期、编号、条款、原句 | 原始知识搜索或切片正则搜索 | 读取命中切片的邻近上下文或文档分块 |
|
||||
| 同时需要框架与事实 | 先原始 RAG,再用 Wiki 扩展 | 用新的原始查询或切片正则核验 Wiki 暴露的关键事实 |
|
||||
|
||||
原始知识搜索沿用 WeKnora 的 knowledge-search 接口。 该接口由 WeKnora 执行向量或混合召回、融合和排序。 精确词、代码、编号和固定短语由切片正则检索补充。
|
||||
Wiki 搜索采用大小写不敏感的安全 POSIX 风格正则表达式。 Wiki 搜索结果只作为页面入口。 模型需要调用页面读取工具获得正文,不能仅凭标题或摘要完成事实回答。
|
||||
原始切片命中后,模型需要调用文档分块工具补齐相邻内容或后续分页。 Wiki 与原始文档冲突时,系统提示模型优先采用能定位到原始文档切片的证据,并说明差异。 首轮无结果时,系统提示模型改写查询一次。
|
||||
仍有明确证据缺口时,系统提示模型选择能弥补该缺口的检索面。没有新增收益时应停止检索。中间件会在同一用户回合对原始查询、正则、Wiki pattern、`page_ref+cursor` 和文档分页做确定性去重;Wiki 的新 `next_cursor` 属于有效续读,不视为重复。上游核查入口:
|
||||
|
||||
- Agent preset:https://github.com/Tencent/WeKnora/blob/3d5d8bfcdfeeea266b292b71cea616847af28d0f/config/agent_type_presets.yaml
|
||||
- Agent system prompt:https://github.com/Tencent/WeKnora/blob/3d5d8bfcdfeeea266b292b71cea616847af28d0f/config/prompt_templates/agent_system_prompt.yaml
|
||||
- Knowledge search tool:https://github.com/Tencent/WeKnora/blob/3d5d8bfcdfeeea266b292b71cea616847af28d0f/internal/agent/tools/knowledge_search.go
|
||||
- Chunk grep tool:https://github.com/Tencent/WeKnora/blob/3d5d8bfcdfeeea266b292b71cea616847af28d0f/internal/agent/tools/grep_chunks.go
|
||||
- Document reader:https://github.com/Tencent/WeKnora/blob/3d5d8bfcdfeeea266b292b71cea616847af28d0f/internal/agent/tools/list_knowledge_chunks.go
|
||||
- Wiki tools:https://github.com/Tencent/WeKnora/blob/3d5d8bfcdfeeea266b292b71cea616847af28d0f/internal/agent/tools/wiki_tools.go
|
||||
|
||||
## 3. 模式与开关
|
||||
|
||||
会话请求字段为 knowledge_retrieval_mode。 允许值只有:
|
||||
|
||||
- classic
|
||||
- smart_reasoning
|
||||
|
||||
请求省略该字段或传空值时,后端按 classic 处理。 前端每次发送都显式携带当前模式。 线程 metadata 只用于恢复前端控件,不是后端本轮执行的隐式输入。
|
||||
这保证旧客户端和缺少合法模式 metadata 的旧会话继续使用经典模式。新对话在设置读取完成后按全局开关选择默认模式:开关开启时为 smart_reasoning,关闭或读取失败时为 classic。管理员切换全局开关不会批量改变历史会话。
|
||||
管理员关闭开关也不会删除历史对话。 历史线程若保存为 smart_reasoning,关闭全局开关后必须切回 classic 才能继续发送。 模式不写入全局浏览器偏好。
|
||||
新会话页面间的临时交接使用现有页面状态和 sessionStorage。
|
||||
|
||||
## 4. 数据库存储
|
||||
|
||||
全局开关表为 knowledge_retrieval_settings。 该表使用 scope 作为主键。 当前全局记录的 scope 为 global。
|
||||
主要字段如下:
|
||||
|
||||
| 字段 | 含义 |
|
||||
|---|---|
|
||||
| scope | 设置作用域,当前为 global |
|
||||
| smart_reasoning_enabled | 是否允许使用智能模式,并决定新对话的前端默认模式 |
|
||||
| revision | 设置修订号 |
|
||||
| updated_by | 最近修改人 |
|
||||
| updated_at | 最近修改时间 |
|
||||
|
||||
数据库没有记录时,读取结果为 false。 迁移只创建表,不插入开启记录。 因此升级后功能保持默认关闭。
|
||||
迁移文件为 20261014_01_knowledge_retrieval_settings.py。 SQL 部署使用 SQLAlchemy Repository。 无数据库的单元测试使用同一 Store 接口的内存实现。
|
||||
生产功能不读取或写入本地配置文件。
|
||||
|
||||
## 5. 管理接口
|
||||
|
||||
读取接口:
|
||||
|
||||
GET /api/system-settings/ordinary-knowledge-retrieval
|
||||
|
||||
响应字段为 smart_reasoning_enabled。
|
||||
|
||||
更新接口:
|
||||
|
||||
PUT /api/system-settings/ordinary-knowledge-retrieval
|
||||
|
||||
请求字段为 smart_reasoning_enabled。
|
||||
|
||||
GET 供对话客户端和设置页读取当前状态。
|
||||
PUT 仅管理员可调用。
|
||||
|
||||
前端读取失败时按关闭处理,并允许用户重试读取。 后端每次启动智能模式请求时重新读取数据库开关。 前端隐藏控件不能替代后端校验。
|
||||
|
||||
## 6. 适用知识范围
|
||||
|
||||
智能模式只支持 WeKnora 普通知识库。 至少需要选择一个普通知识库。 选择助手知识库时拒绝智能模式。
|
||||
普通库与助手库混选时也拒绝智能模式。 智能体会话只能使用该智能体已绑定的普通知识库。 普通用户只能使用自己有读取权限的本地 mapping。
|
||||
管理员也必须通过现有 mapping 存储读取有效映射。 请求和工具参数始终使用本地 mapping ID。 模型不能直接传入远端 WeKnora knowledge base ID 扩大范围。
|
||||
越权、不存在和未绑定的知识库统一按不可访问处理。 智能模式启动前完成一次范围授权。 五个检索工具每次调用时再次读取当前用户、智能体绑定和 mapping 权限。
|
||||
因此管理员撤权或绑定发生变化后,后续工具调用会立即失败。 深读工具还会校验 chunk 或 document 实际所属的远端知识库。 只知道其他知识库的 chunk ID、document ID 或 page_ref 不能越权读取。
|
||||
|
||||
## 7. 请求执行流程
|
||||
|
||||
一次请求按以下顺序执行:
|
||||
|
||||
1. Gateway 解析本轮显式 knowledge_retrieval_mode。
|
||||
2. classic 直接进入原有经典检索链路。
|
||||
3. smart_reasoning 读取数据库全局开关。
|
||||
4. Gateway 校验普通库数量、助手库混选、智能体绑定和用户权限。
|
||||
5. Gateway 校验 WeKnora runtime 与 mapping Store 可用。
|
||||
6. Gateway 持久化本轮模式和知识库选择到线程 metadata。
|
||||
7. metadata 写入失败时,将尚未启动的 run 标记为失败,不启动模型。
|
||||
8. Gateway 把服务端确认后的模式和范围写入 RunnableConfig。
|
||||
9. 智能模式关闭经典的预检索提示和编号引用提示。
|
||||
10. Lead Agent 挂载五个智能检索工具和专用中间件。
|
||||
11. 专用中间件保证本轮先执行原始文档 RAG,再执行 Wiki 搜索;Wiki 有命中时至少完整读取一个相关页面。模型再根据 Wiki 暴露的真实证据缺口自主执行后续搜索和深读。
|
||||
12. 模型在证据充分或继续检索无收益时生成最终回答。
|
||||
|
||||
智能模式的 Gateway 上下文构建只做授权和范围准备。 它不在第一次模型调用前塞入固定数量的一次性检索片段。 检索决策由本轮模型和工具循环完成。
|
||||
服务端会覆盖调用方试图设置的 disable_tools。 服务端也会从 excluded_tools 中移除五个智能检索工具。 智能模式会排除经典 llmwiki_search。
|
||||
经典模式会排除五个智能检索工具。 两种模式不会同时暴露两套知识检索入口。
|
||||
|
||||
## 8. 五个真实工具
|
||||
|
||||
### 8.1 ordinary_knowledge_search
|
||||
|
||||
用途:对原始文档切片做向量或混合检索。 输入为 queries,允许 1 至 5 个非空查询。 单个查询最长 500 个字符。
|
||||
多个查询并行调用 WeKnora /api/v1/knowledge-search。 结果按 chunk 合并。 同一 chunk 命中多个改写时保留 matched_queries。
|
||||
同一 chunk 的分数取各次命中的最高值。 结果按分数倒序,最多返回 30 条。 某个查询失败时,其余查询结果仍可返回。
|
||||
所有查询都失败时返回结构化失败。
|
||||
|
||||
### 8.2 ordinary_chunk_regex_search
|
||||
|
||||
用途:在原始文档标题和切片中查找精确词、编号、代码或固定短语。 输入为一个大小写不敏感的安全 POSIX 风格正则表达式。 表达式最长 256 个字符。
|
||||
不接受 Python 专属扩展、反向引用和明显的嵌套量词风险模式。 每次正则执行有 50 毫秒硬超时。 首先调用 POST /api/v1/chunks/grep。
|
||||
原生响应必须声明 contract 为 weknora-v0.7.2-grep-v1。 合同不匹配按上游错误处理,不静默接受未知格式。 只有原生接口返回 HTTP 404 或 405 时,才进入兼容回退。
|
||||
兼容回退只使用现有 list_documents 和 list_chunks 接口,并按知识库、文档和切片页轮转扫描。
|
||||
一个大知识库不能独占全局扫描预算。 兼容回退最多扫描 500 个切片,最多返回 30 条结果。 兼容响应明确返回 compatibility_fallback 为 true。
|
||||
兼容响应的 contract 为 weknora-existing-api-compat-v1。 部分知识库失败时返回已成功范围的结果和结构化 warnings。 全部知识库失败时返回结构化失败。
|
||||
|
||||
### 8.3 ordinary_document_chunks
|
||||
|
||||
用途:读取命中切片的邻近上下文,或分页读取整篇文档的分块。 identifier_type 支持 chunk_id 和 knowledge_id。 chunk_id 模式先读取切片并核验所属知识库。
|
||||
校验通过后返回该切片前后各两个相邻切片。 knowledge_id 模式先核验文档所属知识库。 校验通过后按 page 和 page_size 读取分块。
|
||||
page 从 1 开始。 page_size 限制为 1 至 100。 返回 total,模型可继续翻页直到覆盖所需正文。
|
||||
|
||||
### 8.4 ordinary_wiki_search
|
||||
|
||||
用途:在 Wiki 页面中做正则搜索。 匹配字段包括:
|
||||
|
||||
- title
|
||||
- slug
|
||||
- summary
|
||||
- aliases
|
||||
- content
|
||||
|
||||
每个 mapping 单独选择读取通道。 本地镜像只有同时满足以下条件才被采用:
|
||||
|
||||
- 有 last_completed_at
|
||||
- state 为 idle
|
||||
- 没有 last_error
|
||||
- failed_page_count 为 0
|
||||
- local_page_count 等于 remote_page_count
|
||||
- 最近同步结果不是失败或部分完成
|
||||
|
||||
某个 mapping 的镜像不完整时,只将该 mapping 回退到 WeKnora provider。 其他镜像完整的 mapping 继续走本地通道。 全局最多扫描 500 个页面候选。
|
||||
扫描预算按 mapping 公平分配。 正文匹配范围对单页限制为前 50000 个字符。 最多返回 30 个页面入口。
|
||||
响应带 channel、coverage、partial、warnings 和 scan_truncated。 全部 mapping 都不可用时返回结构化失败。 检索成功但没有匹配时返回 ok 为 true、result_count 为 0。
|
||||
|
||||
### 8.5 ordinary_wiki_read_page
|
||||
|
||||
用途:读取 ordinary_wiki_search 命中的完整 Wiki 正文。 输入 page_ref 必须来自 Wiki 搜索结果。 page_ref 是范围引用,不是权限凭据。
|
||||
工具调用时仍会重新校验 mapping 权限。 读取通道与 Wiki 搜索相同:完整镜像优先,否则走 provider。 单次最多返回 30000 个字符。
|
||||
长页面返回以下续读字段:
|
||||
|
||||
- complete
|
||||
- next_cursor
|
||||
- content_offset
|
||||
- content_end
|
||||
- content_length
|
||||
- content_truncated
|
||||
|
||||
模型把 next_cursor 传回同一工具即可读取下一段。 cursor 与 page_ref 绑定。 把一个页面的 cursor 用到另一个页面会返回 INVALID_CONTINUATION_CURSOR。
|
||||
按顺序拼接各段 content_markdown 可以重建完整正文。 页面结果同时返回 title、slug、summary、aliases、links 和 source_refs。
|
||||
|
||||
### 8.6 检索进度与 `result_items` 合同
|
||||
|
||||
五个工具通过 LangGraph custom stream 发出 `knowledge_retrieval_progress` v1 事件。事件只展示实际查询、公开聚合统计和有界结果预览,不包含模型内部思考、系统提示、原始 provider 异常或未授权范围的数据。`started` 事件用于显示正在执行的步骤;成功的 `completed` 事件除 `result_count`、`partial`、`channel`、扫描数量等聚合字段外,还可包含:
|
||||
|
||||
```json
|
||||
{
|
||||
"result_items": [],
|
||||
"result_items_truncated": false
|
||||
}
|
||||
```
|
||||
|
||||
`result_items` 最多 20 项。每项公共字段如下:
|
||||
|
||||
| 字段 | 含义 |
|
||||
|---|---|
|
||||
| `kind` | `wiki_page` 或 `document_chunk` |
|
||||
| `knowledge_base_id` | 本地、已选择且已授权的 mapping ID |
|
||||
| `knowledge_base` | 知识库显示名称 |
|
||||
| `title` | Wiki 页面或原始文档标题 |
|
||||
|
||||
Wiki 项还可包含 `page_ref`、`slug` 和最长 1200 字符的 `summary`。Wiki 读取项可附带最长 8000 字符的 `content_markdown`、`content_offset`、`content_end`、`content_length` 和 `content_truncated`。原始文档项可包含 `chunk_id`、`knowledge_id`、最长 1600 字符的 `content`、`content_truncated`,以及可选的 `chunk_index`、`score`、`is_requested`。超过 20 项或投影时丢弃了不能安全展示的结果时,`result_items_truncated` 为 true。
|
||||
|
||||
进度投影只从本次工具已经授权的结果生成。它不会复制远端 WeKnora/provider knowledge base ID,也不会把 provider 返回的任意未知字段透传给浏览器。缺少本地 mapping ID、mapping 不在本轮选择范围、或 `page_ref` 解码后与本地 mapping/slug 不一致的项会被丢弃。右侧详情接口还会根据当前用户重新授权本地 mapping ID,`result_items` 中的标识不能替代权限校验。
|
||||
|
||||
## 9. 多轮推理约束
|
||||
|
||||
智能模式在系统提示中加入所选知识空间约束。模型把本轮选择的普通知识库作为具体事实的首要来源,不能用 Web、MCP、外部技能或子智能体扩大检索范围。模型原有的理解、归纳、写作和推理能力仍然可用,也可以用稳定通用知识解释背景;但必须明确区分库内事实、基于材料的推断和一般知识,不能靠模型记忆补造知识库专属事实。用户明确要求只依据知识库时,按更严格范围回答。
|
||||
模型可以在五个工具之间多次切换。每个新问题必须先调用 `ordinary_knowledge_search`,一次可提交 1 至 5 个查询改写;再结合原始命中调用 `ordinary_wiki_search`。有 Wiki 命中时,优先保留模型选择的相关页面;模型未选择页面时,中间件读取排序第一的页面,不再强制读取最多 5 个命中的全部页面。即使模型试图越序或直接回答,中间件也会补齐这个最低证据基线。完成后,多轮次数由证据是否覆盖问题决定,不固定为 N 次,也不要求每轮把所有工具都调用一遍。
|
||||
精确命中后可调用 ordinary_document_chunks 深读。Wiki 命中后只深读确实与问题有关的页面。已选择的长 Wiki 页面由中间件携带 `next_cursor` 连续读取到 `complete=true`;失败、畸形结果或无法继续的空游标作为终止尝试处理,避免无限循环。
|
||||
同一回合中,相同 raw query、正则 pattern、Wiki pattern、`page_ref+cursor` 和文档分页会被过滤;`chunk_id` 上下文读取按切片本身去重,不能通过无效的分页参数重复执行。一个批次同时包含旧查询和新查询时,只删除旧查询并继续执行新的扩展查询。模型调用前还会收到不展示给用户的已执行动作清单;如果模型仍只返回重复调用且没有正文,中间件会移除五个智能检索工具并以 `tool_choice=none` 做一次隐藏的最终合成重试,要求基于本轮已有证据完整回答。该重试不会再次进入检索循环;重试仍无正文时才返回证据不足的安全说明。
|
||||
Wiki 与原始文档冲突时,以本轮原始文档切片为权威证据。知识库专属事实证据不足时必须明确说明缺少什么;一般背景可以由模型能力补充,但不能伪装成知识库结论。
|
||||
|
||||
## 10. 历史证据清理
|
||||
|
||||
完整聊天记录和工具轨迹仍保存在 checkpoint 中。 前端历史展示和审计数据不会被删除。 在新问题发给模型前,SmartReasoningRetrievalMiddleware 只重写本次模型请求。
|
||||
它找到最近一个真实用户消息作为当前轮边界。 边界之前的五类智能检索 tool call 和对应 ToolMessage 不再发给模型。 旧工具正文会被固定提示替代,说明历史检索不构成本轮证据。
|
||||
当前用户轮中的工具调用和结果保持不变。 非智能检索工具调用保持不变。 这样每个新问题都重新检索,同时保留完整持久化历史。
|
||||
|
||||
## 11. 40 次模型调用上限
|
||||
|
||||
智能模式对每个真实用户回合设置最多 40 次 Lead Agent 模型调用。 计数从最近一个真实用户消息之后开始。 前 39 次允许模型继续检索或回答。
|
||||
第 40 次被保留为最终合成调用。 进入第 40 次时,五个检索工具从请求中移除。 请求强制 tool_choice 为 none。
|
||||
系统追加隐藏指令,要求以本轮已获取证据为事实基础立即回答,并允许用通用知识解释背景但不得补造知识库专属事实。 如果模型仍返回工具调用,Middleware 会剥离工具调用并结束图执行。 如果第 40 次没有可用回答,返回固定的证据不足提示。
|
||||
达到上限后不会发生第 41 次模型调用。 该限制只挂载在 smart_reasoning 模式。
|
||||
|
||||
## 12. 回答与引用
|
||||
|
||||
智能模式将 rag_mode_enabled 强制设为 false。 它不注入经典的 RAG_CITATION_PROMPT。 它不挂载 RagCitationMiddleware。
|
||||
因此最终正文不强制出现数字编号引用。 模型可以自然提及文档名或 Wiki 页面名。 模型应先覆盖用户问题,再说明资料冲突或证据缺口。
|
||||
工具仍返回结构化知识库名、文档名、page_ref、chunk_id 和读取范围。 完整工具轨迹仍进入现有运行记录。
|
||||
|
||||
## 13. 失败语义
|
||||
|
||||
Gateway 在模型启动前使用 HTTP 错误阻止无效请求。 主要错误如下:
|
||||
|
||||
| 场景 | HTTP | code |
|
||||
|---|---:|---|
|
||||
| mode 不是允许值 | 422 | INVALID_KNOWLEDGE_RETRIEVAL_MODE |
|
||||
| 管理员未开启智能模式 | 409 | SMART_REASONING_DISABLED |
|
||||
| 设置 Store 不可用 | 503 | KNOWLEDGE_RETRIEVAL_SETTINGS_UNAVAILABLE |
|
||||
| 混选或只选助手知识库 | 422 | SMART_REASONING_ORDINARY_ONLY |
|
||||
| 没有选择普通知识库 | 422 | SMART_REASONING_SCOPE_REQUIRED |
|
||||
| WeKnora runtime 不可用 | 503 | SMART_REASONING_PROVIDER_UNAVAILABLE |
|
||||
| mapping Store 不可用 | 503 | SMART_REASONING_STORE_UNAVAILABLE |
|
||||
| mapping 不存在、越权或未绑定 | 404 | KNOWLEDGE_SCOPE_NOT_FOUND |
|
||||
|
||||
相关 metadata 写入失败时,尚未启动的 run 被标记为失败。 模型不会在模式或范围没有持久化的情况下启动。 工具失败使用统一 JSON 结构。
|
||||
成功响应包含 ok 为 true、operation、result_count 和 results。 全部检索通道失败时返回 ok 为 false 和安全错误信息。 部分范围失败时返回 ok 为 true、partial 为 true 和 warnings。
|
||||
成功执行但零命中不是系统错误。 零命中返回 ok 为 true、result_count 为 0,交给模型改写或切换检索面。 无效正则返回 INVALID_REGEX。
|
||||
正则执行超时返回 REGEX_TIMEOUT。 跨范围 chunk、document 或页面读取返回通用的不可访问错误。 未归一化异常会转换为统一失败,不向模型返回 Python 堆栈。
|
||||
每个工具输出有 48000 字符上限。 超过上限时按完整结果项裁剪,并标记 output_truncated。
|
||||
|
||||
## 14. 经典模式兼容
|
||||
|
||||
classic 继续调用 build_llmwiki_rag_context。 它保留原有一次性预检索、上下文注入和 llmwiki_search 工具。 经典模式原有编号引用行为不因本功能改变。
|
||||
旧客户端不传 mode 时自动进入 classic。全局开关控制 smart_reasoning 是否允许使用,并决定新对话的前端默认模式;关闭开关不会影响 classic。
|
||||
本功能也不复用前端已有的 multi_round_rag execution profile。 multi_round_rag 的外部调用语义与本功能的知识库范围、权限和会话执行不同。 knowledge_retrieval_mode 与模型选择、推理强度和 execution_profile 相互独立。
|
||||
|
||||
## 15. 前端实现
|
||||
|
||||
管理员入口位于“设置 → 知识检索”。 设置页展示当前数据库开关和说明。 通用问答与智能体问答输入框都支持“知识库智能检索”切换。
|
||||
管理员关闭全局开关时,新会话初始化为 classic;管理员开启后,新会话默认使用 smart_reasoning,classic 保留为候选。已有线程始终从 metadata 恢复各自保存的模式。前端每次发送前执行以下校验:
|
||||
|
||||
- 全局开关已读取并开启
|
||||
- 知识库选择已加载完成
|
||||
- 至少选择一个普通知识库
|
||||
- 没有选择助手知识库
|
||||
|
||||
校验失败时显示明确提示并停止发送。 后端仍重复执行全部权威校验。 输入框可显示历史线程的智能模式状态,便于用户切回 classic。
|
||||
前端不会在开关关闭时把 smart_reasoning 静默降级成 classic。
|
||||
|
||||
智能检索执行期间,“智能检索过程”保持展开,连续显示首轮原始文档搜索、Wiki 搜索、相关页面读取和按需扩展深读。检索结束后自动收起,只保留轮数、原始文档命中数和 Wiki 命中数摘要;用户可以随时手动展开或再次收起。历史消息重放时也默认收起。
|
||||
|
||||
展开后,每个检索步骤在“查询结果”中显示可查看的结果项。带有本地 mapping ID 和稳定页面/文档标识的结果可以点击,并在当前聊天页面右侧打开现有 WeKnora 来源抽屉:Wiki 项定位到 Wiki 页面,原始项定位到对应文档或切片。打开抽屉不会改变聊天主路由。旧历史轨迹若没有本地 mapping ID、Wiki slug、chunk ID 或 document ID 等稳定标识,只显示不可点击的标题摘要,前端不会按标题猜测来源。
|
||||
|
||||
## 16. 主要实现文件
|
||||
|
||||
后端设置与迁移:
|
||||
|
||||
- packages/harness/deerflow/persistence/knowledge_retrieval_settings/
|
||||
- packages/harness/deerflow/persistence/migrations/versions/20261014_01_knowledge_retrieval_settings.py
|
||||
- app/gateway/routers/system_settings.py
|
||||
- app/gateway/deps.py
|
||||
|
||||
Gateway 范围与运行配置:
|
||||
|
||||
- app/gateway/llmwiki_rag.py
|
||||
- app/gateway/services.py
|
||||
|
||||
工具与 WeKnora 适配:
|
||||
|
||||
- packages/harness/deerflow/tools/builtins/llmwiki_reasoning_tools.py
|
||||
- packages/harness/deerflow/integrations/weknora/client.py
|
||||
- packages/harness/deerflow/tools/tools.py
|
||||
|
||||
模型提示与运行保护:
|
||||
|
||||
- packages/harness/deerflow/agents/lead_agent/agent.py
|
||||
- packages/harness/deerflow/agents/middlewares/smart_reasoning_retrieval_middleware.py
|
||||
|
||||
前端:
|
||||
|
||||
- frontend-web/src/components/workspace/settings/knowledge-retrieval-settings-page.tsx
|
||||
- frontend-web/src/components/workspace/settings/settings-dialog.tsx
|
||||
- frontend-web/src/components/workspace/input-box.tsx
|
||||
- frontend-web/src/core/threads/knowledge-retrieval-mode.ts
|
||||
- frontend-web/src/core/threads/hooks.ts
|
||||
- frontend-web/src/core/threads/smart-knowledge-retrieval.ts
|
||||
- frontend-web/src/components/workspace/messages/smart-knowledge-retrieval-trace.tsx
|
||||
- frontend-web/src/strategy-components/components/resource-management/llmwiki/WeKnoraSourceDrawer.tsx
|
||||
- frontend-web/src/pages/ChatPage.tsx
|
||||
- frontend-web/src/pages/AgentChatPage.tsx
|
||||
|
||||
## 17. 测试
|
||||
|
||||
设置和权限测试:
|
||||
|
||||
- tests/test_knowledge_retrieval_settings.py
|
||||
- tests/test_llmwiki_smart_reasoning.py
|
||||
|
||||
检索工具测试覆盖:
|
||||
|
||||
- 多查询合并和 matched_queries
|
||||
- 原始搜索全部失败与部分失败
|
||||
- 安全正则和执行超时
|
||||
- provider grep 原生合同校验
|
||||
- 404 和 405 时的兼容回退
|
||||
- 多知识库公平扫描
|
||||
- 兼容回退标记和结构化告警
|
||||
- 文档和切片跨范围拒绝
|
||||
- Wiki 按 mapping 选择本地或远端通道
|
||||
- Wiki 全部失败和部分失败
|
||||
- Wiki 长页面续读和 cursor 绑定
|
||||
- `result_items` 只包含本轮已授权的本地 mapping ID
|
||||
- provider knowledge base ID 和未知 provider 字段不会进入进度流
|
||||
- Wiki `page_ref` 与 mapping/slug 不一致时拒绝投影
|
||||
- 结果项数量和预览长度有界,并正确标记截断
|
||||
|
||||
Middleware 测试:
|
||||
|
||||
- tests/test_smart_reasoning_retrieval_middleware.py
|
||||
- 历史智能检索 call/result 配对清理
|
||||
- 当前轮证据保留
|
||||
- 非智能工具保留
|
||||
- OpenAI 原始 tool_calls 兼容
|
||||
- 第 40 次强制合成
|
||||
- 禁止第 41 次模型调用
|
||||
- 首轮原始 RAG 在 Wiki 搜索前执行,原始命中标题可参与 Wiki pattern 生成
|
||||
- Wiki 多页命中只读取模型选择的相关页面,不强制遍历全部页面
|
||||
- 同一轮对查询、正则、页面游标与文档分页去重,混合批次保留新的扩展动作
|
||||
- 长 Wiki 页面按 `next_cursor` 续读完成后才进入后续扩展或回答
|
||||
- 失败或无后续游标时安全终止,不形成注入循环
|
||||
|
||||
前端模式测试:
|
||||
|
||||
- frontend-web/src/core/threads/knowledge-retrieval-mode.test.ts
|
||||
- 模式归一化
|
||||
- 每次请求显式携带 mode
|
||||
- 管理员开关、选库状态和助手库混选校验
|
||||
- 检索中保持展开、完成和历史轨迹默认收起,并支持手动开合
|
||||
- 新轨迹结果可点击打开右侧来源抽屉
|
||||
- 缺少稳定标识的旧历史结果保持不可点击
|
||||
|
||||
2026-10-18 调整后的中间件专项为 34 passed;相关四组后端测试合计 145 passed。相关 Python 文件通过 Ruff。
|
||||
本次前端模式、目录解析、进度匹配、抽屉定位与检索轨迹专项测试为 31 passed,pnpm build 通过。
|
||||
|
||||
## 18. 验收标准
|
||||
|
||||
以下条件同时满足时,本功能可验收:
|
||||
|
||||
1. 新数据库无设置记录时,智能模式默认关闭。
|
||||
2. 非管理员不能修改全局开关。
|
||||
3. 关闭开关时,伪造 smart_reasoning 请求被后端拒绝。
|
||||
4. classic 在开关关闭和开启时都可正常使用。
|
||||
5. 全局开关开启时新会话默认 smart_reasoning,关闭时默认 classic;历史线程始终按 metadata 恢复显示。
|
||||
6. smart_reasoning 请求必须显式携带模式。
|
||||
7. 智能模式没有普通库、混选助手库或越权时不能启动模型。
|
||||
8. 每个工具调用重新校验当前权限。
|
||||
9. 用户只直接提问时,每轮新问题都依次完成原始 RAG、Wiki 搜索和至少一个相关 Wiki 页面的完整读取,并可按证据缺口继续扩展。
|
||||
10. 原始正则在 provider 不支持 grep 时自动使用有界兼容回退。
|
||||
11. Wiki 搜索按 mapping 独立选择完整本地镜像或 provider。
|
||||
12. 长 Wiki 页面能用 next_cursor 连续读完。
|
||||
13. 全部通道失败与零命中具有不同语义。
|
||||
14. 新用户轮不会把旧轮智能检索正文重新当作证据。
|
||||
15. 单个用户回合最多执行 40 次 Lead Agent 模型调用。
|
||||
16. 智能模式最终回答不强制编号引用。
|
||||
17. 智能检索过程在执行时展开,结束和历史重放时默认收起,并允许手动展开或收起。
|
||||
18. 带本地授权 mapping ID 和稳定来源标识的新检索结果可点击,并在聊天页右侧抽屉查看。
|
||||
19. 缺少稳定来源标识的旧历史结果不可点击,前端不按标题猜测来源。
|
||||
20. 进度流不泄露 provider knowledge base ID、未授权结果、提示词或模型内部思考。
|
||||
21. 设置、模式和 Wiki 生成相关新增配置均由数据库持久化,不写运行时配置文件。
|
||||
22. 同一用户回合不会重复执行相同 query、正则、Wiki `page_ref+cursor` 或文档分页;没有证据缺口时不会为了展示流程继续检索。
|
||||
23. 知识库证据作为事实基础,但不会关闭模型原有的归纳、解释和写作能力;通用背景与库内事实保持可辨认。
|
||||
|
||||
## 19. 已知边界
|
||||
|
||||
切片正则兼容扫描是有界扫描,不承诺遍历无限大的全部语料。 响应中的 scan_truncated 会明确表示达到扫描上限。 模型可据此改写查询,优先使用向量或混合检索缩小范围,再做精确深读。
|
||||
Wiki 搜索对候选页面和单页匹配正文也有明确上限。 Wiki 页面正文读取支持 continuation,因此已命中的长页面可以完整读完。 page_ref 和 continuation cursor 是内部不透明引用。
|
||||
它们用于绑定页面与续读位置,不替代每次工具调用的权限校验。 本实现不改变原有 WeKnora 数据管理、经典检索和现有会话记录。
|
||||
旧会话仍可展示当时保存的检索步骤和标题,但早期工具结果若没有本地 mapping ID 或稳定来源标识,就无法安全构造详情目标,因此保持不可点击。新产生的进度事件最多投影 20 个结果项;完整工具结果和回答证据仍按既有运行记录保存。
|
||||
@@ -11,11 +11,14 @@ This directory contains detailed documentation for the DeerFlow backend.
|
||||
| [CONFIGURATION.md](CONFIGURATION.md) | Configuration options |
|
||||
| [SETUP.md](SETUP.md) | Quick setup guide |
|
||||
|
||||
## Feature Documentation
|
||||
|
||||
| Document | Description |
|
||||
|----------|-------------|
|
||||
| [STREAMING.md](STREAMING.md) | Token-level streaming design: Gateway vs DeerFlowClient paths, `stream_mode` semantics, per-id dedup |
|
||||
## Feature Documentation
|
||||
|
||||
| Document | Description |
|
||||
|----------|-------------|
|
||||
| [ORDINARY_KNOWLEDGE_SMART_REASONING_RETRIEVAL_ZH.md](ORDINARY_KNOWLEDGE_SMART_REASONING_RETRIEVAL_ZH.md) | 普通知识库智能推理检索:管理员数据库开关、原文与 Wiki 首轮双检索、WeKnora 式多轮深读、权限与验收设计 |
|
||||
| [WIKI_HIERARCHY_AND_FOCUS_GENERATION_ZH.md](WIKI_HIERARCHY_AND_FOCUS_GENERATION_ZH.md) | Wiki 创建时按目录层级生成、按重点要求生成及异步协调设计 |
|
||||
| [KNOWLEDGE_SPACE_RETRIEVAL_AND_WIKI_EXPANSION_ZH.md](KNOWLEDGE_SPACE_RETRIEVAL_AND_WIKI_EXPANSION_ZH.md) | 知识空间严格优先检索、混合召回、Wiki 图扩展与可编辑全库摘要实现方案 |
|
||||
| [STREAMING.md](STREAMING.md) | Token-level streaming design: Gateway vs DeerFlowClient paths, `stream_mode` semantics, per-id dedup |
|
||||
| [FILE_UPLOAD.md](FILE_UPLOAD.md) | File upload functionality |
|
||||
| [PATH_EXAMPLES.md](PATH_EXAMPLES.md) | Path types and usage examples |
|
||||
| [summarization.md](summarization.md) | Context summarization feature |
|
||||
|
||||
@@ -143,6 +143,12 @@ sequenceDiagram
|
||||
|
||||
**`StreamBridge` 的存在价值**:当生产者(`run_agent` 任务)和消费者(HTTP 连接)在不同的 asyncio task 里运行时,需要一个可以跨 task 传递事件的中介。Queue 同时还承担断连重连的 buffer 和多订阅者的 fan-out。
|
||||
|
||||
### Gateway 多进程约束
|
||||
|
||||
当前 `MemoryStreamBridge` 和 `RunManager` 都只保存当前 Python 进程内的实时状态。首次 `POST /runs/stream` 会在同一个 uvicorn worker 中创建后台 run 并消费事件,因此首连可以正常工作;但响应里的 `Location`、显式 `/join` 和携带 `Last-Event-ID` 的恢复请求都是新的 HTTP 请求。多个 uvicorn worker 会独立调度这些请求,恢复请求一旦落到另一个 worker,该进程看不到原 worker 的 live run 和事件缓冲,只能把持久化 run 记录当作没有可恢复流并提前返回 `end`。持久化 checkpointer/RunStore 不能替代实时事件桥。
|
||||
|
||||
因此,使用内存 bridge 时 Gateway 必须运行 **1 个 worker**。两个生产离线启动脚本 `scripts/start_backend-online.sh` 与 `offline-backend-20260512/start_backend.sh` 都默认并校验 `GATEWAY_WORKERS=1`;显式配置更大值会拒绝启动,除非同时设置 `DEERFLOW_ALLOW_UNSAFE_MULTIWORKER=1` 确认接受丢失重连流的风险。真正支持多 worker 需要共享的 StreamBridge 与 live-run 控制面(发布、订阅、结束、取消和活跃 run 查询),不能只依赖负载均衡粘性或数据库中的最终状态。
|
||||
|
||||
---
|
||||
|
||||
## DeerFlowClient 路径:sync + in-process
|
||||
|
||||
@@ -0,0 +1,429 @@
|
||||
# 任务分析定时 · 对外接口对接文档
|
||||
|
||||
> 面向外部任务系统(下称「对方系统」)的开发者。覆盖任务接入、时间线详情拉取、
|
||||
> 总控问答、**定时数据交付**(任务 6 块 JSON)四组接口。智能体编排配置
|
||||
> (选业务链 / 执行频率)在 DeerFlow 任务分析页操作,对方系统无需开发。
|
||||
>
|
||||
> 相关实现:`app/gateway/routers/task_analysis.py`(任务/绑定/运行记录)、
|
||||
> `app/gateway/routers/task_analysis_qa.py`(问答)。
|
||||
|
||||
## 0. 通用约定
|
||||
|
||||
- **Base URL**:DeerFlow Gateway 地址(如 `http://<host>:8001`)。
|
||||
- **鉴权**:所有接口需带 `Authorization: Bearer <token>` 请求头(token 获取方式见登录对接,本文不重复)。
|
||||
- **Content-Type**:`application/json`(SSE 接口除外,响应为 `text/event-stream`)。
|
||||
- **时间格式**:ISO 8601 字符串(含时区,如 `2026-09-10T10:14:55+08:00`)。
|
||||
- **两类任务标识**:
|
||||
- `source_task_id`:对方系统的任务号(创建时由你们传入);
|
||||
- `task_id`:DeerFlow 内部任务 id(**创建接口的响应里返回**,问答接口使用它)。
|
||||
创建任务后请保存响应中的 `task_id`;也可随时通过时间线详情接口重新取到。
|
||||
|
||||
## 1. 任务接入
|
||||
|
||||
### 1.1 创建任务
|
||||
|
||||
对方系统创建任务完成后调用,DeerFlow 落一条任务记录(初始未绑定分析方案)。
|
||||
|
||||
```
|
||||
POST /api/task-analysis/external/tasks
|
||||
```
|
||||
|
||||
请求体:
|
||||
|
||||
```json
|
||||
{
|
||||
"source_task_id": "TS-2026-0147",
|
||||
"name": "东南亚舆论环境监测",
|
||||
"description": "持续跟踪东南亚重点渠道舆论动向,输出结构化分析详情。"
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| source_task_id | string(≤128) | 是 | 对方任务号,全局唯一 |
|
||||
| name | string(≤512) | 是 | 任务名称 |
|
||||
| description | string(≤8192) | 否 | 任务简介。**定时执行时注入各智能体的任务上下文以此为准**(存量快照,执行时不会回拉) |
|
||||
|
||||
响应 `200`:
|
||||
|
||||
```json
|
||||
{
|
||||
"task_id": "ta-TS-2026-0147",
|
||||
"default_chain_id": "ta-demo-chain"
|
||||
}
|
||||
```
|
||||
|
||||
- `task_id`:DeerFlow 内部任务 id,**问答接口需要它**,请保存。
|
||||
- `default_chain_id`:DeerFlow 侧最近一次绑定使用的业务链 id,可作为你们界面上绑定流程的默认选项;无绑定为 `null`。
|
||||
|
||||
错误:`409` —— `source_task_id` 已存在。
|
||||
|
||||
### 1.2 删除任务
|
||||
|
||||
对方系统删除任务时同步调用,**彻底删除**:任务、绑定、全部执行记录、过程消息与时间线版本一并清除(不可恢复)。
|
||||
|
||||
```
|
||||
DELETE /api/task-analysis/external/tasks/{source_task_id}
|
||||
```
|
||||
|
||||
响应 `200`:`{"deleted": true, "task_id": "..."}`。错误:`404` —— 任务不存在。
|
||||
|
||||
## 2. 任务详情(时间线)
|
||||
|
||||
对方系统详情页的数据源。每次定时执行成功会落一期**时间线版本**(含各智能体产出),按时间倒序返回。
|
||||
|
||||
```
|
||||
GET /api/task-analysis/external/tasks/{source_task_id}/detail?limit=20&offset=0
|
||||
```
|
||||
|
||||
响应 `200`:
|
||||
|
||||
```json
|
||||
{
|
||||
"task": {
|
||||
"task_id": "ta-TS-2026-0147",
|
||||
"source_task_id": "TS-2026-0147",
|
||||
"name": "东南亚舆论环境监测"
|
||||
},
|
||||
"binding": {
|
||||
"chain_id": "ta-demo-chain",
|
||||
"cron_expr": "0 9 * * *",
|
||||
"schedule_type": "cron",
|
||||
"enabled": true
|
||||
},
|
||||
"timeline": [
|
||||
{
|
||||
"id": "…",
|
||||
"external_task_id": "ta-TS-2026-0147",
|
||||
"run_id": "tar-…",
|
||||
"blocks": {
|
||||
"eightbf-roundtable-intent": {
|
||||
"name": "8BF 任务理解",
|
||||
"seat_agent_id": "eightbf-roundtable-intent",
|
||||
"content": "(该智能体本次产出的 Markdown 正文)",
|
||||
"format": "markdown"
|
||||
},
|
||||
"c0a8799da8724e179e086ea9ca723967": { "name": "策略分析师", "…": "…" }
|
||||
},
|
||||
"created_at": "2026-09-10T10:15:13+08:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- `binding`:当前绑定信息(未绑定为 `null`,此时 `timeline` 不会有新版本)。
|
||||
`schedule_type` 为执行方式:`cron` = 按 `cron_expr` 频率定时执行;
|
||||
`once` = 只执行一次(创建后立即执行,之后不再自动执行,`cron_expr` 不生效)。
|
||||
- `timeline[].blocks`:**每个参与执行的智能体一块**,key 为该智能体的 agent_id,
|
||||
`name` 为执行时的名称快照,`content` 为其交付正文(Markdown)。
|
||||
按你们的详情分区规则渲染即可(如按 block 顺序分栏)。
|
||||
- 分页:`limit`(默认 20)/ `offset`,按 `created_at` 倒序。
|
||||
|
||||
## 3. 总控问答(详情页对话)
|
||||
|
||||
详情页的对话入口:用户提问 → DeerFlow 总控按任务绑定的编排给对应智能体派活 →
|
||||
智能体执行 → 总控汇总结论。一次问答会话记为一条「手动执行」记录,过程消息可查。
|
||||
|
||||
### 3.1 发起问答会话(init)
|
||||
|
||||
```
|
||||
POST /api/task-analysis/tasks/{task_id}/qa/init
|
||||
```
|
||||
|
||||
> 注意:此处 `{task_id}` 为 **DeerFlow 内部任务 id**(创建接口响应里的 `task_id`)。
|
||||
|
||||
请求体:空(无需参数)。
|
||||
|
||||
响应 `200`:
|
||||
|
||||
```json
|
||||
{
|
||||
"run_id": "tar-0743654e-…",
|
||||
"coordinator": "roundtable-coordinator",
|
||||
"thread_ids": {
|
||||
"eightbf-roundtable-intent": "ta-seat-…-…",
|
||||
"c0a8799da8724e179e086ea9ca723967": "ta-seat-…-…",
|
||||
"roundtable-coordinator": "ta-seat-…-…"
|
||||
},
|
||||
"seats": [
|
||||
{ "agent_id": "eightbf-roundtable-intent", "name": "8BF 任务理解" },
|
||||
{ "agent_id": "c0a8799da8724e179e086ea9ca723967", "name": "策略分析师" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- `run_id`:本次问答会话的运行记录 id(查历史 / 应用方向都用它)。
|
||||
- `thread_ids`:**会话线程映射,请保存并在后续每轮 stream 原样传回**(会话不跨请求保存在服务端)。
|
||||
- init 时已自动把任务上下文(名称/简介/编排/历次方向备注)注入总控,之后每轮消息传用户原话即可。
|
||||
|
||||
错误:`400` —— 任务未绑定分析方案 / 绑定链没有可调度席位。
|
||||
|
||||
### 3.2 发送消息(stream,SSE)
|
||||
|
||||
```
|
||||
POST /api/task-analysis/tasks/{task_id}/qa/stream
|
||||
Content-Type: application/json
|
||||
Accept: text/event-stream
|
||||
```
|
||||
|
||||
请求体:
|
||||
|
||||
```json
|
||||
{
|
||||
"run_id": "tar-0743654e-…",
|
||||
"message": "请让两个席位各自分析当前的主要风险点,然后给我综合结论。",
|
||||
"thread_ids": { "…init 返回的映射原样传回…" }
|
||||
}
|
||||
```
|
||||
|
||||
响应为 SSE 流,每帧 `data: {JSON}\n\n`。**帧类型**:
|
||||
|
||||
| 帧 | 触发时机 | 关键字段 |
|
||||
|---|---|---|
|
||||
| LangGraph 上游事件(无 `event` 字段的多种帧) | 总控正在生成(token 流) | 透传自运行时,可用于打字机效果 |
|
||||
| `{"event": "leader", "status": [[seat_id, task], …], …}` | 总控一轮结束 | `status` 为派活表(二维数组)时表示已派活 |
|
||||
| `{"event": "leader", "status": "clarification", …}` | 总控需要澄清 | `question` / `options`,前端展示追问,等用户补充后发下一轮 |
|
||||
| `{"event": "leader", "status": [], "content": …}` | 总控给出结论(共识) | `content` 为结论正文 |
|
||||
| `{"event": "seat", "agent": id, "name": …, "content": …}` | 某智能体执行完成 | `content` 为该智能体交付正文 |
|
||||
| `{"event": "round_done", "consensus": true/false}` | 本轮结束 | `consensus=true` 会话已收口(run 置 succeeded);`false`(如澄清)可继续发下一轮 |
|
||||
|
||||
一轮完整时序:`leader(派活)→ 若干 seat(交付)→ leader(综合结论)→ round_done`。
|
||||
|
||||
约定:
|
||||
- 澄清(`consensus=false, kind=clarification`):继续对**同一 run_id** 发下一轮 stream;
|
||||
- 共识(`consensus=true`):run 已自动收口,如需再问请新开一次 `qa/init`;
|
||||
- 客户端中断流不影响服务端继续执行(消息仍会落库,可从历史接口补读)。
|
||||
|
||||
错误:`404` 会话不存在;`409` 会话已收口(需重新 init)或 `thread_ids` 缺失(重新 init)。
|
||||
|
||||
### 3.3 应用调整方向(可选)
|
||||
|
||||
问答结束后,若用户确认「把本次调整应用到后续执行」,调用本接口把结论沉淀为
|
||||
**执行方向备注**——该备注会注入此任务后续**每次定时执行**各智能体的任务上下文。
|
||||
|
||||
```
|
||||
POST /api/task-analysis/qa/{run_id}/apply-direction
|
||||
```
|
||||
|
||||
请求体:`{"note": "后续执行请重点关注台湾海峡北段动态,风险输出按高/中/低分级。"}`
|
||||
|
||||
响应 `200`:`{"applied": true, "direction_notes": ["…", "…"]}`(全部累计备注)。
|
||||
|
||||
### 3.4 对话历史查询
|
||||
|
||||
按运行记录查询完整过程消息(总控 + 各智能体),供会话回放或补读。
|
||||
|
||||
```
|
||||
GET /api/task-analysis/external/runs/{run_id}/messages?seat=&limit=500
|
||||
```
|
||||
|
||||
- `seat` 可选:传智能体 agent_id 只看该席位;传 `__coordinator__` 只看总控;不传看全部。
|
||||
- 响应:`{"run": {"id", "trigger", "status", "started_at", "finished_at"}, "messages": […]}`
|
||||
- 消息字段:`seq`(顺序)、`seat_agent_id`、`stage_index`、`event_type`
|
||||
(`input` 输入 / `llm.ai` 智能体输出 / `artifact` 产物备注)、`content`。
|
||||
|
||||
## 4. 定时数据交付(任务 6 块数据)
|
||||
|
||||
按任务展示 **6 块结构化数据**(相当于 6 个页面分区,每块含多个部分),每块一个
|
||||
JSON 对象。DeerFlow 只负责数据管道与调度:智能体在定时执行过程中把成果提交进
|
||||
数据块,归配智能体 / 业务链的配置由管理方维护。
|
||||
|
||||
**口径要点**:
|
||||
|
||||
- 块 key 定死 `block_1`~`block_6`,标题默认「数据块一~六」,可经 4.4 修改;
|
||||
存量 5 块任务在读接口 / 首次提交时懒补 `block_6`,已改过的标题不覆盖;
|
||||
- payload 不做 schema 校验(结构会变),只要求是 **JSON 对象**;
|
||||
- 不定席位↔块映射:多个智能体可各写一个块的不同部分,一个智能体也可写多个块;
|
||||
- 同一块多次提交按「**顶层 key 合并、同 key 后写覆盖**」合并——某个部分一提交,
|
||||
立刻体现在任务的当前数据里(实时更新);
|
||||
- **不挂时间轴**:一个任务的数据只有一份「当前状态」,所有提交(定时轮次、
|
||||
手动触发、对话修复)都合并进它,越提交越新;
|
||||
- 调度不管上一轮跑没跑完:到期照常触发,**同任务允许多轮并行**,不跳过不顺延;
|
||||
多轮提交按流水顺序收敛到同一份数据;
|
||||
- 首次提交时若任务还没有数据承载记录,自动创建一条(`trigger=external`),
|
||||
保证写入永远成功。
|
||||
|
||||
### 4.1 数据块定义列表
|
||||
|
||||
```
|
||||
GET /api/task-analysis/external/tasks/{source_task_id}/blocks
|
||||
```
|
||||
|
||||
响应 `200`:
|
||||
|
||||
```json
|
||||
{
|
||||
"task": {"task_id": "ta-TS-2026-0147", "source_task_id": "TS-2026-0147", "name": "东南亚舆论环境监测"},
|
||||
"blocks": [
|
||||
{"block_key": "block_1", "title": "数据块一", "sort_order": 1},
|
||||
{"block_key": "block_2", "title": "数据块二", "sort_order": 2},
|
||||
{"block_key": "block_3", "title": "舆情热点", "sort_order": 3},
|
||||
{"block_key": "block_4", "title": "数据块四", "sort_order": 4},
|
||||
{"block_key": "block_5", "title": "数据块五", "sort_order": 5},
|
||||
{"block_key": "block_6", "title": "数据块六", "sort_order": 6}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
固定 6 条,按 `sort_order` 升序返回;`title` 为修改后的标题(见 4.4)。
|
||||
|
||||
### 4.2 当前数据状态(兼容旧路径 periods)
|
||||
|
||||
```
|
||||
GET /api/task-analysis/external/tasks/{source_task_id}/periods
|
||||
```
|
||||
|
||||
时间轴已去掉:任务的数据只有一份当前状态,本接口固定返回**唯一一条**当前数据
|
||||
承载记录(`periods` 数组长度恒为 1,`is_current` 恒为 `true`),供消费方感知
|
||||
数据更新时间。响应 `200`:
|
||||
|
||||
```json
|
||||
{
|
||||
"task": {"task_id": "…", "source_task_id": "…", "name": "…"},
|
||||
"current_period_index": 3,
|
||||
"total": 1,
|
||||
"periods": [
|
||||
{
|
||||
"period_index": 3,
|
||||
"opened_at": "2026-09-14T15:00:00+08:00",
|
||||
"trigger": "cron",
|
||||
"run_id": "tar-…",
|
||||
"is_current": true,
|
||||
"blocks": {
|
||||
"block_1": {"updated_at": "2026-09-14T15:03:12+08:00"},
|
||||
"block_2": {"updated_at": null},
|
||||
"block_3": {"updated_at": null},
|
||||
"block_4": {"updated_at": null},
|
||||
"block_5": {"updated_at": null},
|
||||
"block_6": {"updated_at": null}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- 各块 `updated_at` 为 `null` 表示该块尚无数据;
|
||||
- 首条承载记录由首次数据提交自动创建(`trigger=external`);存量任务沿用
|
||||
历史期号(`period_index` 数值仅作标识,无时间轴含义)。
|
||||
|
||||
### 4.3 单期数据详情
|
||||
|
||||
```
|
||||
GET /api/task-analysis/external/tasks/{source_task_id}/periods/{period}
|
||||
```
|
||||
|
||||
`{period}` 传期号数字(如 `2`)或 `latest`(当前最新一期)。响应 `200`:
|
||||
|
||||
```json
|
||||
{
|
||||
"task": {"task_id": "…", "source_task_id": "…", "name": "…"},
|
||||
"period": {
|
||||
"period_index": 2,
|
||||
"opened_at": "2026-09-14T12:00:00+08:00",
|
||||
"trigger": "cron",
|
||||
"run_id": "tar-…",
|
||||
"is_current": false
|
||||
},
|
||||
"blocks": [
|
||||
{
|
||||
"block_key": "block_1",
|
||||
"title": "数据块一",
|
||||
"sort_order": 1,
|
||||
"payload": {"热点列表": ["…"], "统计": {"提及量": 128}},
|
||||
"updated_at": "2026-09-14T12:04:55+08:00"
|
||||
},
|
||||
{"block_key": "block_2", "title": "数据块二", "sort_order": 2, "payload": null, "updated_at": null}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- `blocks` 固定 6 条、按 `sort_order` 升序;尚未提交的块 `payload` / `updated_at`
|
||||
为 `null`;
|
||||
- `payload` 即该块的**合并结果**(多次提交按顶层 key 合并、同 key 后写覆盖),
|
||||
结构由智能体产出决定,请按 JSON 对象渲染;
|
||||
- `{period}` 推荐传 `latest`(当前数据);传数字期号可访问存量任务的历史
|
||||
遗留数据(时间轴去掉前的旧期,仅供排查)。
|
||||
|
||||
错误:`404` —— 期不存在;`400` —— `period` 既不是数字也不是 `latest`。
|
||||
|
||||
### 4.4 修改块标题 / 排序(管理端)
|
||||
|
||||
块标题支持后续修改(页面 + API)。`{task_id}` 为 **DeerFlow 内部任务 id**(创建
|
||||
接口响应里保存的那个)。
|
||||
|
||||
```
|
||||
PUT /api/task-analysis/tasks/{task_id}/blocks
|
||||
```
|
||||
|
||||
请求体(只传要改的块;块集合固定 6 条,不能增删):
|
||||
|
||||
```json
|
||||
{
|
||||
"blocks": [
|
||||
{"block_key": "block_3", "title": "舆情热点"},
|
||||
{"block_key": "block_1", "sort_order": 9}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
响应 `200`:`{"blocks": […]}`(修改后的完整定义列表,形状同 4.1)。
|
||||
错误:`400` —— `block_key` 不在 `block_1`~`block_6` 内。
|
||||
|
||||
### 4.5 演示数据(已预存)
|
||||
|
||||
开发环境已预存一个演示任务,可直接用于联调:
|
||||
|
||||
- 任务号 `source_task_id = BUSINESS-OVERVIEW-DEMO`;`block_1` 标题「商业态势总览」;
|
||||
- 预存时按 13 期小时刻度写入(时间轴去掉前的演示口径);当前对外仅暴露最新一期
|
||||
的 `block_1` payload = 页面整卡数据(`pageTitle` / `briefs` / `objectives` /
|
||||
`events` / `businessNarratives` / `insights` / `chart` / `snapshotName` 等,
|
||||
与页面 mock 快照同构);`block_2`~`block_6` 暂无数据;
|
||||
- 预存脚本:`scripts/seed_business_overview_demo.py`(可重复运行,幂等重建)。
|
||||
|
||||
### 4.6 智能体数据提交(内部,供参考)
|
||||
|
||||
智能体侧由内置技能 `task-data-submit`(`skills/public/task-data-submit/`)调以下
|
||||
接口完成入库,**对方系统无需调用**,此处仅供联调排查参考:
|
||||
|
||||
```
|
||||
POST /api/task-analysis/data/submit
|
||||
```
|
||||
|
||||
请求体:`{"task_id": "<source_task_id>", "block_key": "block_2", "data": {…JSON 对象…}}`。
|
||||
响应 `200`:`{"source_task_id", "task_id", "block_key", "period_index", "updated_at", "keys"}`。
|
||||
错误:`400` —— 块 key 非法 / payload 不是对象;`404` —— 任务不存在;`422` ——
|
||||
请求体不是合法 JSON 对象。
|
||||
|
||||
## 5. 典型对接流程
|
||||
|
||||
```
|
||||
对方系统创建任务
|
||||
└─ POST /external/tasks ──► 保存 task_id(+ default_chain_id)
|
||||
│
|
||||
│ (DeerFlow 管理页配置:选业务链 + 执行频率 —— 无需开发)
|
||||
▼
|
||||
按 cron 自动执行(多轮并行,产出时间线版本)
|
||||
├─ 详情页定期/手动 GET /external/tasks/{source_task_id}/detail ──► 渲染 blocks
|
||||
└─ 数据交付(任务 6 块 JSON,见第 4 章)
|
||||
├─ GET /external/tasks/{source_task_id}/periods ──► 当前数据状态(兼容路径)
|
||||
└─ GET /external/tasks/{source_task_id}/periods/latest ──► 6 块完整 JSON
|
||||
│
|
||||
▼
|
||||
详情页问答(可选)
|
||||
├─ POST /tasks/{task_id}/qa/init ──► 保存 run_id + thread_ids
|
||||
├─ POST /tasks/{task_id}/qa/stream(每轮,SSE 渲染对话与席位交付)
|
||||
├─ (可选)POST /qa/{run_id}/apply-direction ──► 调整注入后续执行
|
||||
└─ GET /external/runs/{run_id}/messages ──► 历史回放
|
||||
|
||||
对方系统删除任务
|
||||
└─ DELETE /external/tasks/{source_task_id} ──► DeerFlow 侧级联清除
|
||||
```
|
||||
|
||||
## 6. 错误码汇总
|
||||
|
||||
| 状态 | 场景 |
|
||||
|---|---|
|
||||
| 400 | 任务未绑定分析方案 / 链无席位 / 请求参数非法 / 数据块 key 非法 |
|
||||
| 401 | 未带 token 或 token 失效 |
|
||||
| 404 | 任务 / 会话 / 运行记录 / 期不存在 |
|
||||
| 409 | `source_task_id` 重复创建;会话已收口或线程映射缺失;执行被跳过(业务链已删除/停用) |
|
||||
@@ -0,0 +1,253 @@
|
||||
# Wiki 知识库参考目录与重点要求生成设计
|
||||
|
||||
> 状态:已实现,与当前代码一致
|
||||
> 更新日期:2026-10-19
|
||||
|
||||
## 1. 目标与语义边界
|
||||
|
||||
创建或编辑 Wiki 知识库时提供两个互相独立的选填项:
|
||||
|
||||
- **参考目录**:管理员或知识库所有者给出一至三级目录,帮助生成模型理解期望的信息组织方式。它不是目录白名单,也不是必须逐项套用的固定模板。
|
||||
- **重点要求**:用户描述允许抽取、组织和呈现的重点范围。系统把它作为严格的内容范围约束,并在生成完成后清理重点范围之外的自动生成页面。
|
||||
|
||||
模型可以依据原始资料新增、合并、拆分、改名、调整或省略参考目录。内容与参考目录冲突时,以资料实际内容和清晰表达为准。系统不得为了匹配参考目录预建空目录、强制移动页面、删除目录或删除参考目录之外的有效结构。
|
||||
|
||||
两项均可留空。全部留空时沿用 WeKnora 原有自动生成方式;原有 `content_instructions` 和 `extraction_instructions` 高级指令继续有效。本功能的权威配置全部保存在数据库,不写入 `config.yaml`、JSON 或其他运行时配置文件。
|
||||
|
||||
## 2. 用户交互
|
||||
|
||||
### 2.1 参考目录
|
||||
|
||||
前端支持两种输入格式,最多三级,不能混用。
|
||||
|
||||
Markdown 标题:
|
||||
|
||||
```markdown
|
||||
# 政策法规
|
||||
## 国内政策
|
||||
### 2026 年
|
||||
## 国际规则
|
||||
# 操作指南
|
||||
## 申报流程
|
||||
```
|
||||
|
||||
每级两个空格的缩进文本:
|
||||
|
||||
```text
|
||||
政策法规
|
||||
国内政策
|
||||
2026 年
|
||||
国际规则
|
||||
操作指南
|
||||
申报流程
|
||||
```
|
||||
|
||||
无缩进的普通逐行文本按一级参考目录处理。用户也可以下载示例文件,修改后导入 `.md` 或 `.txt` 文件。表单实时显示树形预览、节点数、规范化长度和逐行错误,并明确提示模型可以按实际资料调整结构。
|
||||
|
||||
### 2.2 重点要求
|
||||
|
||||
重点要求是最多 2000 个字符的自由文本,例如:
|
||||
|
||||
```text
|
||||
重点提取适用对象、申请条件、时间期限、审批部门和例外情形;
|
||||
涉及不同年份时优先说明最新规则,并保留历史差异。
|
||||
```
|
||||
|
||||
重点要求限制自动生成页面的内容范围,同时不允许模型降低事实依据要求或凭空补全资料。只填写重点、不填写参考目录时,系统保留 WeKnora 自动生成的目录结构,只过滤与重点无关的自动生成页面。
|
||||
|
||||
### 2.3 编辑语义
|
||||
|
||||
- 两项都支持创建后修改。
|
||||
- 清空参考目录只表示不再向后续生成提供该参考,不删除或移动已经存在的远端目录和页面。
|
||||
- 清空重点要求后,后续生成不再追加重点范围提示,也不再运行重点页面过滤。
|
||||
- 保存时只更新请求中明确出现的 Wiki 配置字段,未出现字段保持原值。
|
||||
|
||||
### 2.4 生成后的智能检索与查看
|
||||
|
||||
参考目录和重点要求只影响 Wiki 的生成与整理,不要求用户在问答时重复这些指令。管理员开启普通知识库智能检索、且用户在会话中选择该模式后,用户可以直接提问;运行时先检索原始文档分片,再根据原始命中检索 Wiki 并读取相关页面,之后只针对证据缺口扩展检索。Wiki 与原始文档冲突时以原文为准。最终回答不强制输出 `[1]`、`[2]` 等编号或编号参考文献列表。
|
||||
|
||||
智能检索过程在执行时展开,完成后默认收起。用户展开后可以点击带稳定来源标识的 Wiki 页面或原始文档结果,在聊天页右侧来源抽屉查看详情。详情读取仍按当前用户和本地 mapping 重新鉴权。
|
||||
|
||||
## 3. API 合同
|
||||
|
||||
创建接口:
|
||||
|
||||
```http
|
||||
POST /api/llmwiki/knowledge-bases
|
||||
```
|
||||
|
||||
编辑接口:
|
||||
|
||||
```http
|
||||
PATCH /api/llmwiki/knowledge-bases/{mapping_id}
|
||||
```
|
||||
|
||||
两者的 `wiki_config` 使用同一结构:
|
||||
|
||||
```json
|
||||
{
|
||||
"content_instructions": "原有内容生成要求,可选",
|
||||
"extraction_instructions": "原有信息抽取要求,可选",
|
||||
"directory_paths": [
|
||||
["政策法规"],
|
||||
["政策法规", "国内政策"],
|
||||
["政策法规", "国内政策", "2026 年"]
|
||||
],
|
||||
"focus_instructions": "重点说明适用对象和截止时间"
|
||||
}
|
||||
```
|
||||
|
||||
`parent_directories` 作为旧版一级目录兼容字段继续接受。无论使用 `directory_paths` 还是 `parent_directories`,目录都只是生成提示中的参考结构,不能触发远端目录预建或生成后目录整理。若同一请求同时提供两个字段,两者的根目录必须完全一致;新客户端统一使用 `directory_paths`。
|
||||
|
||||
知识库详情响应中的 `wiki_config` 返回:
|
||||
|
||||
- 原始内容指令、原始抽取指令;
|
||||
- `directory_paths`、兼容的 `parent_directories`;
|
||||
- `focus_instructions`;
|
||||
- 配置 schema、revision 和应用状态;
|
||||
- 参考目录提示写入 WeKnora 指令的覆盖状态及节点计数。
|
||||
|
||||
## 4. 参考目录规范化与校验
|
||||
|
||||
前后端执行一致的基础校验,后端是最终安全边界:
|
||||
|
||||
| 规则 | 限制 |
|
||||
|---|---:|
|
||||
| 最大深度 | 3 级 |
|
||||
| 最大节点数 | 100 |
|
||||
| 单个目录名 | 100 个字符 |
|
||||
| 规范化目录总长度 | 6000 个字符 |
|
||||
| 重点要求 | 2000 个字符 |
|
||||
|
||||
目录名称先执行 Unicode NFKC 规范化和首尾空白清理,并拒绝空名称、`.`、`..`、路径分隔字符、控制字符、同一父目录下的等价重复名称、跨级跳转以及非法缩进。后端将缺失的父路径自动补齐,并按父节点先于子节点的稳定顺序保存完整路径集合。
|
||||
|
||||
这些规则只保证参考数据能够稳定存储并安全写入提示词,不把参考目录变成业务上的硬约束。
|
||||
|
||||
## 5. 有效提示编译
|
||||
|
||||
内容生成指令依次合并:
|
||||
|
||||
1. 用户原始 `content_instructions`;
|
||||
2. 重点要求及“只生成直接相关且有资料支撑页面”的严格内容范围;
|
||||
3. 参考目录树及“可以按资料新增、合并、拆分、改名、调整或省略目录”的软性组织指引。
|
||||
|
||||
信息抽取指令依次合并:
|
||||
|
||||
1. 用户原始 `extraction_instructions`;
|
||||
2. 重点要求及“只抽取直接相关、可追溯事实”的严格内容范围。
|
||||
|
||||
原始指令和重点要求不会被静默截断。基础内容超过 4000 字符时返回 422。参考目录提示按父子顺序装入剩余预算;无法放入全部节点时,详情响应返回 `partial` 覆盖和实际节点数。未写入的节点仍保存在数据库中,但不执行任何本地目录补建或强制归档。
|
||||
|
||||
## 6. 生成与生成后处理
|
||||
|
||||
### 6.1 创建和编辑时不操作目录
|
||||
|
||||
创建或编辑知识库时,Gateway 只把编译后的提示传给 WeKnora,并把原始配置保存到本地数据库。无论输入来自 `directory_paths` 还是兼容字段 `parent_directories`,都不得:
|
||||
|
||||
- 预建参考目录骨架;
|
||||
- 因参考目录变更而移动 Wiki 页面或文件夹;
|
||||
- 删除参考目录之外的文件夹、目录页或普通页面;
|
||||
- 周期性扫描并强制恢复参考目录。
|
||||
|
||||
WeKnora 生成出的目录结构就是当前资料对应的有效结构,可在后续生成中自然调整。
|
||||
|
||||
### 6.2 重点范围过滤
|
||||
|
||||
只有非空的重点要求需要生成后协调。WeKnora 的知识库级 `is_processing` 可能早于 Wiki 生成结束变为 `false`,因此协调器还会检查每篇文档的 `pending_subtasks_count` 和 finalizing/processing 状态;确认全部结束后再执行重点判定。
|
||||
|
||||
重点判定遵循以下规则:
|
||||
|
||||
- 页面主体直接提供重点所需的事实、指标、事件、关系、责任或实施细节时保留;
|
||||
- 只作为背景出现、与重点无关的人物、机构、地点或主题自动生成页面可以删除;
|
||||
- 人工维护页不因重点过滤被删除;
|
||||
- 判定按有界批次执行,漏项会补判,不能把“没有返回”解释成删除;
|
||||
- 肯定决定可按配置签名和正文指纹写入页面元数据,供相同内容重跑复用;
|
||||
- 所有判定完成前不做删除;判定缺项、格式非法、标记写入失败或模型不可用时,本轮不做破坏性变更;
|
||||
- 重点过滤始终保留 WeKnora 已生成的目录结构,不移动页面,不删除文件夹或目录页。
|
||||
|
||||
参考目录本身不触发 mutation-settle 或周期协调;只有重点过滤和本地 Wiki 索引同步按各自条件调度。
|
||||
|
||||
## 7. 数据库存储
|
||||
|
||||
迁移 `20261014_02_llmwiki_generation_config.py` 在 `llmwiki_knowledge_bases` 增加:
|
||||
|
||||
| 字段 | 用途 |
|
||||
|---|---|
|
||||
| `wiki_directory_paths` | 完整一至三级参考目录路径数组 |
|
||||
| `wiki_focus_instructions` | 原始重点要求 |
|
||||
| `wiki_content_instructions_raw` | 原始内容生成要求 |
|
||||
| `wiki_extraction_instructions_raw` | 原始信息抽取要求 |
|
||||
| `wiki_generation_managed` | 是否由当前 Wiki 配置流程管理 |
|
||||
| `wiki_config_schema_version` | 当前 schema 版本;参考目录语义为版本 3 |
|
||||
| `wiki_config_revision` | 每次有效配置变更递增 |
|
||||
| `wiki_config_apply_status` | `active` 或 `not_applicable` |
|
||||
| `wiki_config_apply_error` | 保留的应用错误字段 |
|
||||
|
||||
旧字段 `wiki_parent_directories` 继续保存根目录投影,兼容既有数据和旧客户端,但不保留旧的强制目录行为。SQL、内存存储与映射响应使用相同字段语义。上述生成选项、原始指令、应用状态和 revision 均以数据库记录为权威来源;前端本地状态只用于未提交表单。
|
||||
|
||||
后台协调器会幂等升级版本 2 的受管 Wiki:从数据库保存的原始指令、重点要求和参考目录重新编译有效提示,远端更新成功后再写入版本 3 并增加 revision;失败时保留版本 2、记录错误并稍后重试。未知版本或缺少原始配置的旧记录不会被自动覆盖。
|
||||
|
||||
页面级重点保留判定存放在 WeKnora 的数据库页元数据中,不使用本地文件。它只缓存某一重点版本和正文指纹下的肯定决定,不替代管理员原始配置。
|
||||
|
||||
## 8. 一致性和失败处理
|
||||
|
||||
### 8.1 创建
|
||||
|
||||
创建顺序为:校验与编译、创建 WeKnora 知识库、写入本地映射。参考目录不增加远端目录初始化步骤。WeKnora 创建成功但数据库写入失败时,系统尝试删除新建的远端知识库,避免留下无本地映射的孤儿库。
|
||||
|
||||
### 8.2 编辑
|
||||
|
||||
编辑前读取当前远端配置和数据库原始配置:
|
||||
|
||||
- 参考目录或指令校验失败:不修改远端和数据库;
|
||||
- WeKnora 更新失败:数据库保持原值;
|
||||
- WeKnora 更新成功但数据库提交失败:尝试恢复 WeKnora 之前的名称、描述和两段有效指令,然后把错误返回调用方。
|
||||
|
||||
目录参考的保存失败不会留下预建目录,因为该流程不调用远端目录创建接口。
|
||||
|
||||
### 8.3 权限
|
||||
|
||||
沿用知识库原有授权边界:只有知识库所有者或管理员可创建和修改配置;公开浏览接口只返回调用者有权查看的内容。
|
||||
|
||||
## 9. 主要实现位置
|
||||
|
||||
后端:
|
||||
|
||||
- `app/gateway/wiki_generation_config.py`:重点规范化、参考目录提示与有效指令编译;
|
||||
- `app/gateway/wiki_directory_constraints.py`:输入规范化和重点范围过滤,不负责强制目录整理;
|
||||
- `app/gateway/routers/llmwiki.py`:创建、编辑、响应投影和补偿事务;
|
||||
- `app/gateway/llmwiki_index_scheduler.py`:重点过滤与本地 Wiki 索引的异步协调;
|
||||
- `packages/harness/deerflow/persistence/llmwiki/`:数据库和内存存储;
|
||||
- `packages/harness/deerflow/persistence/migrations/versions/20261014_02_llmwiki_generation_config.py`:数据库迁移。
|
||||
|
||||
前端:
|
||||
|
||||
- `src/core/llmwiki/directory-tree.ts`:参考目录文本解析、校验和格式化;
|
||||
- `src/strategy-components/api/llmwiki.ts`:API 类型和请求;
|
||||
- `src/strategy-components/components/resource-management/llmwiki/WeKnoraKnowledgeWorkspace.tsx`:创建、编辑、示例下载、文件导入和树预览。
|
||||
|
||||
## 10. 测试与验收
|
||||
|
||||
自动化至少覆盖:
|
||||
|
||||
- Markdown、缩进文本、路径规范化、重复和越界校验;
|
||||
- 重点指令、参考目录和原始高级指令的合并、4000 字符预算和目录提示覆盖计数;
|
||||
- 创建与编辑会持久化参考目录,但不调用远端文件夹创建、页面移动或目录删除接口;
|
||||
- 新字段和兼容字段都不会启动目录协调;
|
||||
- 重点页面过滤、失败前不破坏及幂等重跑;
|
||||
- 编辑时远端失败不写本地,数据库失败恢复远端指令;
|
||||
- SQL/内存存储字段往返、迁移单头和迁移兼容性;
|
||||
- 前端参考目录解析、示例文件和生产构建。
|
||||
|
||||
验收时至少使用下列组合:
|
||||
|
||||
1. 两项均为空,行为与原有 Wiki 创建一致;
|
||||
2. 只给参考目录,模型可以采用、补充、合并或调整目录,系统不创建固定骨架,也不移动或删除生成结构;
|
||||
3. 只给重点要求,只保留重点范围内且有来源依据的自动生成页面,同时目录保持不变;
|
||||
4. 两项同时填写,参考目录提供组织方向,重点范围独立生效;
|
||||
5. 使用兼容字段 `parent_directories` 创建或编辑,仍然只有参考语义;
|
||||
6. 编辑后 revision 增加,刷新页面仍从数据库恢复原始配置;
|
||||
7. 清空参考目录后不再向后续生成提供参考,已有目录和内容不被删除或移动;
|
||||
8. 重启服务或更换浏览器后,参考目录、重点要求、原始指令和 revision 仍从数据库恢复;
|
||||
9. 生成后的 Wiki 页面可进入智能检索,并可在右侧抽屉查看来源详情;
|
||||
10. 文档 Wiki 子任务尚未结束时不提前运行重点过滤,全部结束后才清理重点范围外的自动生成页面。
|
||||
@@ -0,0 +1,143 @@
|
||||
# 上下文超限自动恢复与压测记录
|
||||
|
||||
验证日期:2026-09-11。
|
||||
|
||||
## 行为
|
||||
|
||||
- 模型管理的 `max_input_tokens` 保持可空,不增加 32000 或任何默认模型窗口。明确配置或已有模型 profile 时做预裁剪;两者均无时,首个请求保持原样。
|
||||
- 主代理、子代理的最终 `ContextWindowGuardMiddleware` 在模型明确返回超限时,对实际发送的请求副本缩短后重试,不重新执行外层摘要、RAG 或记忆注入。原始会话历史不改写。
|
||||
- 最多首次调用加四次恢复调用。恢复预算来自失败请求的估算值,每次降到约 35%;最后尝试最小请求。30 秒用于限制继续发起恢复尝试,单次调用的超时仍由模型配置控制。
|
||||
- 不盲目缩减已较小的输出额度,避免推理模型耗尽额度却没有正文。恢复后仍只有思考内容或空白时,普通问答返回自然澄清。
|
||||
- 不发送空消息列表;不静默删除结构化输出格式和强制工具约束。必要约束无法满足时保留内部失败标记,供业务调用方识别,不能伪造成功 JSON 或已执行的操作。
|
||||
- 摘要限制针对最终渲染的请求(包括 XML 和摘要提示词)。使用已有摘要裁剪配置与摘要模型上限中较小者,不新增默认模型窗口;`trim_tokens_to_summarize: null` 现在会正确传递。
|
||||
- 摘要超限也缩短重试;摘要失败时保留可用旧摘要、最近消息和真实用户问题,不缓存失败文案、不阻断主问答。内部摘要调用隔离父级流式回调,避免摘要文字混入回答。
|
||||
- 上下文错误识别兼容实测 MiniMax 的 `invalid params, context window exceeds limit (2013)`。普通鉴权、额度、网络等错误仍由原错误处理流程负责。
|
||||
|
||||
## 自动回归
|
||||
|
||||
以下命令从 backend 目录运行:
|
||||
|
||||
```powershell
|
||||
.venv/Scripts/python.exe -m pytest tests/test_context_recovery.py tests/test_context_recovery_transport.py tests/test_llm_error_handling_middleware.py tests/test_summarization_human_message.py tests/test_summarization_multiturn.py tests/test_model_management.py tests/test_writing_mode_report_middleware.py tests/test_roundtable_role_boundary.py -q -p no:cacheprovider
|
||||
```
|
||||
|
||||
首轮结果:135 passed;补充多轮问答后的最终结果:**168 passed**。
|
||||
|
||||
覆盖内容:
|
||||
|
||||
- 未填写输入上限时不擅自预裁剪,显式上限和绑定模型 profile 生效。
|
||||
- 中英文、表情、单条超长输入;持续超限;输出预算;极小无效预算。
|
||||
- 50 组固定随机种子的历史消息、工具调用/结果、隐藏上下文和系统提示组合,验证长度、工具配对与原始历史不变。
|
||||
- 同步/异步恢复、取消、LangGraph 中断、重试时限、熔断半开探针释放。
|
||||
- 摘要输入超限、摘要不可用、失败不污染缓存、摘要消息流不泄漏。
|
||||
- 真实 `PatchedChatOpenAI` 客户端 + 模拟 HTTP 400/SSE + LangGraph 的端到端请求链,验证只返回一次正文、不重复上下文注入、不泄漏超限异常。
|
||||
|
||||
Python 改动与新增测试、探针脚本通过 Ruff 检查。
|
||||
|
||||
## 真实模型联调
|
||||
|
||||
使用当前配置中的 MiniMax-M2.7,仅发送自动生成的合成材料,不使用用户会话或业务文档。40000 行输入的本地估算从 **1,840,102** 降到 **644,001**;这些是保守估算值,不是供应商统计的实际 token 数。
|
||||
|
||||
| 模式 | 首次调用 | 第二次调用 | 可见正文 | 耗时 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 普通调用 | 真实上下文超限 2013 | 自动缩短后成功 | 验证通过 | 18.83 秒 |
|
||||
| SSE 流式 | 真实上下文超限 2013 | 自动缩短后成功 | 验证通过 | 21.26 秒 |
|
||||
|
||||
可重复运行的脚本(真实请求会消耗已配置模型额度):
|
||||
|
||||
```powershell
|
||||
.venv/Scripts/python.exe scripts/probe_context_recovery.py --live --model MiniMax-M2.7 --rows 40000
|
||||
.venv/Scripts/python.exe scripts/probe_context_recovery.py --live --model MiniMax-M2.7 --rows 40000 --stream
|
||||
```
|
||||
|
||||
探针只在进程内设置 90 秒请求超时与 1024 输出预算,不修改配置文件。初期以较小输出预算测试时发现只有思考、没有正文的问题;30 秒网络超时测试也曾超时,扩大探针超时后完成上述验证。生产调用仍使用自身超时配置。
|
||||
|
||||
## 多轮问答追加验证
|
||||
|
||||
当前配置的主动摘要开关为 `summarization.enabled: false`,前端默认 `contextCompressionEnabled: false`。前端开启“上下文压缩”后,通过 `summarization_enabled` 传到后端。触发条件为 **120000 token 或 160 条消息**,保留目标为 40000 token。未开启主动摘要时,不会因为对话轮次增加而调用摘要模型;上下文超限后的裁剪重试仍可独立工作。本次没有修改这些开关或生产阈值。
|
||||
|
||||
多轮测试发现并修复:
|
||||
|
||||
1. 消息数达到阈值但历史尚未达到 token 保留目标时,提前返回导致不触发摘要。现在先执行保留窗口调整,再判断是否有历史可摘要。
|
||||
2. 反复从全量旧历史重新摘要,旧事实可能被摘要输入截断删除。现在使用“上次摘要 + 新增历史”增量合并,并保留完整原始消息。
|
||||
3. 摘要提示词与历史拼接后整体截断,可能保留提示词却删除历史开头。现在先计算提示词与已有摘要开销,再给历史分配预算。
|
||||
4. 历史回复的思考文本、历史 token 用量缩放、摘要输出的思考文本可能干扰事实提取。现在清理这些信息的临时副本,存储的原始聊天记录不改写。
|
||||
5. 摘要模型可能执行历史中的“只回复已记住”。现在明确区分摘要任务和历史数据,过滤纯确认回复;摘要失败时保留尚未成功摘要的事实,由最终 guard 按实际需要裁剪,不把确认文案当记忆。
|
||||
6. 补齐 fraction 阈值、复合条件和绑定模型 profile 的多轮处理。
|
||||
|
||||
| 测试 | 真正生成摘要的轮次 | 结果 |
|
||||
| --- | --- | --- |
|
||||
| 当前阈值的 105 轮模拟,逐轮重建 agent | 81、101 | 210 条消息完整,早期事实可回忆 |
|
||||
| 小摘要预算的 24 轮模拟 | 5、8、11、14、17、20、23 | 多次合并后仍保留首轮事实 |
|
||||
| 12 轮工具问答,同步/异步分别运行 | 多次触发 | 每轮工具恰好执行一次,48 条消息完整,调用与结果不拆散 |
|
||||
| 真实 MiniMax 12 轮,第一次最终复测 | 5、8、11 | 正确回忆代号、更新后的预算和期限;59.02 秒 |
|
||||
| 真实 MiniMax 12 轮,第二次独立复测 | 5、8、11 | 再次通过相同事实检查;68.16 秒 |
|
||||
|
||||
真实测试为加快验证,仅在探针进程内把摘要触发调为 8 条消息、保留 2 条消息,并使用 2048 输出预算;**未设置默认输入上限,也未修改配置文件**。第 9 轮清空进程摘要缓存,检查能否从会话检查点恢复。第 6、7、9、10、12 轮复用摘要,不额外调用摘要模型。每次真正摘要均检查 checkpoint 中的摘要覆盖范围确实更新,而不只检查触发事件。
|
||||
|
||||
首轮提供代号 `ORBIT-42`、预算 35 万元和期限 `2026-12-20`,第 6 轮把预算改为 48 万元,第 8 轮把期限改为 `2026-12-28`。两次最终复测第 12 轮均正确返回:
|
||||
|
||||
```json
|
||||
{"code":"ORBIT-42","budget":48,"deadline":"2026-12-28"}
|
||||
```
|
||||
|
||||
复测过程中曾出现“摘要更新了,但答案事实错误”的失败,未计为通过;上述两次成功记录均来自清理历史思考及用量缩放后的版本。逐轮输入场景、正文、摘要正文和缓存更新记录保存在:
|
||||
|
||||
- [第一次最终复测](multiturn-probe-20260911.json)
|
||||
- [第二次独立复测](multiturn-probe-20260911-repeat.json)
|
||||
|
||||
复现命令(使用合成材料,会调用已配置模型):
|
||||
|
||||
```powershell
|
||||
.venv/Scripts/python.exe scripts/probe_multiturn_compression.py --live --output docs/multiturn-probe-20260911.json
|
||||
```
|
||||
|
||||
## 前端及验证边界
|
||||
|
||||
- 模型管理页已注明输入上限可留空;该 TSX 文件单独转换通过。
|
||||
- `pnpm typecheck` **未通过**:未改动的 `message-list-item.tsx`、`core/blog`(缺少 nextra)、`GeneralQA.tsx` 等存在类型或依赖错误。本次不把前端全量检查记为通过。
|
||||
- 真实供应商测试覆盖 MiniMax-M2.7,不能等同于所有供应商均已实测。主代理/子代理和摘要调用以外的独立模型调用不因该中间件自动获得保护;标题生成原本已有本地标题降级。
|
||||
- 极端情况下会丢失部分材料并请求用户补充;恢复机制不保证缺失信息时仍能完成原任务,也不掩盖鉴权失败、额度不足或服务不可用。
|
||||
|
||||
|
||||
## 部署版本兼容修复(2026-09-11)
|
||||
|
||||
前面的本机测试使用 LangChain 1.3.15,未覆盖部署的 1.2.15,这是此前验证的遗漏。
|
||||
已在真实 1.2.15 包上复现 `_trigger_clauses` AttributeError:旧基类只提供 `_trigger_conditions`。
|
||||
现在应用自行保存和计算触发条件,不再读取这两个版本相关的私有属性;字典条件的 AND、条件列表的 OR 和旧参数映射保持一致。
|
||||
此前新增的 `internal_call_transformer` 依赖已移除。没有要求升级部署依赖,没有引入默认输入上限。
|
||||
|
||||
本次将用户提供的全部 20 个包安装到隔离目录,以 PYTHONPATH 优先加载,未替换本机虚拟环境。
|
||||
其余未提供版本的依赖沿用本机,测试主机为 Windows;不把该结果宣称为生产服务器完整环境的启动验证。
|
||||
|
||||
- 用户版本组合(LangChain 1.2.15、Core 1.3.2、LangGraph 1.1.9 等):177 项通过,26.26 秒。
|
||||
- 本机新版组合:同样 177 项通过,24.64 秒。
|
||||
- 覆盖 Gateway / 主代理导入、105 轮问答、重复压缩、缓存恢复、同步/异步工具和消息流隔离。
|
||||
- 新增删除基类两个私有触发属性后仍能正常判断阈值的回归测试,涵盖 tuple、OR、AND、混合和未启用情况。
|
||||
- 用户版本组合下真实 MiniMax-M2.7 12 轮问答:第 5、8、11 轮摘要,第 9 轮清空进程缓存后正常恢复;最终准确返回代号 ORBIT-42、更新预算 48、更新期限 2026-12-28。54.53 秒,无属性错误。
|
||||
- 所有被调用的摘要中间件方法均已检查能在 1.2.15 下解析;Ruff 通过。
|
||||
|
||||
[用户版本组合的真实多轮记录及 20 个版本号](multiturn-probe-langchain-1.2.15.json)。
|
||||
|
||||
|
||||
## 隐藏压缩提示与连续问答复测
|
||||
|
||||
后台仅记录真实摘要日志,不再向 custom 流发送 `context_compacting` 通知;前端也忽略旧后台的同名事件,不弹 toast。摘要开关和压缩功能保留。
|
||||
|
||||
新增模型每轮上报 150000 输入 token 的 12 轮测试,使用 2000 token 摘要触发阈值:同步和异步均仅在第 2 轮摘要,后续复用;历史用量数据未修改。该测试在用户的 1.2.15 依赖组合和本机新版均通过。部署组合相关回归共 118 项通过(22.28 秒)。
|
||||
|
||||
尚未拿到用户连续弹提示时的后台 cache / counted_tokens / msgs 日志,未复现该会话的每轮重复摘要,不能把隐藏提示当作已修复重复摘要根因。
|
||||
|
||||
|
||||
## 修复压缩后每轮重新摘要
|
||||
|
||||
已用过长摘要复现另一条真实缺陷路径:摘要加保留内容在生成当时就超过阈值,旧逻辑之后每轮都会把同一个超阈值基础内容再次送摘要模型。12 轮测试中第 2~12 轮均调用摘要。此前的简短摘要测试没有覆盖此边界;尚无生产会话日志,不能据此断言这是用户会话唯一原因。
|
||||
|
||||
修复:缓存可用且上次压缩结果本身就超阈值时,要求**压缩后新增内容**累计达到原触发阈值的 25% 才再摘要。正常压缩后低于阈值的路径保持原判定。这个比例用于避免无效重复摘要,不是模型输入上限;最终 ContextWindowGuard 的裁剪及超限重试继续工作。子集计数移除原请求 usage_metadata 的缩放影响,只修改临时副本。日志新增 waiting_for_growth,并区分 unusable 与 stale 缓存。
|
||||
|
||||
用户提供的 1.2.15 等 20 个包组合最终回归 **187 passed,26.59 秒**:
|
||||
|
||||
- 12 轮过长摘要 + 短追问:仅第 2 轮摘要,修复前为第 2~12 轮;同步/异步均通过。
|
||||
- 36 轮持续增加材料:第 2、19、36 轮摘要;每轮清空进程缓存,仍可由 checkpoint 恢复,不是直接禁用后续摘要。
|
||||
- 超长摘要 + 严格 800 token 测试模型 + 连续 8 轮:分别验证配置上限(预裁剪,无拒绝)和不配置(真实抛出 context_length_exceeded 后缩短重试),同步/异步四种组合均正常回答最新问题;原会话消息数完整。这里使用可控测试模型,未宣称所有供应商均实测。
|
||||
- 既有 105 轮、工具执行、首次阈值、缓存恢复、旧版导入及摘要流隔离回归继续通过。Ruff 通过。
|
||||
@@ -370,7 +370,8 @@ client.get_memory()
|
||||
| POST | `/api/threads/{id}/runs/{rid}/cancel` | 取消运行 |
|
||||
| GET | `/api/threads/{id}/runs/{rid}/join` | 加入 SSE 流 |
|
||||
| GET | `/api/threads/{id}/runs/{rid}/messages` | 分页获取消息 |
|
||||
| GET | `/api/threads/{id}/messages` | 线程所有消息(含反馈) |
|
||||
| GET | `/api/threads/{id}/messages` | 从最新 checkpoint 读取按时间正序的完整消息字段 |
|
||||
| GET | `/api/threads/{id}/run-messages` | 跨运行读取 event-store 展示消息(含反馈) |
|
||||
|
||||
### 8.3 无状态运行
|
||||
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
{
|
||||
"model": "MiniMax-M2.7",
|
||||
"trigger_messages": 8,
|
||||
"keep_messages": 2,
|
||||
"rows": [
|
||||
{
|
||||
"turn": 1,
|
||||
"compression": "passthrough",
|
||||
"history_messages": 2,
|
||||
"has_cache": false,
|
||||
"visible": "已记住。",
|
||||
"elapsed_seconds": 6.34,
|
||||
"summary_updated": false
|
||||
},
|
||||
{
|
||||
"turn": 2,
|
||||
"compression": "passthrough",
|
||||
"history_messages": 4,
|
||||
"has_cache": false,
|
||||
"visible": "已记住。",
|
||||
"elapsed_seconds": 2.16,
|
||||
"summary_updated": false
|
||||
},
|
||||
{
|
||||
"turn": 3,
|
||||
"compression": "passthrough",
|
||||
"history_messages": 6,
|
||||
"has_cache": false,
|
||||
"visible": "已记住。",
|
||||
"elapsed_seconds": 1.45,
|
||||
"summary_updated": false
|
||||
},
|
||||
{
|
||||
"turn": 4,
|
||||
"compression": "passthrough",
|
||||
"history_messages": 8,
|
||||
"has_cache": false,
|
||||
"visible": "已记住。",
|
||||
"elapsed_seconds": 2.77,
|
||||
"summary_updated": false
|
||||
},
|
||||
{
|
||||
"turn": 5,
|
||||
"compression": "summarize",
|
||||
"history_messages": 10,
|
||||
"has_cache": true,
|
||||
"visible": "项目代号为ORBIT-42,预算为35万元,截止日期为2026年12月20日。",
|
||||
"elapsed_seconds": 18.06,
|
||||
"summary_updated": true,
|
||||
"summary_text": "## SESSION INTENT\nThe user is establishing project parameters for a knowledge base/Q&A system with report export capabilities. The AI is being used as a memory keeper to retain key project constraints and requirements for future reference.\n\n## SUMMARY\nProject ORBIT-42 is defined with the following parameters:\n- **Budget**: 35万元 (350,000 yuan)\n- **Deadline**: 2026-12-20\n- **Scope**: System must support document retrieval, question answering, and report export\n- **Data Constraint**: Real customer data cannot be uploaded; only synthetic test materials are permitted\n- **Acceptance Criteria**: Must verify citation sources, output format compliance, and interface stability\n\nNo implementation work has begun. This session consists entirely of requirements gathering and acknowledgment.\n\n## ARTIFACTS\nNone. No files, code, or resources have been created or modified during this session.\n\n## NEXT STEPS\n- Begin system architecture design and implementation planning\n- Create synthetic test data materials for development and testing\n- Implement core functionalities: document retrieval, Q&A engine, and report export module\n- Establish validation framework for citation checking, format compliance, and interface stability testing"
|
||||
},
|
||||
{
|
||||
"turn": 6,
|
||||
"compression": "reuse",
|
||||
"history_messages": 12,
|
||||
"has_cache": true,
|
||||
"visible": "已更新。",
|
||||
"elapsed_seconds": 2.14,
|
||||
"summary_updated": false
|
||||
},
|
||||
{
|
||||
"turn": 7,
|
||||
"compression": "reuse",
|
||||
"history_messages": 14,
|
||||
"has_cache": true,
|
||||
"visible": "已记住。",
|
||||
"elapsed_seconds": 3.05,
|
||||
"summary_updated": false
|
||||
},
|
||||
{
|
||||
"turn": 8,
|
||||
"compression": "summarize",
|
||||
"history_messages": 16,
|
||||
"has_cache": true,
|
||||
"visible": "已更新。",
|
||||
"elapsed_seconds": 14.64,
|
||||
"summary_updated": true,
|
||||
"summary_text": "## SESSION INTENT\n\n用户正在建立知识库/问答系统的项目参数,需要AI作为记忆 keeper 保留关键项目约束和需求供后续参考。\n\n## SUMMARY\n\n项目 ORBIT-42 的参数如下:\n\n- **项目代号**:ORBIT-42\n- **预算**:48万元(已从35万元更新)\n- **截止日期**:2026年12月20日\n- **项目范围**:系统需支持文档检索、问答、报告导出三大功能\n- **数据约束**:不得使用真实客户数据,仅可使用合成测试材料\n- **报告格式约束**:报告必须用中文撰写,且先写结论再写依据\n- **验收标准**:验证引用来源、输出格式合规、接口稳定性\n\n## ARTIFACTS\n\n无。暂无文件或代码被创建或修改。\n\n## NEXT STEPS\n\n- 开始系统架构设计与实现规划\n- 创建合成测试数据材料用于开发和测试\n- 实现核心功能:文档检索、问答引擎、报告导出模块\n- 建立验证框架用于引用检查、格式合规和接口稳定性测试"
|
||||
},
|
||||
{
|
||||
"turn": 9,
|
||||
"compression": "reuse",
|
||||
"history_messages": 18,
|
||||
"has_cache": true,
|
||||
"visible": "已记住。",
|
||||
"elapsed_seconds": 3.31,
|
||||
"summary_updated": false
|
||||
},
|
||||
{
|
||||
"turn": 10,
|
||||
"compression": "reuse",
|
||||
"history_messages": 20,
|
||||
"has_cache": true,
|
||||
"visible": "已记住。",
|
||||
"elapsed_seconds": 2.72,
|
||||
"summary_updated": false
|
||||
},
|
||||
{
|
||||
"turn": 11,
|
||||
"compression": "summarize",
|
||||
"history_messages": 22,
|
||||
"has_cache": true,
|
||||
"visible": "已记住。",
|
||||
"elapsed_seconds": 9.44,
|
||||
"summary_updated": true,
|
||||
"summary_text": "## SESSION INTENT\n\n用户正在建立知识库/问答系统的项目参数,需要AI作为记忆 keeper 保留关键项目约束和需求供后续参考。\n\n## SUMMARY\n\n项目 ORBIT-42 的参数如下:\n\n- **项目代号**:ORBIT-42\n- **预算**:48万元\n- **截止日期**:2026年12月28日(已更新)\n- **项目范围**:系统需支持文档检索、问答、报告导出三大功能\n- **数据约束**:不得使用真实客户数据,仅可使用合成测试材料\n- **报告格式约束**:报告必须用中文撰写,且先写结论再写依据\n- **验收标准**:\n - 验证引用来源、输出格式合规、接口稳定性\n - 必须覆盖多轮问答和长材料(新增)\n- **数据留存要求**:测试报告必须留存,不得删除已有聊天记录(新增)\n\n## ARTIFACTS\n\n无。暂无文件或代码被创建或修改。\n\n## NEXT STEPS\n\n- 开始系统架构设计与实现规划\n- 创建合成测试数据材料用于开发和测试\n- 实现核心功能:文档检索、问答引擎、报告导出模块\n- 建立验证框架用于引用检查、格式合规和接口稳定性测试\n- 设计聊天记录和测试报告的持久化存储方案"
|
||||
},
|
||||
{
|
||||
"turn": 12,
|
||||
"compression": "reuse",
|
||||
"history_messages": 24,
|
||||
"has_cache": true,
|
||||
"visible": "```json\n{\n \"code\": \"ORBIT-42\",\n \"budget\": 48,\n \"deadline\": \"2026-12-28\"\n}\n```",
|
||||
"elapsed_seconds": 2.02,
|
||||
"summary_updated": false
|
||||
}
|
||||
],
|
||||
"fact_check": true,
|
||||
"summary_rounds": [
|
||||
5,
|
||||
8,
|
||||
11
|
||||
],
|
||||
"reuse_rounds": [
|
||||
6,
|
||||
7,
|
||||
9,
|
||||
10,
|
||||
12
|
||||
],
|
||||
"outcome": "passed",
|
||||
"elapsed_seconds": 68.16
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
{
|
||||
"model": "MiniMax-M2.7",
|
||||
"trigger_messages": 8,
|
||||
"keep_messages": 2,
|
||||
"rows": [
|
||||
{
|
||||
"turn": 1,
|
||||
"compression": "passthrough",
|
||||
"history_messages": 2,
|
||||
"has_cache": false,
|
||||
"visible": "已记住",
|
||||
"elapsed_seconds": 9.39,
|
||||
"summary_updated": false
|
||||
},
|
||||
{
|
||||
"turn": 2,
|
||||
"compression": "passthrough",
|
||||
"history_messages": 4,
|
||||
"has_cache": false,
|
||||
"visible": "已记住",
|
||||
"elapsed_seconds": 2.08,
|
||||
"summary_updated": false
|
||||
},
|
||||
{
|
||||
"turn": 3,
|
||||
"compression": "passthrough",
|
||||
"history_messages": 6,
|
||||
"has_cache": false,
|
||||
"visible": "已记住",
|
||||
"elapsed_seconds": 1.62,
|
||||
"summary_updated": false
|
||||
},
|
||||
{
|
||||
"turn": 4,
|
||||
"compression": "passthrough",
|
||||
"history_messages": 8,
|
||||
"has_cache": false,
|
||||
"visible": "已记住",
|
||||
"elapsed_seconds": 3.27,
|
||||
"summary_updated": false
|
||||
},
|
||||
{
|
||||
"turn": 5,
|
||||
"compression": "summarize",
|
||||
"history_messages": 10,
|
||||
"has_cache": true,
|
||||
"visible": "项目代号为ORBIT-42,预算35万元,截止日期为2026年12月20日。",
|
||||
"elapsed_seconds": 12.16,
|
||||
"summary_updated": true,
|
||||
"summary_text": "## SESSION INTENT\nEstablish and remember project ORBIT-42 parameters for an upcoming project session, including budget, deadline, scope, constraints, and acceptance criteria.\n\n## SUMMARY\n- Project Code: ORBIT-42\n- Budget: 35万元 (350,000 RMB)\n- Deadline: 2026-12-20\n- Scope: System must support document retrieval, Q&A functionality, and report export capabilities\n- Constraint: Real customer data cannot be uploaded; only synthetic test materials may be used\n- Acceptance Criteria: Must verify citation sources, output format compliance, and API stability\n\n## ARTIFACTS\nNone created or modified.\n\n## NEXT STEPS\nReady to begin project ORBIT-42 implementation. Next steps will involve setting up the system architecture, creating synthetic test datasets, and implementing the three core features (document retrieval, Q&A, report export) within the budget and timeline constraints."
|
||||
},
|
||||
{
|
||||
"turn": 6,
|
||||
"compression": "reuse",
|
||||
"history_messages": 12,
|
||||
"has_cache": true,
|
||||
"visible": "已更新。",
|
||||
"elapsed_seconds": 2.12,
|
||||
"summary_updated": false
|
||||
},
|
||||
{
|
||||
"turn": 7,
|
||||
"compression": "reuse",
|
||||
"history_messages": 14,
|
||||
"has_cache": true,
|
||||
"visible": "已记住。",
|
||||
"elapsed_seconds": 2.84,
|
||||
"summary_updated": false
|
||||
},
|
||||
{
|
||||
"turn": 8,
|
||||
"compression": "summarize",
|
||||
"history_messages": 16,
|
||||
"has_cache": true,
|
||||
"visible": "已更新。",
|
||||
"elapsed_seconds": 8.55,
|
||||
"summary_updated": true,
|
||||
"summary_text": "## SESSION INTENT\nEstablish and remember project ORBIT-42 parameters for an upcoming project session, including budget, deadline, scope, constraints, and acceptance criteria.\n\n## SUMMARY\n- Project Code: ORBIT-42\n- Budget: 48万元 (480,000 RMB)\n- Deadline: 2026-12-20\n- Scope: System must support document retrieval, Q&A functionality, and report export capabilities\n- Constraint: Real customer data cannot be uploaded; only synthetic test materials may be used\n- Additional Constraint: Reports must be in Chinese, with conclusions written first followed by supporting evidence/reasoning\n- Acceptance Criteria: Must verify citation sources, output format compliance, and API stability\n\n## ARTIFACTS\nNone created or modified.\n\n## NEXT STEPS\nReady to begin project ORBIT-42 implementation. Next steps will involve setting up the system architecture, creating synthetic test datasets, and implementing the three core features (document retrieval, Q&A, report export) within the budget and timeline constraints."
|
||||
},
|
||||
{
|
||||
"turn": 9,
|
||||
"compression": "reuse",
|
||||
"history_messages": 18,
|
||||
"has_cache": true,
|
||||
"visible": "已记住。",
|
||||
"elapsed_seconds": 1.51,
|
||||
"summary_updated": false
|
||||
},
|
||||
{
|
||||
"turn": 10,
|
||||
"compression": "reuse",
|
||||
"history_messages": 20,
|
||||
"has_cache": true,
|
||||
"visible": "已记住。",
|
||||
"elapsed_seconds": 1.74,
|
||||
"summary_updated": false
|
||||
},
|
||||
{
|
||||
"turn": 11,
|
||||
"compression": "summarize",
|
||||
"history_messages": 22,
|
||||
"has_cache": true,
|
||||
"visible": "已记住。",
|
||||
"elapsed_seconds": 10.69,
|
||||
"summary_updated": true,
|
||||
"summary_text": "## SESSION INTENT\nEstablish and remember project ORBIT-42 parameters for an upcoming project session, including budget, deadline, scope, constraints, and acceptance criteria.\n\n## SUMMARY\n- Project Code: ORBIT-42\n- Budget: 48万元 (480,000 RMB)\n- Deadline: 2026-12-28 (updated from 2026-12-20)\n- Scope: System must support document retrieval, Q&A functionality, and report export capabilities\n- Constraint: Real customer data cannot be uploaded; only synthetic test materials may be used\n- Additional Constraint: Reports must be in Chinese, with conclusions written first followed by supporting evidence/reasoning\n- Acceptance Criteria: Must verify citation sources, output format compliance, and API stability\n- Updated Acceptance Criteria: Must cover multi-turn Q&A and long materials testing\n- Data Retention Requirement: Must retain test reports; cannot delete existing chat records\n\n## ARTIFACTS\nNone created or modified.\n\n## NEXT STEPS\nReady to begin project ORBIT-42 implementation. Next steps will involve setting up the system architecture, creating synthetic test datasets, and implementing the three core features (document retrieval, Q&A, report export) within the budget and timeline constraints."
|
||||
},
|
||||
{
|
||||
"turn": 12,
|
||||
"compression": "reuse",
|
||||
"history_messages": 24,
|
||||
"has_cache": true,
|
||||
"visible": "```json\n{\n \"code\": \"ORBIT-42\",\n \"budget\": 48,\n \"deadline\": \"2026-12-28\"\n}\n```",
|
||||
"elapsed_seconds": 3.02,
|
||||
"summary_updated": false
|
||||
}
|
||||
],
|
||||
"fact_check": true,
|
||||
"summary_rounds": [
|
||||
5,
|
||||
8,
|
||||
11
|
||||
],
|
||||
"reuse_rounds": [
|
||||
6,
|
||||
7,
|
||||
9,
|
||||
10,
|
||||
12
|
||||
],
|
||||
"outcome": "passed",
|
||||
"elapsed_seconds": 59.02
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
{
|
||||
"model": "MiniMax-M2.7",
|
||||
"trigger_messages": 8,
|
||||
"keep_messages": 2,
|
||||
"rows": [
|
||||
{
|
||||
"turn": 1,
|
||||
"compression": "passthrough",
|
||||
"history_messages": 2,
|
||||
"has_cache": false,
|
||||
"visible": "已记住",
|
||||
"elapsed_seconds": 4.78,
|
||||
"summary_updated": false
|
||||
},
|
||||
{
|
||||
"turn": 2,
|
||||
"compression": "passthrough",
|
||||
"history_messages": 4,
|
||||
"has_cache": false,
|
||||
"visible": "已记住。",
|
||||
"elapsed_seconds": 3.19,
|
||||
"summary_updated": false
|
||||
},
|
||||
{
|
||||
"turn": 3,
|
||||
"compression": "passthrough",
|
||||
"history_messages": 6,
|
||||
"has_cache": false,
|
||||
"visible": "已记住。",
|
||||
"elapsed_seconds": 1.91,
|
||||
"summary_updated": false
|
||||
},
|
||||
{
|
||||
"turn": 4,
|
||||
"compression": "passthrough",
|
||||
"history_messages": 8,
|
||||
"has_cache": false,
|
||||
"visible": "已记住。",
|
||||
"elapsed_seconds": 1.39,
|
||||
"summary_updated": false
|
||||
},
|
||||
{
|
||||
"turn": 5,
|
||||
"compression": "summarize",
|
||||
"history_messages": 10,
|
||||
"has_cache": true,
|
||||
"visible": "项目代号为ORBIT-42,预算为35万元,截止日期为2026年12月20日。",
|
||||
"elapsed_seconds": 13.5,
|
||||
"summary_updated": true,
|
||||
"summary_text": "## SESSION INTENT\nSet up and remember project ORBIT-42 parameters for future reference in this session.\n\n## SUMMARY\n- Project Code: ORBIT-42\n- Budget: 35万元 (350,000 RMB)\n- Deadline: 2026-12-20\n- Scope: System must support information retrieval, Q&A functionality, and report export\n- Constraint: Real customer data cannot be uploaded; only synthetic test materials may be used\n- Acceptance Criteria: Must verify reference sources, output format compliance, and interface stability\n\n## ARTIFACTS\nNone created or modified.\n\n## NEXT STEPS\nProject parameters are stored. Awaiting further instructions on ORBIT-42 project implementation."
|
||||
},
|
||||
{
|
||||
"turn": 6,
|
||||
"compression": "reuse",
|
||||
"history_messages": 12,
|
||||
"has_cache": true,
|
||||
"visible": "已更新。",
|
||||
"elapsed_seconds": 1.62,
|
||||
"summary_updated": false
|
||||
},
|
||||
{
|
||||
"turn": 7,
|
||||
"compression": "reuse",
|
||||
"history_messages": 14,
|
||||
"has_cache": true,
|
||||
"visible": "已记住。",
|
||||
"elapsed_seconds": 1.31,
|
||||
"summary_updated": false
|
||||
},
|
||||
{
|
||||
"turn": 8,
|
||||
"compression": "summarize",
|
||||
"history_messages": 16,
|
||||
"has_cache": true,
|
||||
"visible": "已更新。",
|
||||
"elapsed_seconds": 9.2,
|
||||
"summary_updated": true,
|
||||
"summary_text": "## SESSION INTENT\nSet up and remember project ORBIT-42 parameters for future reference in this session.\n\n## SUMMARY\n- Project Code: ORBIT-42\n- Budget: 48万元 (updated from 35万元)\n- Deadline: 2026-12-20\n- Scope: System must support information retrieval, Q&A functionality, and report export\n- Constraint: Real customer data cannot be uploaded; only synthetic test materials may be used\n- NEW Constraint: Reports must be in Chinese, conclusions first then basis\n- Acceptance Criteria: Must verify reference sources, output format compliance, and interface stability\n\n## ARTIFACTS\nNone created or modified.\n\n## NEXT STEPS\nProject parameters are stored. Awaiting further instructions on ORBIT-42 project implementation."
|
||||
},
|
||||
{
|
||||
"turn": 9,
|
||||
"compression": "reuse",
|
||||
"history_messages": 18,
|
||||
"has_cache": true,
|
||||
"visible": "已记住。",
|
||||
"elapsed_seconds": 2.24,
|
||||
"summary_updated": false
|
||||
},
|
||||
{
|
||||
"turn": 10,
|
||||
"compression": "reuse",
|
||||
"history_messages": 20,
|
||||
"has_cache": true,
|
||||
"visible": "已记住。",
|
||||
"elapsed_seconds": 1.67,
|
||||
"summary_updated": false
|
||||
},
|
||||
{
|
||||
"turn": 11,
|
||||
"compression": "summarize",
|
||||
"history_messages": 22,
|
||||
"has_cache": true,
|
||||
"visible": "已记住。",
|
||||
"elapsed_seconds": 10.02,
|
||||
"summary_updated": true,
|
||||
"summary_text": "## SESSION INTENT\nSet up and remember project ORBIT-42 parameters for future reference in this session.\n\n## SUMMARY\n- Project Code: ORBIT-42\n- Budget: 48万元\n- Deadline: 2026-12-28 (updated from 2026-12-20)\n- Scope: System must support information retrieval, Q&A functionality, and report export\n- Constraint: Real customer data cannot be uploaded; only synthetic test materials may be used\n- Constraint: Reports must be in Chinese, conclusions first then basis\n- Acceptance Criteria:\n - Must verify reference sources\n - Output format compliance\n - Interface stability\n - Must cover multi-turn Q&A and long documents\n - Must retain test reports, cannot delete existing chat records\n\n## ARTIFACTS\nNone created or modified.\n\n## NEXT STEPS\nProject parameters are stored. Awaiting further instructions on ORBIT-42 project implementation."
|
||||
},
|
||||
{
|
||||
"turn": 12,
|
||||
"compression": "reuse",
|
||||
"history_messages": 24,
|
||||
"has_cache": true,
|
||||
"visible": "```json\n{\n \"code\": \"ORBIT-42\",\n \"budget\": 48,\n \"deadline\": \"2026-12-28\"\n}\n```",
|
||||
"elapsed_seconds": 3.62,
|
||||
"summary_updated": false
|
||||
}
|
||||
],
|
||||
"fact_check": true,
|
||||
"summary_rounds": [
|
||||
5,
|
||||
8,
|
||||
11
|
||||
],
|
||||
"reuse_rounds": [
|
||||
6,
|
||||
7,
|
||||
9,
|
||||
10,
|
||||
12
|
||||
],
|
||||
"outcome": "passed",
|
||||
"elapsed_seconds": 54.53,
|
||||
"tested_dependency_versions": {
|
||||
"langchain": "1.2.15",
|
||||
"langchain-anthropic": "1.4.1",
|
||||
"langchain-core": "1.3.2",
|
||||
"langchain-deepseek": "1.0.1",
|
||||
"langchain-google-genai": "4.2.2",
|
||||
"langchain-mcp-adapters": "0.2.2",
|
||||
"langchain-openai": "1.2.1",
|
||||
"langchain-protocol": "0.0.12",
|
||||
"langfuse": "4.5.1",
|
||||
"langgraph": "1.1.9",
|
||||
"langgraph-api": "0.8.1",
|
||||
"langgraph-checkpoint": "4.0.2",
|
||||
"langgraph-checkpoint-postgres": "3.0.5",
|
||||
"langgraph-checkpoint-sqlite": "3.0.3",
|
||||
"langgraph-cli": "0.4.24",
|
||||
"langgraph-prebuilt": "1.0.11",
|
||||
"langgraph-runtime-inmem": "0.28.0",
|
||||
"langgraph-sdk": "0.3.13",
|
||||
"langsmith": "0.7.36",
|
||||
"lark-oapi": "1.5.5"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,59 @@
|
||||
# Conversation Summarization
|
||||
|
||||
DeerFlow includes automatic conversation summarization to handle long conversations that approach model token limits. When enabled, the system automatically condenses older messages while preserving recent context.
|
||||
DeerFlow includes automatic conversation summarization to handle long conversations that approach model token limits. When enabled, the system automatically condenses older messages while preserving recent context.
|
||||
|
||||
## Administrator recent-three-turn window
|
||||
|
||||
Administrators can enable a simpler deployment-wide sliding window in
|
||||
「设置 → 上下文管理」. It is stored as
|
||||
`system_settings.json -> conversation_context.recent_turn_window_enabled`, is
|
||||
disabled by default, and is exposed through
|
||||
`GET/PUT /api/system-settings/conversation-context` (write access is admin-only).
|
||||
|
||||
When enabled, every lead-agent model call receives only the latest three real
|
||||
user turns, counting the current request, plus the assistant messages and tool
|
||||
results that belong to those turns. Hidden business context immediately attached
|
||||
to the first retained turn and system instructions are preserved. Trimming is
|
||||
performed on `ModelRequest` only, so checkpoint history and the transcript shown
|
||||
to the user remain complete.
|
||||
|
||||
This mode takes priority over the per-chat 「上下文压缩」 option and does not run
|
||||
the summarization model. Turning it off restores the existing summarization
|
||||
behavior without losing earlier messages.
|
||||
|
||||
## Hard input limit (always-on safety net)
|
||||
|
||||
Summarization is a quality feature and may be disabled by the user. A model can
|
||||
also exceed its real window because the final request contains more than chat
|
||||
history: the system prompt, tool schemas, hidden memory/RAG context, and
|
||||
historical reasoning are all sent to the provider. For that reason DeerFlow
|
||||
also supports a model-specific, always-on hard limit:
|
||||
|
||||
```yaml
|
||||
models:
|
||||
- name: qwen-local
|
||||
use: langchain_openai:ChatOpenAI
|
||||
model: Qwen3.8-27B
|
||||
# This is the safe INPUT ceiling, not the model's advertised total window.
|
||||
# For a 200k deployment, 160k leaves room for output and tokenizer variance.
|
||||
max_input_tokens: 160000
|
||||
max_tokens: 8192
|
||||
```
|
||||
|
||||
`max_tokens` limits generated output. `max_input_tokens` limits the complete
|
||||
input request and is consumed only by DeerFlow; it is never sent to the
|
||||
OpenAI-compatible endpoint. The final model middleware conservatively counts
|
||||
the system message, ordinary and hidden messages, tool schemas, response
|
||||
format, tool calls/results, and historical reasoning. If the request is over
|
||||
budget it transiently removes the oldest complete turns, drops expendable
|
||||
hidden context, and truncates oversized tool/message bodies as a last resort.
|
||||
The checkpoint and the conversation displayed in the UI are unchanged.
|
||||
|
||||
For LangChain-compatible configuration, the same limit may be expressed as
|
||||
`profile.max_input_tokens`. A top-level `max_input_tokens` is preferred in this
|
||||
project because it is visible alongside `max_tokens` in model configuration.
|
||||
Do not set `max_tokens: 200000`: that asks the provider to generate up to 200k
|
||||
tokens and does not protect the input context.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -19,7 +72,7 @@ The summarization feature uses DeerFlow's `DeerFlowSummarizationMiddleware` (a s
|
||||
LangChain's stock `SummarizationMiddleware` runs in `before_model` and persists a `RemoveMessage(REMOVE_ALL_MESSAGES)` to the checkpoint — which permanently deletes the original Q&A from both the model context *and* the conversation the frontend renders. DeerFlow overrides this:
|
||||
|
||||
- `before_model` / `abefore_model` are **no-ops** (the destructive path is disabled).
|
||||
- Compression runs in `wrap_model_call` / `awrap_model_call`: for a single model call, the message list is replaced with `[summary, *recent]` via `request.override(messages=...)`. **Nothing is written back to state.**
|
||||
- Compression runs in `wrap_model_call` / `awrap_model_call`: for a single model call, the hidden summary is merged into LangChain's dedicated `ModelRequest.system_message` slot and the ordinary message list is replaced with the recent tail. This guarantees exactly one leading system message even for strict Qwen-style or generic `langchain_chat` adapters. **Nothing is written back to state.**
|
||||
- The full original messages stay in the checkpoint, so the frontend keeps showing the user's real conversation. The summary message is named `summary` and flagged `hide_from_ui`, and because it only ever lives in the transient request it never reaches the frontend at all.
|
||||
- The generated summary is cached **per-thread** so we don't pay an extra LLM call on every model invocation. The cached summary is reused (no LLM call) until enough *new* messages accumulate to push the compressed context back over the trigger threshold (a sticky boundary mirroring the stock middleware's post-compaction behavior). The cache is bounded (oldest threads evicted first).
|
||||
- **Threshold-first**: while the full current conversation is still under the trigger, the request is passed through untouched — no cache lookup, no summary substitution. Compression only ever engages once the real context crosses the configured trigger.
|
||||
@@ -185,7 +238,7 @@ The default LangChain prompt instructs the model to:
|
||||
- Messages to summarize (older messages beyond the `keep` threshold)
|
||||
- Messages to preserve (recent messages within the `keep` threshold)
|
||||
4. **Summary Generation**: The model generates a concise summary of the older messages
|
||||
5. **Context Replacement (transient)**: The message list **handed to the model for this call** is rebuilt as `[summary, *recent]`. The persisted state is left untouched — old messages are **not** removed, so the frontend still renders the full original conversation.
|
||||
5. **Context Replacement (transient)**: The request **handed to the model for this call** is rebuilt with one system message (agent prompt + hidden summary) and the recent non-system tail. The persisted state is left untouched — old messages are **not** removed, so the frontend still renders the full original conversation.
|
||||
6. **AI/Tool Pair Protection**: The system ensures AI messages and their corresponding tool messages stay together
|
||||
7. **Skill Rescue**: Before the summary is generated, the most recently loaded skill files (tool results whose tool name is in `skill_file_read_tool_names` and whose target path is under `skills.container_path`) are lifted out of the summarization set and prepended to the preserved tail. Selection walks newest-first under three budgets: `preserve_recent_skill_count`, `preserve_recent_skill_tokens`, and `preserve_recent_skill_tokens_per_skill`. The triggering AIMessage and all of its paired ToolMessages move together so tool_call ↔ tool_result pairing stays intact.
|
||||
|
||||
@@ -202,12 +255,14 @@ The middleware intelligently preserves message context:
|
||||
|
||||
- **Recent Messages**: Always kept intact based on `keep` configuration
|
||||
- **AI/Tool Pairs**: Never split - if a cutoff point falls within tool messages, the system adjusts to keep the entire AI + Tool message sequence together
|
||||
- **Summary Format**: Summary is injected as a HumanMessage with the format:
|
||||
```
|
||||
Here is a summary of the conversation to date:
|
||||
|
||||
[Generated summary text]
|
||||
```
|
||||
- **Summary Format**: Summary is merged into the leading SystemMessage with the format:
|
||||
```
|
||||
Internal conversation summary for continuity only...
|
||||
|
||||
<internal_context_summary>
|
||||
[generated summary text]
|
||||
</internal_context_summary>
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
@@ -329,7 +384,8 @@ Summarization runs after ThreadData and Sandbox initialization but before Title
|
||||
|
||||
- Configuration is loaded once at startup
|
||||
- Summarization is **non-destructive**: the summary replaces older messages only in the transient model request (`wrap_model_call`), never in persisted state. The checkpointer therefore keeps the full original conversation
|
||||
- The per-thread summary cache lives in a **process-wide, thread-safe LRU** (`_GLOBAL_SUMMARY_CACHE`, bounded), **not** on the middleware instance. `make_lead_agent` rebuilds a fresh middleware every run, so an instance cache would be cold each turn and re-run a blocking summary LLM call on every turn of a long conversation; the module-level cache keyed by `thread_id` lets a summary survive across turns. It is a cost optimization only — a cold/stale cache simply re-summarizes, which is always correct
|
||||
- The per-thread summary cache lives in a **process-wide, thread-safe LRU** (`_GLOBAL_SUMMARY_CACHE`, bounded), **not** on the middleware instance. `make_lead_agent` rebuilds a fresh middleware every run, so an instance cache would be cold each turn and re-run a blocking summary LLM call on every turn of a long conversation; the module-level cache keyed by `thread_id` lets a summary survive across turns. It is a cost optimization only — a cold/stale cache simply re-summarizes, which is always correct.
|
||||
- Compression normalizes LangChain's split request representation before invoking the next middleware/model: the dedicated `system_message` contains all system context exactly once, while `messages` contains no `SystemMessage`. This makes the fix independent of the configured provider adapter.
|
||||
|
||||
## Example Configurations
|
||||
|
||||
|
||||
Reference in new issue
Block a user