cc 1 týždeň pred
rodič
commit
2f83ad83e8

+ 51 - 40
.zcode/plans/plan-sess_f2e7ba1e-4b44-4bff-92c2-d69b0de3e486.md

@@ -1,56 +1,67 @@
-# 本地嵌入模块(DJL + ONNX Runtime + bge-m3)—— 独立模块方案
+# 输入框附件问答 —— 终版(三路 + 最小流程线)
 
-## 定位(按你的要求调整)
+## 一、路由决策(f.txt 步骤 3-5 落地,不加冗余流水线)
 
-- **完全独立**的新模块,不用 agentscope 任何 API,不接入 `EmbeddingModelFactory`/`agent_model` 表/RAG 流程,前端无感知。这些现有代码**零改动**。
-- 对业务暴露一套简洁的 **Java API**(注入 `EmbeddingService` 即用);另附一个极小的调试 HTTP 接口用于人工验证(可随时删)。
-- 配置走 `application.yaml`(`zsjz.embedding.*`),与项目 `zsjz.duckdb`/`zsjz.insight` 等段风格一致,不进数据库。
+**上传时**做结构化类型路由(后缀 + Tika MIME,无 LLM):
 
-## 模块结构(新包 `com.zsjz.ai.module.embedding`)
+| 路径 | 条件 | 处理 | 进大模型的方式 |
+|---|---|---|---|
+| **拒绝** | 图片/音视频/压缩包/加密 | 明确提示不支持(无 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 片段注入 + 指引 |
 
-```
-module/embedding/
-├── config/EmbeddingProperties.java      # @ConfigurationProperties("zsjz.embedding")
-├── core/OnnxEmbeddingEngine.java        # 分词 + ONNX 推理 + CLS池化 + L2归一化(懒加载)
-├── core/EmbeddingException.java         # 模块内异常
-├── service/EmbeddingService.java        # 业务门面(具体类,风格仿 module/search 的 SearchQueryService)
-└── controller/EmbeddingDebugController.java  # 可选调试接口
-```
+**计算意图 = 已有沙箱,零新开发**:`PythonAnalysisTool.execute_python`(ProcessBuilder 子进程隔离,pandas/numpy/duckdb/matplotlib,只读 DuckDB 快照,超时控制)+ `execute_sql`。**不加前置意图分类器**——意图路由由会话绑定 Agent 内化(IntentMiddleware + 工具选择),注入块写死指引:
 
-## 业务 API(EmbeddingService,业务代码这样用)
+> 表数据文件:`{path}`(N 行,列:…,前 20 行样本见上)。**计算/统计必须用 execute_python 读该文件全量计算,不要只依据样本**;文档问题基于上方全文/检索片段回答。
 
-```java
-float[]        embed(String text);              // 单文本 → 1024 维归一化向量
-List<float[]>  embedBatch(List<String> texts);  // 批量(内部按 batch-size 分批推理)
-double         cosineSimilarity(float[] a, float[] b);
-int            getDimension();                  // 从模型输出自动探测(1024)
-boolean        isReady();  void warmUp();       // 懒加载状态 / 主动预热
-```
+执行过程经现有 `tool_call/tool_input/tool_result` SSE 在 ProcessTimeline 可视化(写了什么代码、跑了什么、返回什么)——f.txt 的"生成代码→沙箱执行→返回"完整呈现。
 
-## 依赖(ai-server/pom.xml,实施时先校验 Maven Central 最新可用版)
+**业界对应**:A=Claude document block / ChatGPT 小文件直注入;B=Code Interpreter 直读文件;C=ChatGPT file_search RAG。
 
-- `ai.djl.huggingface:tokenizers`(0.36.0,2026-09-10 发布)—— 直接加载 `bge-m3/tokenizer.json`,原生 Rust 分词
-- `com.microsoft.onnxruntime:onnxruntime`(1.30.0 左右,校验 metadata 后定版)—— 各平台 CPU native 内置
+## 二、数据隔离(三层,你的硬要求)
 
-## 核心实现要点
+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 过滤当前会话**——同案件其他会话搜不到别人的附件片段
 
-1. **配置项** `zsjz.embedding`:`model-path`(默认 `bge-m3`,相对 user.dir)、`max-seq-len`(默认 8192)、`batch-size`(默认 8)、`intra-op-threads`、`opt-level`(默认 BASIC)、`enabled`
-2. **引擎懒加载单例**(Spring bean,volatile + synchronized):首次调用才加载 2.3GB 权重,不阻塞启动;session 从**文件路径**创建,天然支持 `onnx/model.onnx + model.onnx_data` 外部权重(找不到 `onnx/model.onnx` 时回退 `model.onnx`)
-3. **推理**:tokenizer 逐条编码(截断到 max-seq-len)→ 批内手动 padding(input_ids / attention_mask / token_type_ids 按会话实际输入名动态构建,输入输出名从 session 动态读取)→ 取输出 `last_hidden_state[:, 0]`(CLS)→ L2 归一化(bge-m3 官方 dense 用法)
-4. **推理在线程池隔离执行**,避免阻塞调用线程;失败抛 `EmbeddingException`,模型目录缺失给出明确提示
+(不写 DuckDB,故无需 DuckDB 层隔离;沙箱可读服务器路径是既有属性,非本次新增面。)
 
-## 调试接口(验证用,Result<T> 包装,符合项目惯例)
+## 三、后端设计
 
-`POST /js/a/embedding/debug`,body `{"texts": ["转账给可疑账户", "向陌生账户转移资金"]}` → 返回维度、各向量、两两余弦相似度,用于人工确认语义区分度。
+**新增 `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)
 
-1. `mvn -pl ai-server compile` 通过
-2. 启动应用,curl 调试接口:确认返回 1024 维、归一化正确(模长≈1)、相关语句相似度显著高于无关语句、首次调用触发模型加载日志
-3. 业务接入示例写入类 Javadoc
+**修改**:`ChatRequestDTO` + `attachmentIds`;`AgentChatServiceImpl#doStream` 的 `msgs` 组装(附件块+问题原文单条 USER Msg);`persistUserMessage` 落**原文** + `metadata.attachments` 徽标。
 
-## 风险与注意事项
+## 四、前端设计
 
-- **内存**:权重在堆外(native),进程 RSS 约 +2.5~3GB;首次加载 20~60 秒
-- **CPU 速度**:表结构/短文本(几百 token)毫秒级~秒级;超长文本可通过 `max-seq-len` 控制
-- **GPU**:本期 CPU;后续换 `onnxruntime_gpu` 依赖 + ExecutionProvider 配置即可,API 不变
+- **`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 导入整条流水线。

+ 83 - 0
f.txt

@@ -0,0 +1,83 @@
+用户上传文件 + 提问
+        │
+        ▼
+┌─────────────────────┐
+│  1. 文件上传与校验     │
+│  - 分片上传           │
+│  - MD5/SHA 校验       │
+│  - 存入对象存储        │
+└─────────┬───────────┘
+          │
+          ▼
+┌─────────────────────┐
+│  2. 文件类型识别       │
+│  - 读后缀名           │
+│  - 读 MIME 类型       │
+│  - 文件头探测          │
+└─────────┬───────────┘
+          │
+          ▼
+┌─────────────────────┐
+│  3. 用户意图识别       │
+│  - 关键词匹配          │
+│  - 或 LLM 分类        │
+│  输出:QA/计算/总结/识别│
+└─────────┬───────────┘
+          │
+          ▼
+┌─────────────────────────────────────────────┐
+│              4. 路由决策                       │
+│                                              │
+│  ┌──────────┐  ┌──────────┐  ┌──────────┐  │
+│  │ 文档类    │  │ 表格类    │  │ 图片类    │  │
+│  │PDF/Word  │  │CSV/Excel │  │JPG/PNG   │  │
+│  └────┬─────┘  └────┬─────┘  └────┬─────┘  │
+│       │             │             │          │
+│       ▼             ▼             ▼          │
+│  ┌──────────┐  ┌──────────┐  ┌──────────┐  │
+│  │有文本层? │  │计算意图? │  │OCR/多模态│  │
+│  └─┬────┬───┘  └─┬────┬───┘  └────┬─────┘  │
+│  是│    │否      是│    │否        │          │
+│    ▼    ▼        ▼    ▼           ▼          │
+│  ┌────┐┌────┐┌────┐┌────┐  ┌──────────┐    │
+│  │RAG ││OCR ││Code││RAG │  │OCR结果   │    │
+│  │    ││→RAG││解释││    │  │交给模型   │    │
+│  └────┘└────┘└────┘└────┘  └──────────┘    │
+│                                              │
+│  ┌──────────┐  ┌──────────┐  ┌──────────┐  │
+│  │音视频类   │  │代码类     │  │压缩包类   │  │
+│  │MP3/MP4   │  │PY/JS/LOG │  │ZIP/RAR   │  │
+│  └────┬─────┘  └────┬─────┘  └────┬─────┘  │
+│       │             │             │          │
+│       ▼             ▼             ▼          │
+│  ┌──────────┐  ┌──────────┐  ┌──────────┐  │
+│  │ASR转写   │  │代码RAG/  │  │解压后    │  │
+│  │→文本处理 │  │沙箱执行   │  │逐一路由   │  │
+│  └──────────┘  └──────────┘  └──────────┘  │
+└──────────────────┬──────────────────────────┘
+                   │
+                   ▼
+┌─────────────────────────────────────────────┐
+│         5. 执行对应处理路径                    │
+│                                              │
+│  RAG:  解析 → 切片 → 向量化 → 检索 → Rerank  │
+│  Code: 生成代码 → 沙箱执行 → 返回结果          │
+│  OCR:  识别文字 → 可选 RAG                    │
+│  ASR:  转写文本 → 摘要/检索                   │
+└──────────────────┬──────────────────────────┘
+                   │
+                   ▼
+┌─────────────────────┐
+│  6. 组装 Prompt       │
+│  - 系统指令           │
+│  - 检索片段/执行结果   │
+│  - 用户问题           │
+└─────────┬───────────┘
+          │
+          ▼
+┌─────────────────────┐
+│  7. 调用大模型生成答案  │
+└─────────┬───────────┘
+          │
+          ▼
+     返回给用户