|
|
@@ -0,0 +1,461 @@
|
|
|
+# 文档编辑 AI 对话代码结构评审
|
|
|
+
|
|
|
+> 评审日期:2026-05-27
|
|
|
+> 评审范围:`views/document_chat`、`core/document_chat`、相关 `config` 配置文件
|
|
|
+> 评审重点:AI 对话功能的代码结构层级、文件组织、职责边界与后续可维护性
|
|
|
+
|
|
|
+## 1. 总体结论
|
|
|
+
|
|
|
+当前 AI 对话功能的整体分层方向是合理的,主链路也比较清楚:
|
|
|
+
|
|
|
+```text
|
|
|
+HTTP 接口层 views/document_chat
|
|
|
+ -> 工作流编排层 core/document_chat/workflows
|
|
|
+ -> 业务组件层 core/document_chat/component
|
|
|
+ -> 技能层 core/document_chat/skills
|
|
|
+ -> 基础设施层 foundation / Milvus / LLM
|
|
|
+```
|
|
|
+
|
|
|
+从现在只有 `document-answer` 和 `document-modify` 两个技能的规模来看,当前结构可以支撑功能运行和短期维护。
|
|
|
+
|
|
|
+但如果后续继续扩展更多技能、检索策略、SSE 事件或复杂对话能力,现有结构会逐渐变重。主要风险集中在两个文件:
|
|
|
+
|
|
|
+- `core/document_chat/component/retrieval_service.py`
|
|
|
+- `core/document_chat/workflows/document_chat_workflow.py`
|
|
|
+
|
|
|
+这两个文件已经承担了较多职责,是后续重构的优先位置。
|
|
|
+
|
|
|
+综合评价:**7/10**。
|
|
|
+
|
|
|
+## 2. 当前模块结构
|
|
|
+
|
|
|
+### 2.1 对外入口
|
|
|
+
|
|
|
+文件:`views/document_chat/views.py`
|
|
|
+
|
|
|
+主要职责:
|
|
|
+
|
|
|
+- 注册 `/sgbx/document_chat` 接口。
|
|
|
+- 支持 JSON 同步响应和 SSE 流式响应。
|
|
|
+- 生成 `callback_task_id`。
|
|
|
+- 记录请求和响应日志。
|
|
|
+- 调用 `document_chat_workflow`。
|
|
|
+- 将 LangGraph 的节点更新转换为前端 SSE 事件。
|
|
|
+
|
|
|
+调用入口:
|
|
|
+
|
|
|
+```text
|
|
|
+POST /sgbx/document_chat
|
|
|
+GET /sgbx/document_chat/health
|
|
|
+```
|
|
|
+
|
|
|
+### 2.2 核心工作流
|
|
|
+
|
|
|
+文件:`core/document_chat/workflows/document_chat_workflow.py`
|
|
|
+
|
|
|
+主要职责:
|
|
|
+
|
|
|
+- 构建 LangGraph 状态图。
|
|
|
+- 定义对话流程节点。
|
|
|
+- 路由意图识别结果。
|
|
|
+- 串联 RAG 链路。
|
|
|
+- 调用具体技能。
|
|
|
+- 组装最终响应数据。
|
|
|
+
|
|
|
+核心流程:
|
|
|
+
|
|
|
+```text
|
|
|
+validate_input
|
|
|
+ -> 无选中章节: general_answer -> complete
|
|
|
+ -> 有选中章节: load_context
|
|
|
+ -> load_skill_registry
|
|
|
+ -> recognize_intent
|
|
|
+ -> route_intent
|
|
|
+ -> clarify -> complete
|
|
|
+ -> unsupported -> complete
|
|
|
+ -> answer/modify
|
|
|
+ -> build_retrieval_query
|
|
|
+ -> vector_recall
|
|
|
+ -> rerank_context
|
|
|
+ -> quality_gate
|
|
|
+ -> run_answer_skill / run_modify_skill
|
|
|
+ -> complete
|
|
|
+```
|
|
|
+
|
|
|
+### 2.3 数据模型
|
|
|
+
|
|
|
+文件:
|
|
|
+
|
|
|
+- `core/document_chat/schemas.py`
|
|
|
+- `core/document_chat/component/state_models.py`
|
|
|
+
|
|
|
+`schemas.py` 定义 API 请求、响应、技能输入输出等结构:
|
|
|
+
|
|
|
+- `DocumentChatRequest`
|
|
|
+- `IntentResult`
|
|
|
+- `DocumentChatSkillInput`
|
|
|
+- `DocumentChatSkillOutput`
|
|
|
+- `DocumentChatData`
|
|
|
+- `DocumentChatResponse`
|
|
|
+
|
|
|
+`state_models.py` 定义 LangGraph 内部状态:
|
|
|
+
|
|
|
+- `DocumentChatState`
|
|
|
+
|
|
|
+整体上,API schema 和 workflow state 分开是合理的。
|
|
|
+
|
|
|
+### 2.4 业务组件
|
|
|
+
|
|
|
+目录:`core/document_chat/component`
|
|
|
+
|
|
|
+主要文件:
|
|
|
+
|
|
|
+| 文件 | 当前职责 |
|
|
|
+| --- | --- |
|
|
|
+| `intent_recognizer.py` | LLM 意图识别和启发式兜底 |
|
|
|
+| `skill_dispatcher.py` | 加载技能 YAML,分发到技能 handler |
|
|
|
+| `retrieval_service.py` | Query 构建、多路召回、RRF 融合、候选清洗、scope 过滤 |
|
|
|
+| `rerank_service.py` | 对召回结果做重排 |
|
|
|
+| `retrieval_quality_gate.py` | 过滤低质量参考资料 |
|
|
|
+| `conversation_context.py` | 对上下文做轻量整理 |
|
|
|
+| `prompt_loader.py` | 加载 prompt 配置 |
|
|
|
+| `llm_utils.py` | JSON 提取、模型输出解析等工具 |
|
|
|
+| `document_chat_logger.py` | 文档对话结构化日志 |
|
|
|
+
|
|
|
+### 2.5 技能层
|
|
|
+
|
|
|
+目录:`core/document_chat/skills`
|
|
|
+
|
|
|
+当前技能:
|
|
|
+
|
|
|
+- `document-answer`
|
|
|
+- `document-modify`
|
|
|
+
|
|
|
+实现文件:
|
|
|
+
|
|
|
+- `document_answer.py`
|
|
|
+- `document_modify.py`
|
|
|
+- `base.py`
|
|
|
+
|
|
|
+配置文件:
|
|
|
+
|
|
|
+- `skills/document-answer/skill.yaml`
|
|
|
+- `skills/document-modify/skill.yaml`
|
|
|
+
|
|
|
+当前做法是:YAML 描述技能元信息,Python 文件实现具体能力,`SkillDispatcher` 负责加载和分发。
|
|
|
+
|
|
|
+### 2.6 配置层
|
|
|
+
|
|
|
+相关文件:
|
|
|
+
|
|
|
+- `config/document_chat_retrieval.yaml`
|
|
|
+- `config/prompt/document_chat_intent.yaml`
|
|
|
+- `config/prompt/document_answer_prompt.yaml`
|
|
|
+- `config/prompt/document_modify_prompt.yaml`
|
|
|
+
|
|
|
+检索参数和 prompt 外置是合理的,方便调参和迭代提示词。
|
|
|
+
|
|
|
+## 3. 结构合理的地方
|
|
|
+
|
|
|
+### 3.1 主链路清晰
|
|
|
+
|
|
|
+从接口到工作流,再到组件和技能,方向比较明确。阅读 `document_chat_workflow.py` 可以快速理解 AI 对话功能的完整执行路径。
|
|
|
+
|
|
|
+### 3.2 工作流和技能有基本解耦
|
|
|
+
|
|
|
+工作流并不直接写死问答和修改的全部实现,而是通过 `SkillDispatcher` 调用具体技能。后续新增技能时,理论上可以沿用 YAML + handler 的方式扩展。
|
|
|
+
|
|
|
+### 3.3 RAG 链路被拆成多个阶段
|
|
|
+
|
|
|
+当前 RAG 链路包括:
|
|
|
+
|
|
|
+- 检索 query 构建
|
|
|
+- 多路召回
|
|
|
+- rerank
|
|
|
+- quality gate
|
|
|
+- approved references 注入技能 prompt
|
|
|
+
|
|
|
+这个流程划分是清楚的,比直接把向量库结果全部塞进 LLM 更稳。
|
|
|
+
|
|
|
+### 3.4 配置外置
|
|
|
+
|
|
|
+检索阈值、召回数量、prompt 都放在配置文件中,便于调参,不需要频繁改业务代码。
|
|
|
+
|
|
|
+### 3.5 技能注册有白名单
|
|
|
+
|
|
|
+`SkillDispatcher` 中的 `_HANDLER_CLASSES` 是显式白名单,比从 YAML 中任意动态 import 更安全,也更容易追踪依赖。
|
|
|
+
|
|
|
+## 4. 主要结构问题
|
|
|
+
|
|
|
+### 4.1 `component` 层职责偏杂
|
|
|
+
|
|
|
+`component` 当前包含了服务类、工具函数、状态模型、日志工具、prompt loader、检索核心算法等多类内容。
|
|
|
+
|
|
|
+短期可以接受,但长期来看,`component` 会越来越像杂物层。建议逐步拆成更明确的子包:
|
|
|
+
|
|
|
+```text
|
|
|
+core/document_chat/
|
|
|
+ application/
|
|
|
+ workflows/
|
|
|
+ retrieval/
|
|
|
+ skills/
|
|
|
+ events/
|
|
|
+ schemas.py
|
|
|
+```
|
|
|
+
|
|
|
+### 4.2 `retrieval_service.py` 过重
|
|
|
+
|
|
|
+文件:`core/document_chat/component/retrieval_service.py`
|
|
|
+
|
|
|
+该文件当前承担了较多职责:
|
|
|
+
|
|
|
+- 检索配置加载。
|
|
|
+- 检索 query 构建。
|
|
|
+- 关键词提取。
|
|
|
+- scope 提取。
|
|
|
+- Milvus filter 表达式构建。
|
|
|
+- parent vector 召回。
|
|
|
+- child locator 召回。
|
|
|
+- tag 召回。
|
|
|
+- chapter similarity 召回。
|
|
|
+- RRF 融合。
|
|
|
+- 候选对象构建。
|
|
|
+- metadata 标准化。
|
|
|
+- 候选清洗和去重。
|
|
|
+- 日志打包。
|
|
|
+
|
|
|
+这已经不是单一 service,而是完整的 retrieval 子系统。后续如果要调某一路召回,很容易影响整个文件。
|
|
|
+
|
|
|
+建议拆分为:
|
|
|
+
|
|
|
+```text
|
|
|
+core/document_chat/retrieval/
|
|
|
+ config.py
|
|
|
+ query_builder.py
|
|
|
+ service.py
|
|
|
+ fusion.py
|
|
|
+ scope.py
|
|
|
+ candidate.py
|
|
|
+ keyword_extractor.py
|
|
|
+ retrievers/
|
|
|
+ parent_vector.py
|
|
|
+ child_locator.py
|
|
|
+ tag.py
|
|
|
+ chapter_similarity.py
|
|
|
+```
|
|
|
+
|
|
|
+### 4.3 `document_chat_workflow.py` 偏重
|
|
|
+
|
|
|
+文件:`core/document_chat/workflows/document_chat_workflow.py`
|
|
|
+
|
|
|
+该文件当前同时承担:
|
|
|
+
|
|
|
+- 图结构定义。
|
|
|
+- 所有节点实现。
|
|
|
+- 路由函数。
|
|
|
+- 通用回答 LLM 调用。
|
|
|
+- SSE stream writer 捕获。
|
|
|
+- 技能输入构造。
|
|
|
+- 最终响应组装。
|
|
|
+- 错误状态构造。
|
|
|
+
|
|
|
+建议把图定义和节点实现拆开:
|
|
|
+
|
|
|
+```text
|
|
|
+core/document_chat/workflows/
|
|
|
+ graph.py
|
|
|
+ nodes.py
|
|
|
+ response_mapper.py
|
|
|
+```
|
|
|
+
|
|
|
+其中:
|
|
|
+
|
|
|
+- `graph.py` 只负责 LangGraph 节点和边。
|
|
|
+- `nodes.py` 负责节点逻辑。
|
|
|
+- `response_mapper.py` 负责 state -> response data。
|
|
|
+
|
|
|
+### 4.4 `general_answer` 没有进入技能体系
|
|
|
+
|
|
|
+当前:
|
|
|
+
|
|
|
+- `document-answer` 是技能。
|
|
|
+- `document-modify` 是技能。
|
|
|
+- `general_answer` 直接写在 workflow 中。
|
|
|
+
|
|
|
+这会导致能力扩展模式不一致。
|
|
|
+
|
|
|
+建议把通用回答也做成技能:
|
|
|
+
|
|
|
+```text
|
|
|
+core/document_chat/skills/general-answer/
|
|
|
+ skill.yaml
|
|
|
+
|
|
|
+core/document_chat/skills/general_answer.py
|
|
|
+```
|
|
|
+
|
|
|
+这样工作流只负责路由,不直接内联 LLM 调用。
|
|
|
+
|
|
|
+### 4.5 SSE 层依赖内部节点名
|
|
|
+
|
|
|
+文件:`views/document_chat/views.py`
|
|
|
+
|
|
|
+当前 SSE 事件构建依赖这些节点名:
|
|
|
+
|
|
|
+- `recognize_intent`
|
|
|
+- `rerank_context`
|
|
|
+- `quality_gate`
|
|
|
+- `run_answer_skill`
|
|
|
+- `run_modify_skill`
|
|
|
+- `general_answer`
|
|
|
+
|
|
|
+如果 workflow 节点名调整,前端事件逻辑也要跟着改。
|
|
|
+
|
|
|
+建议后续让 workflow 或事件转换层输出标准事件,例如:
|
|
|
+
|
|
|
+```text
|
|
|
+DocumentChatEvent(
|
|
|
+ type="retrieval_result",
|
|
|
+ payload={...}
|
|
|
+)
|
|
|
+```
|
|
|
+
|
|
|
+`views` 层只负责把事件格式化为 SSE。
|
|
|
+
|
|
|
+### 4.6 数据契约有少量漂移
|
|
|
+
|
|
|
+`schemas.py` 中定义了:
|
|
|
+
|
|
|
+- `DiffItem`
|
|
|
+- `DiffResult`
|
|
|
+- `DocumentChatData.old_content_hash`
|
|
|
+- `DocumentChatData.new_content_hash`
|
|
|
+- `DocumentChatData.diff`
|
|
|
+- `DocumentChatData.diff_granularity`
|
|
|
+
|
|
|
+`state_models.py` 中也有:
|
|
|
+
|
|
|
+- `diff_result`
|
|
|
+
|
|
|
+但当前 workflow 没有实际生成和回填 diff 结果。
|
|
|
+
|
|
|
+这说明有部分字段可能是预留或遗留设计。建议确认前端是否仍需要这些字段:
|
|
|
+
|
|
|
+- 如果需要,应补齐 diff 生成流程。
|
|
|
+- 如果不需要,应减少响应契约中的无效字段。
|
|
|
+
|
|
|
+## 5. 推荐目标结构
|
|
|
+
|
|
|
+一个更适合后续扩展的结构可以是:
|
|
|
+
|
|
|
+```text
|
|
|
+core/document_chat/
|
|
|
+ __init__.py
|
|
|
+ schemas.py
|
|
|
+
|
|
|
+ workflows/
|
|
|
+ graph.py
|
|
|
+ nodes.py
|
|
|
+ response_mapper.py
|
|
|
+
|
|
|
+ retrieval/
|
|
|
+ config.py
|
|
|
+ query_builder.py
|
|
|
+ service.py
|
|
|
+ fusion.py
|
|
|
+ scope.py
|
|
|
+ candidate.py
|
|
|
+ keyword_extractor.py
|
|
|
+ quality_gate.py
|
|
|
+ rerank_service.py
|
|
|
+ retrievers/
|
|
|
+ base.py
|
|
|
+ parent_vector.py
|
|
|
+ child_locator.py
|
|
|
+ tag.py
|
|
|
+ chapter_similarity.py
|
|
|
+
|
|
|
+ intent/
|
|
|
+ recognizer.py
|
|
|
+
|
|
|
+ skills/
|
|
|
+ base.py
|
|
|
+ dispatcher.py
|
|
|
+ document_answer.py
|
|
|
+ document_modify.py
|
|
|
+ general_answer.py
|
|
|
+ document-answer/
|
|
|
+ skill.yaml
|
|
|
+ document-modify/
|
|
|
+ skill.yaml
|
|
|
+ general-answer/
|
|
|
+ skill.yaml
|
|
|
+
|
|
|
+ events/
|
|
|
+ sse_mapper.py
|
|
|
+
|
|
|
+ observability/
|
|
|
+ logger.py
|
|
|
+
|
|
|
+ utils/
|
|
|
+ llm_output.py
|
|
|
+ prompt_loader.py
|
|
|
+```
|
|
|
+
|
|
|
+这不是必须一次性完成的重构目标,可以按风险和收益逐步迁移。
|
|
|
+
|
|
|
+## 6. 建议重构优先级
|
|
|
+
|
|
|
+### P1:优先拆检索层
|
|
|
+
|
|
|
+收益最大,风险可控。
|
|
|
+
|
|
|
+建议顺序:
|
|
|
+
|
|
|
+1. 提取 `RetrievalConfig` 和 `load_retrieval_config` 到 `retrieval/config.py`。
|
|
|
+2. 提取 query 和关键词逻辑到 `retrieval/query_builder.py`。
|
|
|
+3. 提取 scope 逻辑到 `retrieval/scope.py`。
|
|
|
+4. 提取 RRF 融合到 `retrieval/fusion.py`。
|
|
|
+5. 四路召回分别拆到 `retrieval/retrievers/`。
|
|
|
+6. 保留一个薄的 `DocumentChatRetrievalService` 作为编排入口。
|
|
|
+
|
|
|
+### P2:拆 workflow
|
|
|
+
|
|
|
+建议顺序:
|
|
|
+
|
|
|
+1. 提取 `to_response_data` 到 `response_mapper.py`。
|
|
|
+2. 提取 `_build_skill_input` 到单独 mapper 或 factory。
|
|
|
+3. 把节点实现移到 `nodes.py`。
|
|
|
+4. 让 `graph.py` 只负责定义节点和边。
|
|
|
+
|
|
|
+### P3:统一技能体系
|
|
|
+
|
|
|
+建议把 `general_answer` 也做成技能,避免 workflow 内联 LLM 调用。
|
|
|
+
|
|
|
+### P4:清理 schema 和 state
|
|
|
+
|
|
|
+确认 diff 字段是否仍属于对话功能的稳定契约:
|
|
|
+
|
|
|
+- 如果需要,补上 diff 生成。
|
|
|
+- 如果不需要,标记废弃或移除。
|
|
|
+
|
|
|
+### P5:抽象 SSE 事件
|
|
|
+
|
|
|
+将前端事件构建从 `views.py` 中移出,避免接口层直接感知 workflow 内部节点名。
|
|
|
+
|
|
|
+## 7. 保持现状时的注意事项
|
|
|
+
|
|
|
+如果短期不做结构重构,至少建议做到:
|
|
|
+
|
|
|
+- 新增检索策略时,不继续往 `retrieval_service.py` 中堆大段逻辑。
|
|
|
+- 新增对话能力时,优先做成 skill,不直接写进 workflow。
|
|
|
+- 修改 workflow 节点名时,同步检查 `views/document_chat/views.py` 的 SSE 事件映射。
|
|
|
+- 调整 `DocumentChatData` 字段时,同步确认前端消费逻辑。
|
|
|
+- 给 `intent_recognizer`、`retrieval_quality_gate`、`rerank_service`、`skill_dispatcher` 增加单元测试。
|
|
|
+
|
|
|
+## 8. 最终判断
|
|
|
+
|
|
|
+当前代码结构不是乱的,主干清楚、功能边界已有雏形,适合继续迭代。
|
|
|
+
|
|
|
+真正需要警惕的是:`retrieval_service.py` 和 `document_chat_workflow.py` 已经开始承担子系统级职责。继续扩展时,如果不拆分,这两个文件会成为后续维护、测试和排查问题的主要成本点。
|
|
|
+
|
|
|
+建议后续重构从检索层开始,因为它体量最大、职责最多,而且拆分后对工作流和 API 的外部行为影响最小。
|