|
@@ -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 导入整条流水线。
|