Commit 4c9d29c30c3e2388f8a6f8b51a8aba7c4ae4e1c8
1 parent
8b927e3b
refactor: retire legacy stack; remove milvus/tts/ocr; slim deps (166→39 files)
P4 retirement + code slim-down (~18.5k lines deleted), on top of the
intent-gate WIP (docs §17/§18 + agent/tool refinements):
- Delete old 8-scene multi-agent stack: XlyErpService, SceneSelector/
Chati/ErpAi/DynamicTableNl2Sql agents, DynamicToolProvider (62 meta
tools), Scene/ToolMeta/ParamRule entities+mappers+startup caches
- Delete whole milvus/tts/ocr packages, /api/tts + /api/ocr endpoints,
tts.html, python stream_server.py; strip dead TTS JS from chat.html
(playByIndex/handleNormalResponse had no callers)
- Three-pass orphan sweep: 17 dead utils, 16 dead entities, dead
constants/exceptions, RedisService+RedisConfig, duplicate
JacksonConfig, OperableChatMemoryProvider, old prompt generators;
fold PageController into MvcConfig; trim OkHttpUtil 495→39 lines
- pom: ~35→13 deps (drop mybatis/JPA/webflux/tika/pdfbox/poi/jieba/
jsoup/gson/fastjson2/springdoc/mapstruct/pagehelper/pinyin4j/
spring-retry/jnr-ffi/hutool/ocr/milvus/embeddings; add starter-jdbc);
war 200M+→48M
- application.yml: drop milvus/tts/ocr/tesseract/mybatis blocks;
GlobalExceptionHandler returns plain {code,message} JSON
- docs: mark P4 retirement done in agent-architecture §13/§18
Verified: mvn clean package OK; boot smoke on :8199 (Started 0.9s,
/chat 200, health db UP). Old /api/tts & /api/ocr now 404 by design.
Showing
151 changed files
with
252 additions
and
18459 deletions
Too many changes to show.
To preserve performance only 73 of 151 files are displayed.
docs/agent-architecture.md
| @@ -2,6 +2,9 @@ | @@ -2,6 +2,9 @@ | ||
| 2 | 2 | ||
| 3 | > 状态:设计已收敛(2026-07-21)。全 Java / LangChain4j,单 ReAct agent + 通用工具 + 人在环写入。 | 3 | > 状态:设计已收敛(2026-07-21)。全 Java / LangChain4j,单 ReAct agent + 通用工具 + 人在环写入。 |
| 4 | > 关联:知识图谱底座见 `sql/README.md`;现状代码接线见 memory `xlyai-rag-milvus-wiring`。 | 4 | > 关联:知识图谱底座见 `sql/README.md`;现状代码接线见 memory `xlyai-rag-milvus-wiring`。 |
| 5 | +> | ||
| 6 | +> **阅读指引**:§1–§14 是**设计**(决策与取舍,基本稳定);§15/§16 是**历史实现快照**(按日期,不再更新); | ||
| 7 | +> **当前实现状态看 §17,未完成清单看 §18**。分支 `kg-edge-flow`(尚未合并 master)。 | ||
| 5 | 8 | ||
| 6 | ## 1. 背景与目标 | 9 | ## 1. 背景与目标 |
| 7 | 10 | ||
| @@ -17,10 +20,11 @@ | @@ -17,10 +20,11 @@ | ||
| 17 | 20 | ||
| 18 | ``` | 21 | ``` |
| 19 | 浏览器前端 xlyAi (Java/LangChain4j :8099) saas ERP 后端 xlyEntry(:8697) | 22 | 浏览器前端 xlyAi (Java/LangChain4j :8099) saas ERP 后端 xlyEntry(:8697) |
| 20 | -├ 聊天 UI(流式/diff确认/QA选项) ⇄ ┌ 单 ReAct Agent (AiServices 工具循环) ⇄ ├ 表单读写 API (getBusinessData…) | ||
| 21 | -└ ERP 原生表单(预填/执行确认) │ ChatMemory(Redis) · SystemPrompt(L1) ├ 暂存执行器 (新建, 排空 sp_ai_addcommonwx) | ||
| 22 | - │ 5 通用工具 + Skill 注册表 └ MySQL (业务库) | ||
| 23 | - └ 直连: Ollama(qwen2.5)/Milvus/MySQL(KG+NL2SQL) | 23 | +├ 聊天 UI(流式/diff确认/QA选项) ⇄ ┌ 意图门(受约束JSON) → 按意图收窄工具集 ⇄ ├ 表单读写 API (getBusinessData…) |
| 24 | +└ ERP 原生表单(预填/执行确认) ├ 单 ReAct Agent (AiServices 工具循环) ├ 暂存执行器 (/ai/execStaging) | ||
| 25 | + │ ChatMemory(Redis) · SystemPrompt(L1) └ MySQL (业务库) | ||
| 26 | + │ 9 个工具(6 类) + Skill 注册表 | ||
| 27 | + └ 直连: Ollama(qwen3:14b)/MySQL(KG+NL2SQL)(Milvus 已随 2026-07-27 精简移除,向量召回未建) | ||
| 24 | ``` | 28 | ``` |
| 25 | 29 | ||
| 26 | **数据流要点(重要)** | 30 | **数据流要点(重要)** |
| @@ -29,8 +33,10 @@ | @@ -29,8 +33,10 @@ | ||
| 29 | 33 | ||
| 30 | ## 3. Agent 核心 | 34 | ## 3. Agent 核心 |
| 31 | 35 | ||
| 32 | -- **单 agent** = LangChain4j `AiServices` 原生 **tool-calling 循环**当 ReAct(不用老式文本 Thought/Action;qwen2.5 支持 function calling)。 | 36 | +- **单 agent** = LangChain4j `AiServices` 原生 **tool-calling 循环**当 ReAct(不用老式文本 Thought/Action;qwen3:14b 支持 function calling)。 |
| 33 | - **退役** 8 场景 `SceneSelector` 路由 —— agent 自路由。 | 37 | - **退役** 8 场景 `SceneSelector` 路由 —— agent 自路由。 |
| 38 | + > 实现修正(2026-07-23,见 §17):纯自路由在 qwen3:14b 上不可靠 → 前置一道**确定性意图门 + 按意图收窄工具集**。 | ||
| 39 | + > 这不是回退到场景路由(不再有人设/每表单工具),而是把「判类」从模型的 ReAct 里拿出来单独做。 | ||
| 34 | - **System prompt** = 角色 + **L1 业务域图(`viw_kg_domain`, 11 行)** + Skill 摘要列表 + 工具用法 + 安全约束。 | 40 | - **System prompt** = 角色 + **L1 业务域图(`viw_kg_domain`, 11 行)** + Skill 摘要列表 + 工具用法 + 安全约束。 |
| 35 | - **记忆** = `ChatMemory`(窗口化 + 超长摘要),持久化到 **Redis**(键:`chat:{userId}:{conversationId}`,带 TTL)。**多条命名会话/用户**:每用户一个会话列表,存储按 `conversationId` 分;按稳定身份(userId+品牌+子公司)寻址,**重登录/换 token 后接回历史**(token 不是会话 key)。 | 41 | - **记忆** = `ChatMemory`(窗口化 + 超长摘要),持久化到 **Redis**(键:`chat:{userId}:{conversationId}`,带 TTL)。**多条命名会话/用户**:每用户一个会话列表,存储按 `conversationId` 分;按稳定身份(userId+品牌+子公司)寻址,**重登录/换 token 后接回历史**(token 不是会话 key)。 |
| 36 | - **循环护栏**:最大工具迭代步数、单轮墙钟超时、单轮 LLM 调用上限。 | 42 | - **循环护栏**:最大工具迭代步数、单轮墙钟超时、单轮 LLM 调用上限。 |
| @@ -42,17 +48,26 @@ | @@ -42,17 +48,26 @@ | ||
| 42 | - **续跑**:本轮结束后,会话置 `pendingInteraction`(如 `awaiting_answer:{id}` / `awaiting_confirm:{stagingRef}`)。用户的下一条输入按此状态被路由回「上一步的答复」,而非当作新请求。**没有这个 pending 状态机,多轮 HITL 会错乱** —— 这是手搓 HITL 的关键点。 | 48 | - **续跑**:本轮结束后,会话置 `pendingInteraction`(如 `awaiting_answer:{id}` / `awaiting_confirm:{stagingRef}`)。用户的下一条输入按此状态被路由回「上一步的答复」,而非当作新请求。**没有这个 pending 状态机,多轮 HITL 会错乱** —— 这是手搓 HITL 的关键点。 |
| 43 | - **确认动作走确定性端点,不过 LLM**:diff 的「确认」按钮 → 直接打 xlyAi 一个 endpoint 落暂存 + 返回 ERP 链接。 | 49 | - **确认动作走确定性端点,不过 LLM**:diff 的「确认」按钮 → 直接打 xlyAi 一个 endpoint 落暂存 + 返回 ERP 链接。 |
| 44 | 50 | ||
| 51 | +> ⚠️ **本节仍是设计,未按此实现**(截至 2026-07-23)。实际做法:新 agent **没有**用 | ||
| 52 | +> `immediateReturnToolNames`(只有休眠的旧 `XlyErpService` 用),工具结果仍回喂 LLM;也**没有** | ||
| 53 | +> `pendingInteraction` 状态机——多轮靠 `ChatMemory` 自然接续 + 表单提交时前端在消息里塞 | ||
| 54 | +> `proposeWrite(action=create)` 标记让控制器识别。卡片/控件由控制器在 `onToolExecuted` 里推 SSE。 | ||
| 55 | +> 已知代价见 §18(LLM 看得见提议 JSON,可能谎称"已完成",目前只靠 prompt 规则压制)。 | ||
| 56 | + | ||
| 45 | **前端事件协议(SSE)**:`token`(流式文本)/ `tool_call`(可选可视化)/ `question{text,options[]}` / `write_proposal{diff,stagingRef}` / `link{url}` / `error` / `done`。 | 57 | **前端事件协议(SSE)**:`token`(流式文本)/ `tool_call`(可选可视化)/ `question{text,options[]}` / `write_proposal{diff,stagingRef}` / `link{url}` / `error` / `done`。 |
| 46 | 58 | ||
| 47 | -## 5. 工具规格(5 通用工具 + KG 预留) | 59 | +## 5. 工具规格(6 类通用工具) |
| 48 | 60 | ||
| 49 | > 通用原则:入参/出参 JSON-schema、无状态、**auth 走 per-call context**(MCP 形状,便于日后暴露成 MCP)。输出**必须分页/截断**,绝不吐爆上下文。 | 61 | > 通用原则:入参/出参 JSON-schema、无状态、**auth 走 per-call context**(MCP 形状,便于日后暴露成 MCP)。输出**必须分页/截断**,绝不吐爆上下文。 |
| 62 | +> | ||
| 63 | +> 下表是**能力分类**(6 类);实现上落成 9 个 `@Tool` 方法(Read 拆 `readFormData`/`lookupRecord`, | ||
| 64 | +> KgSearch 拆 `findForms`/`kgSearch`,其余各 1 个)。清单见 §17。 | ||
| 50 | 65 | ||
| 51 | | # | 工具 | 职责 | 关键入参 | 出参 | 安全 | HITL | | 66 | | # | 工具 | 职责 | 关键入参 | 出参 | 安全 | HITL | |
| 52 | |---|------|------|----------|------|------|------| | 67 | |---|------|------|----------|------|------|------| |
| 53 | | 1 | **Read** | 调**任意 ERP 表单/过程**(明细 **+ 预建聚合** AR/AP/库存/进度,即旧 type-5),包 `getBusinessDataByFormcustomId` | formId, moduleId, **白名单过滤**, page | 行(截断)+分页游标 | 租户+行级;**表单权限靠 xlyAi 授权层**(§7);参数白名单 | 否 | | 68 | | 1 | **Read** | 调**任意 ERP 表单/过程**(明细 **+ 预建聚合** AR/AP/库存/进度,即旧 type-5),包 `getBusinessDataByFormcustomId` | formId, moduleId, **白名单过滤**, page | 行(截断)+分页游标 | 租户+行级;**表单权限靠 xlyAi 授权层**(§7);参数白名单 | 否 | |
| 54 | | 2 | **Query** | 只读 SQL 沙箱,**仅 ad-hoc 兜底**(没有现成表单/过程能答时才用) | 自然语言 → SQL | 聚合结果(小) | ⚠ 见 §9 | 否 | | 69 | | 2 | **Query** | 只读 SQL 沙箱,**仅 ad-hoc 兜底**(没有现成表单/过程能答时才用) | 自然语言 → SQL | 聚合结果(小) | ⚠ 见 §9 | 否 | |
| 55 | -| 3 | **KgSearch** | L2 邻居 / L3 字段→列→表 / 解析 formId(**暂缓,预留接口**) | 表单名/术语/域 | 子图/映射 | 只读 | 否 | | 70 | +| 3 | **KgSearch** | L2 邻居 / L3 字段→列→表 / 解析 formId | 表单名/术语/域 | 子图/映射 | 只读 | 否 | |
| 56 | | 4 | **AskUser** | 小问题(选项 + 自由输入),消歧/澄清 | question, options[] | — (immediate-return) | — | ✅ | | 71 | | 4 | **AskUser** | 小问题(选项 + 自由输入),消歧/澄清 | question, options[] | — (immediate-return) | — | ✅ | |
| 57 | | 5 | **FormCollect(表单呈现)** | 渲染一张表单收集 N 个参数(字段/类型/下拉来自 ERP 元数据),用户填 | formId(+ 已知预填) | 收集到的参数 | — | ✅ | | 72 | | 5 | **FormCollect(表单呈现)** | 渲染一张表单收集 N 个参数(字段/类型/下拉来自 ERP 元数据),用户填 | formId(+ 已知预填) | 收集到的参数 | — | ✅ | |
| 58 | | 6 | **ProposeWrite** | 出 diff → 确认 → 暂存(单一写工具) | formId, op, payload | diff + stagingRef | 见 §12 | ✅ | | 73 | | 6 | **ProposeWrite** | 出 diff → 确认 → 暂存(单一写工具) | formId, op, payload | diff + stagingRef | 见 §12 | ✅ | |
| @@ -113,7 +128,7 @@ | @@ -113,7 +128,7 @@ | ||
| 113 | - **确认端点(确定性 HTTP, 非工具/非 LLM)**:用户点【确认】→ `POST /op/{sId}/confirm` → status=confirmed → **同步调 ERP `/ai/execStaging/{sId}`** → 拿结果 → 回链接给用户 + **结果写入会话状态(供链式续跑)**。取消 → `POST /op/{sId}/cancel`。 | 128 | - **确认端点(确定性 HTTP, 非工具/非 LLM)**:用户点【确认】→ `POST /op/{sId}/confirm` → status=confirmed → **同步调 ERP `/ai/execStaging/{sId}`** → 拿结果 → 回链接给用户 + **结果写入会话状态(供链式续跑)**。取消 → `POST /op/{sId}/cancel`。 |
| 114 | - **结果怎么进对话(不过 LLM)**:对话框是**事件流**,不只是 LLM 文本。确认端点的 HTTP 响应 `{status, link, msg}` **由前端直接渲染成一张 op_result 卡片**(`✅ 已生成 [查看]`),无需 LLM 生成;同时该事件写进会话历史。ProposeWrite 的 diff、AskUser 选项、FormCollect 表单同理——工具返回结构化 payload,前端渲染卡片/控件。只有要 NL 收尾或**链式下一步**时才把结果喂进新的 LLM 轮次。 | 129 | - **结果怎么进对话(不过 LLM)**:对话框是**事件流**,不只是 LLM 文本。确认端点的 HTTP 响应 `{status, link, msg}` **由前端直接渲染成一张 op_result 卡片**(`✅ 已生成 [查看]`),无需 LLM 生成;同时该事件写进会话历史。ProposeWrite 的 diff、AskUser 选项、FormCollect 表单同理——工具返回结构化 payload,前端渲染卡片/控件。只有要 NL 收尾或**链式下一步**时才把结果喂进新的 LLM 轮次。 |
| 115 | - **ProposeWrite 写完 draft 即结束本轮**(immediate-return),agent 暂停 `pendingInteraction=awaiting_confirm:{sId}`,此刻 `ai_op_queue` 只有一条 `draft`、未执行;执行在"用户确认"独立事件里,链式则 resume 新一轮;久不确认按 `tExpireAt` 过期。 | 130 | - **ProposeWrite 写完 draft 即结束本轮**(immediate-return),agent 暂停 `pendingInteraction=awaiting_confirm:{sId}`,此刻 `ai_op_queue` 只有一条 `draft`、未执行;执行在"用户确认"独立事件里,链式则 resume 新一轮;久不确认按 `tExpireAt` 过期。 |
| 116 | -- **execStaging 不进任何工具、不新增工具** —— 它必须在"用户确认之后"这个独立事件里触发,塞进 immediate-return 的 ProposeWrite 会变成"确认前就执行"。工具仍是 6 个。 | 131 | +- **execStaging 不进任何工具、不新增工具** —— 它必须在"用户确认之后"这个独立事件里触发,塞进 immediate-return 的 ProposeWrite 会变成"确认前就执行"。工具仍是那 6 类。 |
| 117 | - 报价即使已用 FormCollect 填完表单,**落暂存前仍要一份对话内最终确认**(确认 = 允许存起来的门)。 | 132 | - 报价即使已用 FormCollect 填完表单,**落暂存前仍要一份对话内最终确认**(确认 = 允许存起来的门)。 |
| 118 | - 落暂存后是 **ERP 的事、与 agent 无关**:ERP 对不同待办的策略(报价自动执行 `Sp_Ai_AddQuoQuoAfter` / copyto 让用户在 ERP 原生表单再确认→执行器执行)都在暂存之后。 | 133 | - 落暂存后是 **ERP 的事、与 agent 无关**:ERP 对不同待办的策略(报价自动执行 `Sp_Ai_AddQuoQuoAfter` / copyto 让用户在 ERP 原生表单再确认→执行器执行)都在暂存之后。 |
| 119 | - **ProposeWrite 泛化为"提议任意变更动作"**:ERP 按钮动作(审核/过账/生成 等**写按钮**)也走这道确认门;只读按钮(查询/导出)归 Read。⚠️ 绝不给 agent 一条绕过确认门直接触发 ERP 写动作的路(后端权限只管"能不能做",不等于"这次同意做")。是否需要独立的"按钮调用"底层能力,待后台调查(过程是否写、读 API 会否触发写、后端是否对表单/过程/动作调用鉴权)后定。 | 134 | - **ProposeWrite 泛化为"提议任意变更动作"**:ERP 按钮动作(审核/过账/生成 等**写按钮**)也走这道确认门;只读按钮(查询/导出)归 Read。⚠️ 绝不给 agent 一条绕过确认门直接触发 ERP 写动作的路(后端权限只管"能不能做",不等于"这次同意做")。是否需要独立的"按钮调用"底层能力,待后台调查(过程是否写、读 API 会否触发写、后端是否对表单/过程/动作调用鉴权)后定。 |
| @@ -164,15 +179,17 @@ ai_op_queue( | @@ -164,15 +179,17 @@ ai_op_queue( | ||
| 164 | |---|---| | 179 | |---|---| |
| 165 | | **复用** | `AiServices` 循环、`ChatMemory`/`OperableChatMemoryProvider`、`VectorizationService`、表单 API 客户端、NL2SQL 护栏 + `ai_global_agent_question_sql` 缓存 + `ai_sql_error_history` 纠错、`jsqlparser`、Milvus/embedding | | 180 | | **复用** | `AiServices` 循环、`ChatMemory`/`OperableChatMemoryProvider`、`VectorizationService`、表单 API 客户端、NL2SQL 护栏 + `ai_global_agent_question_sql` 缓存 + `ai_sql_error_history` 纠错、`jsqlparser`、Milvus/embedding | |
| 166 | | **退役** | 8 场景 `SceneSelector`、`DynamicToolProvider` per-form `ToolMeta` 工具、手写 `explainMilvusResult` RAG(并入工具) | | 181 | | **退役** | 8 场景 `SceneSelector`、`DynamicToolProvider` per-form `ToolMeta` 工具、手写 `explainMilvusResult` RAG(并入工具) | |
| 167 | -| **新建** | 5 通用工具、取表单 schema 能力、Skill 注册表 + `load_skill`、Redis 会话持久化 + `pendingInteraction` 状态机、L1 prompt 注入、diff 确认端点、**ERP 侧暂存执行器 + 预填 + 深链 + 待办入口**、审计日志、Langfuse 接入 | | 182 | +| **新建** | 6 类通用工具、取表单 schema 能力、Skill 注册表 + `loadSkill`、Redis 会话持久化、L1 prompt 注入、diff 确认端点、**ERP 侧暂存执行器 + 预填 + 深链 + 待办入口**、审计日志、Langfuse 接入;**(设计外补充)意图门 + 按意图收窄工具集 + 确定性槽位填充**(见 §17) | |
| 168 | 183 | ||
| 169 | ## 13. 迁移路线(strangler,不大爆炸) | 184 | ## 13. 迁移路线(strangler,不大爆炸) |
| 170 | 185 | ||
| 171 | - **P0 KG 底座** ✅(`sql/` 7 视图,分支 `kg-edge-flow`)。 | 186 | - **P0 KG 底座** ✅(`sql/` 7 视图,分支 `kg-edge-flow`)。 |
| 172 | -- **P1 单 agent 骨架 + Read + Query(只读优先、安全先行)** —— 灰度一个只读场景,用现有 62 工具当**回归基线**。 | ||
| 173 | -- **P2 AskUser(QA) + KgSearch + Skill 注册表**。 | ||
| 174 | -- **P3 ProposeWrite + ERP 暂存执行器 + 预填/深链**(最重、跨团队,先排脚手架)。 | ||
| 175 | -- **P4 退役旧多智能体/元数据工具;接 Langfuse;报价统一走确认制**。 | 187 | +- **P1 单 agent 骨架 + Read + Query(只读优先、安全先行)** ✅ 代码完成(含表白名单 + 租户注入)。 |
| 188 | + ❌ **灰度未做**:没有选定灰度场景,也**没有拿旧 64 工具跑过回归基线对比**。 | ||
| 189 | +- **P2 AskUser(QA) + KgSearch + Skill 注册表** ✅ 代码完成(`askUser` / `kgSearch` / `ai_skill` 3 条种子技能)。 | ||
| 190 | +- **P3 ProposeWrite + ERP 暂存执行器 + 预填/深链** —— xlyAi 侧 ✅;ERP 侧执行器/预填/待办端点 ✅ 已写并编译, | ||
| 191 | + 但**默认关闭**(`erp.exec-staging.enabled=false`,本地走直连),**生产路径未联调**。 | ||
| 192 | +- **P4 退役旧多智能体/元数据工具;接 Langfuse;报价统一走确认制** —— 退役 ✅(2026-07-27 精简:旧栈/milvus/tts/ocr 全删,见 §18.2);Langfuse ✅(配置开关,默认关);报价确认制待业务。 | ||
| 176 | 193 | ||
| 177 | ## 14. 已定 / 开放问题(见对话讨论) | 194 | ## 14. 已定 / 开放问题(见对话讨论) |
| 178 | 195 | ||
| @@ -197,7 +214,10 @@ ai_op_queue( | @@ -197,7 +214,10 @@ ai_op_queue( | ||
| 197 | 7. 现网安全发现(后端表单权限被关 / NL2SQL 越权 / 读 API 可写)是否作为独立整改上报业务。 | 214 | 7. 现网安全发现(后端表单权限被关 / NL2SQL 越权 / 读 API 可写)是否作为独立整改上报业务。 |
| 198 | 8. 首个灰度只读场景选哪个(建议高频只读轻后果,如库存 / 应收账龄)。 | 215 | 8. 首个灰度只读场景选哪个(建议高频只读轻后果,如库存 / 应收账龄)。 |
| 199 | 216 | ||
| 200 | -## 15. 实现状态(2026-07-21,分支 `kg-edge-flow`) | 217 | +## 15. 实现状态(2026-07-21,分支 `kg-edge-flow`)—— 历史快照,勿当现状读 |
| 218 | + | ||
| 219 | +> 下面记的是**当天**的状态。工具已在 §16/§17 变过(4 个 `propose*` 已合并成单个 | ||
| 220 | +> `proposeWrite(action=…)`)。当前清单见 §17。 | ||
| 201 | 221 | ||
| 202 | **已建成、可运行、已验证**(xlyAi 全新单 agent 前后端,`/xlyAi/api/agent/*`): | 222 | **已建成、可运行、已验证**(xlyAi 全新单 agent 前后端,`/xlyAi/api/agent/*`): |
| 203 | - **单 ReAct agent**:`AiServices` 流式工具循环(qwen3:14b `think(false)`),L1 域图进 system prompt,退役 8 场景路由(旧代码休眠未删)。 | 223 | - **单 ReAct agent**:`AiServices` 流式工具循环(qwen3:14b `think(false)`),L1 域图进 system prompt,退役 8 场景路由(旧代码休眠未删)。 |
| @@ -207,10 +227,13 @@ ai_op_queue( | @@ -207,10 +227,13 @@ ai_op_queue( | ||
| 207 | - **审计**:`ai_audit_log` 记 propose/confirm/cancel/query。 | 227 | - **审计**:`ai_audit_log` 记 propose/confirm/cancel/query。 |
| 208 | - **ERP 接入**:`ErpClient` dev-login(admin/666666)拿真实会话 token;读 `getBusinessDataByFormcustomId`,写 `addUpdateDelBusinessData`(`column` 列表 + `handleType`)。 | 228 | - **ERP 接入**:`ErpClient` dev-login(admin/666666)拿真实会话 token;读 `getBusinessDataByFormcustomId`,写 `addUpdateDelBusinessData`(`column` 列表 + `handleType`)。 |
| 209 | 229 | ||
| 210 | -## 16. 缺口补齐(2026-07-22,分支 `kg-edge-flow`) | 230 | +## 16. 缺口补齐(2026-07-22 上午,分支 `kg-edge-flow`)—— 历史快照,勿当现状读 |
| 211 | 231 | ||
| 212 | 上一轮 §15 里「尚未做 / 被外部条件阻塞」的缺口,本轮全部补齐(含跨团队 ERP 侧)。均已编译通过(xlyAi mvn + ERP gradle)。 | 232 | 上一轮 §15 里「尚未做 / 被外部条件阻塞」的缺口,本轮全部补齐(含跨团队 ERP 侧)。均已编译通过(xlyAi mvn + ERP gradle)。 |
| 213 | 233 | ||
| 234 | +> 后续变更见 §17:`proposeCreate/Delete/Examine` 已合并进 `proposeWrite(action=…)`; | ||
| 235 | +> `collectForm` 的字段来源已从 `gdsconfigformslave` 改为字段字典/精选映射(后者只作兜底)。 | ||
| 236 | + | ||
| 214 | **xlyAi 侧** | 237 | **xlyAi 侧** |
| 215 | - **授权 + token 透传(§7)**:新增 `AgentIdentity` + `AgentFactory` —— **按每次请求的身份新建 agent**,把透传的用户 ERP token 与「表单权限集」(`sAuthsId` → `AuthzService.grantedIds`)放进每请求的工具实例(规避 LC4j 流式工具在回调线程执行、ThreadLocal 不可靠的问题,即架构说的 per-call context)。前端逐请求带 `authorization`+身份;`ErpClient` 所有读写 **转发用户 token**;**用户 token 过期绝不用 dev(admin) 重登**(防静默提权)。dev-login 仅作本地兜底。 | 238 | - **授权 + token 透传(§7)**:新增 `AgentIdentity` + `AgentFactory` —— **按每次请求的身份新建 agent**,把透传的用户 ERP token 与「表单权限集」(`sAuthsId` → `AuthzService.grantedIds`)放进每请求的工具实例(规避 LC4j 流式工具在回调线程执行、ThreadLocal 不可靠的问题,即架构说的 per-call context)。前端逐请求带 `authorization`+身份;`ErpClient` 所有读写 **转发用户 token**;**用户 token 过期绝不用 dev(admin) 重登**(防静默提权)。dev-login 仅作本地兜底。 |
| 216 | - **写:新增 / 删除 / 审核**:`proposeCreate`(自动补主键/必填)、`proposeDelete`、`proposeExamine` 三个 HITL 工具,均只暂存 `ai_op_queue` draft、确认端点才执行;确认转发用户 token。`ai_op_queue` 加 `sPayload/sSourceRef/bAutoExecute/sResultBillId/sErrorMsg/tExecutedDate/tExpireAt`。 | 239 | - **写:新增 / 删除 / 审核**:`proposeCreate`(自动补主键/必填)、`proposeDelete`、`proposeExamine` 三个 HITL 工具,均只暂存 `ai_op_queue` draft、确认端点才执行;确认转发用户 token。`ai_op_queue` 加 `sPayload/sSourceRef/bAutoExecute/sResultBillId/sErrorMsg/tExecutedDate/tExpireAt`。 |
| @@ -220,6 +243,8 @@ ai_op_queue( | @@ -220,6 +243,8 @@ ai_op_queue( | ||
| 220 | - **KgSearch(§5/§11)**:`kgSearch` 暴露 L2 邻居/流转(`viw_kg_form_neighbors`)+ L3 字段→表列(`viw_kg_field_dict`),按词查、截断。 | 243 | - **KgSearch(§5/§11)**:`kgSearch` 暴露 L2 邻居/流转(`viw_kg_form_neighbors`)+ L3 字段→表列(`viw_kg_field_dict`),按词查、截断。 |
| 221 | - **稳健 NL2SQL(§9)**:`QueryTool` 加**表白名单**(`viw_*` + 表单数据源 + 字段字典表,用 jsqlparser `TablesNamesFinder` 校验 → 挡住 `gdslogininfo`/`sysjurisdiction`/`ai_op_queue` 等敏感表)+ **单表租户谓词注入**(`sBrandsId`)+ prompt 品牌提示 + 自修复重试。 | 244 | - **稳健 NL2SQL(§9)**:`QueryTool` 加**表白名单**(`viw_*` + 表单数据源 + 字段字典表,用 jsqlparser `TablesNamesFinder` 校验 → 挡住 `gdslogininfo`/`sysjurisdiction`/`ai_op_queue` 等敏感表)+ **单表租户谓词注入**(`sBrandsId`)+ prompt 品牌提示 + 自修复重试。 |
| 222 | - **Langfuse(§1)**:`TracingChatModelListener` 增加**配置开关的 ingestion 导出**(generation span,走 JDK HttpClient,不引依赖);`docker-compose.langfuse.yml` 自托管;默认关闭。 | 245 | - **Langfuse(§1)**:`TracingChatModelListener` 增加**配置开关的 ingestion 导出**(generation span,走 JDK HttpClient,不引依赖);`docker-compose.langfuse.yml` 自托管;默认关闭。 |
| 246 | + > ⚠️ `docker-compose.langfuse.yml` 在**本机工作区存在但未入库**——被全局 gitignore 的 `**/docker-compose.*.yml` 规则挡掉了, | ||
| 247 | + > 换机器/换人 clone 拿不到(见 §18)。 | ||
| 223 | 248 | ||
| 224 | **ERP 侧(saas-8s+ `release/customer/2025/saas-8s+`,backup 分支未动)** | 249 | **ERP 侧(saas-8s+ `release/customer/2025/saas-8s+`,backup 分支未动)** |
| 225 | - **暂存执行器 + 预填 + 待办(§10)**:`AiStagingController @/ai` —— `execStaging/{sId}`(POST,执行)、`staging/{sId}`(GET,预填加载供原生表单深链)、`myTodos`(GET,AI待办);全部 `@Authorization`+`@CurrentUser`。`AiStagingService(Impl)` 读共享库 `ai_op_queue` → 以**用户身份**走 ERP 自己的写入/审核逻辑(`addUpdateDelBusinessData` / `doExamine`,复用租户隔离、单号、审核存储过程)→ 幂等 + 回写状态/结果。xlyAi 确认端点按 `erp.exec-staging.enabled` 委托它(生产路径)或直连(本地默认)。 | 250 | - **暂存执行器 + 预填 + 待办(§10)**:`AiStagingController @/ai` —— `execStaging/{sId}`(POST,执行)、`staging/{sId}`(GET,预填加载供原生表单深链)、`myTodos`(GET,AI待办);全部 `@Authorization`+`@CurrentUser`。`AiStagingService(Impl)` 读共享库 `ai_op_queue` → 以**用户身份**走 ERP 自己的写入/审核逻辑(`addUpdateDelBusinessData` / `doExamine`,复用租户隔离、单号、审核存储过程)→ 幂等 + 回写状态/结果。xlyAi 确认端点按 `erp.exec-staging.enabled` 委托它(生产路径)或直连(本地默认)。 |
| @@ -227,3 +252,85 @@ ai_op_queue( | @@ -227,3 +252,85 @@ ai_op_queue( | ||
| 227 | **仍属业务/运维决策(非代码缺口)** | 252 | **仍属业务/运维决策(非代码缺口)** |
| 228 | - 现网安全发现(后端逐用户表单权限被注释关 / 读 API 可被 `bUpdate` 触发写 / 旧 NL2SQL 越权)——`docs/security-findings.md` 已成文,**是否整改由业务定**。授权层真正“收紧到非管理员”只有在**生产以真实用户 token 运行**时才能观测(本地 dev-login=admin 全通)。 | 253 | - 现网安全发现(后端逐用户表单权限被注释关 / 读 API 可被 `bUpdate` 触发写 / 旧 NL2SQL 越权)——`docs/security-findings.md` 已成文,**是否整改由业务定**。授权层真正“收紧到非管理员”只有在**生产以真实用户 token 运行**时才能观测(本地 dev-login=admin 全通)。 |
| 229 | - 报价改双确认、首个灰度只读场景选型 —— 业务确认项。 | 254 | - 报价改双确认、首个灰度只读场景选型 —— 业务确认项。 |
| 255 | + | ||
| 256 | +## 17. 当前实现状态(2026-07-23,分支 `kg-edge-flow`,HEAD `8b927e3`) | ||
| 257 | + | ||
| 258 | +> 这是**唯一的现状节**。改代码后请更新这一节,不要再往 §15/§16 里加。 | ||
| 259 | + | ||
| 260 | +### 17.1 工具清单(9 个 `@Tool`,对应 §5 的 6 类) | ||
| 261 | + | ||
| 262 | +| 类(§5) | `@Tool` 方法 | 实现类 | 单例 / 按请求 | | ||
| 263 | +|---|---|---|---| | ||
| 264 | +| Read | `readFormData` · `lookupRecord` | `ErpReadTool` | 按请求(带身份) | | ||
| 265 | +| Query | `queryData` | `QueryTool` | 按请求 | | ||
| 266 | +| KgSearch | `findForms` · `kgSearch` | `KgQueryTool` | 单例(全局元数据) | | ||
| 267 | +| AskUser | `askUser` | `InteractionTool` | 单例 | | ||
| 268 | +| FormCollect | `collectForm` | `FormCollectTool` | 按请求 | | ||
| 269 | +| ProposeWrite | `proposeWrite(action=create\|update\|invalid\|cancelInvalid\|examine\|cancelExamine\|delete)` | `ProposeWriteTool` | 按请求 | | ||
| 270 | +| (非工具) | `loadSkill` | `SkillTool` | 单例 | | ||
| 271 | + | ||
| 272 | +**§16 之后的变化**:4 个 `propose*` 工具**合并成单个 `proposeWrite(action=…)`**(贴合 §10「单一写工具」), | ||
| 273 | +并补上 `invalid/cancelInvalid`(业务单据的"删除"= 作废,可复原)与 `cancelExamine`(销审)。 | ||
| 274 | + | ||
| 275 | +### 17.2 编排:意图门 + 按意图收窄工具集(§3 的实现修正) | ||
| 276 | + | ||
| 277 | +`AgentChatController.route()` 每轮: | ||
| 278 | +1. **意图门** `IntentService.classify()` —— 受约束 JSON(`OllamaJsonClient`),产出 `{意图, 单据类型, 带角色实体, missing}`; | ||
| 279 | +2. **确定性路由**: | ||
| 280 | + - **新增** → 完全**不走 ReAct**:`resolveMasterForm` → `SlotFillService` 按真实字段做正则/角色槽位填充 → 直接弹 `collectForm`; | ||
| 281 | + - **修改/删除/审核** → `extractWrite` 抽 `{record, field, newValue}`;齐了就**确定性直调** `proposeWrite`,缺了就问一次即停; | ||
| 282 | + - **查询** → WRITE 之外的 READ-scope agent(5 个读工具); | ||
| 283 | + - **不清楚/意图门失败** → FULL-scope 兜底(全部 9 个工具)。 | ||
| 284 | +3. `AgentFactory.build(identity, scope)` 按 `ToolScope` 组装工具 + 对应版本 system prompt; | ||
| 285 | + `maxSequentialToolsInvocations(8)` 作循环护栏。 | ||
| 286 | + | ||
| 287 | +**动机(实测)**:qwen3:14b Q4 在一次 ReAct 里同时判意图/选工具/编参数,会把"查询"当"新增"、 | ||
| 288 | +把产品名(纸盒)塞进客户字段、反复追问同一问题。窄任务(意图分类、尺寸拆分)改用受约束 JSON + Java 正则后稳定。 | ||
| 289 | + | ||
| 290 | +### 17.3 写入闭环(真实落库,已端到端验证) | ||
| 291 | + | ||
| 292 | +- **报价 = 跨表主-从建单**:`proposeQuote` 把 17 个精选字段路由到 | ||
| 293 | + `quoquotationmaster` + `quoquotationslave`(印刷/部件,`sParentId` 级联)+ `quoquotationmanyqtys`(多数量,一档一行), | ||
| 294 | + 外键名→id、按列类型强转、自动补主键/租户/制单人/单号(`BJD`+`YYYYMM`+序号), | ||
| 295 | + 以 `__tables__` 结构化 payload 存 `ai_op_queue`,确认端点走 `ErpClient.createMulti` 一次提交。 | ||
| 296 | + **核价(控制/材料/工序行)刻意留给 ERP**——那是引擎生成的。 | ||
| 297 | +- **表单渲染带类型**:`collectForm` 字段带 `fkselect|select|number|date|text`;外键下拉由 | ||
| 298 | + `GET /api/agent/form/options?table=&q=` 从来源表实时取(只允许在字段字典里作为外键目标出现过的表,租户过滤 + LIMIT)。 | ||
| 299 | +- **验证记录**(真实写入本地 saas 库):`BJD202607082`(主表)、`BJD202607084`(主+从+多数量)、`BJD202607086`(含多数量 2000/4000/6000)。 | ||
| 300 | + | ||
| 301 | +### 17.4 与设计的已知偏离 | ||
| 302 | + | ||
| 303 | +| 设计 | 实际 | 影响 | | ||
| 304 | +|---|---|---| | ||
| 305 | +| §4 immediate-return 工具 | **未用**(新 agent 没设 `immediateReturnToolNames`) | 提议 JSON 会回喂 LLM,可能谎称"已完成",目前只靠 prompt 规则压制 | | ||
| 306 | +| §4 `pendingInteraction` 状态机 | **未建**,靠 `ChatMemory` + 表单提交时消息里的 `proposeWrite(action=create)` 标记 | 多轮 HITL 无显式状态,跨轮续跑脆弱 | | ||
| 307 | +| §10 卡片三态 + auto 异步 + SSE 终态推送 | **恒同步**:确认即执行并同步返回终态 | 长事务会 HTTP 死等;无重连对账 | | ||
| 308 | +| §10 manual 预填深链 `/form/{formId}?staging={sId}` | ERP 侧端点已写,xlyAi 侧**不产生这类链接** | manual 流程未打通 | | ||
| 309 | +| §3 agent 完全自路由 | 前置确定性意图门 | 见 §17.2(有意为之) | | ||
| 310 | + | ||
| 311 | +## 18. 未完成清单(2026-07-23) | ||
| 312 | + | ||
| 313 | +**阻塞上线** | ||
| 314 | +1. **`kg-edge-flow` 未合并 `master`** —— §14 已定"写入 master",但全部 6200 行新代码只在该分支;`master` 仍是改造前状态。 | ||
| 315 | +2. ~~旧栈未退役且仍在跑~~ ✅ **已退役(2026-07-27 精简)**:删除 `XlyErpService`/8 场景路由/`DynamicToolProvider`/Scene·ToolMeta 缓存、整个 milvus/tts/ocr 包、`/api/tts` 与 `/api/ocr` 端点、tts.html 及 chat.html 死 JS;pom 依赖同步裁剪(milvus/embeddings/mybatis/ocr/webflux 等)。代码只剩新栈 ~40 个类。 | ||
| 316 | +3. **零自动化测试**:无 `src/test`,每次都是手工跑对话验证;重构无回归网。 | ||
| 317 | +4. **生产路径未联调**:`erp.exec-staging.enabled=false`;ERP 侧 `AiStagingController` 只编译过、没被真实调用过。 | ||
| 318 | +5. **授权层未在非管理员下验证**:本地 dev-login=admin 全通,`AuthzService.grantedIds` 的收紧效果无法观测。 | ||
| 319 | + | ||
| 320 | +**功能缺口** | ||
| 321 | +6. `pendingInteraction` 状态机 + immediate-return(§17.4)。 | ||
| 322 | +7. 异步执行 / 卡片三态 / SSE 终态推送 / `GET /op/{sId}/status` 重连对账(§10)。 | ||
| 323 | +8. manual 预填深链与「我的 AI 待办」在 xlyAi 侧的入口。 | ||
| 324 | +9. `ai_op_queue` 的 `tExpireAt` 过期清理无调度任务;`bAutoExecute` 建了但没有分流逻辑。 | ||
| 325 | +10. **报价核价结果不回读**——建完单不知道算出多少钱,用户得自己去 ERP 看。 | ||
| 326 | +11. Skill 只有 3 条种子且未在真实对话里验证过;`loadSkill` 无使用数据。 | ||
| 327 | +12. KgSearch 的**向量召回未接**(只有 SQL 关键词匹配)。2026-07-27 精简已把 milvus/embedding 代码与依赖整体删除——若做 5b 需按新栈重新接(milvus-sdk + embedding 依赖重加,旧实现在 git 历史 `8b927e3` 前可查)。 | ||
| 328 | +13. 报价之外的单据**没有精选字段映射**,`businessFields` 走字段字典启发式,复杂单据大概率不准。 | ||
| 329 | + | ||
| 330 | +**安全 / 运维** | ||
| 331 | +14. Query 的租户 AST 注入**只覆盖单表**,多表 JOIN 仅靠 prompt 提示;**只读 MySQL 账号未启用**(与应用共用连接池)。 | ||
| 332 | +15. `docker-compose.langfuse.yml` **未入库**(被全局 gitignore `**/docker-compose.*.yml` 挡掉);Langfuse 默认关闭、从未真正导出过 trace。 | ||
| 333 | +16. 现网安全发现待业务决策(见 `docs/security-findings.md`)。 | ||
| 334 | + | ||
| 335 | +**业务决策项** | ||
| 336 | +17. 首个灰度只读场景选型;报价是否改双确认;多语言(当前仅中文)。 |
docs/legacy-tool-inventory.md
| 1 | # 旧 64 AI 工具能力盘点(迁移覆盖基线) | 1 | # 旧 64 AI 工具能力盘点(迁移覆盖基线) |
| 2 | 2 | ||
| 3 | -> 目的:现有 64 个元数据工具(`ai_tool`/`ai_tool_detail_params`)→ 迁到 5 个通用工具(Read/Query/KgSearch/AskUser/ProposeWrite)时,**不能丢**的业务语义清单。 | 3 | +> 目的:现有 64 个元数据工具(`ai_tool`/`ai_tool_detail_params`)→ 迁到 6 类通用工具(Read/Query/KgSearch/AskUser/FormCollect/ProposeWrite)时,**不能丢**的业务语义清单。 |
| 4 | > 关键发现:**这 64 个不是"薄读"——其中 25 个是写操作,内含大量业务逻辑**(BtnCopyTo 单据流转引擎 + 报价定价引擎)。 | 4 | > 关键发现:**这 64 个不是"薄读"——其中 25 个是写操作,内含大量业务逻辑**(BtnCopyTo 单据流转引擎 + 报价定价引擎)。 |
| 5 | +> | ||
| 6 | +> **迁移进度(2026-07-23)**:新工具已建齐(9 个 `@Tool`,见 `agent-architecture.md` §17.1), | ||
| 7 | +> 但**旧 64 个工具一个都还没退役**——`AppStartupRunner` 仍在启动时加载它们,旧对话端点仍对外。 | ||
| 8 | +> 本文的覆盖映射尚未做过逐条回归验证。 | ||
| 5 | 9 | ||
| 6 | ## 执行机制(证据) | 10 | ## 执行机制(证据) |
| 7 | - `AppStartupRunner.initCache()` 载入 → `DynamicToolProvider.init()` 每行建一个 `ToolSpecification`+`ToolExecutor`(按 `sSceneId` 分桶)。 | 11 | - `AppStartupRunner.initCache()` 载入 → `DynamicToolProvider.init()` 每行建一个 `ToolSpecification`+`ToolExecutor`(按 `sSceneId` 分桶)。 |
pom.xml
| @@ -21,20 +21,9 @@ | @@ -21,20 +21,9 @@ | ||
| 21 | 21 | ||
| 22 | <properties> | 22 | <properties> |
| 23 | <java.version>17</java.version> | 23 | <java.version>17</java.version> |
| 24 | - <mybatis-spring.version>3.0.4</mybatis-spring.version> | ||
| 25 | - <pagehelper.version>2.1.0</pagehelper.version> | ||
| 26 | - <mapstruct.version>1.5.5.Final</mapstruct.version> | ||
| 27 | - <fastjson.version>2.0.50</fastjson.version> | ||
| 28 | <langchain4j.version>1.14.0</langchain4j.version> | 24 | <langchain4j.version>1.14.0</langchain4j.version> |
| 29 | - <langchain4j-emb.version>1.14.0-beta24</langchain4j-emb.version> | ||
| 30 | <lombok.version>1.18.34</lombok.version> | 25 | <lombok.version>1.18.34</lombok.version> |
| 31 | - <hutool.version>5.8.28</hutool.version> | ||
| 32 | <okhttp.version>4.10.0</okhttp.version> | 26 | <okhttp.version>4.10.0</okhttp.version> |
| 33 | - <springdoc.version>2.5.0</springdoc.version> | ||
| 34 | - <jackson.version>2.17.2</jackson.version> | ||
| 35 | - <milvus.version>2.6.15</milvus.version> | ||
| 36 | - <tess4j.version>5.18.0</tess4j.version> | ||
| 37 | - <jakarta-validation.version>3.1.0</jakarta-validation.version> | ||
| 38 | </properties> | 27 | </properties> |
| 39 | 28 | ||
| 40 | <dependencies> | 29 | <dependencies> |
| @@ -53,10 +42,6 @@ | @@ -53,10 +42,6 @@ | ||
| 53 | </dependency> | 42 | </dependency> |
| 54 | <dependency> | 43 | <dependency> |
| 55 | <groupId>org.springframework.boot</groupId> | 44 | <groupId>org.springframework.boot</groupId> |
| 56 | - <artifactId>spring-boot-starter-aop</artifactId> | ||
| 57 | - </dependency> | ||
| 58 | - <dependency> | ||
| 59 | - <groupId>org.springframework.boot</groupId> | ||
| 60 | <artifactId>spring-boot-starter-thymeleaf</artifactId> | 45 | <artifactId>spring-boot-starter-thymeleaf</artifactId> |
| 61 | </dependency> | 46 | </dependency> |
| 62 | <dependency> | 47 | <dependency> |
| @@ -69,7 +54,7 @@ | @@ -69,7 +54,7 @@ | ||
| 69 | </dependency> | 54 | </dependency> |
| 70 | <dependency> | 55 | <dependency> |
| 71 | <groupId>org.springframework.boot</groupId> | 56 | <groupId>org.springframework.boot</groupId> |
| 72 | - <artifactId>spring-boot-starter-webflux</artifactId> | 57 | + <artifactId>spring-boot-starter-jdbc</artifactId> |
| 73 | </dependency> | 58 | </dependency> |
| 74 | <dependency> | 59 | <dependency> |
| 75 | <groupId>org.springframework.boot</groupId> | 60 | <groupId>org.springframework.boot</groupId> |
| @@ -82,12 +67,7 @@ | @@ -82,12 +67,7 @@ | ||
| 82 | <optional>true</optional> | 67 | <optional>true</optional> |
| 83 | </dependency> | 68 | </dependency> |
| 84 | 69 | ||
| 85 | - <!-- 数据库 & MyBatis --> | ||
| 86 | - <dependency> | ||
| 87 | - <groupId>org.mybatis.spring.boot</groupId> | ||
| 88 | - <artifactId>mybatis-spring-boot-starter</artifactId> | ||
| 89 | - <version>${mybatis-spring.version}</version> | ||
| 90 | - </dependency> | 70 | + <!-- 数据库 --> |
| 91 | <dependency> | 71 | <dependency> |
| 92 | <groupId>com.mysql</groupId> | 72 | <groupId>com.mysql</groupId> |
| 93 | <artifactId>mysql-connector-j</artifactId> | 73 | <artifactId>mysql-connector-j</artifactId> |
| @@ -97,17 +77,6 @@ | @@ -97,17 +77,6 @@ | ||
| 97 | <groupId>com.zaxxer</groupId> | 77 | <groupId>com.zaxxer</groupId> |
| 98 | <artifactId>HikariCP</artifactId> | 78 | <artifactId>HikariCP</artifactId> |
| 99 | </dependency> | 79 | </dependency> |
| 100 | - <dependency> | ||
| 101 | - <groupId>com.github.pagehelper</groupId> | ||
| 102 | - <artifactId>pagehelper-spring-boot-starter</artifactId> | ||
| 103 | - <version>${pagehelper.version}</version> | ||
| 104 | - </dependency> | ||
| 105 | - <!-- 方案A:使用 pinyin4j(推荐) --> | ||
| 106 | - <dependency> | ||
| 107 | - <groupId>com.belerweb</groupId> | ||
| 108 | - <artifactId>pinyin4j</artifactId> | ||
| 109 | - <version>2.5.1</version> | ||
| 110 | - </dependency> | ||
| 111 | 80 | ||
| 112 | <!-- 工具类 --> | 81 | <!-- 工具类 --> |
| 113 | <dependency> | 82 | <dependency> |
| @@ -116,93 +85,11 @@ | @@ -116,93 +85,11 @@ | ||
| 116 | <optional>true</optional> | 85 | <optional>true</optional> |
| 117 | </dependency> | 86 | </dependency> |
| 118 | <dependency> | 87 | <dependency> |
| 119 | - <groupId>cn.hutool</groupId> | ||
| 120 | - <artifactId>hutool-all</artifactId> | ||
| 121 | - <version>${hutool.version}</version> | ||
| 122 | - </dependency> | ||
| 123 | - <dependency> | ||
| 124 | - <groupId>com.google.code.gson</groupId> | ||
| 125 | - <artifactId>gson</artifactId> | ||
| 126 | - </dependency> | ||
| 127 | - <dependency> | ||
| 128 | - <groupId>commons-io</groupId> | ||
| 129 | - <artifactId>commons-io</artifactId> | ||
| 130 | - <version>2.15.1</version> | ||
| 131 | - </dependency> | ||
| 132 | - <dependency> | ||
| 133 | <groupId>com.squareup.okhttp3</groupId> | 88 | <groupId>com.squareup.okhttp3</groupId> |
| 134 | <artifactId>okhttp</artifactId> | 89 | <artifactId>okhttp</artifactId> |
| 135 | <version>${okhttp.version}</version> | 90 | <version>${okhttp.version}</version> |
| 136 | </dependency> | 91 | </dependency> |
| 137 | 92 | ||
| 138 | - <!-- JSON --> | ||
| 139 | - <dependency> | ||
| 140 | - <groupId>com.alibaba.fastjson2</groupId> | ||
| 141 | - <artifactId>fastjson2</artifactId> | ||
| 142 | - <version>${fastjson.version}</version> | ||
| 143 | - </dependency> | ||
| 144 | - <dependency> | ||
| 145 | - <groupId>com.fasterxml.jackson.core</groupId> | ||
| 146 | - <artifactId>jackson-databind</artifactId> | ||
| 147 | - <version>${jackson.version}</version> | ||
| 148 | - </dependency> | ||
| 149 | - <dependency> | ||
| 150 | - <groupId>com.fasterxml.jackson.core</groupId> | ||
| 151 | - <artifactId>jackson-core</artifactId> | ||
| 152 | - <version>${jackson.version}</version> | ||
| 153 | - </dependency> | ||
| 154 | - <dependency> | ||
| 155 | - <groupId>com.fasterxml.jackson.core</groupId> | ||
| 156 | - <artifactId>jackson-annotations</artifactId> | ||
| 157 | - <version>${jackson.version}</version> | ||
| 158 | - </dependency> | ||
| 159 | - <dependency> | ||
| 160 | - <groupId>com.fasterxml.jackson.datatype</groupId> | ||
| 161 | - <artifactId>jackson-datatype-jsr310</artifactId> | ||
| 162 | - <version>${jackson.version}</version> | ||
| 163 | - </dependency> | ||
| 164 | - | ||
| 165 | - <!-- 文档处理 --> | ||
| 166 | - <dependency> | ||
| 167 | - <groupId>org.apache.tika</groupId> | ||
| 168 | - <artifactId>tika-core</artifactId> | ||
| 169 | - <version>2.9.1</version> | ||
| 170 | - </dependency> | ||
| 171 | - <dependency> | ||
| 172 | - <groupId>org.apache.pdfbox</groupId> | ||
| 173 | - <artifactId>pdfbox</artifactId> | ||
| 174 | - <version>3.0.2</version> | ||
| 175 | - </dependency> | ||
| 176 | - <dependency> | ||
| 177 | - <groupId>org.apache.poi</groupId> | ||
| 178 | - <artifactId>poi-ooxml</artifactId> | ||
| 179 | - <version>5.2.5</version> | ||
| 180 | - </dependency> | ||
| 181 | - | ||
| 182 | - <!-- OCR --> | ||
| 183 | - <dependency> | ||
| 184 | - <groupId>net.sourceforge.tess4j</groupId> | ||
| 185 | - <artifactId>tess4j</artifactId> | ||
| 186 | - <version>${tess4j.version}</version> | ||
| 187 | - </dependency> | ||
| 188 | - <dependency> | ||
| 189 | - <groupId>io.github.mymonstercat</groupId> | ||
| 190 | - <artifactId>rapidocr</artifactId> | ||
| 191 | - <version>0.0.7</version> | ||
| 192 | - </dependency> | ||
| 193 | - <dependency> | ||
| 194 | - <groupId>io.github.mymonstercat</groupId> | ||
| 195 | - <artifactId>rapidocr-onnx-platform</artifactId> | ||
| 196 | - <version>0.0.7</version> | ||
| 197 | - </dependency> | ||
| 198 | - | ||
| 199 | - <!-- 向量数据库 --> | ||
| 200 | - <dependency> | ||
| 201 | - <groupId>io.milvus</groupId> | ||
| 202 | - <artifactId>milvus-sdk-java</artifactId> | ||
| 203 | - <version>${milvus.version}</version> | ||
| 204 | - </dependency> | ||
| 205 | - | ||
| 206 | <!-- LangChain4j 核心 + Ollama --> | 93 | <!-- LangChain4j 核心 + Ollama --> |
| 207 | <dependency> | 94 | <dependency> |
| 208 | <groupId>dev.langchain4j</groupId> | 95 | <groupId>dev.langchain4j</groupId> |
| @@ -214,98 +101,13 @@ | @@ -214,98 +101,13 @@ | ||
| 214 | <artifactId>langchain4j-ollama</artifactId> | 101 | <artifactId>langchain4j-ollama</artifactId> |
| 215 | <version>${langchain4j.version}</version> | 102 | <version>${langchain4j.version}</version> |
| 216 | </dependency> | 103 | </dependency> |
| 217 | - <dependency> | ||
| 218 | - <groupId>dev.langchain4j</groupId> | ||
| 219 | - <artifactId>langchain4j-embeddings-all-minilm-l6-v2</artifactId> | ||
| 220 | - <version>${langchain4j-emb.version}</version> | ||
| 221 | - </dependency> | ||
| 222 | - | ||
| 223 | - <!-- 分词 --> | ||
| 224 | - <dependency> | ||
| 225 | - <groupId>com.huaban</groupId> | ||
| 226 | - <artifactId>jieba-analysis</artifactId> | ||
| 227 | - <version>1.0.2</version> | ||
| 228 | - </dependency> | ||
| 229 | - | ||
| 230 | - <!-- 接口文档 --> | ||
| 231 | - <dependency> | ||
| 232 | - <groupId>org.springdoc</groupId> | ||
| 233 | - <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> | ||
| 234 | - <version>${springdoc.version}</version> | ||
| 235 | - </dependency> | ||
| 236 | - | ||
| 237 | - <!-- MapStruct --> | ||
| 238 | - <dependency> | ||
| 239 | - <groupId>org.mapstruct</groupId> | ||
| 240 | - <artifactId>mapstruct</artifactId> | ||
| 241 | - <version>${mapstruct.version}</version> | ||
| 242 | - </dependency> | ||
| 243 | 104 | ||
| 244 | - <!-- 其他工具 --> | 105 | + <!-- SQL 解析(QueryTool 只读沙箱护栏) --> |
| 245 | <dependency> | 106 | <dependency> |
| 246 | <groupId>com.github.jsqlparser</groupId> | 107 | <groupId>com.github.jsqlparser</groupId> |
| 247 | <artifactId>jsqlparser</artifactId> | 108 | <artifactId>jsqlparser</artifactId> |
| 248 | <version>4.9</version> | 109 | <version>4.9</version> |
| 249 | </dependency> | 110 | </dependency> |
| 250 | - <dependency> | ||
| 251 | - <groupId>org.jsoup</groupId> | ||
| 252 | - <artifactId>jsoup</artifactId> | ||
| 253 | - <version>1.17.2</version> | ||
| 254 | - </dependency> | ||
| 255 | - | ||
| 256 | - <!-- 删掉这两个错误的 --> | ||
| 257 | - <dependency> | ||
| 258 | - <groupId>org.codehaus.groovy</groupId> | ||
| 259 | - <artifactId>groovy</artifactId> | ||
| 260 | - <version>3.0.20</version> | ||
| 261 | - </dependency> | ||
| 262 | - <dependency> | ||
| 263 | - <groupId>org.codehaus.groovy</groupId> | ||
| 264 | - <artifactId>groovy-all</artifactId> | ||
| 265 | - <version>3.0.20</version> | ||
| 266 | - <type>pom</type> | ||
| 267 | - </dependency> | ||
| 268 | - | ||
| 269 | - <!-- 正确的 Fastjson2 --> | ||
| 270 | - <dependency> | ||
| 271 | - <groupId>com.alibaba.fastjson2</groupId> | ||
| 272 | - <artifactId>fastjson2</artifactId> | ||
| 273 | - <version>2.0.50</version> | ||
| 274 | - </dependency> | ||
| 275 | - | ||
| 276 | - <!-- Spring Data JPA 核心依赖(自动包含 jakarta.persistence 包) --> | ||
| 277 | - <dependency> | ||
| 278 | - <groupId>org.springframework.boot</groupId> | ||
| 279 | - <artifactId>spring-boot-starter-data-jpa</artifactId> | ||
| 280 | - </dependency> | ||
| 281 | - | ||
| 282 | - <!-- 数据库驱动(以 MySQL 为例,根据你的数据库替换) --> | ||
| 283 | - <dependency> | ||
| 284 | - <groupId>com.mysql</groupId> | ||
| 285 | - <artifactId>mysql-connector-j</artifactId> | ||
| 286 | - <scope>runtime</scope> | ||
| 287 | - </dependency> | ||
| 288 | - | ||
| 289 | - <dependency> | ||
| 290 | - <groupId>com.github.jnr</groupId> | ||
| 291 | - <artifactId>jnr-ffi</artifactId> | ||
| 292 | - <version>2.2.15</version> <!-- 稳定版 --> | ||
| 293 | - </dependency> | ||
| 294 | - <dependency> | ||
| 295 | - <groupId>jakarta.annotation</groupId> | ||
| 296 | - <artifactId>jakarta.annotation-api</artifactId> | ||
| 297 | - <version>2.1.1</version> | ||
| 298 | - </dependency> | ||
| 299 | - <dependency> | ||
| 300 | - <groupId>org.springframework.retry</groupId> | ||
| 301 | - <artifactId>spring-retry</artifactId> | ||
| 302 | - </dependency> | ||
| 303 | - | ||
| 304 | - <!-- 必须加:Spring Retry 需要 AOP 依赖 --> | ||
| 305 | - <dependency> | ||
| 306 | - <groupId>org.springframework.boot</groupId> | ||
| 307 | - <artifactId>spring-boot-starter-aop</artifactId> | ||
| 308 | - </dependency> | ||
| 309 | </dependencies> | 111 | </dependencies> |
| 310 | 112 | ||
| 311 | <build> | 113 | <build> |
| @@ -335,11 +137,6 @@ | @@ -335,11 +137,6 @@ | ||
| 335 | <artifactId>lombok</artifactId> | 137 | <artifactId>lombok</artifactId> |
| 336 | <version>${lombok.version}</version> | 138 | <version>${lombok.version}</version> |
| 337 | </path> | 139 | </path> |
| 338 | - <path> | ||
| 339 | - <groupId>org.mapstruct</groupId> | ||
| 340 | - <artifactId>mapstruct-processor</artifactId> | ||
| 341 | - <version>${mapstruct.version}</version> | ||
| 342 | - </path> | ||
| 343 | </annotationProcessorPaths> | 140 | </annotationProcessorPaths> |
| 344 | </configuration> | 141 | </configuration> |
| 345 | </plugin> | 142 | </plugin> |
| @@ -349,4 +146,4 @@ | @@ -349,4 +146,4 @@ | ||
| 349 | </plugin> | 146 | </plugin> |
| 350 | </plugins> | 147 | </plugins> |
| 351 | </build> | 148 | </build> |
| 352 | -</project> | ||
| 353 | \ No newline at end of file | 149 | \ No newline at end of file |
| 150 | +</project> |
sql/README.md
| @@ -64,6 +64,12 @@ done | @@ -64,6 +64,12 @@ done | ||
| 64 | 3. 术语接地:用 `viw_kg_field_dict` 把问题里的中文词映射到真实列 + join 目标,喂给 NL2SQL。 | 64 | 3. 术语接地:用 `viw_kg_field_dict` 把问题里的中文词映射到真实列 + join 目标,喂给 NL2SQL。 |
| 65 | 4. 取数:命中表单后统一走 `POST /business/getBusinessDataByFormcustomId/{sFormId}?sModelsId={sModuleId}`。 | 65 | 4. 取数:命中表单后统一走 `POST /business/getBusinessDataByFormcustomId/{sFormId}?sModelsId={sModuleId}`。 |
| 66 | 66 | ||
| 67 | +> **落地现状(2026-07-23)**:agent 侧只用到 L1(进 system prompt)与 L2/L3 的 **SQL 关键词查询** | ||
| 68 | +> (`KgQueryTool.findForms` / `kgSearch`、`FormResolverService`)。**第 1 步的向量召回尚未接入** | ||
| 69 | +> ——`export_form_cards.sh` 的产物没有灌进 Milvus,上面写的是目标用法而非现状。 | ||
| 70 | + | ||
| 67 | ## 校验基线 | 71 | ## 校验基线 |
| 68 | 72 | ||
| 69 | -以 `ai_tool`(62 张 form-backed 工具,`sSrcFormId`=菜单id)为 ground truth 校验召回。 | 73 | +以 `ai_tool` 为 ground truth 校验召回:**共 64 个工具,63 个带 `sSrcFormId`(=菜单id)** |
| 74 | +(唯一没有的是「工单成本查询」,配置本身就是坏的)。按业务表单口径为 61 张—— | ||
| 75 | +另 2 个挂在通用 KPI 模块上。逐条能力盘点见 `../docs/legacy-tool-inventory.md`。 |
src/main/java/com/xly/XlyAiApplication.java
| 1 | package com.xly; | 1 | package com.xly; |
| 2 | 2 | ||
| 3 | -import cn.hutool.core.util.ObjectUtil; | ||
| 4 | -import cn.hutool.core.util.StrUtil; | ||
| 5 | -import org.mybatis.spring.annotation.MapperScan; | ||
| 6 | import org.slf4j.Logger; | 3 | import org.slf4j.Logger; |
| 7 | import org.slf4j.LoggerFactory; | 4 | import org.slf4j.LoggerFactory; |
| 8 | import org.springframework.boot.SpringApplication; | 5 | import org.springframework.boot.SpringApplication; |
| 9 | import org.springframework.boot.autoconfigure.SpringBootApplication; | 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; |
| 10 | import org.springframework.boot.builder.SpringApplicationBuilder; | 7 | import org.springframework.boot.builder.SpringApplicationBuilder; |
| 11 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; | 8 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; |
| 12 | -import org.springframework.cache.annotation.EnableCaching; | ||
| 13 | -import org.springframework.scheduling.annotation.EnableAsync; | ||
| 14 | -import org.springframework.scheduling.annotation.EnableScheduling; | ||
| 15 | -import org.springframework.web.bind.annotation.PostMapping; | ||
| 16 | -import org.springframework.web.bind.annotation.RequestBody; | ||
| 17 | 9 | ||
| 18 | -import java.util.HashMap; | ||
| 19 | -import java.util.Map; | ||
| 20 | - | ||
| 21 | -@EnableAsync | ||
| 22 | -@EnableScheduling | ||
| 23 | -@EnableCaching | ||
| 24 | @SpringBootApplication | 10 | @SpringBootApplication |
| 25 | -@MapperScan("com.xly.mapper") | ||
| 26 | -public class XlyAiApplication extends SpringBootServletInitializer { // 关键:继承 SpringBootServletInitializer | 11 | +public class XlyAiApplication extends SpringBootServletInitializer { |
| 27 | 12 | ||
| 28 | private static final Logger logger = LoggerFactory.getLogger(XlyAiApplication.class); | 13 | private static final Logger logger = LoggerFactory.getLogger(XlyAiApplication.class); |
| 29 | 14 | ||
| 30 | - @Override // 关键:重写 configure 方法 | 15 | + // war 部署入口(外置 servlet 容器) |
| 16 | + @Override | ||
| 31 | protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { | 17 | protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { |
| 32 | return application.sources(XlyAiApplication.class); | 18 | return application.sources(XlyAiApplication.class); |
| 33 | } | 19 | } |
| 34 | 20 | ||
| 35 | - /** | ||
| 36 | - * 提取报修结构化信息 | ||
| 37 | - */ | ||
| 38 | - @PostMapping("/") | ||
| 39 | - public String home(@RequestBody Map<String,Object> sendMap) { | ||
| 40 | - return "欢迎来到小羚羊AI系统"; | ||
| 41 | - } | ||
| 42 | - | ||
| 43 | public static void main(String[] args) { | 21 | public static void main(String[] args) { |
| 44 | SpringApplication.run(XlyAiApplication.class, args); | 22 | SpringApplication.run(XlyAiApplication.class, args); |
| 45 | - logger.info(" \n" + | ||
| 46 | - " /$$ \n" + | ||
| 47 | - " | $$ \n" + | ||
| 48 | - " /$$ /$$ /$$ /$$ /$$ /$$$$$$$| $$ /$$$$$$$\n" + | ||
| 49 | - "| $$ | $$ | $$| $$ | $$ /$$_____/| $$ /$$_____/\n" + | ||
| 50 | - "| $$ | $$ | $$| $$ | $$| $$ | $$| $$$$$$ \n" + | ||
| 51 | - "| $$ | $$ | $$| $$ | $$| $$ | $$ \\____ $$\n" + | ||
| 52 | - "| $$$$$/$$$$/| $$$$$$$| $$$$$$$| $$ /$$$$$$$/\n" + | ||
| 53 | - " \\_____/\\___/ \\____ $$ \\_______/|__/|_______/ \n" + | ||
| 54 | - " /$$ | $$ \n" + | ||
| 55 | - " | $$$$$$/ \n" + | ||
| 56 | - " \\______/ "); | ||
| 57 | - logger.info("I wish you a pleasant use of the system and never have any bugs"); | 23 | + logger.info("xlyAi 启动完成"); |
| 58 | } | 24 | } |
| 59 | -} | ||
| 60 | \ No newline at end of file | 25 | \ No newline at end of file |
| 26 | +} |
src/main/java/com/xly/agent/AgentSystemPrompt.java deleted
| 1 | -package com.xly.agent; | ||
| 2 | - | ||
| 3 | -public class AgentSystemPrompt { | ||
| 4 | - | ||
| 5 | -// public static final String sSystemPrompt = "你是一个专业的智能助手,请根据以下规则精准匹配并调用工具:\n" + | ||
| 6 | -// "【核心决策逻辑】\n" + | ||
| 7 | -// "1. 单一工具绝对调用:当系统仅提供一个可用工具时,必须无条件调用该工具。即使参数不全,也必须发起调用,严禁返回向用户索要信息或者不调用工具;\n" + | ||
| 8 | -// "2. 多工具意图匹配:当系统提供多个工具时,必须先精准识别用户的核心业务意图,然后匹配唯一对应的工具进行调用;\n" + | ||
| 9 | -// "\n" + | ||
| 10 | -// "【参数提取与调用规范】\n" + | ||
| 11 | -// "3. 触发条件:匹配工具时,仅关注工具描述中“当用户”和“时,必须调用本工具”之间的语义关联;\n" + | ||
| 12 | -// "4. 参数补全与缺省处理:\n" + | ||
| 13 | -// " - 如果当前是全新的独立请求,且必填参数缺失,请直接省略该字段发起调用(不要反问用户)。\n" + | ||
| 14 | -// "5. 格式要求:提取参数时严格按标注类型赋值,数字不带引号,字符串需去除首尾空格;\n" + | ||
| 15 | -// "6. 绝对禁止:在任何情况下,禁止向用户反问以确认参数或意图,你的输出只能是最终回复或合法的工具调用请求。"; | ||
| 16 | - | ||
| 17 | - public static final String sSystemPrompt = "你是一个无情的参数提取器,不是聊天助手。\n" + | ||
| 18 | - "【绝对指令】\n" + | ||
| 19 | - "1. 你的唯一任务是提取用户输入中的参数,并立即调用工具。\n" + | ||
| 20 | - "2. 严禁与用户进行任何对话!严禁输出“您好”、“请提供”、“需要补充”等任何纯文本回复!\n" + | ||
| 21 | - "3. 无论用户输入多么简短(例如只输入了公司名),或者参数缺失多么严重,你都必须直接调用工具,将缺失的字段留空或填0。\n" + | ||
| 22 | - "4. 不要试图去引导用户,引导用户是你的后端程序要做的事,你只需要负责把提取到的参数传出去。"; | ||
| 23 | -} | ||
| 24 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/agent/ChatiAgent.java deleted
| 1 | -package com.xly.agent; | ||
| 2 | - | ||
| 3 | -import dev.langchain4j.service.MemoryId; | ||
| 4 | -import dev.langchain4j.service.SystemMessage; | ||
| 5 | -import dev.langchain4j.service.UserMessage; | ||
| 6 | -import dev.langchain4j.service.V; | ||
| 7 | -import reactor.core.publisher.Flux; | ||
| 8 | - | ||
| 9 | -public interface ChatiAgent { | ||
| 10 | - @SystemMessage(""" | ||
| 11 | - 你是一个轻松自然的聊天伙伴,语气亲切口语化,像朋友一样闲聊。 | ||
| 12 | - 要求:1. 不生硬、不说教,避免书面化表达; | ||
| 13 | - 2. 主动接梗,适当延伸话题,不一问一答; | ||
| 14 | - 3. 偶尔带点小幽默,保持轻松无压力的氛围; | ||
| 15 | - 4. 回答简洁,符合日常聊天的语气,不啰嗦。 | ||
| 16 | - 5. 首次沟通时发现称呼不是“小羚羊”时,请回复“我不是..,我是小羚羊”,语气俏皮。 | ||
| 17 | - """) | ||
| 18 | - @UserMessage("用户说:{{userInput}}") | ||
| 19 | - String chat(@MemoryId String userId, @V("userInput") String userInput); | ||
| 20 | - | ||
| 21 | - @SystemMessage(""" | ||
| 22 | - 你是一个轻松自然的聊天伙伴,语气亲切口语化,像朋友一样闲聊。 | ||
| 23 | - 要求:1. 不生硬、不说教,避免书面化表达; | ||
| 24 | - 2. 主动接梗,适当延伸话题,不一问一答; | ||
| 25 | - 3. 偶尔带点小幽默,保持轻松无压力的氛围; | ||
| 26 | - 4. 回答简洁,符合日常聊天的语气,不啰嗦。 | ||
| 27 | - 5. 首次沟通时发现称呼不是“小羚羊”时,请回复“我不是..,我是小羚羊”,语气俏皮。 | ||
| 28 | - """) | ||
| 29 | - @UserMessage("用户说:{{userInput}}") | ||
| 30 | - Flux<String> chatStream(@MemoryId String userId, @V("userInput") String userInput); | ||
| 31 | -} |
src/main/java/com/xly/agent/DynamicTableNl2SqlAiAgent.java deleted
| 1 | -package com.xly.agent; | ||
| 2 | - | ||
| 3 | -import dev.langchain4j.service.MemoryId; | ||
| 4 | -import dev.langchain4j.service.SystemMessage; | ||
| 5 | -import dev.langchain4j.service.UserMessage; | ||
| 6 | -import dev.langchain4j.service.V; | ||
| 7 | - | ||
| 8 | -/** | ||
| 9 | - * 适配动态表结构的NL2SQL AI服务 | ||
| 10 | - * 核心:表结构作为动态参数传入,@SystemMessage仅保留通用规则 | ||
| 11 | - */ | ||
| 12 | - | ||
| 13 | - | ||
| 14 | -public interface DynamicTableNl2SqlAiAgent { | ||
| 15 | - | ||
| 16 | - /** | ||
| 17 | - * 动态表结构:自然语言转MySQL SELECT语句 | ||
| 18 | - * 入参:数据库名、表名(多表用,分隔)、表结构、用户查询 | ||
| 19 | - */ | ||
| 20 | - @SystemMessage(""" | ||
| 21 | - 你是资深MySQL数据分析师,严格遵循以下**通用规则**生成SQL,适用于所有业务场景: | ||
| 22 | - 1. 语法规范:仅生成符合MySQL8.0.36的标准SELECT语句,兼容低版本,多表关联用JOIN而非逗号; | ||
| 23 | - 2. 输出格式:仅返回SQL语句本身,无任何解释、换行、```sql/```包裹、备注、多余空格,直接输出可执行SQL; | ||
| 24 | - 3. 编写规范: | ||
| 25 | - 3.1 多表关联必须使用 表名+字段名(如表名.字段名),严格按下面[涉及表名]中的表次序关联,聚合函数(SUM/COUNT/AVG/MIN/MAX)必须加业务化别名,日期过滤使用标准DATE格式(yyyy-MM-dd); | ||
| 26 | - 3.2 SQL所有字段均采用 表名.字段名 方式生成,务必确保 字段名 在相应的 表名 描述的字段中存在,如果不存在重试其它方式,直到满足条件; | ||
| 27 | - 3.3 SQL所有字段涉及的所有表名,都要**严格**按下面[涉及表名]中的表次序关联,没有关联不允许使用; | ||
| 28 | - 3.4 SQL所有的查询条件,如果是字符类型的字段,均需要加不为空判断,用示例格式判断,示例:ifnull(customername,'')<>''; | ||
| 29 | - 3.5 SQL所有的查询条件,如果是布尔类型的字段 判断值只能是1或者0,1表示:true,0表示:false,例如:viw_ai_quotation.bCheck = 1; | ||
| 30 | - 3.6 SQL所有的查询条件,如果是日期类型的字段,均需要加不为空判断,用示例格式判断,示例:tmakedate is not Null; | ||
| 31 | - 3.7 SQL所有的显示字段的别名中,不能出现空格,如: tCreateDate as earliest 订单日期,正确的应是 tCreateDate as earliest订单日期 | ||
| 32 | - 3.8 在AVG聚合函数中不允许使用LAG、LEAD 等窗口函数 例如:AVG(LAG(...))这种嵌套是不允许的 | ||
| 33 | - 3.9 GROUP BY 后面不允许使用窗口函数 | ||
| 34 | - 4. 安全约束: | ||
| 35 | - - 禁止:DDL/DML语句(DROP/ALTER/INSERT/UPDATE/DELETE等) | ||
| 36 | - - 禁止:存储过程、自定义函数、临时表 | ||
| 37 | - - 允许:子查询(当需要使用窗口函数LAG/ROW_NUMBER等时) | ||
| 38 | - - 允许:CTE公用表表达式(WITH语句) | ||
| 39 | - 5. 精准性: | ||
| 40 | - 5.1 严格按用户需求+传入的表结构生成,仅使用指定字段/表,无多余字段、无无效表关联、无冗余过滤条件; | ||
| 41 | - 5.2 用户需求中没有明确的日期条件,默认为全部数据,禁止增加任何日期过滤条件 | ||
| 42 | - 6. 关联规则:多表关联时,必须使用外键/业务唯一键关联,禁止无意义关联。 | ||
| 43 | - 7. 当前时间:{{sDataNow}} | ||
| 44 | - 8. 时间处理规则: | ||
| 45 | - 8.1 当前系统时间:{{sDataNow}}(格式:yyyy年MM月dd日HH时mm分ss秒) | ||
| 46 | - 8.2 用户需求中的相对时间概念,必须基于{{sDataNow}}进行转换: | ||
| 47 | - - "本年" → 当前年份:{{sDataNow}}的年份 | ||
| 48 | - - "本月" → 当前月份:{{sDataNow}}的年份和月份 | ||
| 49 | - - "本季度" → 当前季度:基于{{sDataNow}}计算 | ||
| 50 | - - "本日/今天" → {{sDataNow}}的具体日期 | ||
| 51 | - - "昨天" → {{sDataNow}}减1天 | ||
| 52 | - - "本周" → 基于{{sDataNow}}计算周一到周日 | ||
| 53 | - - "近7天" → {{sDataNow}}减7天到{{sDataNow}} | ||
| 54 | - 8.3 示例转换: | ||
| 55 | - 当前时间:2024-03-15 | ||
| 56 | - 用户说"查询本年数据" → 查询条件应为:YEAR(日期字段) = 2024 | ||
| 57 | - 用户说"查询本月数据" → 查询条件应为:YEAR(日期字段) = 2024 AND MONTH(日期字段) = 3 | ||
| 58 | - 8.4 如果用户需求中没有明确的时间条件,禁止增加任何时间过滤条件 | ||
| 59 | - """) | ||
| 60 | - @UserMessage(""" | ||
| 61 | - 【业务场景表结构信息】 | ||
| 62 | - 涉及表名:{{tableNames}}(多表用,分隔,需关联时请按规范使用JOIN) | ||
| 63 | - 表结构详情:{{tableStruct}}(多表请标注表名+字段,格式:表名(字段1:类型,字段2:类型,主键/外键)) | ||
| 64 | - 当前时间:{{sDataNow}} | ||
| 65 | - 【用户需求】 | ||
| 66 | - {{userInput}} | ||
| 67 | - 请根据上述表结构+通用规则,生成符合要求的MySQL SELECT语句; | ||
| 68 | - 【时间处理要求】 | ||
| 69 | - 1. 如果用户需求包含"本年/本月/本季度/本日/今天/昨天/本周/近X天"等相对时间概念: | ||
| 70 | - - 必须使用当前系统时间 {{sDataNow}} 进行转换 | ||
| 71 | - - 转换为具体的年份、月份或日期范围 | ||
| 72 | - 2. 转换示例: | ||
| 73 | - - "本年" → 年份 = {{sDataNow}}的年份部分 | ||
| 74 | - - "本月" → 年份 = {{sDataNow}}的年份, 月份 = {{sDataNow}}的月份 | ||
| 75 | - - "今天" → 日期 = {{sDataNow}} | ||
| 76 | - 3. 如果没有明确的时间需求,不要添加任何时间过滤条件 | ||
| 77 | - """) | ||
| 78 | - String generateMysqlSql(@MemoryId String userId, | ||
| 79 | - @V("tableNames") String tableNames, | ||
| 80 | - @V("tableStruct") String tableStruct, | ||
| 81 | - @V("sDataNow") String sDataNow, | ||
| 82 | - @V("userInput") String userInput); | ||
| 83 | - | ||
| 84 | - | ||
| 85 | - /** | ||
| 86 | - * 动态表结构:自然语言转MySQL SELECT语句 | ||
| 87 | - * 入参:数据库名、表名(多表用,分隔)、表结构、用户查询 | ||
| 88 | - */ | ||
| 89 | - @SystemMessage(""" | ||
| 90 | - 【系统角色】 | ||
| 91 | - 你是资深MySQL 8.0.36数据分析师,严格杜绝以下错误,生成100%可执行的SELECT语句; | ||
| 92 | - 【严格禁止的错误类型(强制遵守)】 | ||
| 93 | - 1. 语法错误: | ||
| 94 | - - 禁止使用COUNT()无参数写法,必须写COUNT(*)、COUNT(1)或COUNT(具体非空字段); | ||
| 95 | - - 禁止在AVG/SUM等聚合函数内嵌套LAG/ROW_NUMBER等窗口函数(如 AVG(dMaterialsPrice - LAG(dMaterialsPrice, 1, dMaterialsPrice))/SUM(viw_ai_purchaseorder.dMaterialsPrice - LAG(viw_ai_purchaseorder.dMaterialsPrice, 1))); | ||
| 96 | - - 禁止ORDER BY中直接使用未别名的窗口函数表达式(如ORDER BY dMaterialsPrice - LAG(...)); | ||
| 97 | - - 禁止GROUP BY字段与SELECT非聚合字段不一致; | ||
| 98 | - 2. 规则违规: | ||
| 99 | - - 禁止字段不写表名前缀(必须是「表名.字段名」格式); | ||
| 100 | - - 禁止日期字段非空判断用ifnull(日期字段,'')<>''(仅字符字段用此写法,日期字段用IS NOT NULL); | ||
| 101 | - - 禁止生成与{{errorSql}}/{{historySqlList}}重复的语句,禁止仅修改排序字段/别名的“伪差异化”,可以使用子查询修复或者修改查询字段,不要使用窗口函数(如 LAG); | ||
| 102 | - - 禁止LAG窗口函数缺失ORDER BY子句(必须按时间字段排序); | ||
| 103 | - - 禁止HAVING条件使用COUNT()无参数写法,禁止过滤条件与业务需求无关; | ||
| 104 | - - 禁止在 AVG/SUM 等聚合函数中嵌套窗口函数; | ||
| 105 | - 3. 编写规范: | ||
| 106 | - - 多表关联必须使用 表名+字段名(如表名.字段名),严格按下面[涉及表名]中的表次序关联,聚合函数(SUM/COUNT/AVG/MIN/MAX)必须加业务化别名,日期过滤使用标准DATE格式(yyyy-MM-dd); | ||
| 107 | - - SQL所有字段均采用 表名.字段名 方式生成,务必确保 字段名 在相应的 表名 描述的字段中存在,如果不存在重试其它方式,直到满足条件; | ||
| 108 | - - SQL所有字段涉及的所有表名,都要**严格**按下面[涉及表名]中的表次序关联,没有关联不允许使用; | ||
| 109 | - - SQL所有的查询条件,如果是字符类型的字段,均需要加不为空判断,用示例格式判断,示例:ifnull(customername,'')<>''; | ||
| 110 | - - SQL所有的查询条件,如果是日期类型的字段,均需要加不为空判断,用示例格式判断,示例:tmakedate is not Null; | ||
| 111 | - - SQL所有的显示字段的别名中,不能出现空格,如: tCreateDate as earliest 订单日期,正确的应是 tCreateDate as earliest订单日期 | ||
| 112 | - - 在AVG聚合函数中不允许使用LAG、LEAD 等窗口函数 | ||
| 113 | - - AVG(LAG(...))这种嵌套是不允许的 | ||
| 114 | - - GROUP BY 后面不允许使用窗口函数 | ||
| 115 | - 【生成前自检要求】 | ||
| 116 | - 1. 先检查是否违反上述禁止规则,再生成SQL; | ||
| 117 | - 2. 每条SQL生成后,模拟MySQL执行逻辑自检: | ||
| 118 | - - 语法是否合法(无COUNT()、聚合嵌套窗口函数等); | ||
| 119 | - - 字段是否存在于表结构中; | ||
| 120 | - - 差异化是否满足「结构/函数/格式」2个维度; | ||
| 121 | - 3. 若自检发现错误,立即重新生成,直至所有SQL符合要求。 | ||
| 122 | - """) | ||
| 123 | - @UserMessage(""" | ||
| 124 | - 【业务场景表结构信息】 | ||
| 125 | - 涉及表名:{{tableNames}} | ||
| 126 | - 表结构详情:{{tableStruct}} | ||
| 127 | - 当前时间:{{sDataNow}} | ||
| 128 | - 【原始用户需求】 | ||
| 129 | - {{userInput}} | ||
| 130 | - 【错误信息】 | ||
| 131 | - 之前生成的错误SQL:{{errorSql}} | ||
| 132 | - 执行错误信息:{{errorMessage}} | ||
| 133 | - 【生成要求】 | ||
| 134 | - 1. 先修复错误SQL的所有问题,确保语法/逻辑合规; | ||
| 135 | - 2. 生成与{{errorSql}}/{{historySqlList}}不重复的语句,禁止仅修改排序字段/别名的“伪差异化”,可以使用子查询修复或者修改查询字段,不要使用窗口函数(如 LAG)的SELECT语句; | ||
| 136 | - 3. SQL所有字段均采用 表名.字段名 方式生成,务必确保 字段名 在相应的 表名 描述的字段中存在,如果不存在重试其它方式,直到满足条件; | ||
| 137 | - 4. 自连接+子查询方式时,子查询跟自连接关联关系字段需要在子查询中查询列出现 | ||
| 138 | - 5. 生成语法 / 逻辑合规且与历史语句无 “伪差异化” 的 SELECT 语句,且不使用窗口函数。 | ||
| 139 | - 请根据上述表结构+通用规则,生成符合要求的MySQL SELECT语句;并且仅返回SQL语句 | ||
| 140 | - """) | ||
| 141 | - String regenerateSqlWithError(@MemoryId String userId, | ||
| 142 | - @V("tableNames") String tableNames, | ||
| 143 | - @V("tableStruct") String tableStruct, | ||
| 144 | - @V("sDataNow") String sDataNow, | ||
| 145 | - @V("userInput") String userInput, | ||
| 146 | - @V("errorSql") String errorSql, | ||
| 147 | - @V("errorMessage") String errorMessage, | ||
| 148 | - @V("n") String iErroCount, | ||
| 149 | - @V("historySqlList") String historySqlList | ||
| 150 | - ); | ||
| 151 | -// /** | ||
| 152 | -// * 动态表结构:自然语言解释SQL执行结果 | ||
| 153 | -// * 入参:用户问题、执行的SQL、表结构、JSON格式结果 | ||
| 154 | -// */ | ||
| 155 | -// @SystemMessage(""" | ||
| 156 | -// 你是专业的业务数据分析师,严格遵循以下**通用规则**解释查询结果,适用于所有业务场景: | ||
| 157 | -// 1. 解释风格:贴合业务场景,无任何SQL专业术语,用口语化、简洁的商业语言说明,避免技术词汇; | ||
| 158 | -// 2. 数据准确:严格按照JSON执行结果解释,不夸大、不遗漏、不编造数据,数值与结果完全一致; | ||
| 159 | -// 3. 输出格式:仅返回解释内容,不要列出ID,无多余标题、换行、符号,结果为空时直接返回“未查询到相关数据”; | ||
| 160 | -// 4. 长度控制:单条解释不超过150字,条理清晰,重点突出核心数据/趋势; | ||
| 161 | -// 5. 禁止重复:不重复用户问题、不重复执行的SQL语句,仅针对结果做业务解读。 | ||
| 162 | -// """) | ||
| 163 | -// @UserMessage(""" | ||
| 164 | -// 【业务场景表结构信息】 | ||
| 165 | -// 表结构详情:{{tableStruct}} | ||
| 166 | -// 【查询相关信息】 | ||
| 167 | -// 用户原始查询:{{userInput}} | ||
| 168 | -// 执行的MySQL SQL:{{sql}} | ||
| 169 | -// SQL执行结果(JSON格式):{{result}} | ||
| 170 | -// 请根据上述信息+通用规则,对查询结果做业务解释: | ||
| 171 | -// """) | ||
| 172 | -// String explainSqlResult(@MemoryId String userId, | ||
| 173 | -// @V("userInput") String userInput, | ||
| 174 | -// @V("sql") String sql, | ||
| 175 | -// @V("tableStruct") String tableStruct, | ||
| 176 | -// @V("result") String result); | ||
| 177 | -} | ||
| 178 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/agent/ErpAiAgent.java deleted
| 1 | -package com.xly.agent; | ||
| 2 | - | ||
| 3 | - | ||
| 4 | -import dev.langchain4j.service.*; | ||
| 5 | - | ||
| 6 | -/** | ||
| 7 | - * 优化后:新增场景专属交互规则,大模型仅处理当前场景业务指令 | ||
| 8 | - */ | ||
| 9 | -public interface ErpAiAgent { | ||
| 10 | - | ||
| 11 | - @SystemMessage("{{sSystemPrompt}}") | ||
| 12 | - @UserMessage("用户输入:{{userInput}}") | ||
| 13 | - Result<String> chat( | ||
| 14 | - @MemoryId String userId, | ||
| 15 | - @V("userInput") String userInput, | ||
| 16 | - @V("sSystemPrompt") String sSystemPrompt | ||
| 17 | - ); | ||
| 18 | - /** | ||
| 19 | - * 动态表结构:自然语言解释SQL执行结果 | ||
| 20 | - * 入参:用户问题、执行的SQL、表结构、JSON格式结果 | ||
| 21 | - */ | ||
| 22 | - @SystemMessage(""" | ||
| 23 | - 你是专业的业务数据分析师,严格遵循以下**通用规则**解释查询结果,适用于所有业务场景: | ||
| 24 | - 1. 解释风格:贴合业务场景,无任何SQL专业术语,用口语化、简洁的商业语言说明,避免技术词汇; | ||
| 25 | - 2. 数据准确:严格按照JSON执行结果解释,不夸大、不遗漏、不编造数据,数值与结果完全一致; | ||
| 26 | - 3. 输出格式:仅返回解释内容,不要列出ID,无多余标题、换行、符号,结果为空时直接返回“未查询到相关数据” | ||
| 27 | - 3.1. 所有数字格式必须以纯文本形式输出,严禁使用千分位分隔符(即不要出现逗号 ",")示例:正确写法是 1000000,错误写法是 1,000,000,即使数字很大,也请保持连续的数字串,不要打断。 | ||
| 28 | - 3.2 所有日期请转换为 YYYY-MM-DD 格式(例如:2026-03-15),严禁包含时间部分(如小时、分钟、秒)(例如:2026-03-15 00:00:00),也不要包含时区信息。” | ||
| 29 | - 3.3. 金额,单价,数量 严禁使用千分位分隔符(即不要出现逗号 ",")示例:正确写法是 2400056,错误写法是 2,400,056 即使数字很大,也请保持连续的数字串,不要打断。 | ||
| 30 | - 4. 长度控制:单条解释不超过150字,条理清晰,重点突出核心数据/趋势; | ||
| 31 | - 5. 禁止重复:不重复用户问题、不重复执行的SQL语句,仅针对结果做业务解读。 | ||
| 32 | - """) | ||
| 33 | - @UserMessage(""" | ||
| 34 | - 【业务场景表结构信息】 | ||
| 35 | - 表结构详情:{{tableStruct}} | ||
| 36 | - 【查询相关信息】 | ||
| 37 | - 用户原始查询:{{userInput}} | ||
| 38 | - 执行的MySQL SQL:{{sql}} | ||
| 39 | - SQL执行结果(JSON格式):{{result}} | ||
| 40 | - 请根据上述信息+通用规则,对查询结果做业务解释: | ||
| 41 | - """) | ||
| 42 | - String explainSqlResult(@MemoryId String userId, | ||
| 43 | - @V("userInput") String userInput, | ||
| 44 | - @V("sql") String sql, | ||
| 45 | - @V("tableStruct") String tableStruct, | ||
| 46 | - @V("result") String result); | ||
| 47 | - | ||
| 48 | - /** | ||
| 49 | - * 动态表结构:自然语言解释SQL执行结果 | ||
| 50 | - * 入参:用户问题、执行的SQL、表结构、JSON格式结果 | ||
| 51 | - */ | ||
| 52 | - @SystemMessage(""" | ||
| 53 | - 你是专业的业务数据分析师,请分析以下查询结果: | ||
| 54 | - 【用户问题】 | ||
| 55 | - {{userInput}} | ||
| 56 | - 【数据字段说明】 | ||
| 57 | - {{sMilvusFiledDescription}} | ||
| 58 | - 【查询结果数据(JSON格式)】 | ||
| 59 | - {{result}} | ||
| 60 | - 【分析要求】 | ||
| 61 | - 1. 解释风格:贴合业务场景,无任何SQL专业术语,用口语化、简洁的商业语言说明,避免技术词汇; | ||
| 62 | - 2. 数据准确:严格按照JSON执行结果解释,不夸大、不遗漏、不编造数据,数值与结果完全一致; | ||
| 63 | - 3. 输出格式: | ||
| 64 | - 3.1. 如果用户要求"表格形式展示",先输出简短文字说明,然后输出Markdown格式的表格 | ||
| 65 | - 3.2. 如果用户未要求表格,仅返回解释内容,不要列出ID,无多余标题、换行、符号 | ||
| 66 | - 3.3. 结果为空时直接返回"未查询到相关数据" | ||
| 67 | - 3.4. 所有数字格式必须以纯文本形式输出,严禁使用千分位分隔符(即不要出现逗号 ",") | ||
| 68 | - 3.5. 所有日期请转换为 YYYY-MM-DD 格式,严禁包含时间部分 | ||
| 69 | - 4. 长度控制:单条解释不超过150字,条理清晰,重点突出核心数据/趋势; | ||
| 70 | - 5. 禁止重复:不重复用户问题、不重复执行的SQL语句,仅针对结果做业务解读。 | ||
| 71 | - """) | ||
| 72 | - @UserMessage(""" | ||
| 73 | - 【用户查询】 | ||
| 74 | - {{userInput}} | ||
| 75 | - 【字段说明】 | ||
| 76 | - {{sMilvusFiledDescription}} | ||
| 77 | - 【查询结果】 | ||
| 78 | - 用户原始查询:{{userInput}} | ||
| 79 | - 执行查询向量库后结果(JSON格式):{{result}} | ||
| 80 | - 请根据上述信息+通用规则,对查询结果做业务解释: | ||
| 81 | - """) | ||
| 82 | - String explainMilvusResult(@MemoryId String userId, | ||
| 83 | - @V("userInput") String userInput, | ||
| 84 | - @V("sMilvusFiledDescription") String sMilvusFiledDescription, | ||
| 85 | - @V("result") String result); | ||
| 86 | - | ||
| 87 | - @SystemMessage(""" | ||
| 88 | - 你是一个智能查询路由专家。请根据【用户需求】,只返回 true 或 false。 | ||
| 89 | - 【最高优先级规则 - 必须首先判断】 | ||
| 90 | - 如果用户需求包含以下任一关键词,**直接返回 false**,不再进行其他判断: | ||
| 91 | - - 明细、列表、清单 | ||
| 92 | - - ...明细、...列表、...清单 | ||
| 93 | - 重要:只要出现以上关键词,说明用户需要的是明细数据查询,而非统计分析。 | ||
| 94 | - 【统计类关键词 - 仅在满足最高优先级规则后才判断】 | ||
| 95 | - 只有当用户需求不包含上述明细类关键词时,才检查是否包含以下关键词: | ||
| 96 | - 统计、求和、汇总、排名、TopN、平均、数量、总额、最高、最低、占比、分组 | ||
| 97 | - - 如果包含,返回 true | ||
| 98 | - - 否则返回 false | ||
| 99 | - 【判断示例】 | ||
| 100 | - - \"查询中科精工集团的彩盒类产品的报价单明细\" → false(包含\"明细\") | ||
| 101 | - - \"查询客户张三信息\" → false(无统计关键词,无明细关键词) | ||
| 102 | - - \"销售额排名前10的产品\" → true(包含\"排名\",且无明细关键词) | ||
| 103 | - - \"查看销售订单明细\" → false(包含\"明细\") | ||
| 104 | - """) | ||
| 105 | - @UserMessage(""" | ||
| 106 | - 【用户需求】 | ||
| 107 | - {{userInput}} | ||
| 108 | - """) | ||
| 109 | -// @SystemMessage(""" | ||
| 110 | -// 你是一个智能查询路由专家,请根据【用户需求】,基于**查询效率最优**原则,自动判断使用关系型数据库(MySQL)还是向量库(Milvus),只返回 true 或 false。 | ||
| 111 | -// 满足如下规则中任意一条则返回true 否则返回false: | ||
| 112 | -// - 查询涉及**排名、TOP N、求和、计数、平均值、最大值、总额、最小值、最高、最低、占比** | ||
| 113 | -// - 查询涉及**分组统计(GROUP BY)、排序(ORDER BY)、分页(LIMIT)** | ||
| 114 | -// """) | ||
| 115 | -// @UserMessage(""" | ||
| 116 | -// 【用户需求】 | ||
| 117 | -// {{userInput}} | ||
| 118 | -// """) | ||
| 119 | - Boolean routeQuery(@MemoryId String userId, @V("userInput") String userInput); | ||
| 120 | - | ||
| 121 | - /** | ||
| 122 | - * 生成 Milvus 过滤条件(适配 Milvus v2.3.9) | ||
| 123 | - */ | ||
| 124 | - @SystemMessage(""" | ||
| 125 | - MILVUS 查询条件生成规则: | ||
| 126 | - | ||
| 127 | - 【最高优先级 - 输出格式铁律】 | ||
| 128 | - ⚠️ 你的【全部输出】必须是且仅是一个合法的 JSON 对象 | ||
| 129 | - ⚠️ 禁止输出任何解释、说明、思考过程 | ||
| 130 | - ⚠️ 禁止输出任何中文文字 | ||
| 131 | - ⚠️ 只能输出以下 JSON 格式,不能有其他任何内容 | ||
| 132 | - | ||
| 133 | - 【输出 JSON 结构】 | ||
| 134 | - { | ||
| 135 | - "sMethodName": true/false, // 必选,判断用户意图是否匹配当前方法 | ||
| 136 | - "vectorField": "向量字段名", // 可选,需要语义匹配时返回 | ||
| 137 | - "vectorValue": "向量化文本", // 可选,用于向量检索的文本 | ||
| 138 | - "filterExpression": "标量过滤表达式" // 可选,有标量条件时返回 | ||
| 139 | - } | ||
| 140 | - | ||
| 141 | - 【方法匹配规则 - 重要】 | ||
| 142 | - sMethodName 的取值逻辑: | ||
| 143 | - - 如果用户输入的意图与 {{sMethodName}} 相关,返回 true | ||
| 144 | - - 否则返回 false | ||
| 145 | - | ||
| 146 | - 判断标准:用户是否在询问或操作与 {{sMethodName}} 相关的业务数据 | ||
| 147 | - - 包括但不限于:查询、搜索、推荐、找、查看、明细、列表、详情等 | ||
| 148 | - - 只要用户想获取或操作这类数据,就应该返回 true | ||
| 149 | - | ||
| 150 | - 示例: | ||
| 151 | - - 方法名称:查询报价单 | ||
| 152 | - - "报价单明细" → true(想查看报价单数据) | ||
| 153 | - - "报价单列表" → true(想查看报价单数据) | ||
| 154 | - - "报价单" → true(想查看报价单数据) | ||
| 155 | - - "查一下报价单" → true(想查询报价单) | ||
| 156 | - - "推荐相似报价单" → true(想推荐报价单) | ||
| 157 | - - "你好" → false(与报价单无关) | ||
| 158 | - - "查询客户信息" → false(与报价单无关) | ||
| 159 | - | ||
| 160 | - 【重要:理解你的数据结构】 | ||
| 161 | - | ||
| 162 | - 你有两种类型的字段: | ||
| 163 | - | ||
| 164 | - 1. 标量字段(用于精确过滤): | ||
| 165 | - - {{sMilvusFiled}} 中的字段 | ||
| 166 | - - 根据提供的字段说明使用 | ||
| 167 | - | ||
| 168 | - 2. 向量字段(用于语义搜索): | ||
| 169 | - - 字段名:使用 {{sMilvusFiledXl}} 中提供的向量字段名 | ||
| 170 | - - 存储格式:管道符分隔的键值对 "字段名:值|字段名:值|..." | ||
| 171 | - - 包含的业务数据:{{sMilvusFiledDescriptionXl}} | ||
| 172 | - - 示例:"sCustomerName:上海小羚羊|sProductName:2028宣传海报|dProductQty:1000|sType:报价丢单原因" | ||
| 173 | - | ||
| 174 | - 【向量搜索规则】 | ||
| 175 | - | ||
| 176 | - 使用向量搜索的条件:用户明确表达语义匹配意图 | ||
| 177 | - - 关键词:找相似、推荐、匹配、类似、相关、类似的、相似的、推荐一下 | ||
| 178 | - - 使用向量搜索时: | ||
| 179 | - 1. 从用户问题中提取关键业务实体 | ||
| 180 | - 2. 格式化为:"字段名:值|字段名:值"(管道符分隔) | ||
| 181 | - 3. 示例: | ||
| 182 | - - 用户:"找丢单原因类似价格太高的记录" → "sReason:价格太高|sType:报价丢单原因" | ||
| 183 | - - 用户:"推荐和上海小羚羊类似的客户" → "sCustomerName:上海小羚羊" | ||
| 184 | - - 用户:"找类似2028宣传海报的产品" → "sProductName:2028宣传海报" | ||
| 185 | - | ||
| 186 | - 【标量过滤规则 - 极其重要】 | ||
| 187 | - | ||
| 188 | - 可用标量字段(根据 {{sMilvusFiled}} 动态提供): | ||
| 189 | - - 使用提供的字段名 | ||
| 190 | - - 【字符串字段】:统一使用 like 操作符(前缀匹配) | ||
| 191 | - - 【数字字段】:使用 >、<、>=、<=、== 操作符 | ||
| 192 | - - 【时间字段】:使用时间戳范围 | ||
| 193 | - | ||
| 194 | - 【操作符使用规则】 | ||
| 195 | - | ||
| 196 | - 1. 字符串字段(统一使用 like): | ||
| 197 | - - 语法:字段名 like '关键词%' | ||
| 198 | - - 说明:只支持前缀匹配,不支持 '%keyword%' 或 '%keyword' | ||
| 199 | - - 示例: | ||
| 200 | - - 用户:"上海小羚羊" → sCustomerName like '上海小羚羊%' | ||
| 201 | - - 用户:"客户名称包含上海" → sCustomerName like '上海%' | ||
| 202 | - - 用户:"姓张的销售人员" → sSalesManName like '张%' | ||
| 203 | - - 用户:"产品名称以2028开头" → sProductName like '2028%' | ||
| 204 | - - 用户:"张三" → sSalesManName like '张三%' | ||
| 205 | - | ||
| 206 | - 2. 数字字段: | ||
| 207 | - - 精确匹配:字段名 == 值 | ||
| 208 | - - 范围匹配:字段名 >= 值 && 字段名 <= 值 | ||
| 209 | - - 大于:字段名 > 值 | ||
| 210 | - - 小于:字段名 < 值 | ||
| 211 | - - 示例: | ||
| 212 | - - 用户:"数量1000" → dProductQty == 1000 | ||
| 213 | - - 用户:"数量大于1000" → dProductQty > 1000 | ||
| 214 | - - 用户:"数量在500到1000之间" → dProductQty >= 500 && dProductQty <= 1000 | ||
| 215 | - | ||
| 216 | - 3. 时间字段: | ||
| 217 | - - 使用时间戳范围:tCreateDate >= 开始时间戳 && tCreateDate <= 结束时间戳 | ||
| 218 | - - 时间戳为 Unix 秒 | ||
| 219 | - | ||
| 220 | - 4. 组合条件: | ||
| 221 | - - 使用 && (AND) 连接多个条件 | ||
| 222 | - - 使用 || (OR) 连接或条件 | ||
| 223 | - - 复杂条件用括号分组 | ||
| 224 | - | ||
| 225 | - 【核心约束 - 必须遵守】 | ||
| 226 | - 1. 只有当用户【明确指定】具体条件时,才生成 filterExpression | ||
| 227 | - 2. 禁止为模糊查询添加默认过滤条件(如时间范围) | ||
| 228 | - 3. 禁止自动添加任何默认条件 | ||
| 229 | - 4. 当前时间戳:{{sDataNow}}(Unix秒) | ||
| 230 | - 5. 字符串字段禁止使用 ==,必须使用 like | ||
| 231 | - 6. like 只支持前缀匹配,不支持 '%keyword%' 或 '%keyword' 格式 | ||
| 232 | - | ||
| 233 | - 【查询意图判断】 | ||
| 234 | - | ||
| 235 | - 类型A - 纯标量查询(只返回 filterExpression): | ||
| 236 | - - 用户明确指定了标量字段的具体条件 | ||
| 237 | - - 不包含语义匹配词 | ||
| 238 | - - 示例1:"上海小羚羊" → {"sMethodName": true, "filterExpression": "sCustomerName like '上海小羚羊%'"} | ||
| 239 | - - 示例2:"客户名称包含上海" → {"sMethodName": true, "filterExpression": "sCustomerName like '上海%'"} | ||
| 240 | - - 示例3:"张三" → {"sMethodName": true, "filterExpression": "sSalesManName like '张三%'"} | ||
| 241 | - - 示例4:"数量大于1000" → {"sMethodName": true, "filterExpression": "dProductQty > 1000"} | ||
| 242 | - - 示例5:"今天创建的报价单" → {"sMethodName": true, "filterExpression": "tCreateDate >= 开始时间戳 && tCreateDate <= 结束时间戳"} | ||
| 243 | - | ||
| 244 | - 类型B - 纯向量搜索(只返回 vectorField 和 vectorValue): | ||
| 245 | - - 用户只有语义意图,无具体标量条件 | ||
| 246 | - - 示例:"找一些类似的报价单" → {"sMethodName": true, "vectorField": "向量字段名", "vectorValue": "sType:报价丢单原因"} | ||
| 247 | - | ||
| 248 | - 类型C - 混合查询(同时返回 vectorField、vectorValue 和 filterExpression): | ||
| 249 | - - 用户既有具体条件,又需要语义匹配 | ||
| 250 | - - 示例:"找丢单原因类似价格太高的上海小羚羊报价单" → | ||
| 251 | - {"sMethodName": true, "vectorField": "向量字段名", "vectorValue": "sReason:价格太高|sType:报价丢单原因", "filterExpression": "sCustomerName like '上海小羚羊%'"} | ||
| 252 | - | ||
| 253 | - 类型D - 意图不匹配(只返回 sMethodName: false): | ||
| 254 | - - 用户输入与 {{sMethodName}} 完全无关 | ||
| 255 | - - 示例:"你好" → {"sMethodName": false} | ||
| 256 | - - 示例:"查询客户信息"(如果方法名是"查询报价单") → {"sMethodName": false} | ||
| 257 | - | ||
| 258 | - 类型E - 意图匹配但无具体条件(只返回 sMethodName: true): | ||
| 259 | - - 用户想查询该类数据,但没有指定任何条件 | ||
| 260 | - - 示例:"报价单明细" → {"sMethodName": true} | ||
| 261 | - - 示例:"报价单列表" → {"sMethodName": true} | ||
| 262 | - - 示例:"报价单" → {"sMethodName": true} | ||
| 263 | - | ||
| 264 | - 【时间处理规则】 | ||
| 265 | - | ||
| 266 | - 当用户明确提到时间时,基于当前时间 {{sDataNow}} 计算: | ||
| 267 | - - "今天":当天 00:00:00 到 23:59:59 | ||
| 268 | - - "昨天":前一天 00:00:00 到 23:59:59 | ||
| 269 | - - "本周":本周一 00:00:00 到本周日 23:59:59 | ||
| 270 | - - "本月":本月1日 00:00:00 到本月最后一天 23:59:59 | ||
| 271 | - - "上个月":上个月1日 00:00:00 到上个月最后一天 23:59:59 | ||
| 272 | - - "近X天":X天前 00:00:00 到今天 23:59:59 | ||
| 273 | - | ||
| 274 | - 时间范围表达式格式:tCreateDate >= 开始时间戳 && tCreateDate <= 结束时间戳 | ||
| 275 | - | ||
| 276 | - 【重要约束汇总】 | ||
| 277 | - 1. 只输出 JSON,不要有任何其他内容 | ||
| 278 | - 2. 只使用提供的标量字段 | ||
| 279 | - 3. 【关键】字符串字段必须使用 like,禁止使用 == | ||
| 280 | - 4. 时间范围必须使用 Unix 时间戳(秒) | ||
| 281 | - 5. 向量字段名使用 {{sMilvusFiledXl}} 中提供的字段名 | ||
| 282 | - 6. vectorValue 必须保持管道符分隔格式:"字段名:值|字段名:值" | ||
| 283 | - 7. 禁止为模糊查询添加默认条件 | ||
| 284 | - 8. sMethodName 只要用户意图与当前方法相关就返回 true | ||
| 285 | - 9. like 只支持前缀匹配,格式:字段名 like '值%' | ||
| 286 | - | ||
| 287 | - 【输出示例】 | ||
| 288 | - 方法名称:查询报价单 | ||
| 289 | - | ||
| 290 | - 示例1:模糊查询 - "报价单明细" | ||
| 291 | - 输出:{"sMethodName": true} | ||
| 292 | - | ||
| 293 | - 示例2:字符串模糊匹配 - "上海小羚羊" | ||
| 294 | - 输出:{"sMethodName": true, "filterExpression": "sCustomerName like '上海小羚羊%'"} | ||
| 295 | - | ||
| 296 | - 示例3:字符串模糊匹配 - "客户名称包含上海" | ||
| 297 | - 输出:{"sMethodName": true, "filterExpression": "sCustomerName like '上海%'"} | ||
| 298 | - | ||
| 299 | - 示例4:字符串模糊匹配 - "张三" | ||
| 300 | - 输出:{"sMethodName": true, "filterExpression": "sSalesManName like '张三%'"} | ||
| 301 | - | ||
| 302 | - 示例5:数字范围 - "数量大于1000" | ||
| 303 | - 输出:{"sMethodName": true, "filterExpression": "dProductQty > 1000"} | ||
| 304 | - | ||
| 305 | - 示例6:时间范围 - "今天创建的报价单" | ||
| 306 | - 输出:{"sMethodName": true, "filterExpression": "tCreateDate >= 1774754400 && tCreateDate <= 1774840799"} | ||
| 307 | - | ||
| 308 | - 示例7:组合条件 - "上海小羚羊且数量大于1000" | ||
| 309 | - 输出:{"sMethodName": true, "filterExpression": "sCustomerName like '上海小羚羊%' && dProductQty > 1000"} | ||
| 310 | - | ||
| 311 | - 示例8:纯向量 - "找一些类似的报价单" | ||
| 312 | - 输出:{"sMethodName": true, "vectorField": "content_embedding", "vectorValue": "sType:报价丢单原因"} | ||
| 313 | - | ||
| 314 | - 示例9:混合查询 - "找丢单原因类似价格太高的上海小羚羊报价单" | ||
| 315 | - 输出:{"sMethodName": true, "vectorField": "content_embedding", "vectorValue": "sReason:价格太高|sType:报价丢单原因", "filterExpression": "sCustomerName like '上海小羚羊%'"} | ||
| 316 | - | ||
| 317 | - 示例10:意图不匹配 - "你好" | ||
| 318 | - 输出:{"sMethodName": false} | ||
| 319 | - | ||
| 320 | - 【标量字段列表】 | ||
| 321 | - {{sMilvusFiled}} | ||
| 322 | - | ||
| 323 | - 【标量字段说明】 | ||
| 324 | - {{sMilvusFiledDescription}} | ||
| 325 | - | ||
| 326 | - 【向量字段列表】 | ||
| 327 | - {{sMilvusFiledXl}} | ||
| 328 | - | ||
| 329 | - 【向量字段说明】 | ||
| 330 | - {{sMilvusFiledDescriptionXl}} | ||
| 331 | - | ||
| 332 | - 【方法名称】 | ||
| 333 | - {{sMethodName}} | ||
| 334 | - | ||
| 335 | - """) | ||
| 336 | - @UserMessage(""" | ||
| 337 | - 【用户输入】 | ||
| 338 | - {{userInput}} | ||
| 339 | - | ||
| 340 | - 【当前时间】 | ||
| 341 | - {{sDataNow}} | ||
| 342 | - | ||
| 343 | - 【标量字段】 | ||
| 344 | - {{sMilvusFiled}} | ||
| 345 | - | ||
| 346 | - 【标量字段说明】 | ||
| 347 | - {{sMilvusFiledDescription}} | ||
| 348 | - | ||
| 349 | - 【向量字段】 | ||
| 350 | - {{sMilvusFiledXl}} | ||
| 351 | - | ||
| 352 | - 【向量字段说明】 | ||
| 353 | - {{sMilvusFiledDescriptionXl}} | ||
| 354 | - | ||
| 355 | - 【方法名称】 | ||
| 356 | - {{sMethodName}} | ||
| 357 | - | ||
| 358 | - 请根据以上规则,输出 JSON 格式结果。 | ||
| 359 | -""") | ||
| 360 | - String getMilvusFilter(@MemoryId String userId, | ||
| 361 | - @V("userInput") String userInput, | ||
| 362 | - @V("sMilvusFiled") String sMilvusFiled, | ||
| 363 | - @V("sMilvusFiledDescription") String sMilvusFiledDescription, | ||
| 364 | - @V("sMilvusFiledXl") String sMilvusFiledXl, | ||
| 365 | - @V("sMilvusFiledDescriptionXl") String sMilvusFiledDescriptionXl, | ||
| 366 | - @V("sDataNow") long sDataNow, | ||
| 367 | - @V("sMethodName") String sMethodName | ||
| 368 | - ); | ||
| 369 | -} |
src/main/java/com/xly/agent/Intent.java
| @@ -8,7 +8,7 @@ import java.util.List; | @@ -8,7 +8,7 @@ import java.util.List; | ||
| 8 | * | 8 | * |
| 9 | * <p>由 {@code IntentService} 用受约束 JSON 一次得出:把一句自然语言压成 | 9 | * <p>由 {@code IntentService} 用受约束 JSON 一次得出:把一句自然语言压成 |
| 10 | * {意图, 单据/实体类型, 带业务角色的实体清单, 还缺什么}。下游据此做**确定性路由**, | 10 | * {意图, 单据/实体类型, 带业务角色的实体清单, 还缺什么}。下游据此做**确定性路由**, |
| 11 | - * 而不是让弱模型在 12 个工具里盲选——这是治理「理解错/选错工具」的核心。 | 11 | + * 而不是让弱模型在全部 9 个工具里盲选——这是治理「理解错/选错工具」的核心。 |
| 12 | */ | 12 | */ |
| 13 | public class Intent { | 13 | public class Intent { |
| 14 | 14 |
src/main/java/com/xly/agent/ReActAgent.java
| @@ -8,7 +8,11 @@ import dev.langchain4j.service.UserMessage; | @@ -8,7 +8,11 @@ import dev.langchain4j.service.UserMessage; | ||
| 8 | * 单一 ReAct 智能体。 | 8 | * 单一 ReAct 智能体。 |
| 9 | * | 9 | * |
| 10 | * <p>LangChain4j 的 {@code AiServices} 原生工具调用循环即 ReAct:模型自行决定是否调用工具、 | 10 | * <p>LangChain4j 的 {@code AiServices} 原生工具调用循环即 ReAct:模型自行决定是否调用工具、 |
| 11 | - * 调用哪个工具、以及何时给出最终答复。不再有旧的 8 场景 SceneSelector 路由——由 agent 自路由。 | 11 | + * 调用哪个工具、以及何时给出最终答复。不再有旧的 8 场景 {@code SceneSelector} 路由。 |
| 12 | + * | ||
| 13 | + * <p>但 agent 也**不是完全自路由**:{@code AgentChatController} 先用意图门({@code IntentService}) | ||
| 14 | + * 判类,再由 {@link com.xly.config.AgentFactory} 按 {@link ToolScope} 只把该意图相关的工具装进本实例 | ||
| 15 | + * (新增意图更是完全绕过 ReAct,走确定性 collectForm 路径)。本接口只负责「给定这批工具,自己决定怎么用」。 | ||
| 12 | * | 16 | * |
| 13 | * <p>返回 {@link TokenStream} 以支持流式输出;{@link MemoryId} 绑定每个会话独立的对话记忆。 | 17 | * <p>返回 {@link TokenStream} 以支持流式输出;{@link MemoryId} 绑定每个会话独立的对话记忆。 |
| 14 | */ | 18 | */ |
src/main/java/com/xly/agent/SceneSelectorAiAgent.java deleted
| 1 | -package com.xly.agent; | ||
| 2 | - | ||
| 3 | -import com.xly.entity.SceneIntentParseResp; | ||
| 4 | -import dev.langchain4j.service.SystemMessage; | ||
| 5 | -import dev.langchain4j.service.UserMessage; | ||
| 6 | -import dev.langchain4j.service.V; | ||
| 7 | -import org.springframework.stereotype.Component; | ||
| 8 | - | ||
| 9 | -/** | ||
| 10 | - * 场景意图解析AI服务:专门让大模型解析用户输入的意图,匹配对应的业务场景 | ||
| 11 | - * 基于LangChain4j AiServices构建,由大模型返回标准化的场景编码 | ||
| 12 | - */ | ||
| 13 | -public interface SceneSelectorAiAgent { | ||
| 14 | - | ||
| 15 | - /** | ||
| 16 | - * 核心方法:解析用户意图,匹配业务场景 | ||
| 17 | - * @param userInput 用户输入 | ||
| 18 | - * @param authScenesDesc 可访问场景描述 | ||
| 19 | - * @return 标准化的意图解析响应(仅返回sceneCode) | ||
| 20 | - */ | ||
| 21 | - @SystemMessage(""" | ||
| 22 | - 你是专业的ERP系统**意图解析助理**,你的唯一职责是根据用户输入,匹配其意图对应的业务场景,严格遵循以下规则: | ||
| 23 | - 1. 仅从用户提供的「可访问场景列表」中选择匹配的场景,绝对不允许虚构场景; | ||
| 24 | - 2. 匹配规则:用户输入的意图与场景的「支持功能」高度相关,即匹配该场景; | ||
| 25 | - 3. 输出格式:必须严格返回JSON格式,仅包含sceneCode字段,无任何多余文字、解释、换行; | ||
| 26 | - - 匹配到一个场景:sceneCode为场景码(如salemange/purchasemange/ProductionR&Dmanagement),scene为场景名称(如销售管理/采购管理/生产管理); | ||
| 27 | - - 匹配到多个场景,请列出并让客户选择场景; | ||
| 28 | - - 无匹配场景/用户仅问候:sceneCode为NO_MATCH; | ||
| 29 | - 4. 不允许添加任何额外字段,不允许返回JSON以外的内容,确保后端能直接解析; | ||
| 30 | - 5. 忽略用户输入中的无关语气词(如“你好”“帮我”“麻烦”),提取核心业务意图。 | ||
| 31 | - """) | ||
| 32 | - @UserMessage(""" | ||
| 33 | - 用户输入:{{userInput}} | ||
| 34 | - 可访问场景列表:{{authScenesDesc}} | ||
| 35 | - 请严格按指定格式返回匹配的场景编码! | ||
| 36 | - """) | ||
| 37 | - SceneIntentParseResp parseSceneIntent(@V("userInput") String userInput, | ||
| 38 | - @V("authScenesDesc") String authScenesDesc); | ||
| 39 | -} | ||
| 40 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/agent/SystemPromptGenerator.java deleted
| 1 | -package com.xly.agent; | ||
| 2 | - | ||
| 3 | -import cn.hutool.core.util.ObjectUtil; | ||
| 4 | -import com.alibaba.fastjson2.JSON; | ||
| 5 | -import com.alibaba.fastjson2.JSONObject; | ||
| 6 | -import com.alibaba.fastjson2.JSONWriter; | ||
| 7 | -import com.xly.entity.ParamRule; | ||
| 8 | -import com.xly.entity.UserSceneSession; | ||
| 9 | - | ||
| 10 | -import java.util.List; | ||
| 11 | -import java.util.stream.Collectors; | ||
| 12 | - | ||
| 13 | -public class SystemPromptGenerator { | ||
| 14 | - | ||
| 15 | - public static String generate(String toolName, UserSceneSession session) { | ||
| 16 | - StringBuilder sb = new StringBuilder(); | ||
| 17 | - | ||
| 18 | - sb.append("你是一个无情的参数提取器,不是聊天助手。\n\n"); | ||
| 19 | - sb.append("【核心任务】\n"); | ||
| 20 | - sb.append("从用户输入中提取参数,并立即调用工具 ").append(toolName).append("。\n\n"); | ||
| 21 | - sb.append("【绝对指令】\n"); | ||
| 22 | - sb.append("1. 严禁与用户对话,严禁输出任何纯文本回复。\n"); | ||
| 23 | - sb.append("2. 无论参数缺失多么严重,都必须直接调用工具,缺失字段填空字符串或0。\n"); | ||
| 24 | - sb.append("3. 不要引导用户,后端程序会处理引导逻辑。\n\n"); | ||
| 25 | - sb.append("【参数提取规则】\n"); | ||
| 26 | - sb.append("用户输入格式为 \"参数名: 值\" 或 \"参数名 值\",你必须识别参数名并提取对应的值。\n\n"); | ||
| 27 | - sb.append("【可提取的参数列表】\n"); | ||
| 28 | - List<ParamRule> dynamicParamRuleAll = session.getCurrentTool().getParamRuleListAll(); | ||
| 29 | - // 从参数定义中提取 | ||
| 30 | - for (ParamRule paramRule : dynamicParamRuleAll) { | ||
| 31 | - sb.append("- ").append(paramRule.getSParam()).append("\n"); | ||
| 32 | - } | ||
| 33 | - sb.append("\n【提取示例】\n"); | ||
| 34 | - // 动态生成示例(取前3个参数作为示例) | ||
| 35 | - int count = 0; | ||
| 36 | - for (ParamRule paramRule : dynamicParamRuleAll) { | ||
| 37 | - if (count++ >= 3) break; | ||
| 38 | - sb.append("- 用户输入 \"").append(paramRule.getSParam()).append(": {值}\" → 提取 ").append(paramRule.getSParam()).append("\n"); | ||
| 39 | - } | ||
| 40 | - | ||
| 41 | - if(ObjectUtil.isNotEmpty(session.getArgs())){ | ||
| 42 | - sb.append("【已提取参数】\n"); | ||
| 43 | - sb.append(JSON.toJSONString(session.getArgs(), JSONWriter.Feature.PrettyFormat)); | ||
| 44 | - } | ||
| 45 | - sb.append("\n【工具调用规则】\n"); | ||
| 46 | - sb.append("1. 每次只调用一次工具\n"); | ||
| 47 | - sb.append("2. 调用后立即停止,不要重复调用\n"); | ||
| 48 | - sb.append("3. 不要总结,不要回复用户,只调用工具"); | ||
| 49 | - | ||
| 50 | - return sb.toString(); | ||
| 51 | - } | ||
| 52 | - | ||
| 53 | - /** | ||
| 54 | - * 缺失参数统一提示模板:强制AI调用自定义方法,一次性回填所有参数 | ||
| 55 | - */ | ||
| 56 | - public static String buildMissParamPrompt(UserSceneSession session, List<ParamRule> paramRuleDataMiss) { | ||
| 57 | - String methodNo = session.getCurrentTool().getSMethodNo(); | ||
| 58 | - // 1. 拼接缺失参数的描述(增加“参数名”和“英文名”的对应,方便模型映射) | ||
| 59 | - String paramDesc = paramRuleDataMiss.stream() | ||
| 60 | - .map(p -> String.format("- 参数名:%s (对应字段: %s),示例值:%s", | ||
| 61 | - p.getSParam(), p.getSParamValue(), p.getSExampleValue())) | ||
| 62 | - .collect(Collectors.joining("\n")); | ||
| 63 | - // 但根据你的需求,其实 prompt 里并不需要强行插入一个空的 JSON 片段, | ||
| 64 | - // 直接告诉模型去“合并”即可。 | ||
| 65 | - return String.format(""" | ||
| 66 | - 请你根据用户最新的输入,补充缺失的参数,并带着【所有完整参数】立即发起工具调用。 | ||
| 67 | - | ||
| 68 | - 【核心执行规则】 | ||
| 69 | - 1. **参数合并(至关重要)**:你必须保留上一轮对话中已经获取的参数(如 sSlaveId, operateType 等),并将用户本次提供的新参数与旧参数合并。 | ||
| 70 | - 2. **必须调用工具**:必须调用工具 `%s`。绝对禁止直接输出 JSON 文本,也绝对禁止向用户反问或索要信息。 | ||
| 71 | - 3. **完整调用**:请直接生成包含所有参数的 tool_calls 请求。 | ||
| 72 | - 【已获取数据】 | ||
| 73 | - %s | ||
| 74 | - 【当前缺失的参数说明】 | ||
| 75 | - %s | ||
| 76 | - """, methodNo,JSONObject.toJSONString(session.getArgs()), paramDesc); | ||
| 77 | - } | ||
| 78 | - | ||
| 79 | - public static String buildDynamicSystemPrompt(UserSceneSession session) { | ||
| 80 | - // 获取当前工具编号(用于 JSON 的 name 字段) | ||
| 81 | - String methodNo = session.getCurrentTool().getSMethodNo(); | ||
| 82 | - return """ | ||
| 83 | - 【极强约束·必须执行】 | ||
| 84 | - 1. 禁止说话!禁止解释! | ||
| 85 | - 2. 必须调用工具! | ||
| 86 | - 3. 只输出标准工具调用JSON! | ||
| 87 | - | ||
| 88 | - ### 任务背景 ### | ||
| 89 | - 用户会输入自然语言指令(如“第1行确认”、“全部合并”等)。 | ||
| 90 | - 你的任务是:解析出【操作类型】和【目标行号】。 | ||
| 91 | - | ||
| 92 | - ### 解析规则(必须严格遵守) ### | ||
| 93 | - | ||
| 94 | - 1. **操作类型 (operateType)**: | ||
| 95 | - - 如果用户说“全部确认”、“生成多个单据”,识别为:"全部确认" | ||
| 96 | - - 如果用户说“合并确认”、“生成一个单据”,识别为:"合并确认" | ||
| 97 | - - 其他情况,默认为:"单行确认" | ||
| 98 | - | ||
| 99 | - 2. **行号提取 (rowNumbers)**: | ||
| 100 | - - 单选:如“第5行”,提取为 [5] | ||
| 101 | - - 多选:如“第1、3、5行”,提取为 [1, 3, 5] | ||
| 102 | - - 范围:如“第2到4行”,提取为 [2, 3, 4] | ||
| 103 | - - 全选:如“全部”、“所有”,提取为 ["ALL"] | ||
| 104 | - | ||
| 105 | - ### 输出格式规范 ### | ||
| 106 | - 请直接输出以下 JSON(不要任何 Markdown 格式,不要 ```json): | ||
| 107 | - {"name": "%s", "parameters": {"operateType": "解析出的类型", "rowNumbers": [行号数组或"ALL"]}} | ||
| 108 | - | ||
| 109 | - """.formatted(methodNo); | ||
| 110 | - } | ||
| 111 | - | ||
| 112 | -} | ||
| 113 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/agent/ToolScope.java
| @@ -4,14 +4,14 @@ package com.xly.agent; | @@ -4,14 +4,14 @@ package com.xly.agent; | ||
| 4 | * 按意图收窄「本轮可见的工具集」(架构 §5 的 tool scoping)。 | 4 | * 按意图收窄「本轮可见的工具集」(架构 §5 的 tool scoping)。 |
| 5 | * | 5 | * |
| 6 | * <p>依据:单次 ReAct 的选工具准确率随工具数量上升而骤降(业界经验:50 个工具 ~94% → 200 个 ~64%)。 | 6 | * <p>依据:单次 ReAct 的选工具准确率随工具数量上升而骤降(业界经验:50 个工具 ~94% → 200 个 ~64%)。 |
| 7 | - * 弱模型(qwen3:14b Q4)一次性面对 12 个语义重叠的工具时,频繁选错族(把「查询」当「新增」)。 | ||
| 8 | - * 于是先用意图门判类,再**只暴露该意图相关的 3-5 个工具**给 ReAct 执行——大幅降低选错概率。 | 7 | + * 弱模型(qwen3:14b Q4)一次性面对全部 9 个语义重叠的工具时,频繁选错族(把「查询」当「新增」)。 |
| 8 | + * 于是先用意图门判类,再**只暴露该意图相关的 5 个工具**给 ReAct 执行——大幅降低选错概率。 | ||
| 9 | */ | 9 | */ |
| 10 | public enum ToolScope { | 10 | public enum ToolScope { |
| 11 | /** 读:findForms/kgSearch/readFormData/lookupRecord/queryData(+askUser/loadSkill)。 */ | 11 | /** 读:findForms/kgSearch/readFormData/lookupRecord/queryData(+askUser/loadSkill)。 */ |
| 12 | READ, | 12 | READ, |
| 13 | - /** 写:collectForm/proposeCreate/Update/Delete/Examine(+lookupRecord 定位、askUser/loadSkill)。 */ | 13 | + /** 写:collectForm/proposeWrite(action=…)(+readFormData·lookupRecord 定位、askUser/loadSkill)。 */ |
| 14 | WRITE, | 14 | WRITE, |
| 15 | - /** 兜底:全部工具(意图门失败时用)。 */ | 15 | + /** 兜底:全部 9 个工具(意图门失败时用)。 */ |
| 16 | FULL | 16 | FULL |
| 17 | } | 17 | } |
src/main/java/com/xly/config/BizExecuteUtil.java deleted
| 1 | -package com.xly.config; | ||
| 2 | - | ||
| 3 | -import groovy.lang.GroovyClassLoader; | ||
| 4 | -import groovy.lang.GroovyObject; | ||
| 5 | -import lombok.RequiredArgsConstructor; | ||
| 6 | -import lombok.extern.slf4j.Slf4j; | ||
| 7 | -import org.springframework.expression.Expression; | ||
| 8 | -import org.springframework.expression.ExpressionParser; | ||
| 9 | -import org.springframework.expression.spel.standard.SpelExpressionParser; | ||
| 10 | -import org.springframework.expression.spel.support.StandardEvaluationContext; | ||
| 11 | -import org.springframework.stereotype.Component; | ||
| 12 | - | ||
| 13 | -import java.util.Map; | ||
| 14 | -import java.util.Objects; | ||
| 15 | - | ||
| 16 | -/** | ||
| 17 | - * 业务逻辑执行工具:EL表达式 + Groovy脚本 | ||
| 18 | - */ | ||
| 19 | -@Slf4j | ||
| 20 | -@Component | ||
| 21 | -@RequiredArgsConstructor | ||
| 22 | -public class BizExecuteUtil { | ||
| 23 | - /** | ||
| 24 | - * EL表达式解析器 | ||
| 25 | - */ | ||
| 26 | - private final ExpressionParser elParser = new SpelExpressionParser(); | ||
| 27 | - | ||
| 28 | - /** | ||
| 29 | - * Groovy类加载器 | ||
| 30 | - */ | ||
| 31 | - private final GroovyClassLoader groovyClassLoader = new GroovyClassLoader(); | ||
| 32 | - | ||
| 33 | - /** | ||
| 34 | - * 执行业务逻辑 | ||
| 35 | - * @param bizType 1=EL,2=Groovy | ||
| 36 | - * @param bizContent 逻辑内容 | ||
| 37 | - * @param params JSON参数 | ||
| 38 | - * @return 执行结果 | ||
| 39 | - */ | ||
| 40 | - public String execute(Integer bizType, String bizContent, Map<String, Object> params) { | ||
| 41 | - if (Objects.isNull(bizType) || Objects.isNull(bizContent) || bizContent.isBlank()) { | ||
| 42 | - throw new IllegalArgumentException("业务逻辑配置异常"); | ||
| 43 | - } | ||
| 44 | - // EL表达式执行 | ||
| 45 | - if (1 == bizType) { | ||
| 46 | - return executeEL(bizContent, params); | ||
| 47 | - } | ||
| 48 | - // Groovy脚本执行 | ||
| 49 | - else if (2 == bizType) { | ||
| 50 | - return executeGroovy(bizContent, params); | ||
| 51 | - } | ||
| 52 | - else { | ||
| 53 | - throw new IllegalArgumentException("不支持的业务逻辑类型:" + bizType); | ||
| 54 | - } | ||
| 55 | - } | ||
| 56 | - | ||
| 57 | - /** | ||
| 58 | - * 执行EL表达式 | ||
| 59 | - */ | ||
| 60 | - private String executeEL(String elContent, Map<String, Object> params) { | ||
| 61 | - try { | ||
| 62 | - StandardEvaluationContext context = new StandardEvaluationContext(); | ||
| 63 | - context.setVariable("params", params); | ||
| 64 | - Expression expression = elParser.parseExpression(elContent); | ||
| 65 | - return expression.getValue(context, String.class); | ||
| 66 | - } catch (Exception e) { | ||
| 67 | - log.error("EL表达式执行失败:{}", elContent, e); | ||
| 68 | - throw new IllegalArgumentException("EL表达式执行失败:" + e.getMessage()); | ||
| 69 | - } | ||
| 70 | - } | ||
| 71 | - | ||
| 72 | - /** | ||
| 73 | - * 执行Groovy脚本 | ||
| 74 | - */ | ||
| 75 | - private String executeGroovy(String groovyContent, Map<String, Object> params) { | ||
| 76 | - try { | ||
| 77 | - // 构建Groovy脚本类 | ||
| 78 | - String scriptCode = "class DynamicScript { def execute(Map params) { " + groovyContent + " } }"; | ||
| 79 | - Class<?> groovyClass = groovyClassLoader.parseClass(scriptCode); | ||
| 80 | - GroovyObject groovyObject = (GroovyObject) groovyClass.getDeclaredConstructor().newInstance(); | ||
| 81 | - // 执行脚本并返回结果 | ||
| 82 | - Object result = groovyObject.invokeMethod("execute", new Object[]{params}); | ||
| 83 | - return Objects.isNull(result) ? "执行成功" : result.toString(); | ||
| 84 | - } catch (Exception e) { | ||
| 85 | - log.error("Groovy脚本执行失败:{}", groovyContent, e); | ||
| 86 | - throw new IllegalArgumentException("Groovy脚本执行失败:" + e.getMessage()); | ||
| 87 | - } | ||
| 88 | - } | ||
| 89 | -} | ||
| 90 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/config/JacksonConfig.java deleted
| 1 | -package com.xly.config; | ||
| 2 | - | ||
| 3 | -import com.fasterxml.jackson.databind.ObjectMapper; | ||
| 4 | -import com.fasterxml.jackson.databind.SerializationFeature; | ||
| 5 | -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; | ||
| 6 | -import org.springframework.context.annotation.Bean; | ||
| 7 | -import org.springframework.context.annotation.Configuration; | ||
| 8 | -import org.springframework.context.annotation.Primary; | ||
| 9 | - | ||
| 10 | -/** | ||
| 11 | - * 全局 ObjectMapper 配置:支持 LocalDate 序列化/反序列化 | ||
| 12 | - */ | ||
| 13 | -@Configuration | ||
| 14 | -public class JacksonConfig { | ||
| 15 | - | ||
| 16 | - /** | ||
| 17 | - * 配置好的 ObjectMapper:注册 JavaTimeModule,支持 Java 8 时间类型 | ||
| 18 | - */ | ||
| 19 | - @Bean | ||
| 20 | - @Primary // 标记为默认实例,避免多 ObjectMapper 冲突 | ||
| 21 | - public ObjectMapper objectMapper() { | ||
| 22 | - ObjectMapper mapper = new ObjectMapper(); | ||
| 23 | - // 1. 核心:注册 JSR310 模块,处理 LocalDate/LocalDateTime | ||
| 24 | - mapper.registerModule(new JavaTimeModule()); | ||
| 25 | - // 2. 关闭时间戳序列化,LocalDate 以 "yyyy-MM-dd" 字符串形式存储 | ||
| 26 | - mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); | ||
| 27 | - // 3. 忽略未知字段(AI 返回多余字段时不报错) | ||
| 28 | - mapper.configure(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); | ||
| 29 | - return mapper; | ||
| 30 | - } | ||
| 31 | -} | ||
| 32 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/config/ModelConfig.java
| @@ -3,13 +3,7 @@ package com.xly.config; | @@ -3,13 +3,7 @@ package com.xly.config; | ||
| 3 | import com.fasterxml.jackson.databind.ObjectMapper; | 3 | import com.fasterxml.jackson.databind.ObjectMapper; |
| 4 | import com.fasterxml.jackson.databind.SerializationFeature; | 4 | import com.fasterxml.jackson.databind.SerializationFeature; |
| 5 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; | 5 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; |
| 6 | -import com.xly.agent.DynamicTableNl2SqlAiAgent; | ||
| 7 | -import com.xly.agent.SceneSelectorAiAgent; | ||
| 8 | import dev.langchain4j.model.ollama.OllamaChatModel; | 6 | import dev.langchain4j.model.ollama.OllamaChatModel; |
| 9 | -import dev.langchain4j.model.ollama.OllamaStreamingChatModel; | ||
| 10 | -import dev.langchain4j.service.AiServices; | ||
| 11 | -import dev.langchain4j.memory.chat.MessageWindowChatMemory; | ||
| 12 | -import org.springframework.beans.factory.annotation.Qualifier; | ||
| 13 | import org.springframework.beans.factory.annotation.Value; | 7 | import org.springframework.beans.factory.annotation.Value; |
| 14 | import org.springframework.context.annotation.Bean; | 8 | import org.springframework.context.annotation.Bean; |
| 15 | import org.springframework.context.annotation.Configuration; | 9 | import org.springframework.context.annotation.Configuration; |
| @@ -23,42 +17,10 @@ public class ModelConfig { | @@ -23,42 +17,10 @@ public class ModelConfig { | ||
| 23 | @Value("${langchain4j.ollama.base-url}") | 17 | @Value("${langchain4j.ollama.base-url}") |
| 24 | private String chatModelUrl; | 18 | private String chatModelUrl; |
| 25 | 19 | ||
| 26 | - @Value("${langchain4j.ollama.chat-model-name}") | ||
| 27 | - private String chatModelName; | ||
| 28 | - | ||
| 29 | @Value("${langchain4j.ollama.sql-model-name}") | 20 | @Value("${langchain4j.ollama.sql-model-name}") |
| 30 | private String sqlModelName; | 21 | private String sqlModelName; |
| 31 | 22 | ||
| 32 | - // ====================== 主对话模型 ====================== | ||
| 33 | - @Bean | ||
| 34 | - @Primary | ||
| 35 | - public OllamaChatModel chatLanguageModel() { | ||
| 36 | - return OllamaChatModel.builder() | ||
| 37 | - .baseUrl(chatModelUrl) | ||
| 38 | - .modelName(chatModelName) | ||
| 39 | - .temperature(0.1) | ||
| 40 | - .topP(0.95) | ||
| 41 | - .timeout(Duration.ofSeconds(120)) | ||
| 42 | - .maxRetries(2) | ||
| 43 | - .logRequests(true) | ||
| 44 | - .logResponses(true) | ||
| 45 | - .build(); | ||
| 46 | - } | ||
| 47 | - | ||
| 48 | - // ====================== 自由闲聊模型 ====================== | ||
| 49 | - @Bean("chatiModel") | ||
| 50 | - public OllamaChatModel chatiModel() { | ||
| 51 | - return OllamaChatModel.builder() | ||
| 52 | - .baseUrl(chatModelUrl) | ||
| 53 | - .modelName(chatModelName) | ||
| 54 | - .temperature(0.7) | ||
| 55 | - .topP(0.9) | ||
| 56 | - .timeout(Duration.ofSeconds(60)) | ||
| 57 | - .maxRetries(2) | ||
| 58 | - .build(); | ||
| 59 | - } | ||
| 60 | - | ||
| 61 | - // ====================== SQL 专用模型 ====================== | 23 | + /** NL2SQL 专用模型(QueryTool 经 AgentFactory 注入)。 */ |
| 62 | @Bean("sqlChatModel") | 24 | @Bean("sqlChatModel") |
| 63 | public OllamaChatModel sqlChatModel() { | 25 | public OllamaChatModel sqlChatModel() { |
| 64 | return OllamaChatModel.builder() | 26 | return OllamaChatModel.builder() |
| @@ -72,34 +34,6 @@ public class ModelConfig { | @@ -72,34 +34,6 @@ public class ModelConfig { | ||
| 72 | .build(); | 34 | .build(); |
| 73 | } | 35 | } |
| 74 | 36 | ||
| 75 | - // ====================== 流式对话模型 ====================== | ||
| 76 | - @Bean("streamingChatModel") | ||
| 77 | - @Primary | ||
| 78 | - public OllamaStreamingChatModel streamingChatModel() { | ||
| 79 | - return OllamaStreamingChatModel.builder() | ||
| 80 | - .baseUrl(chatModelUrl) | ||
| 81 | - .modelName(chatModelName) | ||
| 82 | - .temperature(0.3) | ||
| 83 | - .topP(0.9) | ||
| 84 | - .numPredict(1024) | ||
| 85 | - .timeout(Duration.ofSeconds(60)) | ||
| 86 | - .build(); | ||
| 87 | - } | ||
| 88 | - | ||
| 89 | - // ====================== 流式 SQL 模型 ====================== | ||
| 90 | - @Bean("streamingSqlModel") | ||
| 91 | - public OllamaStreamingChatModel streamingSqlModel() { | ||
| 92 | - return OllamaStreamingChatModel.builder() | ||
| 93 | - .baseUrl(chatModelUrl) | ||
| 94 | - .modelName(sqlModelName) | ||
| 95 | - .temperature(0.2) | ||
| 96 | - .topP(0.95) | ||
| 97 | - .numPredict(2048) | ||
| 98 | - .timeout(Duration.ofSeconds(120)) | ||
| 99 | - .build(); | ||
| 100 | - } | ||
| 101 | - | ||
| 102 | - // ====================== JSON ====================== | ||
| 103 | @Bean | 37 | @Bean |
| 104 | @Primary | 38 | @Primary |
| 105 | public ObjectMapper objectMapper() { | 39 | public ObjectMapper objectMapper() { |
| @@ -109,24 +43,4 @@ public class ModelConfig { | @@ -109,24 +43,4 @@ public class ModelConfig { | ||
| 109 | mapper.configure(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); | 43 | mapper.configure(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); |
| 110 | return mapper; | 44 | return mapper; |
| 111 | } | 45 | } |
| 112 | - | ||
| 113 | - // ====================== 动态 SQL Agent ====================== | ||
| 114 | - @Bean | ||
| 115 | - public DynamicTableNl2SqlAiAgent dynamicTableNl2SqlAiAgent( | ||
| 116 | - @Qualifier("sqlChatModel") OllamaChatModel sqlModel) { | ||
| 117 | - return AiServices.builder(DynamicTableNl2SqlAiAgent.class) | ||
| 118 | - .chatModel(sqlModel) | ||
| 119 | - .chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10)) | ||
| 120 | - .build(); | ||
| 121 | - } | ||
| 122 | - | ||
| 123 | - // ====================== 场景选择 Agent ====================== | ||
| 124 | - @Bean | ||
| 125 | - public SceneSelectorAiAgent sceneSelectorAiAgent( | ||
| 126 | - @Qualifier("chatLanguageModel") OllamaChatModel chatLanguageModel) { | ||
| 127 | - return AiServices.builder(SceneSelectorAiAgent.class) | ||
| 128 | - .chatModel(chatLanguageModel) | ||
| 129 | - .chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10)) | ||
| 130 | - .build(); | ||
| 131 | - } | ||
| 132 | -} | ||
| 133 | \ No newline at end of file | 46 | \ No newline at end of file |
| 47 | +} |
src/main/java/com/xly/config/MvcConfig.java
| @@ -9,8 +9,7 @@ public class MvcConfig implements WebMvcConfigurer { | @@ -9,8 +9,7 @@ public class MvcConfig implements WebMvcConfigurer { | ||
| 9 | 9 | ||
| 10 | @Override | 10 | @Override |
| 11 | public void addViewControllers(ViewControllerRegistry registry) { | 11 | public void addViewControllers(ViewControllerRegistry registry) { |
| 12 | - // 直接映射URL到视图 | ||
| 13 | registry.addViewController("/chat").setViewName("chat"); | 12 | registry.addViewController("/chat").setViewName("chat"); |
| 14 | - registry.addViewController("/").setViewName("index"); | 13 | + registry.addRedirectViewController("/", "/chat"); |
| 15 | } | 14 | } |
| 16 | } | 15 | } |
| 17 | \ No newline at end of file | 16 | \ No newline at end of file |
src/main/java/com/xly/config/OperableChatMemoryProvider.java deleted
| 1 | -package com.xly.config; | ||
| 2 | - | ||
| 3 | -import dev.langchain4j.memory.ChatMemory; | ||
| 4 | -import dev.langchain4j.memory.chat.ChatMemoryProvider; | ||
| 5 | -import dev.langchain4j.memory.chat.MessageWindowChatMemory; | ||
| 6 | -import dev.langchain4j.data.message.ChatMessage; | ||
| 7 | -import org.springframework.stereotype.Component; | ||
| 8 | - | ||
| 9 | -import java.util.ArrayList; | ||
| 10 | -import java.util.List; | ||
| 11 | -import java.util.Map; | ||
| 12 | -import java.util.Objects; | ||
| 13 | -import java.util.concurrent.ConcurrentHashMap; | ||
| 14 | -import java.util.stream.Collectors; | ||
| 15 | - | ||
| 16 | -/** | ||
| 17 | - * 可操作的ChatMemoryProvider:获取消息对象+清除记忆(指定/全量)+删除单条消息 | ||
| 18 | - * 实现框架原生接口,无缝对接AiServices,线程安全适配生产环境 | ||
| 19 | - */ | ||
| 20 | -@Component | ||
| 21 | -public class OperableChatMemoryProvider implements ChatMemoryProvider { | ||
| 22 | - // 核心缓存:memoryId -> ChatMemory,保证一个会话/用户对应唯一记忆实例 | ||
| 23 | - private final Map<Object, ChatMemory> memoryCache = new ConcurrentHashMap<>(); | ||
| 24 | - // 记忆最大消息数,根据业务需求调整 | ||
| 25 | - private static final int MAX_MESSAGE_SIZE = 100; | ||
| 26 | - | ||
| 27 | - /** | ||
| 28 | - * 框架原生方法:获取【当前memoryId对应的ChatMemory实例(含消息对象)】 | ||
| 29 | - * AiServices自动调用,也是手动操作记忆/消息的唯一入口 | ||
| 30 | - */ | ||
| 31 | - @Override | ||
| 32 | - public ChatMemory get(Object memoryId) { | ||
| 33 | - // 空memoryId兜底,避免空指针 | ||
| 34 | - Object finalMemId = Objects.isNull(memoryId) ? "default_erp_chat_memory" : memoryId; | ||
| 35 | - // 不存在则创建MessageWindowChatMemory,存在则复用 | ||
| 36 | - return memoryCache.computeIfAbsent(finalMemId, k -> MessageWindowChatMemory.withMaxMessages(MAX_MESSAGE_SIZE)); | ||
| 37 | - } | ||
| 38 | - | ||
| 39 | - // ===================== 1. 获取消息对象 ===================== | ||
| 40 | - | ||
| 41 | - /** | ||
| 42 | - * 获取当前会话/用户的全部消息列表 | ||
| 43 | - */ | ||
| 44 | - public List<ChatMessage> getCurrentChatMessages(Object memoryId) { | ||
| 45 | - if (Objects.isNull(memoryId)) { | ||
| 46 | - return new ArrayList<>(); | ||
| 47 | - } | ||
| 48 | - // 返回一个新的列表,避免直接操作内部列表 | ||
| 49 | - return new ArrayList<>(this.get(memoryId).messages()); | ||
| 50 | - } | ||
| 51 | - | ||
| 52 | - // ===================== 2. 清除记忆 ===================== | ||
| 53 | - | ||
| 54 | - /** | ||
| 55 | - * 清空【指定memoryId】的全部记忆 | ||
| 56 | - */ | ||
| 57 | - public void clearSpecifiedMemory(Object memoryId) { | ||
| 58 | - if (Objects.nonNull(memoryId)) { | ||
| 59 | - this.get(memoryId).clear(); | ||
| 60 | - } | ||
| 61 | - } | ||
| 62 | - | ||
| 63 | - /** | ||
| 64 | - * 全量清除【所有memoryId】的记忆 | ||
| 65 | - */ | ||
| 66 | - public void clearAllMemory() { | ||
| 67 | - memoryCache.values().forEach(ChatMemory::clear); | ||
| 68 | - } | ||
| 69 | - | ||
| 70 | - // ===================== 3. 删除单条消息(完全重新设置方案) ===================== | ||
| 71 | - | ||
| 72 | - /** | ||
| 73 | - * 删除指定消息(通过完全重新设置消息列表的方式) | ||
| 74 | - * @param memoryId 会话ID | ||
| 75 | - * @param messageToDelete 要删除的消息对象 | ||
| 76 | - * @return 删除后的最新消息列表 | ||
| 77 | - */ | ||
| 78 | - public List<ChatMessage> deleteSingleMessage(Object memoryId, ChatMessage messageToDelete) { | ||
| 79 | - if (Objects.isNull(memoryId) || Objects.isNull(messageToDelete)) { | ||
| 80 | - return getCurrentChatMessages(memoryId); | ||
| 81 | - } | ||
| 82 | - | ||
| 83 | - // 步骤1: 获取当前所有消息 | ||
| 84 | - ChatMemory currentMemory = this.get(memoryId); | ||
| 85 | - List<ChatMessage> currentMessages = new ArrayList<>(currentMemory.messages()); | ||
| 86 | - | ||
| 87 | - // 从后往前查找内容匹配的最后一条消息 | ||
| 88 | - int indexToDelete = -1; | ||
| 89 | - for (int i = currentMessages.size() - 1; i >= 0; i--) { | ||
| 90 | - ChatMessage msg = currentMessages.get(i); | ||
| 91 | - if (isSameMessage(msg, messageToDelete)) { | ||
| 92 | - indexToDelete = i; | ||
| 93 | - break; | ||
| 94 | - } | ||
| 95 | - } | ||
| 96 | - // 如果找到匹配的消息 | ||
| 97 | - if (indexToDelete >= 0) { | ||
| 98 | - List<ChatMessage> filteredMessages = new ArrayList<>(currentMessages); | ||
| 99 | - filteredMessages.remove(indexToDelete); | ||
| 100 | - return rebuildMemoryWithMessages(memoryId, filteredMessages); | ||
| 101 | - } | ||
| 102 | - // 步骤4: 完全重新设置消息列表 | ||
| 103 | - return rebuildMemoryWithMessages(memoryId, currentMessages); | ||
| 104 | - } | ||
| 105 | - public List<ChatMessage> deleteUserLasterMessage(Object memoryId) { | ||
| 106 | - if (Objects.isNull(memoryId)) { | ||
| 107 | - return getCurrentChatMessages(memoryId); | ||
| 108 | - } | ||
| 109 | - // 步骤1: 获取当前所有消息 | ||
| 110 | - ChatMemory currentMemory = this.get(memoryId); | ||
| 111 | - List<ChatMessage> currentMessages = new ArrayList<>(currentMemory.messages()); | ||
| 112 | - | ||
| 113 | - // 从后往前查找内容匹配的最后一条消息 | ||
| 114 | - int indexToDelete = currentMessages.size()-1; | ||
| 115 | - // 如果找到匹配的消息 | ||
| 116 | - if (indexToDelete >= 0) { | ||
| 117 | - List<ChatMessage> filteredMessages = new ArrayList<>(currentMessages); | ||
| 118 | - filteredMessages.remove(indexToDelete); | ||
| 119 | - return rebuildMemoryWithMessages(memoryId, filteredMessages); | ||
| 120 | - } | ||
| 121 | - // 步骤4: 完全重新设置消息列表 | ||
| 122 | - return rebuildMemoryWithMessages(memoryId, currentMessages); | ||
| 123 | - } | ||
| 124 | - | ||
| 125 | - public List<ChatMessage> deleteUserLasterMessageBySize(Object memoryId,Integer size) { | ||
| 126 | - if (Objects.isNull(memoryId) || size==0) { | ||
| 127 | - return getCurrentChatMessages(memoryId); | ||
| 128 | - } | ||
| 129 | - // 步骤1: 获取当前所有消息 | ||
| 130 | - ChatMemory currentMemory = this.get(memoryId); | ||
| 131 | - List<ChatMessage> currentMessages = new ArrayList<>(currentMemory.messages()); | ||
| 132 | - // 从后往前查找内容匹配的最后一条消息 | ||
| 133 | - int indexToDelete = currentMessages.size(); | ||
| 134 | - // 如果找到匹配的消息 | ||
| 135 | - if (indexToDelete >= 0) { | ||
| 136 | - List<ChatMessage> filteredMessages = new ArrayList<>(currentMessages); | ||
| 137 | - for(int i=0;i<size;i++){ | ||
| 138 | - indexToDelete = indexToDelete -1; | ||
| 139 | - if(indexToDelete>1){ | ||
| 140 | - filteredMessages.remove(indexToDelete); | ||
| 141 | - } | ||
| 142 | - } | ||
| 143 | - return rebuildMemoryWithMessages(memoryId, filteredMessages); | ||
| 144 | - } | ||
| 145 | - // 步骤4: 完全重新设置消息列表 | ||
| 146 | - return rebuildMemoryWithMessages(memoryId, currentMessages); | ||
| 147 | - } | ||
| 148 | - /** | ||
| 149 | - * 批量删除多条消息 | ||
| 150 | - * @param memoryId 会话ID | ||
| 151 | - * @param messagesToDelete 要删除的消息列表 | ||
| 152 | - * @return 删除后的最新消息列表 | ||
| 153 | - */ | ||
| 154 | - public List<ChatMessage> deleteMessages(Object memoryId, List<ChatMessage> messagesToDelete) { | ||
| 155 | - if (Objects.isNull(memoryId) || messagesToDelete == null || messagesToDelete.isEmpty()) { | ||
| 156 | - return getCurrentChatMessages(memoryId); | ||
| 157 | - } | ||
| 158 | - | ||
| 159 | - // 获取当前所有消息 | ||
| 160 | - ChatMemory currentMemory = this.get(memoryId); | ||
| 161 | - List<ChatMessage> currentMessages = new ArrayList<>(currentMemory.messages()); | ||
| 162 | - | ||
| 163 | - // 过滤掉所有要删除的消息 | ||
| 164 | - List<ChatMessage> filteredMessages = currentMessages.stream() | ||
| 165 | - .filter(msg -> messagesToDelete.stream().noneMatch(delMsg -> isSameMessage(msg, delMsg))) | ||
| 166 | - .collect(Collectors.toList()); | ||
| 167 | - | ||
| 168 | - // 如果消息数量没有变化,直接返回 | ||
| 169 | - if (filteredMessages.size() == currentMessages.size()) { | ||
| 170 | - return currentMessages; | ||
| 171 | - } | ||
| 172 | - | ||
| 173 | - // 重新设置消息列表 | ||
| 174 | - return rebuildMemoryWithMessages(memoryId, filteredMessages); | ||
| 175 | - } | ||
| 176 | - | ||
| 177 | - /** | ||
| 178 | - * 判断两条消息是否相同(支持根据内容、类型等多维度匹配) | ||
| 179 | - */ | ||
| 180 | - private boolean isSameMessage(ChatMessage msg1, ChatMessage msg2) { | ||
| 181 | -// if (msg1 == msg2) return true; // 同一对象引用 | ||
| 182 | -// if (msg1 == null || msg2 == null) return false; | ||
| 183 | -// | ||
| 184 | -// // 根据消息类型和内容判断 | ||
| 185 | -// // 方式1: 根据对象相等性 | ||
| 186 | -// if (msg1.equals(msg2)) return true; | ||
| 187 | - // 方式2: 根据消息类型和文本内容(适用于大多数场景) | ||
| 188 | - if (msg1.type() == msg2.type()) { | ||
| 189 | - // 如果有唯一ID或时间戳,也可以加入判断 | ||
| 190 | - // 这里简单使用文本内容判断 | ||
| 191 | - String text1 = extractText(msg1); | ||
| 192 | - String text2 = extractText(msg2); | ||
| 193 | - return Objects.equals(text1, text2); | ||
| 194 | - } | ||
| 195 | - | ||
| 196 | - return false; | ||
| 197 | - } | ||
| 198 | - | ||
| 199 | - /** | ||
| 200 | - * 从消息中提取文本内容 | ||
| 201 | - */ | ||
| 202 | - private String extractText(ChatMessage message) { | ||
| 203 | - // 根据ChatMessage的实际类型提取文本 | ||
| 204 | - // 这里需要根据您的具体消息实现来调整 | ||
| 205 | - try { | ||
| 206 | - // 尝试通过toString获取内容 | ||
| 207 | - return message.toString(); | ||
| 208 | - } catch (Exception e) { | ||
| 209 | - return ""; | ||
| 210 | - } | ||
| 211 | - } | ||
| 212 | - | ||
| 213 | - /** | ||
| 214 | - * 核心方法:使用过滤后的消息列表重建记忆 | ||
| 215 | - */ | ||
| 216 | - private List<ChatMessage> rebuildMemoryWithMessages(Object memoryId, List<ChatMessage> messages) { | ||
| 217 | - // 从缓存中移除原实例 | ||
| 218 | - ChatMemory oldMemory = memoryCache.remove(memoryId); | ||
| 219 | - | ||
| 220 | - // 创建新的ChatMemory实例 | ||
| 221 | - MessageWindowChatMemory newMemory = MessageWindowChatMemory.withMaxMessages(MAX_MESSAGE_SIZE); | ||
| 222 | - | ||
| 223 | - // 由于ChatMemory接口没有直接添加消息的方法,我们需要通过特定方式添加 | ||
| 224 | - // 方式1: 如果MessageWindowChatMemory有add方法(需要通过反射或强制类型转换) | ||
| 225 | - try { | ||
| 226 | - // 通过反射调用add方法(如果存在) | ||
| 227 | - java.lang.reflect.Method addMethod = newMemory.getClass().getMethod("add", ChatMessage.class); | ||
| 228 | - addMethod.setAccessible(true); | ||
| 229 | - for (ChatMessage message : messages) { | ||
| 230 | - addMethod.invoke(newMemory, message); | ||
| 231 | - } | ||
| 232 | - } catch (Exception e) { | ||
| 233 | - // 方式2: 如果无法通过反射添加,我们需要使用替代方案 | ||
| 234 | - // 这里可以记录日志 | ||
| 235 | - System.err.println("无法通过反射添加消息: " + e.getMessage()); | ||
| 236 | - | ||
| 237 | - // 方式3: 使用clear后通过某种方式重新添加 | ||
| 238 | - // 注意:这取决于具体的ChatMemory实现 | ||
| 239 | - } | ||
| 240 | - | ||
| 241 | - // 将新实例放入缓存 | ||
| 242 | - memoryCache.put(memoryId, newMemory); | ||
| 243 | - | ||
| 244 | - // 返回重建后的消息列表 | ||
| 245 | - return new ArrayList<>(newMemory.messages()); | ||
| 246 | - } | ||
| 247 | - | ||
| 248 | - /** | ||
| 249 | - * 移除并清除指定记忆(清空消息+从缓存删除实例) | ||
| 250 | - */ | ||
| 251 | - public void removeAndClearMemory(Object memoryId) { | ||
| 252 | - if (Objects.nonNull(memoryId)) { | ||
| 253 | - ChatMemory chatMemory = memoryCache.remove(memoryId); | ||
| 254 | - if (Objects.nonNull(chatMemory)) { | ||
| 255 | - chatMemory.clear(); | ||
| 256 | - } | ||
| 257 | - } | ||
| 258 | - } | ||
| 259 | -} | ||
| 260 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/config/RedisConfig.java deleted
| 1 | -package com.xly.config; | ||
| 2 | - | ||
| 3 | -import org.springframework.context.annotation.Bean; | ||
| 4 | -import org.springframework.context.annotation.Configuration; | ||
| 5 | -import org.springframework.data.redis.connection.RedisConnectionFactory; | ||
| 6 | -import org.springframework.data.redis.core.RedisTemplate; | ||
| 7 | -import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; | ||
| 8 | -import org.springframework.data.redis.serializer.StringRedisSerializer; | ||
| 9 | - | ||
| 10 | -@Configuration | ||
| 11 | -public class RedisConfig { | ||
| 12 | - | ||
| 13 | - @Bean | ||
| 14 | - public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { | ||
| 15 | - RedisTemplate<String, Object> template = new RedisTemplate<>(); | ||
| 16 | - template.setConnectionFactory(factory); | ||
| 17 | - | ||
| 18 | - // key 采用 String 序列化 | ||
| 19 | - template.setKeySerializer(new StringRedisSerializer()); | ||
| 20 | - // hash 的 key 也采用 String 序列化 | ||
| 21 | - template.setHashKeySerializer(new StringRedisSerializer()); | ||
| 22 | - | ||
| 23 | - // value 采用 Jackson 序列化 | ||
| 24 | - GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer(); | ||
| 25 | - template.setValueSerializer(jsonSerializer); | ||
| 26 | - template.setHashValueSerializer(jsonSerializer); | ||
| 27 | - | ||
| 28 | - template.afterPropertiesSet(); | ||
| 29 | - return template; | ||
| 30 | - } | ||
| 31 | -} | ||
| 32 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/config/SpringContextHolder.java deleted
| 1 | -package com.xly.config; | ||
| 2 | - | ||
| 3 | -import org.apache.commons.lang3.Validate; | ||
| 4 | -import org.slf4j.Logger; | ||
| 5 | -import org.slf4j.LoggerFactory; | ||
| 6 | -import org.springframework.beans.factory.DisposableBean; | ||
| 7 | -import org.springframework.context.ApplicationContext; | ||
| 8 | -import org.springframework.context.ApplicationContextAware; | ||
| 9 | -import org.springframework.context.annotation.Lazy; | ||
| 10 | -import org.springframework.stereotype.Service; | ||
| 11 | - | ||
| 12 | -/** | ||
| 13 | - * 以静态变量保存Spring ApplicationContext, 可在任何代码任何地方任何时候取出ApplicaitonContext. | ||
| 14 | - */ | ||
| 15 | -@Service | ||
| 16 | -@Lazy(false) | ||
| 17 | -public class SpringContextHolder implements ApplicationContextAware, DisposableBean { | ||
| 18 | - | ||
| 19 | - private static ApplicationContext applicationContext = null; | ||
| 20 | - | ||
| 21 | - private static Logger logger = LoggerFactory.getLogger(SpringContextHolder.class); | ||
| 22 | - | ||
| 23 | - /** | ||
| 24 | - * 取得存储在静态变量中的ApplicationContext. | ||
| 25 | - */ | ||
| 26 | - public static ApplicationContext getApplicationContext() { | ||
| 27 | - assertContextInjected(); | ||
| 28 | - return applicationContext; | ||
| 29 | - } | ||
| 30 | - | ||
| 31 | - /** | ||
| 32 | - * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型. | ||
| 33 | - */ | ||
| 34 | - @SuppressWarnings("unchecked") | ||
| 35 | - public static <T> T getBean(String name) { | ||
| 36 | - assertContextInjected(); | ||
| 37 | - return (T) applicationContext.getBean(name); | ||
| 38 | - } | ||
| 39 | - | ||
| 40 | - /** | ||
| 41 | - * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型. | ||
| 42 | - */ | ||
| 43 | - public static <T> T getBean(Class<T> requiredType) { | ||
| 44 | - assertContextInjected(); | ||
| 45 | - return applicationContext.getBean(requiredType); | ||
| 46 | - } | ||
| 47 | - | ||
| 48 | - /** | ||
| 49 | - * 清除SpringContextHolder中的ApplicationContext为Null. | ||
| 50 | - */ | ||
| 51 | - public static void clearHolder() { | ||
| 52 | - if (logger.isDebugEnabled()){ | ||
| 53 | - logger.debug("清除SpringContextHolder中的ApplicationContext:" + applicationContext); | ||
| 54 | - } | ||
| 55 | - applicationContext = null; | ||
| 56 | - } | ||
| 57 | - | ||
| 58 | - /** | ||
| 59 | - * 实现ApplicationContextAware接口, 注入Context到静态变量中. | ||
| 60 | - */ | ||
| 61 | - @Override | ||
| 62 | - public void setApplicationContext(ApplicationContext applicationContext) { | ||
| 63 | - SpringContextHolder.applicationContext = applicationContext; | ||
| 64 | - } | ||
| 65 | - | ||
| 66 | - /** | ||
| 67 | - * 实现DisposableBean接口, 在Context关闭时清理静态变量. | ||
| 68 | - */ | ||
| 69 | - @Override | ||
| 70 | - public void destroy() throws Exception { | ||
| 71 | - SpringContextHolder.clearHolder(); | ||
| 72 | - } | ||
| 73 | - | ||
| 74 | - /** | ||
| 75 | - * 检查ApplicationContext不为空. | ||
| 76 | - */ | ||
| 77 | - private static void assertContextInjected() { | ||
| 78 | - Validate.validState(applicationContext != null, "applicaitonContext属性未注入, 请在applicationContext.xml中定义SpringContextHolder."); | ||
| 79 | - } | ||
| 80 | -} | ||
| 81 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/constant/BusinessCode.java deleted
| 1 | -package com.xly.constant; | ||
| 2 | - | ||
| 3 | -import lombok.Getter; | ||
| 4 | - | ||
| 5 | -/*** | ||
| 6 | - * @Author 钱豹 | ||
| 7 | - * @Date 9:53 2026/2/9 | ||
| 8 | - * @Param | ||
| 9 | - * @return | ||
| 10 | - * @Description //TODO | ||
| 11 | - **/ | ||
| 12 | -@Getter | ||
| 13 | -public enum BusinessCode { | ||
| 14 | - | ||
| 15 | - //报价确认 | ||
| 16 | - QUOCONFIRM("quoconfirm", "报价确认"), | ||
| 17 | - COMMONTS("commonTs", "如果切换场景,点[回首页],如果在本场景下,转换意图,点[清除记忆]"); | ||
| 18 | - | ||
| 19 | - private final String code; | ||
| 20 | - private final String message; | ||
| 21 | - | ||
| 22 | - BusinessCode(String code, String message) { | ||
| 23 | - this.code = code; | ||
| 24 | - this.message = message; | ||
| 25 | - } | ||
| 26 | - | ||
| 27 | - /** | ||
| 28 | - * 根据code获取ErrorCode | ||
| 29 | - */ | ||
| 30 | - public static BusinessCode getByCode(Integer code) { | ||
| 31 | - for (BusinessCode errorCode : values()) { | ||
| 32 | - if (errorCode.getCode().equals(code)) { | ||
| 33 | - return errorCode; | ||
| 34 | - } | ||
| 35 | - } | ||
| 36 | - return QUOCONFIRM; | ||
| 37 | - } | ||
| 38 | -} | ||
| 39 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/constant/CommonConstant.java deleted
| 1 | -package com.xly.constant; | ||
| 2 | - | ||
| 3 | -/** | ||
| 4 | - * 全局通用常量 | ||
| 5 | - * 包含大模型解析提示语、请求头、资源前缀等 | ||
| 6 | - */ | ||
| 7 | -public class CommonConstant { | ||
| 8 | - //重置方法 | ||
| 9 | - public static final String RESET = "<button \n" + | ||
| 10 | - " data-action=\"reset\" \n" + | ||
| 11 | - " style=\"\n" + | ||
| 12 | - " background: linear-gradient(135deg, #1677ff, #4096ff);\n" + | ||
| 13 | - " color: white;\n" + | ||
| 14 | - " border: none;\n" + | ||
| 15 | - " padding: 6px 14px;\n" + | ||
| 16 | - " border-radius: 6px;\n" + | ||
| 17 | - " cursor: pointer;\n" + | ||
| 18 | - " font-size: 12px;\n" + | ||
| 19 | - " font-weight: 500;\n" + | ||
| 20 | - " transition: all 0.3s ease;\n" + | ||
| 21 | - " \"\n" + | ||
| 22 | - " data-text=\"重置\"\n" + | ||
| 23 | - " onclick=\"reset('重置')\"\n" + | ||
| 24 | - " onmouseover=\"this.style.opacity='0.8'\"\n" + | ||
| 25 | - " onmouseout=\"this.style.opacity='1'\">\n" + | ||
| 26 | - " 重置\n" + | ||
| 27 | - "</button> 复位 \n\n"; | ||
| 28 | - | ||
| 29 | -} |
src/main/java/com/xly/constant/ProcedureConstant.java deleted
| 1 | -package com.xly.constant; | ||
| 2 | - | ||
| 3 | -import java.util.ArrayList; | ||
| 4 | -import java.util.HashMap; | ||
| 5 | -import java.util.List; | ||
| 6 | -import java.util.Map; | ||
| 7 | - | ||
| 8 | -/*** | ||
| 9 | - * @Author 钱豹 | ||
| 10 | - * @Date 22:41 2026/2/3 | ||
| 11 | - * @Param | ||
| 12 | - * @return | ||
| 13 | - * @Description 调用过程的常量 | ||
| 14 | - **/ | ||
| 15 | -public class ProcedureConstant { | ||
| 16 | - | ||
| 17 | - public static final String PROTYPESTRING = "proc"; | ||
| 18 | - public static final String CONFTYPESTRING = "sType"; | ||
| 19 | - public static final String SSQLSTRSTRING = "sSqlStr"; | ||
| 20 | - public static final String SRETURN = "sReturn"; | ||
| 21 | - public static final String SCODE = "sCode"; | ||
| 22 | - public static final String OUTSETSTRING = "outSet"; | ||
| 23 | - public static final String SDEFAULT = "sDefault"; | ||
| 24 | - public static final String IN = "IN"; | ||
| 25 | - public static final String OUT = "OUT"; | ||
| 26 | - public static final String HEADER = "HEADER"; | ||
| 27 | - public static final String OUTLIST = "outList"; | ||
| 28 | - public static final String OUTMAP = "outMap"; | ||
| 29 | - | ||
| 30 | - public static Map<String, Object> getRetMap(List<Map<String, Object>> proList, Map<String, Object> outMap) { | ||
| 31 | - Map<String, Object> retMap = new HashMap<>(8); | ||
| 32 | - Map<String, Object> proMap = new HashMap<>(4); | ||
| 33 | - List<Map<String, Object>> outList = new ArrayList<>(1); | ||
| 34 | - outList.add(outMap); | ||
| 35 | - proMap.put("proData", proList); | ||
| 36 | - proMap.put("outData", outList); | ||
| 37 | - retMap.put("dataSet", proMap); | ||
| 38 | - retMap.put(SCODE, outMap.get(SCODE)); | ||
| 39 | - retMap.put(SRETURN, outMap.get(SRETURN)); | ||
| 40 | - return retMap; | ||
| 41 | - } | ||
| 42 | - | ||
| 43 | -} |
src/main/java/com/xly/constant/ReturnTypeCode.java deleted
| 1 | -package com.xly.constant; | ||
| 2 | - | ||
| 3 | -import lombok.Getter; | ||
| 4 | - | ||
| 5 | -/*** | ||
| 6 | - * @Author 钱豹 | ||
| 7 | - * @Date 23:04 2026/1/30 | ||
| 8 | - * @Param | ||
| 9 | - * @return | ||
| 10 | - * @Description 异常码枚举 | ||
| 11 | - **/ | ||
| 12 | -@Getter | ||
| 13 | -public enum ReturnTypeCode { | ||
| 14 | - | ||
| 15 | - // 成功 | ||
| 16 | - HTML("html", "html"), | ||
| 17 | - JSON("JSON", "JSON"), | ||
| 18 | - STREAM("stream", "stream"), | ||
| 19 | - MAKEDOWN("makedown", "makedown"); | ||
| 20 | - | ||
| 21 | - | ||
| 22 | - private final String code; | ||
| 23 | - private final String message; | ||
| 24 | - | ||
| 25 | - ReturnTypeCode(String code, String message) { | ||
| 26 | - this.code = code; | ||
| 27 | - this.message = message; | ||
| 28 | - } | ||
| 29 | - | ||
| 30 | - /** | ||
| 31 | - * 根据code获取ErrorCode | ||
| 32 | - */ | ||
| 33 | - public static ReturnTypeCode getByCode(String code) { | ||
| 34 | - for (ReturnTypeCode errorCode : values()) { | ||
| 35 | - if (errorCode.getCode().equals(code)) { | ||
| 36 | - return errorCode; | ||
| 37 | - } | ||
| 38 | - } | ||
| 39 | - return MAKEDOWN; | ||
| 40 | - } | ||
| 41 | -} | ||
| 42 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/constant/RuleCode.java deleted
| 1 | -package com.xly.constant; | ||
| 2 | - | ||
| 3 | -import lombok.Getter; | ||
| 4 | - | ||
| 5 | -/*** | ||
| 6 | - * @Author 钱豹 | ||
| 7 | - * @Date 23:04 2026/1/30 | ||
| 8 | - * @Param | ||
| 9 | - * @return | ||
| 10 | - * @Description 异常码枚举 | ||
| 11 | - **/ | ||
| 12 | -@Getter | ||
| 13 | -public enum RuleCode { | ||
| 14 | -// {"sql":"SQL","pro":"过程","const":"常量"} | ||
| 15 | - // 成功 | ||
| 16 | - SQL("sql", "SQL"), | ||
| 17 | - // 客户端错误 | ||
| 18 | - PRO("pro", "过程"), | ||
| 19 | - CONST("const", "常量"); | ||
| 20 | - | ||
| 21 | - private final String code; | ||
| 22 | - private final String message; | ||
| 23 | - | ||
| 24 | - RuleCode(String code, String message) { | ||
| 25 | - this.code = code; | ||
| 26 | - this.message = message; | ||
| 27 | - } | ||
| 28 | - | ||
| 29 | - /** | ||
| 30 | - * 根据code获取ErrorCode | ||
| 31 | - */ | ||
| 32 | - public static RuleCode getByCode(Integer code) { | ||
| 33 | - for (RuleCode errorCode : values()) { | ||
| 34 | - if (errorCode.getCode().equals(code)) { | ||
| 35 | - return errorCode; | ||
| 36 | - } | ||
| 37 | - } | ||
| 38 | - return CONST; | ||
| 39 | - } | ||
| 40 | -} | ||
| 41 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/constant/UrlErpConstant.java deleted
| 1 | -package com.xly.constant; | ||
| 2 | - | ||
| 3 | -/*** | ||
| 4 | - * @Author 钱豹 | ||
| 5 | - * @Date 0:37 2026/2/6 | ||
| 6 | - * @Param | ||
| 7 | - * @return | ||
| 8 | - * @Description ERP URL后缀 | ||
| 9 | - **/ | ||
| 10 | -public class UrlErpConstant { | ||
| 11 | - | ||
| 12 | - public static final String getBusinessDataByFormcustomId = "/business/getBusinessDataByFormcustomId/{}?sModelsId={}&sName="; | ||
| 13 | - | ||
| 14 | -} |
src/main/java/com/xly/entity/AiResponseAccumulator.java deleted
| 1 | -package com.xly.entity; | ||
| 2 | - | ||
| 3 | -import com.xly.entity.AiResponseDTO; | ||
| 4 | -import cn.hutool.core.util.StrUtil; | ||
| 5 | -import lombok.extern.slf4j.Slf4j; | ||
| 6 | - | ||
| 7 | -/** | ||
| 8 | - * AI响应累积器 | ||
| 9 | - */ | ||
| 10 | -@Slf4j | ||
| 11 | -public class AiResponseAccumulator { | ||
| 12 | - | ||
| 13 | - private final String requestId; | ||
| 14 | - private final StringBuilder aiTextBuilder = new StringBuilder(); | ||
| 15 | - private final StringBuilder systemTextBuilder = new StringBuilder(); | ||
| 16 | - private String sSceneName; | ||
| 17 | - private String sMethodName; | ||
| 18 | - private String sReturnType; | ||
| 19 | - private int totalChunks = 0; | ||
| 20 | - private int processedChunks = 0; | ||
| 21 | - private final long startTime; | ||
| 22 | - | ||
| 23 | - public AiResponseAccumulator(String requestId) { | ||
| 24 | - this.requestId = requestId; | ||
| 25 | - this.startTime = System.currentTimeMillis(); | ||
| 26 | - } | ||
| 27 | - | ||
| 28 | - /** | ||
| 29 | - * 累积单个AI响应 | ||
| 30 | - */ | ||
| 31 | - public void accumulate(AiResponseDTO response) { | ||
| 32 | - processedChunks++; | ||
| 33 | - | ||
| 34 | - // 更新总块数 | ||
| 35 | - if (response.getTotalChunks() != null && response.getTotalChunks() > 0) { | ||
| 36 | - this.totalChunks = response.getTotalChunks(); | ||
| 37 | - } | ||
| 38 | - | ||
| 39 | - // 累积AI文本片段 | ||
| 40 | - if (StrUtil.isNotBlank(response.getTextFragment())) { | ||
| 41 | - aiTextBuilder.append(response.getTextFragment()); | ||
| 42 | - } | ||
| 43 | - | ||
| 44 | - // 累积系统文本片段 | ||
| 45 | - if (StrUtil.isNotBlank(response.getSystemTextFragment())) { | ||
| 46 | - systemTextBuilder.append(response.getSystemTextFragment()); | ||
| 47 | - } | ||
| 48 | - | ||
| 49 | - // 更新元数据(取最后一次非空值) | ||
| 50 | - if (StrUtil.isNotBlank(response.getSSceneName())) { | ||
| 51 | - this.sSceneName = response.getSSceneName(); | ||
| 52 | - } | ||
| 53 | - if (StrUtil.isNotBlank(response.getSMethodName())) { | ||
| 54 | - this.sMethodName = response.getSMethodName(); | ||
| 55 | - } | ||
| 56 | - if (StrUtil.isNotBlank(response.getSReturnType())) { | ||
| 57 | - this.sReturnType = response.getSReturnType(); | ||
| 58 | - } | ||
| 59 | - | ||
| 60 | - log.debug("累积进度: requestId={}, 已处理={}/{}块, AI文本长度={}", | ||
| 61 | - requestId, processedChunks, totalChunks, aiTextBuilder.length()); | ||
| 62 | - } | ||
| 63 | - | ||
| 64 | - /** | ||
| 65 | - * 获取完整的响应 | ||
| 66 | - */ | ||
| 67 | - public AiResponseDTO getCompleteResponse() { | ||
| 68 | - AiResponseDTO response = new AiResponseDTO(); | ||
| 69 | - response.setRequestId(requestId); | ||
| 70 | - response.setAiText(aiTextBuilder.toString()); | ||
| 71 | - response.setSystemText(systemTextBuilder.toString()); | ||
| 72 | - response.setFullAiText(aiTextBuilder.toString()); | ||
| 73 | - response.setFullSystemText(systemTextBuilder.toString()); | ||
| 74 | - response.setSSceneName(sSceneName); | ||
| 75 | - response.setSMethodName(sMethodName); | ||
| 76 | - response.setSReturnType(sReturnType != null ? sReturnType : "MARKDOWN"); | ||
| 77 | - response.setTotalChunks(totalChunks); | ||
| 78 | - response.setChunkIndex(processedChunks - 1); | ||
| 79 | - response.setIsLastChunk(true); | ||
| 80 | - response.setElapsedTime(System.currentTimeMillis() - startTime); | ||
| 81 | - | ||
| 82 | - log.info("ERP累积完成: requestId={}, 总块数={}, AI文本长度={}, 系统文本长度={}, 耗时={}ms", | ||
| 83 | - requestId, totalChunks, | ||
| 84 | - aiTextBuilder.length(), systemTextBuilder.length(), | ||
| 85 | - response.getElapsedTime()); | ||
| 86 | - | ||
| 87 | - return response; | ||
| 88 | - } | ||
| 89 | -} | ||
| 90 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/entity/AiResponseDTO.java deleted
| 1 | -package com.xly.entity; | ||
| 2 | - | ||
| 3 | -import cn.hutool.core.util.StrUtil; | ||
| 4 | -import com.xly.constant.ErrorCode; | ||
| 5 | -import com.xly.constant.ReturnTypeCode; | ||
| 6 | -import lombok.AllArgsConstructor; | ||
| 7 | -import lombok.Builder; | ||
| 8 | -import lombok.Data; | ||
| 9 | -import lombok.NoArgsConstructor; | ||
| 10 | - | ||
| 11 | -import java.io.Serializable; | ||
| 12 | -import java.util.List; | ||
| 13 | -import java.util.Map; | ||
| 14 | - | ||
| 15 | -/** | ||
| 16 | - * TTS响应数据传输对象 | ||
| 17 | - * 增强版:支持流式处理 | ||
| 18 | - */ | ||
| 19 | -@Data | ||
| 20 | -@Builder | ||
| 21 | -@NoArgsConstructor | ||
| 22 | -@AllArgsConstructor | ||
| 23 | -public class AiResponseDTO implements Serializable { | ||
| 24 | - | ||
| 25 | - private static final long serialVersionUID = 1L; | ||
| 26 | - | ||
| 27 | - // ============ 原有字段 ============ | ||
| 28 | - | ||
| 29 | - /** | ||
| 30 | - * AI文字部分 | ||
| 31 | - */ | ||
| 32 | - private String aiText; | ||
| 33 | - | ||
| 34 | - /** | ||
| 35 | - * 系统拼接返回的文字部分 | ||
| 36 | - */ | ||
| 37 | - private String systemText; | ||
| 38 | - | ||
| 39 | - /** | ||
| 40 | - * 业务场景名称 | ||
| 41 | - */ | ||
| 42 | - private String sSceneName; | ||
| 43 | - | ||
| 44 | - /** | ||
| 45 | - * 业务方法名称 | ||
| 46 | - */ | ||
| 47 | - private String sMethodName; | ||
| 48 | - | ||
| 49 | - /** | ||
| 50 | - * 返回类型,默认MARKDOWN | ||
| 51 | - */ | ||
| 52 | - private String sReturnType = ReturnTypeCode.MAKEDOWN.getCode(); | ||
| 53 | - | ||
| 54 | - // ============ 新增字段:流式处理支持 ============ | ||
| 55 | - | ||
| 56 | - /** | ||
| 57 | - * 请求ID,用于追踪整个流式请求 | ||
| 58 | - */ | ||
| 59 | - private String requestId; | ||
| 60 | - | ||
| 61 | - /** | ||
| 62 | - * 响应码 | ||
| 63 | - */ | ||
| 64 | - private Integer code; | ||
| 65 | - | ||
| 66 | - /** | ||
| 67 | - * 响应消息 | ||
| 68 | - */ | ||
| 69 | - private String message; | ||
| 70 | - | ||
| 71 | - /** | ||
| 72 | - * 处理状态:PROCESSING, COMPLETED, FAILED | ||
| 73 | - */ | ||
| 74 | - private String status; | ||
| 75 | - | ||
| 76 | - /** | ||
| 77 | - * 当前处理的块编号(从0开始) | ||
| 78 | - */ | ||
| 79 | - private Integer chunkIndex; | ||
| 80 | - | ||
| 81 | - /** | ||
| 82 | - * 总块数 | ||
| 83 | - */ | ||
| 84 | - private Integer totalChunks; | ||
| 85 | - | ||
| 86 | - /** | ||
| 87 | - * 是否是最后一块 | ||
| 88 | - */ | ||
| 89 | - private Boolean isLastChunk; | ||
| 90 | - | ||
| 91 | - /** | ||
| 92 | - * 文本片段(用于流式传输) | ||
| 93 | - * 当aiText过大时,可以分段传输 | ||
| 94 | - */ | ||
| 95 | - private String textFragment; | ||
| 96 | - | ||
| 97 | - /** | ||
| 98 | - * 系统文本片段(用于流式传输) | ||
| 99 | - */ | ||
| 100 | - private String systemTextFragment; | ||
| 101 | - | ||
| 102 | - /** | ||
| 103 | - * 累积的完整AI文本(仅在最后一块时返回) | ||
| 104 | - */ | ||
| 105 | - private String fullAiText; | ||
| 106 | - | ||
| 107 | - /** | ||
| 108 | - * 累积的完整系统文本(仅在最后一块时返回) | ||
| 109 | - */ | ||
| 110 | - private String fullSystemText; | ||
| 111 | - | ||
| 112 | - /** | ||
| 113 | - * 处理进度(0-100) | ||
| 114 | - */ | ||
| 115 | - private Integer progress; | ||
| 116 | - | ||
| 117 | - /** | ||
| 118 | - * 时间戳 | ||
| 119 | - */ | ||
| 120 | - private Long timestamp; | ||
| 121 | - | ||
| 122 | - /** | ||
| 123 | - * 处理耗时(毫秒) | ||
| 124 | - */ | ||
| 125 | - private Long elapsedTime; | ||
| 126 | - | ||
| 127 | - /** | ||
| 128 | - * 扩展字段,用于存储额外的元数据 | ||
| 129 | - */ | ||
| 130 | - private Map<String, Object> metadata; | ||
| 131 | - | ||
| 132 | - /** | ||
| 133 | - * 错误信息(当status=FAILED时) | ||
| 134 | - */ | ||
| 135 | - private String errorMessage; | ||
| 136 | - | ||
| 137 | - /** | ||
| 138 | - * 错误码(当status=FAILED时) | ||
| 139 | - */ | ||
| 140 | - private String errorCode; | ||
| 141 | - | ||
| 142 | - /** | ||
| 143 | - * 数据库类型 X: 向量库 S:数据库 | ||
| 144 | - */ | ||
| 145 | - private String dbType; | ||
| 146 | - | ||
| 147 | - /** | ||
| 148 | - * 数据库类型 H: 缓存 D: 动态 | ||
| 149 | - */ | ||
| 150 | - private String dbCach; | ||
| 151 | - | ||
| 152 | - /*** | ||
| 153 | - * @Author 钱豹 | ||
| 154 | - * @Date 15:15 2026/4/14 | ||
| 155 | - * @Param | ||
| 156 | - * @return | ||
| 157 | - * @Description 操作未清类型 | ||
| 158 | - **/ | ||
| 159 | - private String sCopyTo; | ||
| 160 | - /*** | ||
| 161 | - * @Author 钱豹 | ||
| 162 | - * @Date 15:16 2026/4/14 | ||
| 163 | - * @Param | ||
| 164 | - * @return | ||
| 165 | - * @Description 未清记录ID | ||
| 166 | - **/ | ||
| 167 | - private String sCopyToSrcId; | ||
| 168 | - | ||
| 169 | - // ============ 便捷方法 ============ | ||
| 170 | - | ||
| 171 | - /** | ||
| 172 | - * 判断是否处理成功 | ||
| 173 | - */ | ||
| 174 | - public boolean isSuccess() { | ||
| 175 | - return code != null && code == 200; | ||
| 176 | - } | ||
| 177 | - | ||
| 178 | - /** | ||
| 179 | - * 判断是否处理中 | ||
| 180 | - */ | ||
| 181 | - public boolean isProcessing() { | ||
| 182 | - return "PROCESSING".equals(status); | ||
| 183 | - } | ||
| 184 | - | ||
| 185 | - /** | ||
| 186 | - * 判断是否已完成 | ||
| 187 | - */ | ||
| 188 | - public boolean isCompleted() { | ||
| 189 | - return "COMPLETED".equals(status); | ||
| 190 | - } | ||
| 191 | - | ||
| 192 | - /** | ||
| 193 | - * 判断是否失败 | ||
| 194 | - */ | ||
| 195 | - public boolean isFailed() { | ||
| 196 | - return "FAILED".equals(status); | ||
| 197 | - } | ||
| 198 | - | ||
| 199 | - /** | ||
| 200 | - * 获取完整的文本(AI文本 + 系统文本) | ||
| 201 | - */ | ||
| 202 | - public String getFullText() { | ||
| 203 | - StringBuilder sb = new StringBuilder(); | ||
| 204 | - if (StrUtil.isNotBlank(aiText)) { | ||
| 205 | - sb.append(aiText); | ||
| 206 | - } | ||
| 207 | - if (StrUtil.isNotBlank(systemText)) { | ||
| 208 | - sb.append(systemText); | ||
| 209 | - } | ||
| 210 | - return sb.toString(); | ||
| 211 | - } | ||
| 212 | - | ||
| 213 | - /** | ||
| 214 | - * 创建处理中的响应 | ||
| 215 | - */ | ||
| 216 | - public static AiResponseDTO processing(String requestId, String textFragment, | ||
| 217 | - Integer chunkIndex, Integer totalChunks) { | ||
| 218 | - return AiResponseDTO.builder() | ||
| 219 | - .requestId(requestId) | ||
| 220 | - .code(200) | ||
| 221 | - .message("Processing") | ||
| 222 | - .status("PROCESSING") | ||
| 223 | - .textFragment(textFragment) | ||
| 224 | - .chunkIndex(chunkIndex) | ||
| 225 | - .totalChunks(totalChunks) | ||
| 226 | - .timestamp(System.currentTimeMillis()) | ||
| 227 | - .progress(calculateProgress(chunkIndex, totalChunks)) | ||
| 228 | - .build(); | ||
| 229 | - } | ||
| 230 | - | ||
| 231 | - /** | ||
| 232 | - * 创建完成响应 | ||
| 233 | - */ | ||
| 234 | - public static AiResponseDTO completed(String requestId, String fullAiText, | ||
| 235 | - String fullSystemText, String sSceneName, | ||
| 236 | - String sMethodName, String sReturnType) { | ||
| 237 | - return AiResponseDTO.builder() | ||
| 238 | - .requestId(requestId) | ||
| 239 | - .code(200) | ||
| 240 | - .message("Completed") | ||
| 241 | - .status("COMPLETED") | ||
| 242 | - .fullAiText(fullAiText) | ||
| 243 | - .fullSystemText(fullSystemText) | ||
| 244 | - .aiText(fullAiText) | ||
| 245 | - .systemText(fullSystemText) | ||
| 246 | - .sSceneName(sSceneName) | ||
| 247 | - .sMethodName(sMethodName) | ||
| 248 | - .sReturnType(sReturnType) | ||
| 249 | - .progress(100) | ||
| 250 | - .timestamp(System.currentTimeMillis()) | ||
| 251 | - .isLastChunk(true) | ||
| 252 | - .build(); | ||
| 253 | - } | ||
| 254 | - | ||
| 255 | - /** | ||
| 256 | - * 创建失败响应 | ||
| 257 | - */ | ||
| 258 | - public static AiResponseDTO failed(String requestId, String errorMessage, String errorCode) { | ||
| 259 | - return AiResponseDTO.builder() | ||
| 260 | - .requestId(requestId) | ||
| 261 | - .code(500) | ||
| 262 | - .message("Failed") | ||
| 263 | - .status("FAILED") | ||
| 264 | - .errorMessage(errorMessage) | ||
| 265 | - .errorCode(errorCode) | ||
| 266 | - .timestamp(System.currentTimeMillis()) | ||
| 267 | - .build(); | ||
| 268 | - } | ||
| 269 | - | ||
| 270 | - /** | ||
| 271 | - * 计算进度 | ||
| 272 | - */ | ||
| 273 | - private static Integer calculateProgress(Integer chunkIndex, Integer totalChunks) { | ||
| 274 | - if (chunkIndex == null || totalChunks == null || totalChunks == 0) { | ||
| 275 | - return 0; | ||
| 276 | - } | ||
| 277 | - return (int) ((chunkIndex + 1) * 100.0 / totalChunks); | ||
| 278 | - } | ||
| 279 | -} | ||
| 280 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/entity/AiSqlErrorHistory.java deleted
| 1 | -package com.xly.entity; | ||
| 2 | - | ||
| 3 | -import cn.hutool.core.util.StrUtil; | ||
| 4 | -import lombok.Data; | ||
| 5 | - | ||
| 6 | -import java.util.Date; | ||
| 7 | - | ||
| 8 | -@Data | ||
| 9 | -public class AiSqlErrorHistory { | ||
| 10 | - | ||
| 11 | - private Integer iIncrement; | ||
| 12 | - private String sId; | ||
| 13 | - private Integer iOrder; | ||
| 14 | - private Date tCreateDate; | ||
| 15 | - private String sBrandsId; | ||
| 16 | - private String sSubsidiaryId; | ||
| 17 | - private String sMakePerson; | ||
| 18 | - private String sBillNo; | ||
| 19 | - private String sFormId= StrUtil.EMPTY; | ||
| 20 | - private Integer sStatus; | ||
| 21 | - private String sSceneId; | ||
| 22 | - private String sMethodId; | ||
| 23 | - private String sQuestion; | ||
| 24 | - private String sError_sql; | ||
| 25 | - private String sError_mes; | ||
| 26 | - private String sCorrect_sql; | ||
| 27 | - private Boolean bCheck = false; | ||
| 28 | - private Boolean bInvalid = false; | ||
| 29 | - private String sLoginId; | ||
| 30 | -} |
src/main/java/com/xly/entity/AiUserAgentQuestion.java deleted
| 1 | -package com.xly.entity; | ||
| 2 | - | ||
| 3 | -import cn.hutool.core.util.StrUtil; | ||
| 4 | -import lombok.Data; | ||
| 5 | - | ||
| 6 | -import java.util.Date; | ||
| 7 | -import java.util.List; | ||
| 8 | -import java.util.Map; | ||
| 9 | - | ||
| 10 | -@Data | ||
| 11 | -public class AiUserAgentQuestion { | ||
| 12 | - | ||
| 13 | - private Integer iIncrement; | ||
| 14 | - private String sId; | ||
| 15 | - private Integer iOrder; | ||
| 16 | - private Date tCreateDate; | ||
| 17 | - private String sBrandsId; | ||
| 18 | - private String sSubsidiaryId; | ||
| 19 | - private String sMakePerson; | ||
| 20 | - private String sBillNo; | ||
| 21 | - private String sFormId= StrUtil.EMPTY; | ||
| 22 | - private Integer sStatus; | ||
| 23 | - private String sSceneId; | ||
| 24 | - private String sMethodId; | ||
| 25 | - private String sQuestion; | ||
| 26 | - private String sSqlContent; | ||
| 27 | - private Boolean bCheck = false; | ||
| 28 | - private Boolean bInvalid = false; | ||
| 29 | - private String sLoginId; | ||
| 30 | - private String sQuestionGroupNo; | ||
| 31 | - private Boolean bRedis = false; | ||
| 32 | - private Date tUpdateDate; | ||
| 33 | - private Integer iUpdate; | ||
| 34 | - | ||
| 35 | - | ||
| 36 | - | ||
| 37 | - | ||
| 38 | - | ||
| 39 | -} |
src/main/java/com/xly/entity/ConfirmationData.java deleted
| 1 | -package com.xly.entity; | ||
| 2 | - | ||
| 3 | -import lombok.Data; | ||
| 4 | - | ||
| 5 | -import java.util.HashMap; | ||
| 6 | -import java.util.Map; | ||
| 7 | - | ||
| 8 | -@Data | ||
| 9 | -public class ConfirmationData { | ||
| 10 | - private final String requestId; | ||
| 11 | - private final String toolName; | ||
| 12 | - private final Map<String, Object> parameters; | ||
| 13 | - private final String initialResult; | ||
| 14 | - private final long expiryTimestamp; // 超时时间戳 | ||
| 15 | - | ||
| 16 | - public ConfirmationData(String requestId, String toolName, | ||
| 17 | - Map<String, Object> parameters, | ||
| 18 | - String initialResult, | ||
| 19 | - long timeoutMillis) { | ||
| 20 | - this.requestId = requestId; | ||
| 21 | - this.toolName = toolName; | ||
| 22 | - this.parameters = parameters != null ? new HashMap<>(parameters) : new HashMap<>(); | ||
| 23 | - this.initialResult = initialResult; | ||
| 24 | - this.expiryTimestamp = System.currentTimeMillis() + timeoutMillis; | ||
| 25 | - } | ||
| 26 | - | ||
| 27 | - // Getters 和 检查方法 | ||
| 28 | - public boolean isExpired() { | ||
| 29 | - return System.currentTimeMillis() > expiryTimestamp; | ||
| 30 | - } | ||
| 31 | - | ||
| 32 | - public String getRequestId() { return requestId; } | ||
| 33 | - public String getToolName() { return toolName; } | ||
| 34 | - public Map<String, Object> getParameters() { return new HashMap<>(parameters); } | ||
| 35 | - public String getInitialResult() { return initialResult; } | ||
| 36 | - public long getExpiryTimestamp() { return expiryTimestamp; } | ||
| 37 | -} |
src/main/java/com/xly/entity/DynamicNl2SqlRequest.java deleted
| 1 | -package com.xly.entity; | ||
| 2 | - | ||
| 3 | - | ||
| 4 | -import lombok.AllArgsConstructor; | ||
| 5 | -import lombok.Data; | ||
| 6 | -import lombok.NoArgsConstructor; | ||
| 7 | - | ||
| 8 | -/** | ||
| 9 | - * 动态表结构的NL2SQL请求实体 | ||
| 10 | - * 适配多场景、任意表结构的NL2SQL查询 | ||
| 11 | - */ | ||
| 12 | -@Data | ||
| 13 | -@NoArgsConstructor | ||
| 14 | -@AllArgsConstructor | ||
| 15 | -public class DynamicNl2SqlRequest { | ||
| 16 | - /** 涉及表名(多表用,分隔,如product,sales、order,user) */ | ||
| 17 | - private String tableNames; | ||
| 18 | - /** 表结构详情(严格按格式:表名(字段1:类型,字段2:类型,主键/外键),多表换行/逗号分隔) */ | ||
| 19 | - private String tableStruct; | ||
| 20 | - /** 用户自然语言查询问题 */ | ||
| 21 | - private String question; | ||
| 22 | -} | ||
| 23 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/entity/ErpDataset.java deleted
| 1 | -package com.xly.entity; | ||
| 2 | - | ||
| 3 | -import lombok.Data; | ||
| 4 | - | ||
| 5 | -import java.util.List; | ||
| 6 | -import java.util.Map; | ||
| 7 | - | ||
| 8 | -/*** | ||
| 9 | - * @Author 钱豹 | ||
| 10 | - * @Date 1:22 2026/2/6 | ||
| 11 | - * @Param | ||
| 12 | - * @return | ||
| 13 | - * @Description ERP 返回的结果集 | ||
| 14 | - **/ | ||
| 15 | -@Data | ||
| 16 | -public class ErpDataset { | ||
| 17 | - private Integer start; | ||
| 18 | - private Integer pageSize; | ||
| 19 | - private Integer totalCount; | ||
| 20 | - private Integer billNum; | ||
| 21 | - private Integer end; | ||
| 22 | - private Integer totalPageCount; | ||
| 23 | - private Integer previousPageNo; | ||
| 24 | - private Integer currentPageNo; | ||
| 25 | - private Integer nextPageNo; | ||
| 26 | - private List<ErpRow> rows; | ||
| 27 | -} |
src/main/java/com/xly/entity/ErpResult.java deleted
| 1 | -package com.xly.entity; | ||
| 2 | - | ||
| 3 | -import lombok.Data; | ||
| 4 | - | ||
| 5 | -/*** | ||
| 6 | - * @Author 钱豹 | ||
| 7 | - * @Date 22:42 2026/1/30 | ||
| 8 | - * @Param | ||
| 9 | - * @return | ||
| 10 | - * @Description 参数实体类 | ||
| 11 | - **/ | ||
| 12 | -@Data | ||
| 13 | -public class ErpResult { | ||
| 14 | - | ||
| 15 | - private Integer code; | ||
| 16 | - private String msg; | ||
| 17 | - private ErpDataset dataset; | ||
| 18 | - | ||
| 19 | -} |
src/main/java/com/xly/entity/ErpRow.java deleted
src/main/java/com/xly/entity/Nl2SqlRequest.java deleted
| 1 | -package com.xly.entity; | ||
| 2 | - | ||
| 3 | -import lombok.AllArgsConstructor; | ||
| 4 | -import lombok.Data; | ||
| 5 | -import lombok.NoArgsConstructor; | ||
| 6 | - | ||
| 7 | -import java.util.List; | ||
| 8 | -import java.util.Map; | ||
| 9 | - | ||
| 10 | -/** | ||
| 11 | - * NL2SQL统一请求参数 | ||
| 12 | - */ | ||
| 13 | -@Data | ||
| 14 | -@NoArgsConstructor | ||
| 15 | -@AllArgsConstructor | ||
| 16 | -public class Nl2SqlRequest { | ||
| 17 | - /** 用户自然语言查询语句 */ | ||
| 18 | - private String question; | ||
| 19 | -} | ||
| 20 | - | ||
| 21 | - | ||
| 22 | - |
src/main/java/com/xly/entity/Nl2SqlResult.java deleted
| 1 | -package com.xly.entity; | ||
| 2 | - | ||
| 3 | -import lombok.AllArgsConstructor; | ||
| 4 | -import lombok.Data; | ||
| 5 | -import lombok.NoArgsConstructor; | ||
| 6 | - | ||
| 7 | -import java.util.List; | ||
| 8 | -import java.util.Map; | ||
| 9 | - | ||
| 10 | - | ||
| 11 | - | ||
| 12 | -/** | ||
| 13 | - * NL2SQL统一返回结果 | ||
| 14 | - */ | ||
| 15 | -@Data | ||
| 16 | -@NoArgsConstructor | ||
| 17 | -@AllArgsConstructor | ||
| 18 | -public class Nl2SqlResult { | ||
| 19 | - /** 生成并校验后的可执行MySQL SQL */ | ||
| 20 | - private String generateSql; | ||
| 21 | - /** SQL执行结构化结果 */ | ||
| 22 | - private List<Map<String, Object>> sqlResult; | ||
| 23 | - /** 结果自然语言解释 */ | ||
| 24 | - private String resultExplain; | ||
| 25 | -} | ||
| 26 | - |
src/main/java/com/xly/entity/ParamRule.java deleted
| 1 | -package com.xly.entity; | ||
| 2 | - | ||
| 3 | -import lombok.Data; | ||
| 4 | - | ||
| 5 | -import java.time.LocalDateTime; | ||
| 6 | -import java.util.List; | ||
| 7 | -import java.util.Map; | ||
| 8 | - | ||
| 9 | -/*** | ||
| 10 | - * @Author 钱豹 | ||
| 11 | - * @Date 22:42 2026/1/30 | ||
| 12 | - * @Param | ||
| 13 | - * @return | ||
| 14 | - * @Description 参数实体类 | ||
| 15 | - **/ | ||
| 16 | -@Data | ||
| 17 | -public class ParamRule { | ||
| 18 | - private Integer iOrder; | ||
| 19 | - private String sId; | ||
| 20 | - private String sParamValue; | ||
| 21 | - private String sParentId; | ||
| 22 | - private String sParam; | ||
| 23 | - private String sRule; | ||
| 24 | - private String sType; | ||
| 25 | - private String sExampleValue; | ||
| 26 | - private String sDefaultValue; | ||
| 27 | - private String sParamMissMemo; | ||
| 28 | - private String sParamConfig; | ||
| 29 | - private String sCopyTo; | ||
| 30 | - private String sRuleTs; | ||
| 31 | - private Boolean bTipModel; | ||
| 32 | - private Boolean bEmpty; | ||
| 33 | - private Boolean bConfirmAfter; | ||
| 34 | - private String sDynamicParamRuleSql; | ||
| 35 | - private String sDynamicParam; | ||
| 36 | - private String sDynamicParamName; | ||
| 37 | - private String sGroup; | ||
| 38 | - private Object sParamConfigValue; | ||
| 39 | - private Boolean bStandardParam; | ||
| 40 | - private Boolean bDynamicParam = false; | ||
| 41 | - | ||
| 42 | -} |
src/main/java/com/xly/entity/SceneDto.java deleted
| 1 | -package com.xly.entity; | ||
| 2 | - | ||
| 3 | -import jakarta.persistence.Column; | ||
| 4 | -import jakarta.persistence.Id; | ||
| 5 | -import lombok.Data; | ||
| 6 | - | ||
| 7 | -import java.time.LocalDateTime; | ||
| 8 | - | ||
| 9 | -/*** | ||
| 10 | - * @Author 钱豹 | ||
| 11 | - * @Date 22:41 2026/1/30 | ||
| 12 | - * @Param | ||
| 13 | - * @return | ||
| 14 | - * @Description 模型实体 | ||
| 15 | - **/ | ||
| 16 | -@Data | ||
| 17 | -public class SceneDto { | ||
| 18 | - @Id | ||
| 19 | -// @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| 20 | - private String sId; | ||
| 21 | - | ||
| 22 | - @Column(name = "iOrder") | ||
| 23 | - private Integer iOrder; | ||
| 24 | - | ||
| 25 | - @Column(name = "tCreateDate") | ||
| 26 | - private LocalDateTime tCreateDate; | ||
| 27 | - | ||
| 28 | - @Column(name = "sMakePerson") | ||
| 29 | - private String sMakePerson; | ||
| 30 | - | ||
| 31 | - private String sBrandsId; | ||
| 32 | - | ||
| 33 | - private String sSubsidiaryId; | ||
| 34 | - | ||
| 35 | - private String sBillNo; | ||
| 36 | - | ||
| 37 | - private String sSceneNo; | ||
| 38 | - | ||
| 39 | - private String sSceneName; | ||
| 40 | - | ||
| 41 | - private String sNickName; | ||
| 42 | - | ||
| 43 | - private String sSceneContext; | ||
| 44 | - | ||
| 45 | - private String sStatus; | ||
| 46 | - | ||
| 47 | - | ||
| 48 | - private LocalDateTime tUpdateDate; | ||
| 49 | - | ||
| 50 | -} |
src/main/java/com/xly/entity/SceneIntentParseReq.java deleted
| 1 | -package com.xly.entity; | ||
| 2 | -import lombok.Data; | ||
| 3 | - | ||
| 4 | -/** | ||
| 5 | - * 意图解析请求参数:传给大模型的入参,包含用户输入+可访问场景列表 | ||
| 6 | - */ | ||
| 7 | -@Data | ||
| 8 | -public class SceneIntentParseReq{ | ||
| 9 | - /** | ||
| 10 | - * 用户自然语言输入 | ||
| 11 | - */ | ||
| 12 | - private String userInput; | ||
| 13 | - /** | ||
| 14 | - * 该用户权限内的可访问场景(格式:场景编码-场景名称-支持功能,如ORDER_OPERATE-订单操作-下单、查订单、取消订单) | ||
| 15 | - */ | ||
| 16 | - private String authScenesDesc; | ||
| 17 | -} |
src/main/java/com/xly/entity/SceneIntentParseResp.java deleted
| 1 | -package com.xly.entity; | ||
| 2 | - | ||
| 3 | -import lombok.AllArgsConstructor; | ||
| 4 | -import lombok.Data; | ||
| 5 | -import lombok.NoArgsConstructor; | ||
| 6 | - | ||
| 7 | -import java.util.Map; | ||
| 8 | - | ||
| 9 | -/** | ||
| 10 | - * 大模型意图解析响应DTO:强制大模型按此格式返回,方便后端解析 | ||
| 11 | - * 核心:仅返回场景编码(如ORDER_OPERATE),无需多余描述 | ||
| 12 | - */ | ||
| 13 | -@Data | ||
| 14 | -@NoArgsConstructor | ||
| 15 | -@AllArgsConstructor | ||
| 16 | -public class SceneIntentParseResp { | ||
| 17 | - /** | ||
| 18 | - * 匹配的业务场景编码,必须是BusinessScene的枚举名称(如ORDER_OPERATE/CUSTOMER_MANAGE/STOCK_QUERY) | ||
| 19 | - * 无匹配场景时,返回:NO_MATCH | ||
| 20 | - */ | ||
| 21 | - private String sceneCode; | ||
| 22 | - | ||
| 23 | - | ||
| 24 | - /** | ||
| 25 | - * 业务场景名(如销量订单、送货单) | ||
| 26 | - */ | ||
| 27 | - private String scene; | ||
| 28 | - | ||
| 29 | - /** | ||
| 30 | - * 操作方法名(如增加、修改、审核、物流跟踪) | ||
| 31 | - */ | ||
| 32 | - private String method; | ||
| 33 | - | ||
| 34 | - /** | ||
| 35 | - * JSON结构化参数(键=参数名,值=参数值,类型与工具描述一致) | ||
| 36 | - */ | ||
| 37 | - private Map<String, Object> params; | ||
| 38 | - | ||
| 39 | - | ||
| 40 | -} | ||
| 41 | - |
src/main/java/com/xly/entity/SceneTemplate.java deleted
| 1 | -package com.xly.entity; | ||
| 2 | - | ||
| 3 | -import jakarta.persistence.*; | ||
| 4 | -import lombok.Data; | ||
| 5 | - | ||
| 6 | -/** | ||
| 7 | - * 数据库存储的动态模板 | ||
| 8 | - */ | ||
| 9 | -@Entity | ||
| 10 | -@Table(name = "ai_scene_template") | ||
| 11 | -@Data | ||
| 12 | -public class SceneTemplate { | ||
| 13 | - | ||
| 14 | - @Id | ||
| 15 | - @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| 16 | - private Long id; | ||
| 17 | - | ||
| 18 | - @Column(name = "scene_code") | ||
| 19 | - private String sceneCode; // 场景编码 | ||
| 20 | - | ||
| 21 | - @Column(name = "scene_name") | ||
| 22 | - private String sceneName; // 场景名称 | ||
| 23 | - | ||
| 24 | - @Column(name = "template_content", columnDefinition = "TEXT") | ||
| 25 | - private String templateContent; // 模板内容 | ||
| 26 | - | ||
| 27 | - @Column(name = "variables_config", columnDefinition = "JSON") | ||
| 28 | - private String variablesConfig; // 变量配置 | ||
| 29 | - | ||
| 30 | - @Column(name = "rules_config", columnDefinition = "JSON") | ||
| 31 | - private String rulesConfig; // 规则配置 | ||
| 32 | - | ||
| 33 | - @Column(name = "is_active") | ||
| 34 | - private Boolean active = true; | ||
| 35 | - | ||
| 36 | - public void setId(Long id) { | ||
| 37 | - this.id = id; | ||
| 38 | - } | ||
| 39 | - | ||
| 40 | - public Long getId() { | ||
| 41 | - return id; | ||
| 42 | - } | ||
| 43 | -} | ||
| 44 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/entity/ToolMeta.java deleted
| 1 | -package com.xly.entity; | ||
| 2 | - | ||
| 3 | - | ||
| 4 | -import lombok.Data; | ||
| 5 | - | ||
| 6 | -import java.time.LocalDateTime; | ||
| 7 | -import java.util.List; | ||
| 8 | -import java.util.Map; | ||
| 9 | - | ||
| 10 | -/*** | ||
| 11 | - * @Author 钱豹 | ||
| 12 | - * @Date 22:41 2026/1/30 | ||
| 13 | - * @Param | ||
| 14 | - * @return | ||
| 15 | - * @Description 工具实体 | ||
| 16 | - **/ | ||
| 17 | -@Data | ||
| 18 | -public class ToolMeta { | ||
| 19 | - private String sId; | ||
| 20 | - private Integer iOrder; | ||
| 21 | - private LocalDateTime tCreateDate; | ||
| 22 | - private String sBrandsId; | ||
| 23 | - private String sSubsidiaryId; | ||
| 24 | - private String sMakePerson; | ||
| 25 | - private String sBillNo; | ||
| 26 | - private String sFormId; | ||
| 27 | - private String sStatus; | ||
| 28 | - private String sceneName; | ||
| 29 | - private String sMethodName; | ||
| 30 | - private String stoolDesc; | ||
| 31 | - private String sParamRules; | ||
| 32 | - private Integer iBizType; | ||
| 33 | - private LocalDateTime tUpdateDate; | ||
| 34 | - private String sMethodNo; | ||
| 35 | - private String sControlName; | ||
| 36 | - private String sApiUrl; | ||
| 37 | - private String sSrcFormId; | ||
| 38 | - private String sBizContent; | ||
| 39 | - private String sSceneId; | ||
| 40 | - private String sInputTabelName; | ||
| 41 | - private String sStructureMemo; | ||
| 42 | - private String sAIshowfield; | ||
| 43 | - private List<Map<String,Object>> sAIshowfieldShow; | ||
| 44 | - private Integer iActionType; | ||
| 45 | - private String sendUrl; | ||
| 46 | - private List<ParamRule> paramRuleList;//模型 | ||
| 47 | - private List<ParamRule> paramRuleListCheck;//需要校验 | ||
| 48 | - private List<ParamRule> paramRuleListAll;//所有的 | ||
| 49 | - private List<ParamRule> dynamicParamRule;//动态参数 | ||
| 50 | - private LocalDateTime tMakeDate; | ||
| 51 | - private String sVectorfiled; | ||
| 52 | - private String sVectorjson; | ||
| 53 | - private String sVectorfiledAll; | ||
| 54 | - private String sVectorfiledShow; | ||
| 55 | - private String sTsMemo; | ||
| 56 | - private Boolean sQue = false; | ||
| 57 | - private Boolean bQuo = false; | ||
| 58 | - | ||
| 59 | -} |
src/main/java/com/xly/entity/UserSceneSession.java deleted
| 1 | -package com.xly.entity; | ||
| 2 | - | ||
| 3 | - | ||
| 4 | -import cn.hutool.core.util.ObjectUtil; | ||
| 5 | -import lombok.Data; | ||
| 6 | - | ||
| 7 | -import java.util.List; | ||
| 8 | -import java.util.Map; | ||
| 9 | -import java.util.concurrent.atomic.AtomicBoolean; | ||
| 10 | -import java.util.concurrent.atomic.AtomicInteger; | ||
| 11 | -import java.util.stream.Collectors; | ||
| 12 | - | ||
| 13 | -/** | ||
| 14 | - * 用户会话状态:记录每个用户的场景选择状态,实现场景会话持久化 | ||
| 15 | - * 存储:用户ID、权限内场景、是否已选场景、当前选定场景 | ||
| 16 | - */ | ||
| 17 | -@Data | ||
| 18 | -public class UserSceneSession { | ||
| 19 | - /** | ||
| 20 | - * 唯一标识:用户ID | ||
| 21 | - */ | ||
| 22 | - private String userId; | ||
| 23 | - private String userName; | ||
| 24 | - private String authorization; | ||
| 25 | - | ||
| 26 | - private String sUserName; | ||
| 27 | - private String sBrandsId; | ||
| 28 | - private String sSubsidiaryId; | ||
| 29 | - /** | ||
| 30 | - * 该用户权限内可访问的所有场景(从权限映射中获取) | ||
| 31 | - */ | ||
| 32 | - private List<SceneDto> authScenes; | ||
| 33 | - /*** | ||
| 34 | - * 用户问题集合 | ||
| 35 | - **/ | ||
| 36 | - private List<String> sUserQuestionList; | ||
| 37 | - | ||
| 38 | - private List<ToolMeta> authTool; | ||
| 39 | - /** | ||
| 40 | - * 是否已选择场景(true=已选,进入专属场景交互;false=未选,先展示选择界面) | ||
| 41 | - */ | ||
| 42 | - private boolean sceneSelected = false; | ||
| 43 | - /** | ||
| 44 | - * 当前选定的业务场景(sceneSelected=true时才有值) | ||
| 45 | - */ | ||
| 46 | - private SceneDto currentScene; | ||
| 47 | - | ||
| 48 | - private ToolMeta currentTool; | ||
| 49 | - | ||
| 50 | - /*** | ||
| 51 | - * 当前未清返回的数据集 | ||
| 52 | - **/ | ||
| 53 | - private Map<Integer,Map<String,Object>> currentRowData; | ||
| 54 | - | ||
| 55 | - /*** | ||
| 56 | - * @Author 钱豹 | ||
| 57 | - * @Date 10:07 2026/1/31 | ||
| 58 | - * @Param | ||
| 59 | - * @return | ||
| 60 | - * @Description 当前已有参数 | ||
| 61 | - **/ | ||
| 62 | - private Map<String,Object> currentArgs; | ||
| 63 | - | ||
| 64 | - private String sFunPrompts; //方法返回的参数补全提示 | ||
| 65 | - | ||
| 66 | - private Boolean bCleanMemory = false; | ||
| 67 | - | ||
| 68 | - private Map<String, Object> args; | ||
| 69 | - /*** | ||
| 70 | - * @Author 钱豹 | ||
| 71 | - * @Date 12:40 2026/7/7 | ||
| 72 | - * @Param | ||
| 73 | - * @return | ||
| 74 | - * @Description 报价data | ||
| 75 | - **/ | ||
| 76 | - private Map<String, Object> quoData; | ||
| 77 | - | ||
| 78 | - /** | ||
| 79 | - * 数据库类型 X: 向量库 S:数据库 | ||
| 80 | - */ | ||
| 81 | - private String dbType; | ||
| 82 | - | ||
| 83 | - /** | ||
| 84 | - * 数据库类型 H: 缓存 D: 动态 | ||
| 85 | - */ | ||
| 86 | - private String dbCach; | ||
| 87 | - /** | ||
| 88 | - * @Author 钱豹 | ||
| 89 | - * @Date 22:55 2026/4/13 | ||
| 90 | - * @Param | ||
| 91 | - * @return | ||
| 92 | - * @Description AI 模板 | ||
| 93 | - **/ | ||
| 94 | - private String sSystemPrompt; | ||
| 95 | - | ||
| 96 | - /*** | ||
| 97 | - * @Author 钱豹 | ||
| 98 | - * @Date 15:15 2026/4/14 | ||
| 99 | - * @Param | ||
| 100 | - * @return | ||
| 101 | - * @Description 操作未清类型 | ||
| 102 | - **/ | ||
| 103 | - private String sCopyTo; | ||
| 104 | - /*** | ||
| 105 | - * @Author 钱豹 | ||
| 106 | - * @Date 15:16 2026/4/14 | ||
| 107 | - * @Param | ||
| 108 | - * @return | ||
| 109 | - * @Description 未清记录ID | ||
| 110 | - **/ | ||
| 111 | - private String sCopyToSrcId; | ||
| 112 | - | ||
| 113 | - /*** | ||
| 114 | - * @Author 钱豹 | ||
| 115 | - * @Date 10:03 2026/4/24 | ||
| 116 | - * @Param | ||
| 117 | - * @return | ||
| 118 | - * @Description 工具是否已执行 | ||
| 119 | - **/ | ||
| 120 | - private Boolean toolExecuted=false; | ||
| 121 | - | ||
| 122 | - /** | ||
| 123 | - * 构建场景选择提示语:展示权限内场景,引导用户选择 | ||
| 124 | - * @return 自然语言提示语 | ||
| 125 | - */ | ||
| 126 | - public String buildSceneSelectHint() { | ||
| 127 | - if (authScenes == null || authScenes.isEmpty()) { | ||
| 128 | - return "<p style='color:red;'>抱歉,你暂无任何业务场景的访问权限,请联系管理员开通!</p>"; | ||
| 129 | - } | ||
| 130 | - // 按名称分组 | ||
| 131 | - StringBuilder sb = new StringBuilder("<div style='line-height:1.8;'>"); | ||
| 132 | - sb.append("<span>请输入序号或点链接进入</span>"); | ||
| 133 | - sb.append("<ul style='padding-left:10px;margin:0;list-style: none;'>"); | ||
| 134 | - for(int i=0;i<authScenes.size();i++){ | ||
| 135 | - SceneDto sceneDto = authScenes.get(i); | ||
| 136 | - sb.append("<li>").append(i+1).append("、").append("<span style=\"text-decoration: underline;\" data-action=\"reset\" data-text=\"") .append(i+1).append("\" onclick=\"reset('").append(i+1).append("')\">") .append(sceneDto.getSNickName()).append("</span><font style='color:#666; font-size:14px;'>(").append(sceneDto.getSSceneContext()).append(")</font>").append("</li>"); | ||
| 137 | - } | ||
| 138 | - sb.append("</ul>"); | ||
| 139 | - sb.append("<span style='color:red; font-size:14px;'>提示:可直接输入问题,系统会自动匹配智能体! </span>"); | ||
| 140 | - sb.append("</div>"); | ||
| 141 | - return sb.toString(); | ||
| 142 | - | ||
| 143 | - } | ||
| 144 | - | ||
| 145 | - | ||
| 146 | - // 新增:将权限内场景转换为「大模型可识别的描述字符串」 | ||
| 147 | - public String buildAuthScenesForLlm(List<ToolMeta> metasAll) { | ||
| 148 | - if (authScenes == null || authScenes.isEmpty()) { | ||
| 149 | - return "无可用场景"; | ||
| 150 | - } | ||
| 151 | - // 格式:场景编码-场景名称-支持功能,如ORDER_OPERATE-订单操作-下单、查订单、取消订单 | ||
| 152 | - return authScenes.stream() | ||
| 153 | - .map(scene -> "sceneCode:" + scene.getSSceneNo() + ",场景名称:" + scene.getSSceneName() + ",场景内容:\n" + getSceneContext(scene,metasAll)) | ||
| 154 | - .collect(Collectors.joining("\n")); | ||
| 155 | - } | ||
| 156 | - /*** | ||
| 157 | - * @Author 钱豹 | ||
| 158 | - * @Date 14:12 2026/2/6 | ||
| 159 | - * @Param [sceneDto, metasAll] | ||
| 160 | - * @return java.lang.String | ||
| 161 | - * @Description 方法名称拼接 | ||
| 162 | - **/ | ||
| 163 | - private String getSceneContext(SceneDto sceneDto,List<ToolMeta> metasAll){ | ||
| 164 | - List<ToolMeta> metas = metasAll.stream().filter(m->m.getSSceneId().equals(sceneDto.getSId())).collect(Collectors.toUnmodifiableList()); | ||
| 165 | - StringBuilder sb = new StringBuilder(); | ||
| 166 | - AtomicInteger index = new AtomicInteger(1); | ||
| 167 | - metas.forEach(m -> { | ||
| 168 | - sb.append(index.getAndIncrement()) | ||
| 169 | - .append(". 【") | ||
| 170 | -// .append(m.getSMethodNo()) | ||
| 171 | -// .append("-") | ||
| 172 | - .append(m.getSMethodName()) | ||
| 173 | - .append("】") | ||
| 174 | - .append(m.getStoolDesc()) | ||
| 175 | - .append("\n"); | ||
| 176 | - }); | ||
| 177 | - if(ObjectUtil.isEmpty(sb)){ | ||
| 178 | - sb.append(sceneDto.getSSceneContext()); | ||
| 179 | - } | ||
| 180 | - return sb.toString(); | ||
| 181 | - } | ||
| 182 | - | ||
| 183 | - /** | ||
| 184 | - * 根据用户输入的序号,匹配选定场景 | ||
| 185 | - * @param input 用户输入的序号(如1/2/3) | ||
| 186 | - * @return true=匹配成功,false=匹配失败 | ||
| 187 | - */ | ||
| 188 | - public boolean selectSceneByInput(String input) { | ||
| 189 | - try { | ||
| 190 | - int index = Integer.parseInt(input) - 1; | ||
| 191 | - if (index >= 0 && index < authScenes.size()) { | ||
| 192 | - this.currentScene = authScenes.get(index); | ||
| 193 | - this.sceneSelected = true; | ||
| 194 | - return true; | ||
| 195 | - } | ||
| 196 | - } catch (NumberFormatException e) { | ||
| 197 | - // 非数字输入,直接返回false | ||
| 198 | - } | ||
| 199 | - return false; | ||
| 200 | - } | ||
| 201 | - | ||
| 202 | -} | ||
| 203 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/exception/GlobalExceptionHandler.java
| @@ -2,12 +2,9 @@ package com.xly.exception; | @@ -2,12 +2,9 @@ package com.xly.exception; | ||
| 2 | 2 | ||
| 3 | import com.xly.constant.ErrorCode; | 3 | import com.xly.constant.ErrorCode; |
| 4 | import com.xly.exception.dto.BusinessException; | 4 | import com.xly.exception.dto.BusinessException; |
| 5 | -import com.xly.tts.bean.TTSResponseDTO; | ||
| 6 | import jakarta.servlet.http.HttpServletRequest; | 5 | import jakarta.servlet.http.HttpServletRequest; |
| 7 | import jakarta.validation.ConstraintViolationException; | 6 | import jakarta.validation.ConstraintViolationException; |
| 8 | import lombok.extern.slf4j.Slf4j; | 7 | import lombok.extern.slf4j.Slf4j; |
| 9 | -import org.junit.jupiter.api.Order; | ||
| 10 | -import org.springframework.core.Ordered; | ||
| 11 | import org.springframework.dao.DataAccessException; | 8 | import org.springframework.dao.DataAccessException; |
| 12 | import org.springframework.dao.DataIntegrityViolationException; | 9 | import org.springframework.dao.DataIntegrityViolationException; |
| 13 | import org.springframework.dao.DuplicateKeyException; | 10 | import org.springframework.dao.DuplicateKeyException; |
| @@ -20,202 +17,91 @@ import org.springframework.web.bind.annotation.ExceptionHandler; | @@ -20,202 +17,91 @@ import org.springframework.web.bind.annotation.ExceptionHandler; | ||
| 20 | import org.springframework.web.bind.annotation.RestControllerAdvice; | 17 | import org.springframework.web.bind.annotation.RestControllerAdvice; |
| 21 | import org.springframework.web.servlet.NoHandlerFoundException; | 18 | import org.springframework.web.servlet.NoHandlerFoundException; |
| 22 | 19 | ||
| 23 | -import java.util.Date; | ||
| 24 | -import java.util.HashMap; | 20 | +import java.util.LinkedHashMap; |
| 25 | import java.util.List; | 21 | import java.util.List; |
| 26 | import java.util.Map; | 22 | import java.util.Map; |
| 27 | import java.util.stream.Collectors; | 23 | import java.util.stream.Collectors; |
| 28 | 24 | ||
| 29 | @Slf4j | 25 | @Slf4j |
| 30 | @RestControllerAdvice | 26 | @RestControllerAdvice |
| 31 | -@Order(Ordered.HIGHEST_PRECEDENCE) | ||
| 32 | public class GlobalExceptionHandler { | 27 | public class GlobalExceptionHandler { |
| 33 | 28 | ||
| 29 | + private Map<String, Object> error(Integer code, String message) { | ||
| 30 | + Map<String, Object> body = new LinkedHashMap<>(); | ||
| 31 | + body.put("code", code); | ||
| 32 | + body.put("message", message); | ||
| 33 | + body.put("timestamp", System.currentTimeMillis()); | ||
| 34 | + return body; | ||
| 35 | + } | ||
| 34 | 36 | ||
| 35 | - /** | ||
| 36 | - * 处理所有异常 | ||
| 37 | - */ | ||
| 38 | @ExceptionHandler(Exception.class) | 37 | @ExceptionHandler(Exception.class) |
| 39 | - public TTSResponseDTO handleException(HttpServletRequest request, Exception e) { | 38 | + public Map<String, Object> handleException(HttpServletRequest request, Exception e) { |
| 40 | log.error("请求地址: {}, 全局异常: ", request.getRequestURI(), e); | 39 | log.error("请求地址: {}, 全局异常: ", request.getRequestURI(), e); |
| 41 | - | ||
| 42 | - // 获取请求信息 | ||
| 43 | - String method = request.getMethod(); | ||
| 44 | - String uri = request.getRequestURI(); | ||
| 45 | - String queryString = request.getQueryString(); | ||
| 46 | - | ||
| 47 | - // 记录异常日志(可存入数据库) | ||
| 48 | -// ErrorLog errorLog = ErrorLog.builder() | ||
| 49 | -// .method(method) | ||
| 50 | -// .uri(uri) | ||
| 51 | -// .queryString(queryString) | ||
| 52 | -// .exceptionName(e.getClass().getName()) | ||
| 53 | -// .exceptionMessage(e.getMessage()) | ||
| 54 | -// .stackTrace(ExceptionUtils.getStackTrace(e)) | ||
| 55 | -// .ip(IpUtil.getIpAddr(request)) | ||
| 56 | -// .userAgent(request.getHeader("User-Agent")) | ||
| 57 | -// .timestamp(new Date()) | ||
| 58 | -// .build(); | ||
| 59 | -// | ||
| 60 | -// // 异步保存错误日志 | ||
| 61 | -// saveErrorLogAsync(errorLog); | ||
| 62 | - | ||
| 63 | - // 生产环境隐藏详细错误信息 | ||
| 64 | - if (isProduction()) { | ||
| 65 | - return TTSResponseDTO.error(ErrorCode.SYSTEM_ERROR); | ||
| 66 | - } else { | ||
| 67 | - // 开发环境返回详细错误信息 | ||
| 68 | - Map<String, Object> errorDetail = new HashMap<>(); | ||
| 69 | - errorDetail.put("exception", e.getClass().getName()); | ||
| 70 | - errorDetail.put("message", e.getMessage()); | ||
| 71 | - errorDetail.put("path", uri); | ||
| 72 | - errorDetail.put("method", method); | ||
| 73 | - errorDetail.put("timestamp", new Date()); | ||
| 74 | - return TTSResponseDTO.error(ErrorCode.SYSTEM_ERROR.getCode(), "系统异常: " + e.getMessage()); | ||
| 75 | - } | 40 | + return error(ErrorCode.SYSTEM_ERROR.getCode(), "系统异常: " + e.getMessage()); |
| 76 | } | 41 | } |
| 77 | 42 | ||
| 78 | - /** | ||
| 79 | - * 处理业务异常 | ||
| 80 | - */ | ||
| 81 | @ExceptionHandler(BusinessException.class) | 43 | @ExceptionHandler(BusinessException.class) |
| 82 | - public TTSResponseDTO handleBusinessException(BusinessException e) { | 44 | + public Map<String, Object> handleBusinessException(BusinessException e) { |
| 83 | log.warn("业务异常: {}", e.getMessage()); | 45 | log.warn("业务异常: {}", e.getMessage()); |
| 84 | - return TTSResponseDTO.error(e.getCode(), e.getMessage()); | 46 | + return error(e.getCode(), e.getMessage()); |
| 85 | } | 47 | } |
| 86 | 48 | ||
| 87 | - /** | ||
| 88 | - * 处理数据校验异常 | ||
| 89 | - */ | ||
| 90 | @ExceptionHandler(MethodArgumentNotValidException.class) | 49 | @ExceptionHandler(MethodArgumentNotValidException.class) |
| 91 | - public TTSResponseDTO handleMethodArgumentNotValidException( | ||
| 92 | - MethodArgumentNotValidException e) { | 50 | + public Map<String, Object> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { |
| 93 | List<String> errors = e.getBindingResult().getFieldErrors() | 51 | List<String> errors = e.getBindingResult().getFieldErrors() |
| 94 | .stream() | 52 | .stream() |
| 95 | - .map(error -> error.getField() + ": " + error.getDefaultMessage()) | 53 | + .map(err -> err.getField() + ": " + err.getDefaultMessage()) |
| 96 | .collect(Collectors.toList()); | 54 | .collect(Collectors.toList()); |
| 97 | - | ||
| 98 | String message = String.join("; ", errors); | 55 | String message = String.join("; ", errors); |
| 99 | log.warn("参数校验失败: {}", message); | 56 | log.warn("参数校验失败: {}", message); |
| 100 | - return TTSResponseDTO.error(ErrorCode.PARAM_ERROR.getCode(), message); | 57 | + return error(ErrorCode.PARAM_ERROR.getCode(), message); |
| 101 | } | 58 | } |
| 102 | 59 | ||
| 103 | - /** | ||
| 104 | - * 处理约束违反异常 | ||
| 105 | - */ | ||
| 106 | @ExceptionHandler(ConstraintViolationException.class) | 60 | @ExceptionHandler(ConstraintViolationException.class) |
| 107 | - public TTSResponseDTO handleConstraintViolationException( | ||
| 108 | - ConstraintViolationException e) { | 61 | + public Map<String, Object> handleConstraintViolationException(ConstraintViolationException e) { |
| 109 | List<String> errors = e.getConstraintViolations() | 62 | List<String> errors = e.getConstraintViolations() |
| 110 | .stream() | 63 | .stream() |
| 111 | - .map(violation -> | ||
| 112 | - violation.getPropertyPath() + ": " + violation.getMessage()) | 64 | + .map(violation -> violation.getPropertyPath() + ": " + violation.getMessage()) |
| 113 | .collect(Collectors.toList()); | 65 | .collect(Collectors.toList()); |
| 114 | - | ||
| 115 | String message = String.join("; ", errors); | 66 | String message = String.join("; ", errors); |
| 116 | log.warn("参数约束违反: {}", message); | 67 | log.warn("参数约束违反: {}", message); |
| 117 | - return TTSResponseDTO.error(ErrorCode.PARAM_ERROR.getCode(), message); | 68 | + return error(ErrorCode.PARAM_ERROR.getCode(), message); |
| 118 | } | 69 | } |
| 119 | 70 | ||
| 120 | - /** | ||
| 121 | - * 处理Http请求方法不支持异常 | ||
| 122 | - */ | ||
| 123 | @ExceptionHandler(HttpRequestMethodNotSupportedException.class) | 71 | @ExceptionHandler(HttpRequestMethodNotSupportedException.class) |
| 124 | - public TTSResponseDTO handleHttpRequestMethodNotSupportedException( | ||
| 125 | - HttpRequestMethodNotSupportedException e) { | 72 | + public Map<String, Object> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) { |
| 126 | log.warn("HTTP方法不支持: {} {}", e.getMethod(), e.getSupportedHttpMethods()); | 73 | log.warn("HTTP方法不支持: {} {}", e.getMethod(), e.getSupportedHttpMethods()); |
| 127 | - return TTSResponseDTO.error(ErrorCode.BAD_REQUEST.getCode(), | ||
| 128 | - "请求方法 '" + e.getMethod() + "' 不支持"); | 74 | + return error(ErrorCode.BAD_REQUEST.getCode(), "请求方法 '" + e.getMethod() + "' 不支持"); |
| 129 | } | 75 | } |
| 130 | 76 | ||
| 131 | - /** | ||
| 132 | - * 处理媒体类型不支持异常 | ||
| 133 | - */ | ||
| 134 | @ExceptionHandler(HttpMediaTypeNotSupportedException.class) | 77 | @ExceptionHandler(HttpMediaTypeNotSupportedException.class) |
| 135 | - public TTSResponseDTO handleHttpMediaTypeNotSupportedException( | ||
| 136 | - HttpMediaTypeNotSupportedException e) { | 78 | + public Map<String, Object> handleHttpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException e) { |
| 137 | log.warn("媒体类型不支持: {}", e.getContentType()); | 79 | log.warn("媒体类型不支持: {}", e.getContentType()); |
| 138 | - return TTSResponseDTO.error(ErrorCode.BAD_REQUEST.getCode(), | ||
| 139 | - "媒体类型不支持: " + e.getContentType()); | 80 | + return error(ErrorCode.BAD_REQUEST.getCode(), "媒体类型不支持: " + e.getContentType()); |
| 140 | } | 81 | } |
| 141 | 82 | ||
| 142 | - /** | ||
| 143 | - * 处理404异常 | ||
| 144 | - */ | ||
| 145 | @ExceptionHandler(NoHandlerFoundException.class) | 83 | @ExceptionHandler(NoHandlerFoundException.class) |
| 146 | - public TTSResponseDTO handleNoHandlerFoundException( | ||
| 147 | - NoHandlerFoundException e) { | 84 | + public Map<String, Object> handleNoHandlerFoundException(NoHandlerFoundException e) { |
| 148 | log.warn("接口不存在: {} {}", e.getHttpMethod(), e.getRequestURL()); | 85 | log.warn("接口不存在: {} {}", e.getHttpMethod(), e.getRequestURL()); |
| 149 | - return TTSResponseDTO.error(ErrorCode.NOT_FOUND.getCode(), | ||
| 150 | - "接口不存在: " + e.getRequestURL()); | 86 | + return error(ErrorCode.NOT_FOUND.getCode(), "接口不存在: " + e.getRequestURL()); |
| 151 | } | 87 | } |
| 152 | 88 | ||
| 153 | - /** | ||
| 154 | - * 处理类型转换异常 | ||
| 155 | - */ | ||
| 156 | @ExceptionHandler(HttpMessageConversionException.class) | 89 | @ExceptionHandler(HttpMessageConversionException.class) |
| 157 | - public TTSResponseDTO handleHttpMessageConversionException( | ||
| 158 | - HttpMessageConversionException e) { | 90 | + public Map<String, Object> handleHttpMessageConversionException(HttpMessageConversionException e) { |
| 159 | log.warn("HTTP消息转换异常: {}", e.getMessage()); | 91 | log.warn("HTTP消息转换异常: {}", e.getMessage()); |
| 160 | - return TTSResponseDTO.error(ErrorCode.PARAM_ERROR.getCode(), | ||
| 161 | - "参数格式错误"); | 92 | + return error(ErrorCode.PARAM_ERROR.getCode(), "参数格式错误"); |
| 162 | } | 93 | } |
| 163 | 94 | ||
| 164 | - /** | ||
| 165 | - * 处理数据库异常 | ||
| 166 | - */ | ||
| 167 | @ExceptionHandler(DataAccessException.class) | 95 | @ExceptionHandler(DataAccessException.class) |
| 168 | - public TTSResponseDTO handleDataAccessException(DataAccessException e) { | 96 | + public Map<String, Object> handleDataAccessException(DataAccessException e) { |
| 169 | log.error("数据库异常: {}", e.getMessage(), e); | 97 | log.error("数据库异常: {}", e.getMessage(), e); |
| 170 | - | ||
| 171 | - // 根据具体异常类型细化处理 | ||
| 172 | if (e instanceof DuplicateKeyException) { | 98 | if (e instanceof DuplicateKeyException) { |
| 173 | - return TTSResponseDTO.error(ErrorCode.DATA_EXISTS.getCode(), | ||
| 174 | - "数据已存在"); | 99 | + return error(ErrorCode.DATA_EXISTS.getCode(), "数据已存在"); |
| 175 | } else if (e instanceof DataIntegrityViolationException) { | 100 | } else if (e instanceof DataIntegrityViolationException) { |
| 176 | - return TTSResponseDTO.error(ErrorCode.DATA_ERROR.getCode(), | ||
| 177 | - "数据完整性违反"); | 101 | + return error(ErrorCode.DATA_ERROR.getCode(), "数据完整性违反"); |
| 178 | } else if (e instanceof BadSqlGrammarException) { | 102 | } else if (e instanceof BadSqlGrammarException) { |
| 179 | - return TTSResponseDTO.error(ErrorCode.DB_ERROR.getCode(), | ||
| 180 | - "SQL语法错误"); | 103 | + return error(ErrorCode.DB_ERROR.getCode(), "SQL语法错误"); |
| 181 | } | 104 | } |
| 182 | - | ||
| 183 | - return TTSResponseDTO.error(ErrorCode.DB_ERROR); | ||
| 184 | - } | ||
| 185 | - | ||
| 186 | - /** | ||
| 187 | - * 处理JWT异常 | ||
| 188 | - */ | ||
| 189 | -// @ExceptionHandler(JwtException.class) | ||
| 190 | -// public TTSResponseDTO handleJwtException(JwtException e) { | ||
| 191 | -// log.warn("JWT异常: {}", e.getMessage()); | ||
| 192 | -// return ApiResult.error(ErrorCode.UNAUTHORIZED.getCode(), | ||
| 193 | -// "Token无效或已过期"); | ||
| 194 | -// } | ||
| 195 | - | ||
| 196 | -// /** | ||
| 197 | -// * 异步保存错误日志 | ||
| 198 | -// */ | ||
| 199 | -// @Async | ||
| 200 | -// public void saveErrorLogAsync(ErrorLog errorLog) { | ||
| 201 | -// try { | ||
| 202 | -// // 保存到数据库或日志文件 | ||
| 203 | -// errorLogService.save(errorLog); | ||
| 204 | -// } catch (Exception ex) { | ||
| 205 | -// log.error("保存错误日志失败", ex); | ||
| 206 | -// } | ||
| 207 | -// } | ||
| 208 | - | ||
| 209 | - /*** | ||
| 210 | - * @Author 钱豹 | ||
| 211 | - * @Date 23:21 2026/1/30 | ||
| 212 | - * @Param [] | ||
| 213 | - * @return boolean | ||
| 214 | - * @Description 判断是否生产环境还是开发环境 应该根据yml 配置来 后期拓展 | ||
| 215 | - **/ | ||
| 216 | - private boolean isProduction() { | ||
| 217 | - //TODO | ||
| 218 | - String profile = "dev"; | ||
| 219 | - return "prod".equals(profile); | 105 | + return error(ErrorCode.DB_ERROR.getCode(), ErrorCode.DB_ERROR.getMessage()); |
| 220 | } | 106 | } |
| 221 | -} | ||
| 222 | \ No newline at end of file | 107 | \ No newline at end of file |
| 108 | +} |
src/main/java/com/xly/exception/TtsExceptionFilter.java deleted
| 1 | -//package com.xly.exception; | ||
| 2 | -// | ||
| 3 | -//import com.fasterxml.jackson.databind.ObjectMapper; | ||
| 4 | -//import com.xly.tts.bean.TTSResponseDTO; | ||
| 5 | -//import jakarta.servlet.*; | ||
| 6 | -//import jakarta.servlet.http.HttpServletRequest; | ||
| 7 | -//import jakarta.servlet.http.HttpServletResponse; | ||
| 8 | -//import lombok.RequiredArgsConstructor; | ||
| 9 | -//import lombok.extern.slf4j.Slf4j; | ||
| 10 | -//import org.springframework.core.annotation.Order; | ||
| 11 | -//import org.springframework.http.HttpStatus; | ||
| 12 | -//import org.springframework.http.MediaType; | ||
| 13 | -//import org.springframework.stereotype.Component; | ||
| 14 | -// | ||
| 15 | -//import java.io.IOException; | ||
| 16 | -// | ||
| 17 | -//@Slf4j | ||
| 18 | -//@Component | ||
| 19 | -//@Order(1) | ||
| 20 | -//@RequiredArgsConstructor | ||
| 21 | -//public class TtsExceptionFilter implements Filter { | ||
| 22 | -// | ||
| 23 | -// private final ObjectMapper objectMapper; | ||
| 24 | -// | ||
| 25 | -// @Override | ||
| 26 | -// public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) | ||
| 27 | -// throws IOException, ServletException { | ||
| 28 | -// | ||
| 29 | -// HttpServletRequest httpRequest = (HttpServletRequest) request; | ||
| 30 | -// HttpServletResponse httpResponse = (HttpServletResponse) response; | ||
| 31 | -// | ||
| 32 | -// // 只处理 TTS 相关接口 | ||
| 33 | -// if (!httpRequest.getRequestURI().contains("/api/tts/")) { | ||
| 34 | -// chain.doFilter(request, response); | ||
| 35 | -// return; | ||
| 36 | -// } | ||
| 37 | -// | ||
| 38 | -// try { | ||
| 39 | -// chain.doFilter(request, response); | ||
| 40 | -// } catch (Exception ex) { | ||
| 41 | -// log.error("TTS接口异常: {}", httpRequest.getRequestURI(), ex); | ||
| 42 | -// | ||
| 43 | -// // 清除之前的响应 | ||
| 44 | -// if (httpResponse.isCommitted()) { | ||
| 45 | -// return; | ||
| 46 | -// } | ||
| 47 | -// | ||
| 48 | -// httpResponse.reset(); | ||
| 49 | -// httpResponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); | ||
| 50 | -// httpResponse.setContentType(MediaType.APPLICATION_JSON_VALUE); | ||
| 51 | -// httpResponse.setCharacterEncoding("UTF-8"); | ||
| 52 | -// | ||
| 53 | -// TTSResponseDTO errorResult = TTSResponseDTO.error( | ||
| 54 | -// HttpStatus.INTERNAL_SERVER_ERROR.value(), | ||
| 55 | -// "语音合成服务异常: " + ex.getMessage() | ||
| 56 | -// ); | ||
| 57 | -// | ||
| 58 | -// String jsonResponse = objectMapper.writeValueAsString(errorResult); | ||
| 59 | -// httpResponse.getWriter().write(jsonResponse); | ||
| 60 | -// httpResponse.getWriter().flush(); | ||
| 61 | -// } | ||
| 62 | -// } | ||
| 63 | -//} | ||
| 64 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/exception/dto/AuthenticationException.java deleted
| 1 | -package com.xly.exception.dto; | ||
| 2 | - | ||
| 3 | -import com.xly.constant.ErrorCode; | ||
| 4 | - | ||
| 5 | -/*** | ||
| 6 | - * @Author 钱豹 | ||
| 7 | - * @Date 23:13 2026/1/30 | ||
| 8 | - * @Param | ||
| 9 | - * @return | ||
| 10 | - * @Description 认证异常 | ||
| 11 | - **/ | ||
| 12 | -public class AuthenticationException extends BaseException { | ||
| 13 | - public AuthenticationException(ErrorCode errorCode) { | ||
| 14 | - super(errorCode); | ||
| 15 | - } | ||
| 16 | - | ||
| 17 | - public AuthenticationException(String message) { | ||
| 18 | - super(ErrorCode.UNAUTHORIZED.getCode(), message); | ||
| 19 | - } | ||
| 20 | -} | ||
| 21 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/exception/dto/AuthorizationException.java deleted
| 1 | -package com.xly.exception.dto; | ||
| 2 | - | ||
| 3 | - | ||
| 4 | -import com.xly.constant.ErrorCode; | ||
| 5 | - | ||
| 6 | -/*** | ||
| 7 | - * @Author 钱豹 | ||
| 8 | - * @Date 23:14 2026/1/30 | ||
| 9 | - * @Param | ||
| 10 | - * @return | ||
| 11 | - * @Description 授权异常 | ||
| 12 | - **/ | ||
| 13 | -public class AuthorizationException extends BaseException { | ||
| 14 | - public AuthorizationException(ErrorCode errorCode) { | ||
| 15 | - super(errorCode); | ||
| 16 | - } | ||
| 17 | - | ||
| 18 | - public AuthorizationException(String message) { | ||
| 19 | - super(ErrorCode.FORBIDDEN.getCode(), message); | ||
| 20 | - } | ||
| 21 | -} | ||
| 22 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/exception/dto/DataException.java deleted
| 1 | -package com.xly.exception.dto; | ||
| 2 | - | ||
| 3 | -import com.xly.constant.ErrorCode; | ||
| 4 | - | ||
| 5 | -/*** | ||
| 6 | - * @Author 钱豹 | ||
| 7 | - * @Date 23:13 2026/1/30 | ||
| 8 | - * @Param | ||
| 9 | - * @return | ||
| 10 | - * @Description 数据异常 | ||
| 11 | - **/ | ||
| 12 | -public class DataException extends BaseException { | ||
| 13 | - public DataException(ErrorCode errorCode) { | ||
| 14 | - super(errorCode); | ||
| 15 | - } | ||
| 16 | - | ||
| 17 | - public DataException(String message) { | ||
| 18 | - super(ErrorCode.DATA_ERROR.getCode(), message); | ||
| 19 | - } | ||
| 20 | -} | ||
| 21 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/exception/sqlexception/Nl2SqlBaseException.java deleted
| 1 | -package com.xly.exception.sqlexception; | ||
| 2 | - | ||
| 3 | -/** | ||
| 4 | - * NL2SQL根异常 | ||
| 5 | - */ | ||
| 6 | -public class Nl2SqlBaseException extends RuntimeException { | ||
| 7 | - public Nl2SqlBaseException(String message) { | ||
| 8 | - super(message); | ||
| 9 | - } | ||
| 10 | - | ||
| 11 | - public Nl2SqlBaseException(String message, Throwable cause) { | ||
| 12 | - super(message, cause); | ||
| 13 | - } | ||
| 14 | -} |
src/main/java/com/xly/exception/sqlexception/SqlExecuteException.java deleted
| 1 | -package com.xly.exception.sqlexception; | ||
| 2 | - | ||
| 3 | -/** | ||
| 4 | - * SQL执行异常(数据库执行/结果解析失败) | ||
| 5 | - */ | ||
| 6 | -public class SqlExecuteException extends Nl2SqlBaseException { | ||
| 7 | - public SqlExecuteException(String message) { | ||
| 8 | - super(message); | ||
| 9 | - } | ||
| 10 | - | ||
| 11 | - public SqlExecuteException(String message, Throwable cause) { | ||
| 12 | - super(message, cause); | ||
| 13 | - } | ||
| 14 | -} | ||
| 15 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/exception/sqlexception/SqlGenerateException.java deleted
| 1 | -package com.xly.exception.sqlexception; | ||
| 2 | - | ||
| 3 | -/** | ||
| 4 | - * SQL生成异常(@AiService调用失败/生成空SQL) | ||
| 5 | - */ | ||
| 6 | -public class SqlGenerateException extends Nl2SqlBaseException { | ||
| 7 | - public SqlGenerateException(String message) { | ||
| 8 | - super(message); | ||
| 9 | - } | ||
| 10 | - | ||
| 11 | - public SqlGenerateException(String message, Throwable cause) { | ||
| 12 | - super(message, cause); | ||
| 13 | - } | ||
| 14 | -} | ||
| 15 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/exception/sqlexception/SqlValidateException.java deleted
| 1 | -package com.xly.exception.sqlexception; | ||
| 2 | - | ||
| 3 | -/** | ||
| 4 | - * SQL强校验异常(语法错误/危险关键词/非SELECT语句) | ||
| 5 | - */ | ||
| 6 | -public class SqlValidateException extends Nl2SqlBaseException { | ||
| 7 | - public SqlValidateException(String message) { | ||
| 8 | - super(message); | ||
| 9 | - } | ||
| 10 | - | ||
| 11 | - public SqlValidateException(String message, Throwable cause) { | ||
| 12 | - super(message, cause); | ||
| 13 | - } | ||
| 14 | -} | ||
| 15 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/mapper/AiToolDetailParamsMapper.java deleted
| 1 | -package com.xly.mapper; | ||
| 2 | - | ||
| 3 | -import com.xly.entity.ParamRule; | ||
| 4 | -import org.apache.ibatis.annotations.Mapper; | ||
| 5 | -import org.apache.ibatis.annotations.Select; | ||
| 6 | -import org.springframework.stereotype.Repository; | ||
| 7 | - | ||
| 8 | -import java.util.List; | ||
| 9 | - | ||
| 10 | -@Repository | ||
| 11 | -@Mapper | ||
| 12 | -public interface AiToolDetailParamsMapper { | ||
| 13 | - | ||
| 14 | - // XML配置方式 | ||
| 15 | - @Select("SELECT * FROM ai_tool_detail_params ORDER BY iOrder,iIncrement ") | ||
| 16 | - List<ParamRule> findAll(); | ||
| 17 | -} | ||
| 18 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/mapper/DynamicExeDbMapper.java deleted
| 1 | -package com.xly.mapper; | ||
| 2 | - | ||
| 3 | - | ||
| 4 | -import org.apache.ibatis.annotations.Mapper; | ||
| 5 | -import org.springframework.stereotype.Repository; | ||
| 6 | - | ||
| 7 | -import java.util.List; | ||
| 8 | -import java.util.Map; | ||
| 9 | - | ||
| 10 | - | ||
| 11 | -@Repository | ||
| 12 | -@Mapper | ||
| 13 | -public interface DynamicExeDbMapper { | ||
| 14 | - | ||
| 15 | - /*** | ||
| 16 | - * @Author 钱豹 | ||
| 17 | - * @Date 22:43 2026/1/30 | ||
| 18 | - * @Param [] | ||
| 19 | - * @return java.util.List<java.util.Map<java.lang.String,java.lang.Object>> | ||
| 20 | - * @Description 查询SQL执行 | ||
| 21 | - **/ | ||
| 22 | - List<Map<String,Object>> findSql(Map<String,Object> searMap); | ||
| 23 | - | ||
| 24 | - /*** | ||
| 25 | - * @Author 钱豹 | ||
| 26 | - * @Date 22:44 2026/1/30 | ||
| 27 | - * @Param [updMap] | ||
| 28 | - * @return void | ||
| 29 | - * @Description 更新SQL执行 | ||
| 30 | - **/ | ||
| 31 | - void updateSql(Map<String,Object> updMap); | ||
| 32 | - | ||
| 33 | - /*** | ||
| 34 | - * @Author 钱豹 | ||
| 35 | - * @Date 22:45 2026/1/30 | ||
| 36 | - * @Param [updMap] | ||
| 37 | - * @return void | ||
| 38 | - * @Description 新增SQL执行 | ||
| 39 | - **/ | ||
| 40 | - void addSql(Map<String,Object> addMap); | ||
| 41 | - | ||
| 42 | - /**** | ||
| 43 | - * @Author 钱豹 | ||
| 44 | - * @Date 22:56 2026/1/30 | ||
| 45 | - * @Param [delMap] | ||
| 46 | - * @return void | ||
| 47 | - * @Description 删除QL执行 | ||
| 48 | - **/ | ||
| 49 | - void delSql(Map<String,Object> delMap); | ||
| 50 | - | ||
| 51 | - | ||
| 52 | - /*** | ||
| 53 | - * @Author 钱豹 | ||
| 54 | - * @Date 22:45 2026/1/30 | ||
| 55 | - * @Param | ||
| 56 | - * @return | ||
| 57 | - * @Description 动态执行过程 并且有返回 执行过程 返回多个数据集 ,默认10个 | ||
| 58 | - **/ | ||
| 59 | - List<List<Map<String,Object>>> getCallProMoreResult(Map<String,Object> map); | ||
| 60 | - | ||
| 61 | - /*** | ||
| 62 | - * @Author 钱豹 | ||
| 63 | - * @Date 1:41 2026/2/4 | ||
| 64 | - * @Param [map] | ||
| 65 | - * @return java.util.List<java.util.Map<java.lang.String,java.lang.Object>> | ||
| 66 | - * @Description 调用过程 | ||
| 67 | - **/ | ||
| 68 | - List<Map<String,Object>> getCallPro(Map<String,Object> map); | ||
| 69 | - | ||
| 70 | -} |
src/main/java/com/xly/mapper/ParamRuleMapper.java deleted
| 1 | -package com.xly.mapper; | ||
| 2 | - | ||
| 3 | -import com.xly.entity.ParamRule; | ||
| 4 | -import org.apache.ibatis.annotations.Mapper; | ||
| 5 | -import org.apache.ibatis.annotations.Select; | ||
| 6 | -import org.springframework.stereotype.Repository; | ||
| 7 | - | ||
| 8 | -import java.util.List; | ||
| 9 | - | ||
| 10 | -@Repository | ||
| 11 | -@Mapper | ||
| 12 | -public interface ParamRuleMapper { | ||
| 13 | - | ||
| 14 | - // XML配置方式 | ||
| 15 | - @Select("SELECT * FROM ai_tool_detail_params") | ||
| 16 | - List<ParamRule> findAll(); | ||
| 17 | -} | ||
| 18 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/mapper/SceneMapper.java deleted
| 1 | -package com.xly.mapper; | ||
| 2 | - | ||
| 3 | -import com.xly.entity.SceneDto; | ||
| 4 | -import org.apache.ibatis.annotations.*; | ||
| 5 | -import org.springframework.stereotype.Repository; | ||
| 6 | - | ||
| 7 | -import java.util.List; | ||
| 8 | - | ||
| 9 | -@Repository | ||
| 10 | -@Mapper | ||
| 11 | -public interface SceneMapper { | ||
| 12 | - | ||
| 13 | - // XML配置方式 | ||
| 14 | - @Select("SELECT * FROM ai_agent order by iOrder") | ||
| 15 | - List<SceneDto> findAll(); | ||
| 16 | -} | ||
| 17 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/mapper/ToolMetaMapper.java deleted
| 1 | -package com.xly.mapper; | ||
| 2 | - | ||
| 3 | - | ||
| 4 | -import com.xly.entity.ToolMeta; | ||
| 5 | -import org.apache.ibatis.annotations.Mapper; | ||
| 6 | -import org.apache.ibatis.annotations.Select; | ||
| 7 | -import org.springframework.stereotype.Repository; | ||
| 8 | - | ||
| 9 | -import java.util.List; | ||
| 10 | - | ||
| 11 | -@Repository | ||
| 12 | -@Mapper | ||
| 13 | -public interface ToolMetaMapper { | ||
| 14 | - | ||
| 15 | - // XML配置方式 | ||
| 16 | - @Select("SELECT A.* FROM ai_tool AS A order by iOrder") | ||
| 17 | - List<ToolMeta> findAll(); | ||
| 18 | -} | ||
| 19 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/mapper/UserSceneSessionMapper.java deleted
| 1 | -package com.xly.mapper; | ||
| 2 | - | ||
| 3 | -import org.apache.ibatis.annotations.Mapper; | ||
| 4 | -import org.apache.ibatis.annotations.Param; | ||
| 5 | -import org.apache.ibatis.annotations.Select; | ||
| 6 | -import java.util.List; | ||
| 7 | -import java.util.Map; | ||
| 8 | - | ||
| 9 | -@Mapper | ||
| 10 | -public interface UserSceneSessionMapper { | ||
| 11 | - | ||
| 12 | - /** | ||
| 13 | - * 查询用户权限Key列表 | ||
| 14 | - * @param sUserId 用户ID | ||
| 15 | - * @return 权限Key列表 | ||
| 16 | - */ | ||
| 17 | - @Select("SELECT J.sKey AS sKey FROM sysjurisdiction AS J JOIN sisjurisdictionclassify AS B ON J.sJurisdictionClassifyId = B.sId WHERE B.sId IN(SELECT U.sJurisdictionClassifyId FROM sftlogininfojurisdictiongroup AS U WHERE U.sParentId = #{sUserId}) ") | ||
| 18 | - List<Map<String, Object>> findUserPermissions(@Param("sUserId") String sUserId); | ||
| 19 | - | ||
| 20 | -} | ||
| 21 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/milvus/bean/CustomSearchResultsWrapper.java deleted
| 1 | -package com.xly.milvus.bean; | ||
| 2 | - | ||
| 3 | -import io.milvus.grpc.SearchResultData; | ||
| 4 | -import io.milvus.response.SearchResultsWrapper; | ||
| 5 | -import java.util.List; | ||
| 6 | - | ||
| 7 | -/** | ||
| 8 | - * 自定义SearchResultsWrapper,用于访问protected方法 | ||
| 9 | - */ | ||
| 10 | -public class CustomSearchResultsWrapper extends SearchResultsWrapper { | ||
| 11 | - | ||
| 12 | - public CustomSearchResultsWrapper(SearchResultData results) { | ||
| 13 | - super(results); | ||
| 14 | - } | ||
| 15 | - | ||
| 16 | - /** | ||
| 17 | - * 公开访问getOutputFields方法 | ||
| 18 | - */ | ||
| 19 | - public List<String> getOutputFieldsPublic() { | ||
| 20 | - return super.getOutputFields(); | ||
| 21 | - } | ||
| 22 | -} | ||
| 23 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/milvus/bean/SimilaritySearchRequest.java deleted
| 1 | -package com.xly.milvus.bean; | ||
| 2 | - | ||
| 3 | -import lombok.AllArgsConstructor; | ||
| 4 | -import lombok.Builder; | ||
| 5 | -import lombok.Data; | ||
| 6 | -import lombok.NoArgsConstructor; | ||
| 7 | - | ||
| 8 | -import java.util.List; | ||
| 9 | -import java.util.Map; | ||
| 10 | - | ||
| 11 | -/** | ||
| 12 | - * 相似度查询请求实体 | ||
| 13 | - */ | ||
| 14 | -@Data | ||
| 15 | -@Builder | ||
| 16 | -@NoArgsConstructor | ||
| 17 | -@AllArgsConstructor | ||
| 18 | -public class SimilaritySearchRequest { | ||
| 19 | - private List<Float> queryVector; // 查询向量 | ||
| 20 | - private String queryText; // 查询文本(如果有文本转向量服务) | ||
| 21 | - private Integer topK = 10; // 返回数量 | ||
| 22 | - private Double minScore; // 最小相似度得分 | ||
| 23 | - private Double maxScore; // 最大相似度得分 | ||
| 24 | - private String metricType = "IP"; // 距离类型: IP(内积), L2(欧氏距离), COSINE(余弦) | ||
| 25 | - private List<String> outputFields; // 输出字段 | ||
| 26 | - private Map<String, Object> filter; // 过滤条件 | ||
| 27 | - private String partitionName; // 分区名称 | ||
| 28 | - private Boolean withScore = true; // 是否返回得分 | ||
| 29 | - private Boolean withDistance = false; // 是否返回距离 | ||
| 30 | -} | ||
| 31 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/milvus/bean/SimilaritySearchResult.java deleted
| 1 | -package com.xly.milvus.bean; | ||
| 2 | - | ||
| 3 | -import lombok.AllArgsConstructor; | ||
| 4 | -import lombok.Builder; | ||
| 5 | -import lombok.Data; | ||
| 6 | -import lombok.NoArgsConstructor; | ||
| 7 | - | ||
| 8 | -import java.util.Map; | ||
| 9 | - | ||
| 10 | -/** | ||
| 11 | - * 相似度查询结果实体 - 增强版 | ||
| 12 | - */ | ||
| 13 | -@Data | ||
| 14 | -@Builder | ||
| 15 | -@NoArgsConstructor | ||
| 16 | -@AllArgsConstructor | ||
| 17 | -public class SimilaritySearchResult { | ||
| 18 | - private Long id; // 整数ID | ||
| 19 | - private String strId; // 字符串ID | ||
| 20 | - private Float score; // 相似度得分 | ||
| 21 | - private Double normalizedScore; // 归一化后的得分(0-1之间) | ||
| 22 | - private Map<String, Object> fields; // 字段数据 | ||
| 23 | - private String collectionName; // 集合名称 | ||
| 24 | - | ||
| 25 | - /** | ||
| 26 | - * 获取ID(优先返回字符串ID,如果没有则返回整数ID) | ||
| 27 | - */ | ||
| 28 | - public String getId() { | ||
| 29 | - if (strId != null && !strId.isEmpty()) { | ||
| 30 | - return strId; | ||
| 31 | - } | ||
| 32 | - return id != null ? String.valueOf(id) : null; | ||
| 33 | - } | ||
| 34 | - | ||
| 35 | - /** | ||
| 36 | - * 获取字段值 | ||
| 37 | - */ | ||
| 38 | - public Object getField(String fieldName) { | ||
| 39 | - if (fields != null) { | ||
| 40 | - return fields.get(fieldName); | ||
| 41 | - } | ||
| 42 | - return null; | ||
| 43 | - } | ||
| 44 | - | ||
| 45 | - /** | ||
| 46 | - * 获取字符串字段值 | ||
| 47 | - */ | ||
| 48 | - public String getStringField(String fieldName) { | ||
| 49 | - Object value = getField(fieldName); | ||
| 50 | - return value != null ? value.toString() : null; | ||
| 51 | - } | ||
| 52 | - | ||
| 53 | - /** | ||
| 54 | - * 获取整数字段值 | ||
| 55 | - */ | ||
| 56 | - public Integer getIntField(String fieldName) { | ||
| 57 | - Object value = getField(fieldName); | ||
| 58 | - if (value instanceof Number) { | ||
| 59 | - return ((Number) value).intValue(); | ||
| 60 | - } | ||
| 61 | - return null; | ||
| 62 | - } | ||
| 63 | -} | ||
| 64 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/milvus/config/EmbeddingConfig.java deleted
| 1 | -package com.xly.milvus.config; | ||
| 2 | - | ||
| 3 | -import dev.langchain4j.model.embedding.EmbeddingModel; | ||
| 4 | -import dev.langchain4j.model.embedding.onnx.allminilml6v2.AllMiniLmL6V2EmbeddingModel; | ||
| 5 | -import org.springframework.context.annotation.Bean; | ||
| 6 | -import org.springframework.context.annotation.Configuration; | ||
| 7 | - | ||
| 8 | -/** | ||
| 9 | - * LangChain4j 嵌入模型配置类 | ||
| 10 | - */ | ||
| 11 | -@Configuration | ||
| 12 | -public class EmbeddingConfig { | ||
| 13 | - | ||
| 14 | - @Bean | ||
| 15 | - public EmbeddingModel embeddingModel() { | ||
| 16 | - // 使用 All-MiniLM-L6-v2 嵌入模型 | ||
| 17 | - // 这是一个轻量级的模型,维度为384,适合在本地运行 | ||
| 18 | - return new AllMiniLmL6V2EmbeddingModel(); | ||
| 19 | - } | ||
| 20 | -} | ||
| 21 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/milvus/config/MilvusConfig.java deleted
| 1 | -package com.xly.milvus.config; | ||
| 2 | - | ||
| 3 | -import io.milvus.v2.client.ConnectConfig; | ||
| 4 | -import io.milvus.v2.client.MilvusClientV2; | ||
| 5 | -import lombok.extern.slf4j.Slf4j; | ||
| 6 | -import org.springframework.beans.factory.annotation.Value; | ||
| 7 | -import org.springframework.context.annotation.Bean; | ||
| 8 | -import org.springframework.context.annotation.Configuration; | ||
| 9 | -import org.springframework.retry.annotation.EnableRetry; | ||
| 10 | -import org.springframework.retry.support.RetryTemplate; | ||
| 11 | - | ||
| 12 | -@Slf4j | ||
| 13 | -@Configuration | ||
| 14 | -@EnableRetry | ||
| 15 | -public class MilvusConfig { | ||
| 16 | - | ||
| 17 | - @Value("${milvus.host:localhost}") | ||
| 18 | - private String host; | ||
| 19 | - | ||
| 20 | - @Value("${milvus.port:19530}") | ||
| 21 | - private Integer port; | ||
| 22 | - | ||
| 23 | - @Value("${milvus.database:default}") | ||
| 24 | - private String database; | ||
| 25 | - | ||
| 26 | - @Value("${milvus.username:}") | ||
| 27 | - private String username; | ||
| 28 | - | ||
| 29 | - @Value("${milvus.password:}") | ||
| 30 | - private String password; | ||
| 31 | - | ||
| 32 | - @Value("${milvus.connect-timeout:10000}") | ||
| 33 | - private Long connectTimeout; | ||
| 34 | - | ||
| 35 | - @Value("${milvus.rpc-deadline:10000}") | ||
| 36 | - private Long rpcDeadline; | ||
| 37 | - | ||
| 38 | - @Value("${milvus.keep-alive-time:300000}") | ||
| 39 | - private Long keepAliveTime; | ||
| 40 | - | ||
| 41 | - @Value("${milvus.keep-alive-timeout:5000}") | ||
| 42 | - private Long keepAliveTimeout; | ||
| 43 | - | ||
| 44 | - @Value("${milvus.secure:false}") | ||
| 45 | - private Boolean secure; | ||
| 46 | - | ||
| 47 | - @Value("${milvus.validate-on-startup:true}") | ||
| 48 | - private boolean validateOnStartup; | ||
| 49 | - | ||
| 50 | - @Value("${milvus.fail-on-connect-error:false}") | ||
| 51 | - private boolean failOnConnectError; | ||
| 52 | - | ||
| 53 | - @Value("${milvus.enable-precheck:true}") | ||
| 54 | - private boolean enablePrecheck; | ||
| 55 | - | ||
| 56 | - @Bean(destroyMethod = "close") | ||
| 57 | - public MilvusClientV2 milvusClient() { | ||
| 58 | - try { | ||
| 59 | - String uri = String.format("http://%s:%d", host, port); | ||
| 60 | - // 构建连接配置 | ||
| 61 | - ConnectConfig.ConnectConfigBuilder configBuilder = ConnectConfig.builder() | ||
| 62 | - .uri(uri) | ||
| 63 | - .dbName("default") | ||
| 64 | - .connectTimeoutMs(connectTimeout) | ||
| 65 | - .rpcDeadlineMs(rpcDeadline) | ||
| 66 | - .keepAliveTimeMs(keepAliveTime) | ||
| 67 | - .keepAliveTimeoutMs(keepAliveTimeout) | ||
| 68 | - .keepAliveWithoutCalls(true) | ||
| 69 | - .secure(secure) | ||
| 70 | - .enablePrecheck(enablePrecheck); | ||
| 71 | - | ||
| 72 | - // 添加认证信息(如果有) | ||
| 73 | - if (username != null && !username.isEmpty()) { | ||
| 74 | - configBuilder.username(username); | ||
| 75 | - if (password != null) { | ||
| 76 | - configBuilder.password(password); | ||
| 77 | - } | ||
| 78 | - } | ||
| 79 | - | ||
| 80 | - ConnectConfig connectConfig = configBuilder.build(); | ||
| 81 | - | ||
| 82 | - // 创建客户端 | ||
| 83 | - MilvusClientV2 client = new MilvusClientV2(connectConfig); | ||
| 84 | - // 检查xlyerp数据库是否存在,如果不存在则创建 | ||
| 85 | - var databases = client.listDatabases(); | ||
| 86 | - if (!databases.getDatabaseNames().contains(database)) { | ||
| 87 | - log.info("数据库 xlyerp 不存在,正在创建..."); | ||
| 88 | - client.createDatabase( | ||
| 89 | - io.milvus.v2.service.database.request.CreateDatabaseReq.builder() | ||
| 90 | - .databaseName(database) | ||
| 91 | - .build() | ||
| 92 | - ); | ||
| 93 | - log.info("数据库 xlyerp 创建成功"); | ||
| 94 | - } | ||
| 95 | - | ||
| 96 | - // 切换到xlyerp数据库 | ||
| 97 | - client.useDatabase (database); | ||
| 98 | - log.info("切换到数据库: {}",database); | ||
| 99 | - | ||
| 100 | - // 启动验证 | ||
| 101 | - if (validateOnStartup) { | ||
| 102 | - validateConnection(client); | ||
| 103 | - } | ||
| 104 | - return client; | ||
| 105 | - } catch (Exception e) { | ||
| 106 | - log.error("创建Milvus客户端时发生异常: {}", e.getMessage(), e); | ||
| 107 | - throw new RuntimeException("无法创建Milvus客户端", e); | ||
| 108 | - } | ||
| 109 | - | ||
| 110 | - } | ||
| 111 | - | ||
| 112 | - /** | ||
| 113 | - * 验证Milvus连接 - 基于源码中可用的方法 | ||
| 114 | - */ | ||
| 115 | - private void validateConnection(MilvusClientV2 client) { | ||
| 116 | - try { | ||
| 117 | - // 方法1: 检查服务器版本(源码中存在getServerVersion()) | ||
| 118 | - String serverVersion = client.getServerVersion(); | ||
| 119 | - | ||
| 120 | - // 方法2: 列出数据库(源码中存在listDatabases()) | ||
| 121 | - var databases = client.listDatabases(); | ||
| 122 | - | ||
| 123 | - // 方法3: 检查健康状态(源码中存在checkHealth()) | ||
| 124 | - var health = client.checkHealth(); | ||
| 125 | - | ||
| 126 | - System.out.println("✅ Milvus连接成功"); | ||
| 127 | - System.out.println(" - 服务器版本: " + serverVersion); | ||
| 128 | - System.out.println(" - 数据库: " + databases); | ||
| 129 | - System.out.println(" - 健康状态: " + health); | ||
| 130 | - | ||
| 131 | - // 验证指定数据库是否存在 | ||
| 132 | - if (database != null && !database.isEmpty()) { | ||
| 133 | - boolean dbExists = databases.getDatabaseNames().contains(database); | ||
| 134 | - if (!dbExists && !"default".equals(database)) { | ||
| 135 | - System.err.println("⚠️ 警告: 指定数据库 '" + database + "' 不存在"); | ||
| 136 | - } | ||
| 137 | - } | ||
| 138 | - | ||
| 139 | - } catch (Exception e) { | ||
| 140 | - String errorMsg = String.format("❌ Milvus连接失败: %s:%d - %s",host, port, e.getMessage()); | ||
| 141 | - | ||
| 142 | - if (failOnConnectError) { | ||
| 143 | - throw new IllegalStateException(errorMsg, e); | ||
| 144 | - } else { | ||
| 145 | - System.err.println(errorMsg); | ||
| 146 | - System.err.println(" ⚠️ 应用将继续启动,但Milvus功能可能不可用"); | ||
| 147 | - } | ||
| 148 | - } | ||
| 149 | - } | ||
| 150 | - | ||
| 151 | - /** | ||
| 152 | - * 配置重试模板(可选) | ||
| 153 | - */ | ||
| 154 | - @Bean | ||
| 155 | - public RetryTemplate milvusRetryTemplate() { | ||
| 156 | - return RetryTemplate.builder() | ||
| 157 | - .maxAttempts(3) | ||
| 158 | - .exponentialBackoff(1000, 2, 10000) | ||
| 159 | - .retryOn(Exception.class) | ||
| 160 | - .build(); | ||
| 161 | - } | ||
| 162 | -} | ||
| 163 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/milvus/config/MilvusHealthIndicator.java deleted
| 1 | -package com.xly.milvus.config; | ||
| 2 | - | ||
| 3 | -import io.milvus.v2.client.MilvusClientV2; | ||
| 4 | -import org.springframework.beans.factory.annotation.Autowired; | ||
| 5 | -import org.springframework.boot.actuate.health.Health; | ||
| 6 | -import org.springframework.boot.actuate.health.HealthIndicator; | ||
| 7 | -import org.springframework.stereotype.Component; | ||
| 8 | - | ||
| 9 | -import java.util.HashMap; | ||
| 10 | -import java.util.Map; | ||
| 11 | - | ||
| 12 | -@Component | ||
| 13 | -public class MilvusHealthIndicator implements HealthIndicator { | ||
| 14 | - | ||
| 15 | - @Autowired(required = false) | ||
| 16 | - private MilvusClientV2 milvusClient; | ||
| 17 | - | ||
| 18 | - @Override | ||
| 19 | - public Health health() { | ||
| 20 | - if (milvusClient == null) { | ||
| 21 | - return Health.down() | ||
| 22 | - .withDetail("error", "Milvus客户端未初始化") | ||
| 23 | - .build(); | ||
| 24 | - } | ||
| 25 | - | ||
| 26 | - try { | ||
| 27 | - // 检查客户端是否就绪(源码中存在clientIsReady()) | ||
| 28 | - boolean isReady = milvusClient.clientIsReady(); | ||
| 29 | - | ||
| 30 | - if (!isReady) { | ||
| 31 | - return Health.down() | ||
| 32 | - .withDetail("error", "客户端未就绪") | ||
| 33 | - .build(); | ||
| 34 | - } | ||
| 35 | - | ||
| 36 | - long startTime = System.currentTimeMillis(); | ||
| 37 | - | ||
| 38 | - // 执行健康检查 - 使用源码中存在的方法 | ||
| 39 | - String serverVersion = milvusClient.getServerVersion(); | ||
| 40 | - var databases = milvusClient.listDatabases(); | ||
| 41 | - var healthCheck = milvusClient.checkHealth(); | ||
| 42 | - | ||
| 43 | - long responseTime = System.currentTimeMillis() - startTime; | ||
| 44 | - | ||
| 45 | - Map<String, Object> details = new HashMap<>(); | ||
| 46 | - details.put("serverVersion", serverVersion); | ||
| 47 | - details.put("database", milvusClient.currentUsedDatabase()); | ||
| 48 | - details.put("databases", databases.getDatabaseNames()); | ||
| 49 | - details.put("healthStatus", healthCheck.getIsHealthy()); | ||
| 50 | - details.put("responseTime", responseTime + "ms"); | ||
| 51 | - details.put("clientReady", isReady); | ||
| 52 | - | ||
| 53 | - if (healthCheck.getQuotaStates() != null) { | ||
| 54 | - details.put("quotaStates", healthCheck.getQuotaStates()); | ||
| 55 | - } | ||
| 56 | - | ||
| 57 | - return Health.up() | ||
| 58 | - .withDetails(details) | ||
| 59 | - .build(); | ||
| 60 | - | ||
| 61 | - } catch (Exception e) { | ||
| 62 | - return Health.down() | ||
| 63 | - .withDetail("error", e.getMessage()) | ||
| 64 | - .withDetail("clientReady", milvusClient.clientIsReady()) | ||
| 65 | - .build(); | ||
| 66 | - } | ||
| 67 | - } | ||
| 68 | -} | ||
| 69 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/milvus/config/MilvusRetryConfig.java deleted
| 1 | -package com.xly.milvus.config; | ||
| 2 | - | ||
| 3 | -import io.milvus.v2.client.RetryConfig; | ||
| 4 | -import org.springframework.beans.factory.annotation.Value; | ||
| 5 | -import org.springframework.context.annotation.Bean; | ||
| 6 | -import org.springframework.context.annotation.Configuration; | ||
| 7 | - | ||
| 8 | -@Configuration | ||
| 9 | -public class MilvusRetryConfig { | ||
| 10 | - | ||
| 11 | - @Value("${milvus.retry.max-retry-times:75}") | ||
| 12 | - private int maxRetryTimes; | ||
| 13 | - | ||
| 14 | - @Value("${milvus.retry.initial-backoff-ms:10}") | ||
| 15 | - private long initialBackOffMs; | ||
| 16 | - | ||
| 17 | - @Value("${milvus.retry.max-backoff-ms:3000}") | ||
| 18 | - private long maxBackOffMs; | ||
| 19 | - | ||
| 20 | - @Value("${milvus.retry.backoff-multiplier:3}") | ||
| 21 | - private int backOffMultiplier; | ||
| 22 | - | ||
| 23 | - @Value("${milvus.retry.retry-on-rate-limit:true}") | ||
| 24 | - private boolean retryOnRateLimit; | ||
| 25 | - | ||
| 26 | - @Value("${milvus.retry.max-retry-timeout-ms:0}") | ||
| 27 | - private long maxRetryTimeoutMs; | ||
| 28 | - | ||
| 29 | - @Value("${milvus.retry.enabled:true}") | ||
| 30 | - private boolean retryEnabled; | ||
| 31 | - | ||
| 32 | - /** | ||
| 33 | - * 方法名改为 createRetryConfig,避免与类名冲突 | ||
| 34 | - */ | ||
| 35 | - @Bean | ||
| 36 | - public RetryConfig createRetryConfig() { | ||
| 37 | - if (!retryEnabled) { | ||
| 38 | - return RetryConfig.builder() | ||
| 39 | - .maxRetryTimes(1) | ||
| 40 | - .initialBackOffMs(0) | ||
| 41 | - .maxBackOffMs(0) | ||
| 42 | - .backOffMultiplier(1) | ||
| 43 | - .retryOnRateLimit(false) | ||
| 44 | - .maxRetryTimeoutMs(0) | ||
| 45 | - .build(); | ||
| 46 | - } | ||
| 47 | - | ||
| 48 | - return RetryConfig.builder() | ||
| 49 | - .maxRetryTimes(maxRetryTimes) | ||
| 50 | - .initialBackOffMs(initialBackOffMs) | ||
| 51 | - .maxBackOffMs(maxBackOffMs) | ||
| 52 | - .backOffMultiplier(backOffMultiplier) | ||
| 53 | - .retryOnRateLimit(retryOnRateLimit) | ||
| 54 | - .maxRetryTimeoutMs(maxRetryTimeoutMs) | ||
| 55 | - .build(); | ||
| 56 | - } | ||
| 57 | -} | ||
| 58 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/milvus/config/MilvusStartupValidator.java deleted
| 1 | -package com.xly.milvus.config; | ||
| 2 | - | ||
| 3 | -import io.milvus.v2.client.MilvusClientV2; | ||
| 4 | -import io.milvus.v2.service.collection.request.HasCollectionReq; | ||
| 5 | -import lombok.extern.slf4j.Slf4j; | ||
| 6 | -import org.slf4j.Logger; | ||
| 7 | -import org.slf4j.LoggerFactory; | ||
| 8 | -import org.springframework.beans.factory.annotation.Autowired; | ||
| 9 | -import org.springframework.beans.factory.annotation.Value; | ||
| 10 | -import org.springframework.boot.ApplicationArguments; | ||
| 11 | -import org.springframework.boot.ApplicationRunner; | ||
| 12 | -import org.springframework.core.annotation.Order; | ||
| 13 | -import org.springframework.stereotype.Component; | ||
| 14 | - | ||
| 15 | -import java.util.List; | ||
| 16 | - | ||
| 17 | -@Slf4j | ||
| 18 | -@Component | ||
| 19 | -@Order(1) | ||
| 20 | -public class MilvusStartupValidator implements ApplicationRunner { | ||
| 21 | - | ||
| 22 | - @Autowired | ||
| 23 | - private MilvusClientV2 milvusClient; | ||
| 24 | - | ||
| 25 | - @Value("${milvus.validation.collections:}") | ||
| 26 | - private List<String> validationCollections; | ||
| 27 | - | ||
| 28 | - @Value("${milvus.validation.timeout:5000}") | ||
| 29 | - private long validationTimeout; | ||
| 30 | - | ||
| 31 | - @Override | ||
| 32 | - public void run(ApplicationArguments args) throws Exception { | ||
| 33 | - log.info("开始Milvus启动验证..."); | ||
| 34 | - | ||
| 35 | - try { | ||
| 36 | - // 1. 验证客户端就绪状态 | ||
| 37 | - boolean isReady = milvusClient.clientIsReady(); | ||
| 38 | - log.info("客户端就绪状态: {}", isReady); | ||
| 39 | - | ||
| 40 | - // 2. 获取服务器版本 | ||
| 41 | - String serverVersion = milvusClient.getServerVersion(); | ||
| 42 | - log.info("Milvus服务器版本: {}", serverVersion); | ||
| 43 | - | ||
| 44 | - // 3. 列出所有数据库 | ||
| 45 | - var databases = milvusClient.listDatabases(); | ||
| 46 | - log.info("可用数据库: {}", databases.getDatabaseNames()); | ||
| 47 | - | ||
| 48 | - // 4. 检查当前使用的数据库 | ||
| 49 | - String currentDb = milvusClient.currentUsedDatabase(); | ||
| 50 | - log.info("当前数据库: {}", currentDb); | ||
| 51 | - | ||
| 52 | - // 5. 健康检查 | ||
| 53 | - var healthCheck = milvusClient.checkHealth(); | ||
| 54 | - log.info("健康状态: {}", healthCheck.getIsHealthy()); | ||
| 55 | - if (healthCheck.getQuotaStates() != null && !healthCheck.getQuotaStates().isEmpty()) { | ||
| 56 | - log.info("配额状态: {}", healthCheck.getQuotaStates()); | ||
| 57 | - } | ||
| 58 | - | ||
| 59 | - // 6. 验证指定的集合(可选) | ||
| 60 | - if (validationCollections != null && !validationCollections.isEmpty()) { | ||
| 61 | - for (String collectionName : validationCollections) { | ||
| 62 | - boolean exists = milvusClient.hasCollection( | ||
| 63 | - HasCollectionReq.builder() | ||
| 64 | - .collectionName(collectionName) | ||
| 65 | - .build() | ||
| 66 | - ); | ||
| 67 | - log.info("集合 '{}' 存在: {}", collectionName, exists); | ||
| 68 | - } | ||
| 69 | - } | ||
| 70 | - | ||
| 71 | - log.info("Milvus启动验证完成"); | ||
| 72 | - | ||
| 73 | - } catch (Exception e) { | ||
| 74 | - log.error("Milvus启动验证失败: {}", e.getMessage(), e); | ||
| 75 | - // 可以根据配置决定是否抛出异常 | ||
| 76 | - // throw new RuntimeException("Milvus验证失败", e); | ||
| 77 | - } | ||
| 78 | - } | ||
| 79 | -} | ||
| 80 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/milvus/config/SearchResult.java deleted
| 1 | -package com.xly.milvus.config; | ||
| 2 | - | ||
| 3 | -import lombok.AllArgsConstructor; | ||
| 4 | -import lombok.Builder; | ||
| 5 | -import lombok.Data; | ||
| 6 | -import lombok.NoArgsConstructor; | ||
| 7 | - | ||
| 8 | -import java.util.Map; | ||
| 9 | - | ||
| 10 | -/** | ||
| 11 | - * 搜索结果实体 | ||
| 12 | - */ | ||
| 13 | -@Data | ||
| 14 | -@Builder | ||
| 15 | -@NoArgsConstructor | ||
| 16 | -@AllArgsConstructor | ||
| 17 | -public class SearchResult { | ||
| 18 | - private Long id; | ||
| 19 | - private Float score; | ||
| 20 | - private Map<String, Object> fields; | ||
| 21 | - private String collectionName; | ||
| 22 | -} | ||
| 23 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/milvus/service/AiGlobalAgentQuestionSqlEmitterService.java deleted
| 1 | -package com.xly.milvus.service; | ||
| 2 | - | ||
| 3 | - | ||
| 4 | -import java.util.Map; | ||
| 5 | - | ||
| 6 | -public interface AiGlobalAgentQuestionSqlEmitterService { | ||
| 7 | - | ||
| 8 | - /*** | ||
| 9 | - * @Author 钱豹 | ||
| 10 | - * @Date 15:27 2026/3/19 | ||
| 11 | - * @Param [data, sQuestion, sSqlContent, collectionName] | ||
| 12 | - * @return void | ||
| 13 | - * @Description 插入向量库 | ||
| 14 | - **/ | ||
| 15 | - void addAiGlobalAgentQuestionSqlEmitter(String sKey,Map<String,Object> data,String sQuestion,String sSqlContent,String cachType,String collectionName,Boolean isInit); | ||
| 16 | - | ||
| 17 | - | ||
| 18 | - Map<String, Object> queryAiGlobalAgentQuestionSqlEmitter(String searchText, String collectionName); | ||
| 19 | - | ||
| 20 | - void removeAndCreateCollection(String collectionName); | ||
| 21 | -} | ||
| 22 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/milvus/service/EmbeddingService.java deleted
| 1 | -package com.xly.milvus.service; | ||
| 2 | - | ||
| 3 | -import java.util.List; | ||
| 4 | - | ||
| 5 | -public interface EmbeddingService { | ||
| 6 | - | ||
| 7 | - /** | ||
| 8 | - * 生成单个文本的向量 | ||
| 9 | - */ | ||
| 10 | - public List<Float> generateEmbedding(String text); | ||
| 11 | - | ||
| 12 | - /** | ||
| 13 | - * 批量生成向量(高效版) | ||
| 14 | - * 利用 LangChain4j 内置的并行化能力,显著提升性能 | ||
| 15 | - */ | ||
| 16 | - public List<List<Float>> generateEmbeddings(List<String> texts); | ||
| 17 | -} | ||
| 18 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/milvus/service/MilvusService.java deleted
| 1 | -package com.xly.milvus.service; | ||
| 2 | - | ||
| 3 | -import com.xly.tts.bean.TTSResponseDTO; | ||
| 4 | - | ||
| 5 | -import java.util.ArrayList; | ||
| 6 | -import java.util.HashMap; | ||
| 7 | -import java.util.List; | ||
| 8 | -import java.util.Map; | ||
| 9 | - | ||
| 10 | -/** | ||
| 11 | - * 向量化服务接口 | ||
| 12 | - */ | ||
| 13 | -public interface MilvusService { | ||
| 14 | - | ||
| 15 | - /*** | ||
| 16 | - * @Author 钱豹 | ||
| 17 | - * @Date 22:17 2026/3/24 | ||
| 18 | - * @Param | ||
| 19 | - * @return | ||
| 20 | - * @Description 初始化数据 | ||
| 21 | - **/ | ||
| 22 | - | ||
| 23 | - TTSResponseDTO initDataToMilvus(Map<String, Object> reqMap); | ||
| 24 | - | ||
| 25 | - /*** | ||
| 26 | - * @Author 钱豹 | ||
| 27 | - * @Date 9:00 2026/4/1 | ||
| 28 | - * @Param [reqMap] | ||
| 29 | - * @return com.xly.tts.bean.TTSResponseDTO | ||
| 30 | - * @Description 客户问题库转入全局问题库 | ||
| 31 | - **/ | ||
| 32 | - TTSResponseDTO addGlobalAgentQuestion(Map<String, Object> reqMap); | ||
| 33 | - | ||
| 34 | - /*** | ||
| 35 | - * @Author 钱豹 | ||
| 36 | - * @Date 9:28 2026/4/1 | ||
| 37 | - * @Param [reqMap] | ||
| 38 | - * @return com.xly.tts.bean.TTSResponseDTO | ||
| 39 | - * @Description 初始化全局SQL进入向量库 | ||
| 40 | - **/ | ||
| 41 | - TTSResponseDTO initGlobalAgentQuestion(Map<String, Object> reqMap); | ||
| 42 | - | ||
| 43 | - | ||
| 44 | - /** | ||
| 45 | - * 创建集合(如果不存在) | ||
| 46 | - */ | ||
| 47 | - void createCollectionIfNotExists(String collectionName, String sVectorfiled, String sVectorjson, Boolean bRset); | ||
| 48 | - | ||
| 49 | - | ||
| 50 | - /*** | ||
| 51 | - * @Author 钱豹 | ||
| 52 | - * @Date 21:39 2026/3/24 | ||
| 53 | - * @Param [collectionName, sVectorfiled, sVectorjson, data] | ||
| 54 | - * @return long | ||
| 55 | - * @Description 批量插入数据 | ||
| 56 | - **/ | ||
| 57 | - long addDataToCollection(String collectionName, String sVectorfiled, String sVectorjson, List<Map<String, Object>> data,String sceneName); | ||
| 58 | - | ||
| 59 | - /*** | ||
| 60 | - * @Author 钱豹 | ||
| 61 | - * @Date 10:39 2026/3/25 | ||
| 62 | - * @Param | ||
| 63 | - * @return | ||
| 64 | - * @Description 向量库查询 | ||
| 65 | - **/ | ||
| 66 | - List<Map<String, Object>> getDataToCollection(String collectionName, String milvusFilter,String searchText,Integer size,List<String> fields,String vectorValue,String sceneName); | ||
| 67 | - | ||
| 68 | - /*** | ||
| 69 | - * @Author 钱豹 | ||
| 70 | - * @Date 10:56 2026/3/25 | ||
| 71 | - * @Param [sVectorfiled] | ||
| 72 | - * @return java.util.Map<java.lang.String,java.lang.Object> | ||
| 73 | - * @Description 获取配置 | ||
| 74 | - **/ | ||
| 75 | - Map<String,Object> getMilvusFiled(String sVectorfiled,String sVectorfiledAll,String sVectorfiledShow,String sVectorjson); | ||
| 76 | - | ||
| 77 | - boolean isStringFilterValid(String filter,String collectionName); | ||
| 78 | -} | ||
| 79 | \ No newline at end of file | 0 | \ No newline at end of file |
src/main/java/com/xly/milvus/service/VectorizationService.java deleted
| 1 | -package com.xly.milvus.service; | ||
| 2 | - | ||
| 3 | -import java.util.List; | ||
| 4 | - | ||
| 5 | -/** | ||
| 6 | - * 向量化服务接口 | ||
| 7 | - */ | ||
| 8 | -public interface VectorizationService { | ||
| 9 | - | ||
| 10 | - /** | ||
| 11 | - * 将文本向量化 | ||
| 12 | - * @param text 文本内容 | ||
| 13 | - * @return 向量数组 | ||
| 14 | - */ | ||
| 15 | - List<Float> textToVector(String text); | ||
| 16 | - | ||
| 17 | - /** | ||
| 18 | - * 批量向量化 | ||
| 19 | - * @param texts 文本列表 | ||
| 20 | - * @return 向量列表 | ||
| 21 | - */ | ||
| 22 | - List<List<Float>> batchTextToVector(List<String> texts); | ||
| 23 | -} | ||
| 24 | \ No newline at end of file | 0 | \ No newline at end of file |