# OMAP AI 问答读取历史数据 —— 设计方案 > 目标:让现有 AI 问答(ChatGXY)能够读取**当前历史(History)**中的数据集内容, > 从而支持"基于 AI 智能解读基因检测报告 / 基因组数据"等功能。 > > 本文为**设计文档**,仅描述改动清单与实现思路,暂不改动任何代码。 --- ## 1. 现状诊断(已核实) ### 1.1 前端调用链 `ChatGXY.vue` 通过 `/api/chat` 调用 AI,请求仅携带: | 字段 | 当前值 | 说明 | |------|--------|------| | `query` | 用户问题文本 | 唯一的数据输入 | | `agent_type` | `"router"` 等 | 指定使用的 agent | | `exchange_id` | 会话 ID | 用于加载历史对话 | | `context` | `null` | **固定为 null,未携带任何历史/数据集信息** | 前端 Store 中**已经存在**所需数据源: - `useHistoryStore().currentHistoryId`(`historyStore.ts` 第 69 行)—— 当前历史 ID - `useHistoryItemsStore()` —— 当前历史的数据集列表(含 `id`、`name`、`hid`、`file_size` 等) ### 1.2 后端调用链 `/api/chat` → `chat.py` → `AgentService.route_and_execute()` / `execute_agent()` → 对应 Agent。 ### 1.3 依赖注入现状(`managers/agents.py`) `AgentService.create_dependencies()`(第 50-57 行)返回 `GalaxyAgentDependencies`: ```python return GalaxyAgentDependencies( trans=trans, user=user, config=self.config, job_manager=self.job_manager, # 注意:未设置 dataset_manager、workflow_manager(默认 None) ) ``` `GalaxyAgentDependencies`(`agents/base.py`)已声明 `dataset_manager` 字段,但当前**为 None**。 ### 1.4 Agent 能力现状 - Router(`agents/router.py`)使用 pydantic-ai `Agent`,仅配置了 `deps_type` + `system_prompt` + `output_type`,**没有注册任何"读取数据"工具**。 - `_build_query_with_context()`(router.py 第 265 行)只把 `conversation_history` 拼进 query,**不读取任何数据集内容**。 - 因此:**当前 AI 问答完全无法看到历史中的任何数据文件内容**,只能看到对话记录。 --- ## 2. 实现目标 打通如下数据流: ``` 前端 ChatGXY │ 携带 currentHistoryId (+ 可选 datasetId) ▼ POST /api/chat (context 结构体) │ ▼ chat.py 解析 context → AgentService │ 注入 dataset_manager ▼ GalaxyAgentDependencies.dataset_manager(非 None) │ ▼ Router Agent 新增工具: - list_history_datasets() 列出当前历史的数据集(名称/id/类型/大小) - read_history_dataset(id) 读取指定数据集内容(分页/截断) ▼ LLM 基于读取到的数据回答(如解读基因报告) ``` --- ## 3. 详细改动清单 ### 3.1 后端:扩展 API 请求结构 **文件**:`lib/galaxy/schema/agents.py`(或 `chat.py` 中定义的结构体) 现状:`ChatPayload.context` 类型为 `Optional[str]`(旧格式),且新格式 `query_context` 当前固定为 `{}`。 改动: - 新增/扩展一个结构化 context 字段,如 `history_id`、`active_dataset_id`(可选)。 - 保持向后兼容:`context` 仍接受字符串旧格式;新增结构化字段单独解析。 建议的新增字段: ```python class ChatContext(BaseModel): history_id: Optional[str] = None # 当前选中的历史 ID active_dataset_id: Optional[str] = None # 可选:当前选中的数据集 ID ``` ### 3.2 后端:注入 dataset_manager **文件**:`lib/galaxy/managers/agents.py` → `create_dependencies()` 改动:从 `trans` 中获取并注入 `DatasetManager`: ```python dataset_manager = trans.app.dataset_manager if hasattr(trans, "app") and hasattr(trans.app, "dataset_manager") else None return GalaxyAgentDependencies( trans=trans, user=user, config=self.config, job_manager=self.job_manager, dataset_manager=dataset_manager, # ← 新增注入 ) ``` > 需确认 `trans.app` 上是否存在 `dataset_manager`;如命名不同,用等价对象(通常为 `galaxy.managers.datasets.DatasetManager`)。 ### 3.3 后端:新增 Router 数据读取工具 **文件**:`lib/galaxy/agents/router.py` 为 router agent 注册两个 pydantic-ai **tool 函数**,并把它传给 `Agent(..., tools=[...])`: ```python # 1) 列出当前历史的数据集 @ctx.tool() async def list_history_datasets(ctx: RunContext[GalaxyAgentDependencies]) -> str: """列出当前历史中的数据集(名称、ID、类型、大小)。 当用户提到"我的历史/历史里的数据/读取数据集"时调用。""" ds_mgr = ctx.deps.dataset_manager history_id = ... # 从 ctx.deps(或上下文)获取当前 history_id datasets = ... # ds_mgr 获取该历史下用户可访问的数据集 return json.dumps([{...} for d in datasets]) # 2) 读取指定数据集内容 @ctx.tool() async def read_history_dataset( ctx: RunContext[GalaxyAgentDependencies], dataset_id: int, max_lines: int = 200 ) -> str: """读取指定数据集的内容(截断为前 max_lines 行)。 用于解读基因检测报告、查看 VCF/TSV/文本数据等。""" ds_mgr = ctx.deps.dataset_manager hda = ds_mgr.get_accessible(dataset_id, ctx.deps.user) # 权限校验 content = hda.dataset.object_store.get_data(hda.dataset, start=0, count=max_lines) return content ``` 要点: - **权限**:必须用 `ds_mgr.get_accessible(dataset_id, user)` 校验,防止越权读取他人数据。 - **截断**:用 `object_store.get_data(obj, start, count)`(`objectstore/__init__.py` 第 244-250 行)只读前 N 行,避免超长上下文。 - **数据类型判断**:读取前检查 `hda.dataset.ext` / `hda.datatype`,对二进制(如 BAM/PDF)返回"不支持预览"提示,而非乱码。 ### 3.4 后端:传递 history_id 到 agent 依赖 `history_id` 需要从 context 一路传递到 agent 工具里。方式: - 在 `GalaxyAgentDependencies` 增加可选字段 `history_id: Optional[str] = None`; - 或在 `AgentService.route_and_execute` 解析 context 后,注入到 deps。 ### 3.5 前端:ChatGXY 携带历史上下文 **文件**:`client/src/components/ChatGXY.vue` 在调用 `/api/chat` 时,从 store 读取当前历史并随请求发送: ```ts const historyStore = useHistoryStore(); const currentHistoryId = historyStore.currentHistoryId; await GalaxyApi().POST("/api/chat", { params: { query: { agent_type: selectedAgentType.value } }, body: { query: currentQuery, context: { history_id: currentHistoryId, // ← 新增 }, exchange_id: currentChatId.value, }, }); ``` ### 3.6 提示词:让 AI 知道它能读取数据 **文件**:`lib/galaxy/agents/prompts/router.md`(及可选 `tool_recommendation.md`) 在系统提示词中加入能力说明,例如: > 当用户询问历史中的数据或要求解读数据集(如基因检测报告、VCF、表格数据)时, > 使用 `list_history_datasets` 列出当前历史的数据集,并用 `read_history_dataset` > 读取相关内容后再回答。读取时只基于提供的数据给出解读,不做超出数据的诊断断言。 > 注意:此文件已按 OMAP 品牌汉化,新增内容需同样使用中文表述并符合 OMAP 品牌(内部符号名保留)。 ### 3.7 兼容性 / 错误处理 - 当 `dataset_manager` 为 None、或未传 `history_id`、或数据集不可访问时,工具返回友好提示(如"无法访问该数据集"),不影响其它问题回答。 - 二进制/大文件回退为元数据摘要。 --- ## 4. 改动文件汇总 | 文件 | 改动类型 | 内容 | |------|---------|------| | `lib/galaxy/schema/agents.py`(或 chat.py) | 修改 | context 增加 `history_id` 等结构化字段 | | `lib/galaxy/managers/agents.py` | 修改 | `create_dependencies()` 注入 `dataset_manager` | | `lib/galaxy/agents/base.py` | 修改 | `GalaxyAgentDependencies` 增加 `history_id` 可选字段 | | `lib/galaxy/agents/router.py` | 修改 | 注册 `list_history_datasets` / `read_history_dataset` 工具 | | `lib/galaxy/agents/prompts/router.md` | 修改 | 提示词加入"读取历史数据"能力说明(中文/OMAP) | | `client/src/components/ChatGXY.vue` | 修改 | 发送请求时携带 `currentHistoryId` | --- ## 5. 安全与边界(必须遵守) 1. **权限校验**:所有数据集读取必须经过 `get_accessible()` 或等价校验,防止越权。 2. **上下文截断**:使用 `object_store.get_data(start, count)` 限制读取行数(建议默认 200 行,可配置)。 3. **二进制文件保护**:对非文本数据类型拒绝直接读取,返回提示。 4. **不做超范围断言**:提示词中明确"仅基于提供的数据解读,不做医疗诊断结论"。 5. **OMAP 品牌**:新增用户可见文本使用中文 + OMAP;保留内部符号(`read_history_dataset`、`list_history_datasets` 等)与真实 URL。 --- ## 6. 后续可扩展方向(本方案之外) - 结构化解读:针对 VCF / 特定报告格式做字段解析(变异位点、临床意义、用药建议)。 - 数据集选择器:对话中让 AI 引导用户选择具体数据集 ID。 - 专用"报告解读"页面(方案 B):独立界面,选报告 → AI 解读 → 结构化展示。 --- ## 7. 待确认项 1. `trans.app` 上 `dataset_manager` 的确切属性名(需运行时确认,可能是 `dataset_manager` 或 `hda_manager`)。 2. 前端 `currentHistoryId` 在未登录/无历史时的取值(可能为 null),需处理空值。 3. 是否需要"读取当前选中的数据集"(比"列出+读取历史所有数据集"更聚焦),可后续加 `active_dataset_id`。