plan-sess_f2e7ba1e-4b44-4bff-92c2-d69b0de3e486.md 7.0 KB

输入框附件问答 —— 终版(三路 + 最小流程线)

一、路由决策(f.txt 步骤 3-5 落地,不加冗余流水线)

上传时做结构化类型路由(后缀 + Tika MIME,无 LLM):

路径 条件 处理 进大模型的方式
拒绝 图片/音视频/压缩包/加密 明确提示不支持(无 OCR/ASR) 不发消息
A 内联 小表格(≤200 行)/ 小文档(全文 ≤30000 字) 一次提取:表格→markdown 全文;文档→Tika 全文 全文拼进 USER 消息(Claude document block 同款)
B Python 直读 大表格(含 100MB CSV) 一次流式扫描:行数+表头+前 20 样本(不物化);xlsx 顺手转 sidecar csv(免 openpyxl 依赖)。不落 DuckDB、不建表 消息注入「文件路径+行数+列+样本+指引」→ Agent 写 execute_python:pd.read_csv(path) pandas/numpy 全量计算(沙箱已验证可读任意服务器路径)
C RAG 大文档(100MB txt/word、全文 >30000 字) 全文落 sidecar → 分块(1000/重叠100, 上限500块) → embedding → pgvector doStream 按用户问题 cosine 检索 top-8 片段注入 + 指引

计算意图 = 已有沙箱,零新开发:PythonAnalysisTool.execute_python(ProcessBuilder 子进程隔离,pandas/numpy/duckdb/matplotlib,只读 DuckDB 快照,超时控制)+ execute_sql。不加前置意图分类器——意图路由由会话绑定 Agent 内化(IntentMiddleware + 工具选择),注入块写死指引:

表数据文件:{path}(N 行,列:…,前 20 行样本见上)。计算/统计必须用 execute_python 读该文件全量计算,不要只依据样本;文档问题基于上方全文/检索片段回答。

执行过程经现有 tool_call/tool_input/tool_result SSE 在 ProcessTimeline 可视化(写了什么代码、跑了什么、返回什么)——f.txt 的"生成代码→沙箱执行→返回"完整呈现。

业界对应:A=Claude document block / ChatGPT 小文件直注入;B=Code Interpreter 直读文件;C=ChatGPT file_search RAG。

二、数据隔离(三层,你的硬要求)

  1. 磁盘:{storage.root}/chat-attachments/{userId}/{caseId}/{sessionId}/{雪花}_{safeName},sanitize + normalize 防穿越(沿用 DmService#saveUploadedFile 手法)
  2. 附件表三重归属:chat_attachment 记 user_id/case_id/session_id,loadForSession 校验不匹配即 400——跨用户/跨案件/跨会话读不到;Python 沙箱只拿到归属校验通过后的路径
  3. RAG 向量表:chat_att_vec_{caseId}_d{dims} 按案件分表(沿 rag_ws{id}_d{dims} 惯例);chunk payload 强制带 sessionId+attachmentId,检索 SQL WHERE 过滤当前会话——同案件其他会话搜不到别人的附件片段

(不写 DuckDB,故无需 DuckDB 层隔离;沙箱可读服务器路径是既有属性,非本次新增面。)

三、后端设计

新增 module/agent/attachment/:

  1. ChatAttachmentService
    • upload(file, session):校验(表格 ≤200MB / 文档 ≤50MB / 白名单 / sanitize)→ 存盘三层路径 → 路由执行(A 提取 / B 流式扫描+(xlsx)sidecar csv / C Tika 全文→sidecar→同步 RAG 索引)→ 落表 → 返回 {id, name, size, kind, rows/chars, truncated, indexedChunks?, status}
    • loadForSession(ids, session, question):三重归属 → 内联块 / 表指引块(路径+schema+样本)/ 大文档用 question 检索 top-8 片段(复用 RagVectorMapper.searchVector 同款 cosine SQL + session 过滤)
    • purgeOrphans():@Scheduled 24h 清理(文件 + sidecar + pgvector 行)
  2. AttachmentSheetScanner:csv 流式一行行读(行数+表头+样本,常数内存);xls/xlsx Fesod AiRowReader.forEachRow 流式(同一回调顺写 sidecar csv)
  3. AttachmentDocIndexer:分块 → embedding(EmbeddingModelFactory.resolveDefault(),与 RagSchemaService 同一模型维度,并发 8 路)→ RagVectorMapper 建表/写入。未配置 embedding 模型 → 上传报错「未配置嵌入模型,无法索引大文档」(与现有 RAG 同一前提)
  4. AttachmentContentRouter:DOCUMENT | TABLE | UNSUPPORTED(纯函数单测)
  5. ChatAttachment entity + Mapper + sql/chat_attachment.sql:id/user_id/case_id/session_id/file_name/file_path/sidecar_path/mime/kind/size_bytes/char_count/row_count/truncated/index_blocks/index_status/status/link_message_id/create_at
  6. ChatAttachmentProperties:zsjz.chat-attachment(enabled/max-doc-mb=50/max-sheet-mb=200/inline-max-chars=30000/inline-max-rows=200/chunk-size=1000/chunk-overlap=100/max-chunks=500/top-k=8/白名单)
  7. AgentChatController 增 POST /chat/attachments(multipart)

修改:ChatRequestDTO + attachmentIds;AgentChatServiceImpl#doStream 的 msgs 组装(附件块+问题原文单条 USER Msg);persistUserMessage 落原文 + metadata.attachments 徽标。

四、前端设计

  • ChatComposer.vue:附件按钮 + hidden input(aiPlugin 三件套)→ 选择即上传(defHttp.uploadFile 显式拼 ctxPath+adminPath,进度抄 governApi)→ chips(文件名+大小+「N 行/已索引 N 片/截断」+转圈/失败×移除);emit('send'):string → { text, attachmentIds }
  • ChatPanel / useAiChatPage / chatStream.ts:载荷透传;乐观 userMessage 带附件;onRegenerate 复用原 attachmentIds
  • chatApi.ts+types.ts:ChatStreamParams.attachmentIds(协议前后端同步,AI_AGENT.md §6)
  • ChatMessageItem.vue:用户气泡徽标;hydrateMessage 从 metadata.attachments 还原历史

五、验证计划

  1. mvn compile + 新单测:Router 三分类、SheetScanner(csv 行数/样本/xlsx sidecar)、Extractor 截断、Indexer 分块与 payload session 字段、loadForSession 跨会话 400、doStream 注入块(mock agent 捕获 Msg)
  2. 浏览器端到端:① 100MB CSV 问总金额 → 时间线出现 execute_python(pd.read_csv)→ 结果对行数核对;② 100MB txt/word 问细节 → RAG 片段注入 → 原文可核对;③ 小 txt/小表直接回答;④ 图片拒绝;⑤ 刷新徽标仍在;⑥ 旧 attachmentId 换会话 400
  3. 既有测试全绿

六、风险

  • RAG 索引耗时:500 块×远程 embedding 并发 8 路 ≈ 十几秒(上传超时 120s);本地 bge-m3 慢 → 建议配远程 embedding(配置可调)
  • 文档 >50 万字:截断索引并在注入块标注「仅索引前 N 字」(诚实告知)
  • 附件生命周期:24h 孤儿清理(文件+sidecar+向量行);已关联随会话留存(级联删列后续)
  • pandas 读文件编码/分隔符报错 → tool_result 回给 Agent 自行纠错(read_csv 参数迭代),这是 Code Interpreter 模式的固有能力

预计改动:后端新增 7 文件 + DDL,修改 3;前端新增 1,修改 7;测试新增 3-4。相比上版砍掉了 DuckDB 导入整条流水线。