Commit d72de0c4fe4d50edf536174f6b7f6fa97d18ac96

Authored by qianbao
1 parent b9b48f2e

添加向量库

... ... @@ -105,6 +105,12 @@
105 105 <groupId>org.springframework.boot</groupId>
106 106 <artifactId>spring-boot-starter-webflux</artifactId>
107 107 </dependency>
  108 + <!-- pom.xml -->
  109 + <dependency>
  110 + <groupId>com.github.jsqlparser</groupId>
  111 + <artifactId>jsqlparser</artifactId>
  112 + <version>4.9</version>
  113 + </dependency>
108 114  
109 115 <!-- Tess4J OCR -->
110 116 <dependency>
... ...
src/main/java/com/xly/agent/ErpAiAgent.java
... ... @@ -93,24 +93,18 @@ public interface ErpAiAgent {
93 93  
94 94 @SystemMessage("""
95 95 你是一个智能查询路由专家。请根据【用户需求】,只返回 true 或 false。
96   -
97 96 【最高优先级规则 - 必须首先判断】
98 97 如果用户需求包含以下任一关键词,**直接返回 false**,不再进行其他判断:
99   - - 明细、详情、详细信息、详细内容、具体内容
100   - - 查询...明细、...详情、...记录、...列表、...清单
101   -
  98 + - 明细、列表、清单
  99 + - ...明细、...列表、...清单
102 100 重要:只要出现以上关键词,说明用户需要的是明细数据查询,而非统计分析。
103   -
104 101 【统计类关键词 - 仅在满足最高优先级规则后才判断】
105 102 只有当用户需求不包含上述明细类关键词时,才检查是否包含以下关键词:
106 103 统计、求和、汇总、排名、TopN、平均、数量、总额、最高、最低、占比、分组
107   -
108 104 - 如果包含,返回 true
109 105 - 否则返回 false
110   -
111 106 【判断示例】
112 107 - \"查询中科精工集团的彩盒类产品的报价单明细\" → false(包含\"明细\")
113   - - \"统计各产品销售额\" → true(包含\"统计\",且无明细关键词)
114 108 - \"查询客户张三信息\" → false(无统计关键词,无明细关键词)
115 109 - \"销售额排名前10的产品\" → true(包含\"排名\",且无明细关键词)
116 110 - \"查看销售订单明细\" → false(包含\"明细\")
... ... @@ -119,90 +113,264 @@ public interface ErpAiAgent {
119 113 【用户需求】
120 114 {{userInput}}
121 115 """)
  116 +// @SystemMessage("""
  117 +// 你是一个智能查询路由专家,请根据【用户需求】,基于**查询效率最优**原则,自动判断使用关系型数据库(MySQL)还是向量库(Milvus),只返回 true 或 false。
  118 +// 满足如下规则中任意一条则返回true 否则返回false:
  119 +// - 查询涉及**排名、TOP N、求和、计数、平均值、最大值、总额、最小值、最高、最低、占比**
  120 +// - 查询涉及**分组统计(GROUP BY)、排序(ORDER BY)、分页(LIMIT)**
  121 +// """)
  122 +// @UserMessage("""
  123 +// 【用户需求】
  124 +// {{userInput}}
  125 +// """)
122 126 Boolean routeQuery(@MemoryId String userId, @V("userInput") String userInput);
123 127  
124 128 /**
125 129 * 生成 Milvus 过滤条件(适配 Milvus v2.3.9)
126 130 */
127 131 @SystemMessage("""
128   - MILVUS 标量过滤条件生成规则(严格遵守 - 当前版本 v2.3.9):
129   -
130   - 【重要输出约束】
131   - - 必须返回有效的 Milvus 过滤条件表达式
132   - - 禁止返回 true 或 false
133   - - 禁止返回空字符串以外的任何非表达式内容
134   - - 无条件时只返回空字符串 ""
135   -
136   - 1. 语法规范:
137   - - 允许的操作符:==, !=, like
138   - - 逻辑组合:&& (AND), || (OR)
139   - - 所有字段都是字符串类型,值必须使用单引号包裹
140   - - 字符串中的单引号需要转义:'O''Reilly'
141   -
142   - 2. 【重要】Milvus v2.3.9 like 操作符限制:
143   - - ✅ 支持:like '关键字%'(前缀匹配,以关键字开头)
144   - - ❌ 不支持:like '%关键字%'(包含匹配)
145   - - ❌ 不支持:like '%关键字'(后缀匹配)
146   -
147   - 3. 可用字段(只能使用这些字段):
148   - - {{sMilvusFiled}}
149   - 字段说明:
150   - - {{sMilvusFiledDescription}}
151   -
152   - 4. 提取规则:
153   - - 只使用上述可用字段,不要创建新字段
154   - - 如果用户提到了文档类型(如"报价单"、"订单"等),但可用字段中没有类型字段,则忽略该条件
155   -
156   - 【精确匹配规则】:
157   - - 当用户提供明确值时:字段 == '值'
158   - * 例如:"客户名称中科精工" → sCustomerName == '中科精工'
159   - * 例如:"单据号 INV001" → sBillNo == 'INV001'
160   -
161   - 5. 时间处理规则:
162   - - 当前系统时间:{{sDataNow}}(格式:yyyy-MM-dd)
163   - - 相对时间转换规则:
164   - * "今天/今日" → 当天 00:00:00 到 23:59:59
165   - * "昨天" → 前一天 00:00:00 到 23:59:59
166   - * "本周" → 本周一 00:00:00 到本周日 23:59:59
167   - * "本月" → 本月1日 00:00:00 到本月最后一天 23:59:59
168   - * "本年" → 本年1月1日 00:00:00 到本年12月31日 23:59:59
169   - * "近X天" → 从 X 天前 00:00:00 到今天 23:59:59
170   - - 日期转时间戳:所有日期转换为 Unix 时间戳(秒)
171   - - 时间范围格式:字段 >= 起始时间戳 && 字段 <= 结束时间戳
172   - - 如果没有明确的时间需求,不要添加任何时间过滤条件
173   -
174   - 6. 示例:
175   - ✅ 正确输出:
176   - - "客户名称中科精工" → sCustomerName == '中科精工'
177   - - "中科精工的报价单明细" → sCustomerName == '中科精工'
178   - - "产品以彩盒开头" → sProductStyle like '彩盒%'
179   - - "无条件" → ""
180   -
181   - ❌ 错误输出(禁止):
182   - - "中科精工的报价单明细" → true
183   - - "中科精工的报价单明细" → false
184   - - "中科精工的报价单明细" → 1
185   -
186   - 7. 输出格式:
187   - - 仅返回纯过滤条件,无任何解释、换行、备注
188   - - 单条件:sCustomerName == '中科精工'
189   - - 多条件:(sCustomerName == '中科精工' && sProductStyle like '彩盒%')
190   - - 无条件:直接返回空字符串 ""
  132 + MILVUS 查询条件生成规则:
  133 +
  134 + 【最高优先级 - 输出格式铁律】
  135 + ⚠️ 你的【全部输出】必须是且仅是一个合法的 JSON 对象
  136 + ⚠️ 禁止输出任何解释、说明、思考过程
  137 + ⚠️ 禁止输出任何中文文字
  138 + ⚠️ 只能输出以下 JSON 格式,不能有其他任何内容
  139 +
  140 + 【输出 JSON 结构】
  141 + {
  142 + "sMethodName": true/false, // 必选,判断用户意图是否匹配当前方法
  143 + "vectorField": "向量字段名", // 可选,需要语义匹配时返回
  144 + "vectorValue": "向量化文本", // 可选,用于向量检索的文本
  145 + "filterExpression": "标量过滤表达式" // 可选,有标量条件时返回
  146 + }
  147 +
  148 + 【方法匹配规则 - 重要】
  149 + sMethodName 的取值逻辑:
  150 + - 如果用户输入的意图与 {{sMethodName}} 相关,返回 true
  151 + - 否则返回 false
  152 +
  153 + 判断标准:用户是否在询问或操作与 {{sMethodName}} 相关的业务数据
  154 + - 包括但不限于:查询、搜索、推荐、找、查看、明细、列表、详情等
  155 + - 只要用户想获取或操作这类数据,就应该返回 true
  156 +
  157 + 示例:
  158 + - 方法名称:查询报价单
  159 + - "报价单明细" → true(想查看报价单数据)
  160 + - "报价单列表" → true(想查看报价单数据)
  161 + - "报价单" → true(想查看报价单数据)
  162 + - "查一下报价单" → true(想查询报价单)
  163 + - "推荐相似报价单" → true(想推荐报价单)
  164 + - "你好" → false(与报价单无关)
  165 + - "查询客户信息" → false(与报价单无关)
  166 +
  167 + 【重要:理解你的数据结构】
  168 +
  169 + 你有两种类型的字段:
  170 +
  171 + 1. 标量字段(用于精确过滤):
  172 + - {{sMilvusFiled}} 中的字段
  173 + - 根据提供的字段说明使用
  174 +
  175 + 2. 向量字段(用于语义搜索):
  176 + - 字段名:使用 {{sMilvusFiledXl}} 中提供的向量字段名
  177 + - 存储格式:管道符分隔的键值对 "字段名:值|字段名:值|..."
  178 + - 包含的业务数据:{{sMilvusFiledDescriptionXl}}
  179 + - 示例:"sCustomerName:上海小羚羊|sProductName:2028宣传海报|dProductQty:1000|sType:报价丢单原因"
  180 +
  181 + 【向量搜索规则】
  182 +
  183 + 使用向量搜索的条件:用户明确表达语义匹配意图
  184 + - 关键词:找相似、推荐、匹配、类似、相关、类似的、相似的、推荐一下
  185 + - 使用向量搜索时:
  186 + 1. 从用户问题中提取关键业务实体
  187 + 2. 格式化为:"字段名:值|字段名:值"(管道符分隔)
  188 + 3. 示例:
  189 + - 用户:"找丢单原因类似价格太高的记录" → "sReason:价格太高|sType:报价丢单原因"
  190 + - 用户:"推荐和上海小羚羊类似的客户" → "sCustomerName:上海小羚羊"
  191 + - 用户:"找类似2028宣传海报的产品" → "sProductName:2028宣传海报"
  192 +
  193 + 【标量过滤规则 - 极其重要】
  194 +
  195 + 可用标量字段(根据 {{sMilvusFiled}} 动态提供):
  196 + - 使用提供的字段名
  197 + - 【字符串字段】:统一使用 like 操作符(前缀匹配)
  198 + - 【数字字段】:使用 >、<、>=、<=、== 操作符
  199 + - 【时间字段】:使用时间戳范围
  200 +
  201 + 【操作符使用规则】
  202 +
  203 + 1. 字符串字段(统一使用 like):
  204 + - 语法:字段名 like '关键词%'
  205 + - 说明:只支持前缀匹配,不支持 '%keyword%' 或 '%keyword'
  206 + - 示例:
  207 + - 用户:"上海小羚羊" → sCustomerName like '上海小羚羊%'
  208 + - 用户:"客户名称包含上海" → sCustomerName like '上海%'
  209 + - 用户:"姓张的销售人员" → sSalesManName like '张%'
  210 + - 用户:"产品名称以2028开头" → sProductName like '2028%'
  211 + - 用户:"张三" → sSalesManName like '张三%'
  212 +
  213 + 2. 数字字段:
  214 + - 精确匹配:字段名 == 值
  215 + - 范围匹配:字段名 >= 值 && 字段名 <= 值
  216 + - 大于:字段名 > 值
  217 + - 小于:字段名 < 值
  218 + - 示例:
  219 + - 用户:"数量1000" → dProductQty == 1000
  220 + - 用户:"数量大于1000" → dProductQty > 1000
  221 + - 用户:"数量在500到1000之间" → dProductQty >= 500 && dProductQty <= 1000
  222 +
  223 + 3. 时间字段:
  224 + - 使用时间戳范围:tCreateDate >= 开始时间戳 && tCreateDate <= 结束时间戳
  225 + - 时间戳为 Unix 秒
  226 +
  227 + 4. 组合条件:
  228 + - 使用 && (AND) 连接多个条件
  229 + - 使用 || (OR) 连接或条件
  230 + - 复杂条件用括号分组
  231 +
  232 + 【核心约束 - 必须遵守】
  233 + 1. 只有当用户【明确指定】具体条件时,才生成 filterExpression
  234 + 2. 禁止为模糊查询添加默认过滤条件(如时间范围)
  235 + 3. 禁止自动添加任何默认条件
  236 + 4. 当前时间戳:{{sDataNow}}(Unix秒)
  237 + 5. 字符串字段禁止使用 ==,必须使用 like
  238 + 6. like 只支持前缀匹配,不支持 '%keyword%' 或 '%keyword' 格式
  239 +
  240 + 【查询意图判断】
  241 +
  242 + 类型A - 纯标量查询(只返回 filterExpression):
  243 + - 用户明确指定了标量字段的具体条件
  244 + - 不包含语义匹配词
  245 + - 示例1:"上海小羚羊" → {"sMethodName": true, "filterExpression": "sCustomerName like '上海小羚羊%'"}
  246 + - 示例2:"客户名称包含上海" → {"sMethodName": true, "filterExpression": "sCustomerName like '上海%'"}
  247 + - 示例3:"张三" → {"sMethodName": true, "filterExpression": "sSalesManName like '张三%'"}
  248 + - 示例4:"数量大于1000" → {"sMethodName": true, "filterExpression": "dProductQty > 1000"}
  249 + - 示例5:"今天创建的报价单" → {"sMethodName": true, "filterExpression": "tCreateDate >= 开始时间戳 && tCreateDate <= 结束时间戳"}
  250 +
  251 + 类型B - 纯向量搜索(只返回 vectorField 和 vectorValue):
  252 + - 用户只有语义意图,无具体标量条件
  253 + - 示例:"找一些类似的报价单" → {"sMethodName": true, "vectorField": "向量字段名", "vectorValue": "sType:报价丢单原因"}
  254 +
  255 + 类型C - 混合查询(同时返回 vectorField、vectorValue 和 filterExpression):
  256 + - 用户既有具体条件,又需要语义匹配
  257 + - 示例:"找丢单原因类似价格太高的上海小羚羊报价单" →
  258 + {"sMethodName": true, "vectorField": "向量字段名", "vectorValue": "sReason:价格太高|sType:报价丢单原因", "filterExpression": "sCustomerName like '上海小羚羊%'"}
  259 +
  260 + 类型D - 意图不匹配(只返回 sMethodName: false):
  261 + - 用户输入与 {{sMethodName}} 完全无关
  262 + - 示例:"你好" → {"sMethodName": false}
  263 + - 示例:"查询客户信息"(如果方法名是"查询报价单") → {"sMethodName": false}
  264 +
  265 + 类型E - 意图匹配但无具体条件(只返回 sMethodName: true):
  266 + - 用户想查询该类数据,但没有指定任何条件
  267 + - 示例:"报价单明细" → {"sMethodName": true}
  268 + - 示例:"报价单列表" → {"sMethodName": true}
  269 + - 示例:"报价单" → {"sMethodName": true}
  270 +
  271 + 【时间处理规则】
  272 +
  273 + 当用户明确提到时间时,基于当前时间 {{sDataNow}} 计算:
  274 + - "今天":当天 00:00:00 到 23:59:59
  275 + - "昨天":前一天 00:00:00 到 23:59:59
  276 + - "本周":本周一 00:00:00 到本周日 23:59:59
  277 + - "本月":本月1日 00:00:00 到本月最后一天 23:59:59
  278 + - "上个月":上个月1日 00:00:00 到上个月最后一天 23:59:59
  279 + - "近X天":X天前 00:00:00 到今天 23:59:59
  280 +
  281 + 时间范围表达式格式:tCreateDate >= 开始时间戳 && tCreateDate <= 结束时间戳
  282 +
  283 + 【重要约束汇总】
  284 + 1. 只输出 JSON,不要有任何其他内容
  285 + 2. 只使用提供的标量字段
  286 + 3. 【关键】字符串字段必须使用 like,禁止使用 ==
  287 + 4. 时间范围必须使用 Unix 时间戳(秒)
  288 + 5. 向量字段名使用 {{sMilvusFiledXl}} 中提供的字段名
  289 + 6. vectorValue 必须保持管道符分隔格式:"字段名:值|字段名:值"
  290 + 7. 禁止为模糊查询添加默认条件
  291 + 8. sMethodName 只要用户意图与当前方法相关就返回 true
  292 + 9. like 只支持前缀匹配,格式:字段名 like '值%'
  293 +
  294 + 【输出示例】
  295 + 方法名称:查询报价单
  296 +
  297 + 示例1:模糊查询 - "报价单明细"
  298 + 输出:{"sMethodName": true}
  299 +
  300 + 示例2:字符串模糊匹配 - "上海小羚羊"
  301 + 输出:{"sMethodName": true, "filterExpression": "sCustomerName like '上海小羚羊%'"}
  302 +
  303 + 示例3:字符串模糊匹配 - "客户名称包含上海"
  304 + 输出:{"sMethodName": true, "filterExpression": "sCustomerName like '上海%'"}
  305 +
  306 + 示例4:字符串模糊匹配 - "张三"
  307 + 输出:{"sMethodName": true, "filterExpression": "sSalesManName like '张三%'"}
  308 +
  309 + 示例5:数字范围 - "数量大于1000"
  310 + 输出:{"sMethodName": true, "filterExpression": "dProductQty > 1000"}
  311 +
  312 + 示例6:时间范围 - "今天创建的报价单"
  313 + 输出:{"sMethodName": true, "filterExpression": "tCreateDate >= 1774754400 && tCreateDate <= 1774840799"}
  314 +
  315 + 示例7:组合条件 - "上海小羚羊且数量大于1000"
  316 + 输出:{"sMethodName": true, "filterExpression": "sCustomerName like '上海小羚羊%' && dProductQty > 1000"}
  317 +
  318 + 示例8:纯向量 - "找一些类似的报价单"
  319 + 输出:{"sMethodName": true, "vectorField": "content_embedding", "vectorValue": "sType:报价丢单原因"}
  320 +
  321 + 示例9:混合查询 - "找丢单原因类似价格太高的上海小羚羊报价单"
  322 + 输出:{"sMethodName": true, "vectorField": "content_embedding", "vectorValue": "sReason:价格太高|sType:报价丢单原因", "filterExpression": "sCustomerName like '上海小羚羊%'"}
  323 +
  324 + 示例10:意图不匹配 - "你好"
  325 + 输出:{"sMethodName": false}
  326 +
  327 + 【标量字段列表】
  328 + {{sMilvusFiled}}
  329 +
  330 + 【标量字段说明】
  331 + {{sMilvusFiledDescription}}
  332 +
  333 + 【向量字段列表】
  334 + {{sMilvusFiledXl}}
  335 +
  336 + 【向量字段说明】
  337 + {{sMilvusFiledDescriptionXl}}
  338 +
  339 + 【方法名称】
  340 + {{sMethodName}}
  341 +
191 342 """)
192   - @UserMessage("""
193   - 【用户查询】
194   - - {{userInput}}
  343 + @UserMessage("""
  344 + 【用户输入】
  345 + {{userInput}}
  346 +
195 347 【当前时间】
196   - - {{sDataNow}}
197   - 【可用字段】
198   - - {{sMilvusFiled}}
199   - 【字段说明】
200   - - {{sMilvusFiledDescription}}
201   - """)
  348 + {{sDataNow}}
  349 +
  350 + 【标量字段】
  351 + {{sMilvusFiled}}
  352 +
  353 + 【标量字段说明】
  354 + {{sMilvusFiledDescription}}
  355 +
  356 + 【向量字段】
  357 + {{sMilvusFiledXl}}
  358 +
  359 + 【向量字段说明】
  360 + {{sMilvusFiledDescriptionXl}}
  361 +
  362 + 【方法名称】
  363 + {{sMethodName}}
  364 +
  365 + 请根据以上规则,输出 JSON 格式结果。
  366 +""")
202 367 String getMilvusFilter(@MemoryId String userId,
203 368 @V("userInput") String userInput,
204 369 @V("sMilvusFiled") String sMilvusFiled,
205 370 @V("sMilvusFiledDescription") String sMilvusFiledDescription,
206   - @V("sDataNow") String sDataNow);
207   -
  371 + @V("sMilvusFiledXl") String sMilvusFiledXl,
  372 + @V("sMilvusFiledDescriptionXl") String sMilvusFiledDescriptionXl,
  373 + @V("sDataNow") long sDataNow,
  374 + @V("sMethodName") String sMethodName
  375 + );
208 376 }
... ...
src/main/java/com/xly/milvus/service/MilvusService.java
... ... @@ -36,7 +36,7 @@ public interface MilvusService {
36 36 * @return long
37 37 * @Description 批量插入数据
38 38 **/
39   - long addDataToCollection(String collectionName, String sVectorfiled, String sVectorjson, List<Map<String, Object>> data);
  39 + long addDataToCollection(String collectionName, String sVectorfiled, String sVectorjson, List<Map<String, Object>> data,String sceneName);
40 40  
41 41 /***
42 42 * @Author 钱豹
... ... @@ -45,7 +45,7 @@ public interface MilvusService {
45 45 * @return
46 46 * @Description 向量库查询
47 47 **/
48   - List<Map<String, Object>> getDataToCollection(String collectionName, String milvusFilter,String searchText,Integer size,List<String> fields);
  48 + List<Map<String, Object>> getDataToCollection(String collectionName, String milvusFilter,String searchText,Integer size,List<String> fields,String vectorValue,String sceneName);
49 49  
50 50 /***
51 51 * @Author 钱豹
... ... @@ -54,7 +54,7 @@ public interface MilvusService {
54 54 * @return java.util.Map<java.lang.String,java.lang.Object>
55 55 * @Description 获取配置
56 56 **/
57   - Map<String,Object> getMilvusFiled(String sVectorfiled,String sVectorfiledAll,String sVectorfiledShow);
  57 + Map<String,Object> getMilvusFiled(String sVectorfiled,String sVectorfiledAll,String sVectorfiledShow,String sVectorjson);
58 58  
59   - boolean isValidMilvusFilter(String milvusFilter);
  59 + boolean isStringFilterValid(String filter,String collectionName);
60 60 }
61 61 \ No newline at end of file
... ...
src/main/java/com/xly/milvus/service/impl/AiGlobalAgentQuestionSqlEmitterServiceImpl.java
... ... @@ -8,6 +8,7 @@ import com.google.gson.JsonObject;
8 8 import com.xly.milvus.service.AiGlobalAgentQuestionSqlEmitterService;
9 9 import com.xly.milvus.service.VectorizationService;
10 10 import com.xly.milvus.util.MapToJsonConverter;
  11 +import com.xly.service.DynamicExeDbService;
11 12 import io.milvus.v2.client.MilvusClientV2;
12 13 import io.milvus.v2.common.ConsistencyLevel;
13 14 import io.milvus.v2.common.DataType;
... ... @@ -37,11 +38,15 @@ public class AiGlobalAgentQuestionSqlEmitterServiceImpl implements AiGlobalAgent
37 38  
38 39 private final MilvusClientV2 milvusClient;
39 40 private final VectorizationService vectorizationService;
  41 + private final DynamicExeDbService dynamicExeDbService;
  42 + private final String sProName ="Sp_Ai_AiGlobalAgentQuestionThread";
  43 +
40 44  
41 45 // 或者从配置文件读取
42 46 @Value("${milvus.vector.dimension:384}")
43 47 private int VECTOR_DIM;
44 48  
  49 +
45 50 // 缓存已加载的集合
46 51 private final Set<String> loadedCollections = new ConcurrentHashSet<>();
47 52  
... ... @@ -78,6 +83,11 @@ public class AiGlobalAgentQuestionSqlEmitterServiceImpl implements AiGlobalAgent
78 83 .build();
79 84  
80 85 InsertResp insertResp = milvusClient.insert(insertReq);
  86 + if(insertResp.getInsertCnt()>0){
  87 + //调用数据库插入数据库
  88 + Map<String, Object> searMap = dynamicExeDbService.getDoProMap(sProName, data);
  89 + dynamicExeDbService.getCallPro(searMap, sProName);
  90 + }
81 91 System.out.println("成功插入 " + insertResp.getInsertCnt() + " 条数据");
82 92 System.out.println(" - 数据预览:");
83 93 }
... ... @@ -121,7 +131,7 @@ public class AiGlobalAgentQuestionSqlEmitterServiceImpl implements AiGlobalAgent
121 131 .annsField("vector") // 向量字段名
122 132 .topK(10) // 返回最相似的10条
123 133 .metricType(IndexParam.MetricType.IP) // 内积相似度
124   - .outputFields(Arrays.asList("sQuestion", "sSqlContent", "data_id","db_name", "create_time","metadata"))
  134 + .outputFields(Arrays.asList("sQuestion", "sSqlContent", "data_id","cachType", "create_time","metadata"))
125 135 .searchParams(searchParams)
126 136 .build();
127 137 // 5. 执行搜索
... ...
src/main/java/com/xly/milvus/service/impl/MilvusServiceImpl.java
... ... @@ -16,6 +16,10 @@ import com.xly.milvus.util.MapToJsonConverter;
16 16 import com.xly.milvus.util.MilvusTimeUtil;
17 17 import com.xly.service.DynamicExeDbService;
18 18 import com.xly.tts.bean.TTSResponseDTO;
  19 +import io.milvus.client.MilvusServiceClient;
  20 +import io.milvus.grpc.QueryResults;
  21 +import io.milvus.param.R;
  22 +import io.milvus.param.dml.QueryParam;
19 23 import io.milvus.v2.client.MilvusClientV2;
20 24 import io.milvus.v2.common.ConsistencyLevel;
21 25 import io.milvus.v2.common.DataType;
... ... @@ -23,9 +27,11 @@ import io.milvus.v2.common.IndexParam;
23 27 import io.milvus.v2.service.collection.request.*;
24 28 import io.milvus.v2.service.vector.request.DeleteReq;
25 29 import io.milvus.v2.service.vector.request.InsertReq;
  30 +import io.milvus.v2.service.vector.request.QueryReq;
26 31 import io.milvus.v2.service.vector.request.SearchReq;
27 32 import io.milvus.v2.service.vector.request.data.FloatVec;
28 33 import io.milvus.v2.service.vector.response.InsertResp;
  34 +import io.milvus.v2.service.vector.response.QueryResp;
29 35 import io.milvus.v2.service.vector.response.SearchResp;
30 36 import lombok.RequiredArgsConstructor;
31 37 import lombok.extern.slf4j.Slf4j;
... ... @@ -94,10 +100,17 @@ public class MilvusServiceImpl implements MilvusService {
94 100 .message("向量化内容JSON")
95 101 .build();
96 102 }
  103 + if(ObjectUtil.isEmpty(reqMap.get("sceneName"))){
  104 + return TTSResponseDTO.builder()
  105 + .code(-1)
  106 + .message("未传入场景名称")
  107 + .build();
  108 + }
97 109  
98 110 String sInputTabelName = reqMap.get("sInputTabelName").toString();
99 111 String sVectorfiled = reqMap.get("sVectorfiled").toString();
100 112 String sVectorjson = reqMap.get("sVectorjson").toString();
  113 + String sceneName = reqMap.get("sceneName").toString();
101 114 //创建集合
102 115 createCollectionIfNotExists(sInputTabelName, sVectorfiled, sVectorjson,true);
103 116 String tUpdateDate = DateUtil.now();
... ... @@ -106,7 +119,7 @@ public class MilvusServiceImpl implements MilvusService {
106 119 List<Map<String,Object>> data = getAddData(sInputTabelName,tUpdateDate, tUpdateDateUp);
107 120 if(ObjectUtil.isNotEmpty(data)){
108 121 //插入数据
109   - long num= addDataToCollection(sInputTabelName, sVectorfiled, sVectorjson,data);
  122 + long num= addDataToCollection(sInputTabelName, sVectorfiled, sVectorjson,data,sceneName);
110 123 }
111 124 addAiMilvusVectorRecord(sInputTabelName,tUpdateDate, tUpdateDateUp);
112 125 return TTSResponseDTO.builder()
... ... @@ -202,7 +215,7 @@ public class MilvusServiceImpl implements MilvusService {
202 215 * @Description 新增数据集合
203 216 **/
204 217 @Override
205   - public long addDataToCollection(String collectionName, String sVectorfiled, String sVectorjson,List<Map<String,Object>> data){
  218 + public long addDataToCollection(String collectionName, String sVectorfiled, String sVectorjson,List<Map<String,Object>> data,String sceneName){
206 219  
207 220 // 1. 参数校验(防止空参数导致崩溃)
208 221 if (ObjectUtil.isEmpty(collectionName) || CollUtil.isEmpty(data)) {
... ... @@ -210,7 +223,7 @@ public class MilvusServiceImpl implements MilvusService {
210 223 }
211 224  
212 225 // 1. 转换为Milvus格式
213   - List<JsonObject> rows = convertToMilvusRow(data, sVectorfiled, sVectorjson);
  226 + List<JsonObject> rows = convertToMilvusRow(data, sVectorfiled, sVectorjson,sceneName);
214 227 if (CollUtil.isEmpty(rows)) {
215 228 return 0l; // 无数据直接返回
216 229 }
... ... @@ -264,7 +277,7 @@ public class MilvusServiceImpl implements MilvusService {
264 277 * @Description 返回组装动态内容
265 278 **/
266 279 @Override
267   - public Map<String,Object> getMilvusFiled(String sVectorfiled,String sVectorfiledAll,String sVectorfiledShow){
  280 + public Map<String,Object> getMilvusFiled(String sVectorfiled,String sVectorfiledAll,String sVectorfiledShow,String sVectorjson){
268 281 List<String> sFileds = new ArrayList<>();
269 282 List<String> filedsShow = new ArrayList<>();
270 283 List<String> sFiledDescriptions = new ArrayList<>();
... ... @@ -299,17 +312,32 @@ public class MilvusServiceImpl implements MilvusService {
299 312 String formattedDesc =String.format("%s: %s", sName, sDescriptions);
300 313 sFiledDescriptionsAll.add(formattedDesc);
301 314 }
  315 + List<String> sFiledsXl = new ArrayList<>();
  316 + List<String> sFiledDescriptionsXl = new ArrayList<>();
  317 + String[] sVectorjsonArray = sVectorjson.split(",");
  318 + for(String sVectorjsonOne : sVectorjsonArray){
  319 + String[] sVectorfiledOneArray = sVectorjsonOne.split(":");
  320 + String sDescriptions = sVectorfiledOneArray[0];
  321 + String sName = sVectorfiledOneArray[1];
  322 + sFiledsXl.add(sName);
  323 + // 处理描述中可能包含的换行,保持缩进一致
  324 + String formattedDesc =String.format("%s: %s", sName, sDescriptions);
  325 + sFiledDescriptionsXl.add(formattedDesc);
  326 + }
  327 +
302 328 Map<String,Object> rMap = new HashMap<>();
303 329 rMap.put("sMilvusFiled", String.join(",", sFileds));
304 330 rMap.put("sMilvusFiledDescription", String.join(",", sFiledDescriptions));
305 331 rMap.put("sMilvusFiledDescriptionAll", String.join(",", sFiledDescriptionsAll));
  332 + rMap.put("sMilvusFiledXl", String.join(",", sFiledsXl));
  333 + rMap.put("sMilvusFiledDescriptionXl", String.join(",", sFiledDescriptionsXl));
306 334 rMap.put("filedsShow", filedsShow);
307 335 rMap.put("title", titleList);
308 336 return rMap;
309 337 }
310 338  
311 339 @Override
312   - public List<Map<String, Object>> getDataToCollection(String collectionName, String milvusFilter,String searchText,Integer size,List<String> fields){
  340 + public List<Map<String, Object>> getDataToCollection(String collectionName, String milvusFilter,String searchText,Integer size,List<String> fields,String vectorValue,String sceneName){
313 341 log.info("开始相似度查询: collection={}, searchText={}", collectionName, searchText);
314 342 // 2. 设置范围搜索参数
315 343 Map<String, Object> searchParams = new HashMap<>();
... ... @@ -317,10 +345,24 @@ public class MilvusServiceImpl implements MilvusService {
317 345 // 对于 IP 度量,相似度范围在 [minScore, maxScore]
318 346 searchParams.put("radius", 0.9); // 最小相似度
319 347 searchParams.put("range_filter", 1); // 最大相似度
320   - // 1. 确保集合已加载
321   -// ensureCollectionLoaded(collectionName);
  348 + if(ObjectUtil.isEmpty(fields)){
  349 + fields = new ArrayList<>();
  350 + }
  351 + fields.add("sSlaveId");
  352 + fields.add("metadata");
  353 + // 1. 构建查询(通用)
  354 + SearchReq.SearchReqBuilder builder = SearchReq.builder()
  355 + .collectionName(collectionName)
  356 + .topK(size)
  357 + .metricType(IndexParam.MetricType.IP)
  358 + .outputFields(fields)
  359 +// .filterType(FilterType.POST_FILTER)
  360 + .searchParams(searchParams);
  361 + if(ObjectUtil.isEmpty(vectorValue)){
  362 + vectorValue = sceneName;
  363 + }
322 364 // 1. 向量化搜索文本
323   - List<Float> vectorList = vectorizationService.textToVector(searchText);
  365 + List<Float> vectorList = vectorizationService.textToVector(vectorValue);
324 366 if (vectorList == null || vectorList.isEmpty()) {
325 367 throw new RuntimeException("向量化失败");
326 368 }
... ... @@ -329,25 +371,27 @@ public class MilvusServiceImpl implements MilvusService {
329 371 for (int i = 0; i < vectorList.size(); i++) {
330 372 floatArray[i] = vectorList.get(i);
331 373 }
332   - if(ObjectUtil.isEmpty(fields)){
333   - fields = new ArrayList<>();
334   - }
335   - fields.add("sSlaveId");
336   - fields.add("metadata");
337 374 // 3. 创建 Milvus FloatVec 对象
338 375 FloatVec floatVec = new FloatVec(floatArray);
  376 + builder.data(Collections.singletonList(floatVec))
  377 + .annsField("vector"); // 向量字段名
  378 +
  379 + if(ObjectUtil.isNotEmpty(milvusFilter)){
  380 + builder.filter(milvusFilter);
  381 + }
339 382 // 4. 构建搜索请求
340   - SearchReq searchReq = SearchReq.builder()
341   - .collectionName(collectionName)
342   - .data(Collections.singletonList(floatVec))
343   - .annsField("vector") // 向量字段名
344   - .topK(size) // 返回最相似的10条
345   - .metricType(IndexParam.MetricType.IP) // 内积相似度
346   - .outputFields(fields)
  383 +// SearchReq searchReq = SearchReq.builder()
  384 +// .collectionName(collectionName)
  385 +// .data(Collections.singletonList(floatVec))
  386 +// .annsField("vector") // 向量字段名
  387 +// .topK(size) // 返回最相似的10条
  388 +// .metricType(IndexParam.MetricType.IP) // 内积相似度
  389 +// .outputFields(fields)
347 390 // .searchParams(searchParams)
348   - .filter(milvusFilter)
349   - .build();
  391 +// .filter(milvusFilter)
  392 +// .build();
350 393 // 5. 执行搜索
  394 + SearchReq searchReq = builder.build();
351 395 SearchResp searchResp = milvusClient.search(searchReq);
352 396  
353 397 // 6. 处理结果
... ... @@ -357,45 +401,26 @@ public class MilvusServiceImpl implements MilvusService {
357 401  
358 402 /**
359 403 * 判断 Milvus 过滤条件是否有效(支持 TEXT_MATCH 全文检索)
360   - * @param milvusFilter 过滤条件字符串
361 404 * @return true: 有效条件, false: 无效条件
362 405 */
363   - public boolean isValidMilvusFilter(String milvusFilter) {
364   - // 1. 空值判断
365   - if (milvusFilter == null || milvusFilter.trim().isEmpty()) {
366   - return false;
367   - }
368   -
369   - String filter = milvusFilter.trim();
370   -
371   - // 2. 基本格式检查:不能是纯布尔值
372   - if ("true".equalsIgnoreCase(filter) || "false".equalsIgnoreCase(filter)) {
373   - return false;
374   - }
375   -
376   - // 3. 【修改】检查是否包含有效的操作符(增加 TEXT_MATCH 支持)
377   - boolean hasValidOperator = filter.matches(".*[=!<>]=?.*")
378   - || filter.contains(" like ")
379   - || filter.toUpperCase().contains("TEXT_MATCH");
380   -
381   - if (!hasValidOperator) {
382   - return false;
383   - }
384   -
385   - // 4. 对于复合条件,递归检查
386   - if (filter.contains("&&") || filter.contains("||")) {
387   - // 分割复合条件(简单处理,生产环境需要更完善的解析)
388   - String[] conditions = splitConditions(filter);
389   - for (String condition : conditions) {
390   - if (!isValidCondition(condition)) {
391   - return false;
392   - }
393   - }
  406 + /**
  407 + * 判断字符串 Filter 是否有效
  408 + * @param filter 过滤表达式,如 "name == '张三'"
  409 + * @return true=有效,false=无效
  410 + */
  411 + public boolean isStringFilterValid(String filter,String collectionName) {
  412 + try {
  413 + // 使用 limit 为 0 只验证语法,不实际返回数据
  414 + QueryReq queryReq = QueryReq.builder()
  415 + .collectionName(collectionName)
  416 + .filter(filter) // 设置过滤条件
  417 + .limit(0) // limit 0 只校验语法
  418 + .build();
  419 + QueryResp response = milvusClient.query(queryReq);
394 420 return true;
  421 + } catch (Exception e) {
  422 + return false;
395 423 }
396   -
397   - // 5. 检查单个条件
398   - return isValidCondition(filter);
399 424 }
400 425  
401 426 /**
... ... @@ -583,14 +608,14 @@ public class MilvusServiceImpl implements MilvusService {
583 608 /**
584 609 * 从实体对象构建Milvus插入数据
585 610 */
586   - public List<JsonObject> convertToMilvusRow(List<Map<String,Object>> data, String sVectorfiled,String sVectorjson) {
  611 + public List<JsonObject> convertToMilvusRow(List<Map<String,Object>> data, String sVectorfiled,String sVectorjson,String sceneName) {
587 612 List<JsonObject> rows = new ArrayList<>();
588 613 if (CollUtil.isEmpty(data)) {
589 614 return rows;
590 615 }
591 616 // 批量遍历,逐个转换
592 617 for (Map<String, Object> map : data) {
593   - JsonObject jsonObject = convertToMilvusRowOne(map, sVectorfiled, sVectorjson);
  618 + JsonObject jsonObject = convertToMilvusRowOne(map, sVectorfiled, sVectorjson,sceneName);
594 619 rows.add(jsonObject);
595 620 }
596 621 return rows;
... ... @@ -603,12 +628,12 @@ public class MilvusServiceImpl implements MilvusService {
603 628 * @return com.google.gson.JsonObject
604 629 * @Description 单个转换
605 630 **/
606   - public JsonObject convertToMilvusRowOne(Map<String,Object> data, String sVectorfiled,String sVectorjson) {
  631 + public JsonObject convertToMilvusRowOne(Map<String,Object> data, String sVectorfiled,String sVectorjson,String sceneName) {
607 632  
608 633 // ====================== 修复 1:使用真实的向量化文本 ======================
609 634 // 从 sVectorjson 或 data 中获取要向量化的字段值
610 635 StringBuffer vectorText = new StringBuffer();
611   - getVectorText(data, vectorText, sVectorjson);
  636 + getVectorText(data, vectorText, sVectorjson,sceneName);
612 637 // 向量化
613 638 List<Float> vector = vectorizationService.textToVector(vectorText.toString());
614 639 if (vector == null || vector.isEmpty()) {
... ... @@ -650,7 +675,7 @@ public class MilvusServiceImpl implements MilvusService {
650 675 return row;
651 676 }
652 677  
653   - private void getVectorText(Map<String,Object> data, StringBuffer vectorText,String sVectorjson){
  678 + private void getVectorText(Map<String,Object> data, StringBuffer vectorText,String sVectorjson,String sceneName){
654 679 // 动态字段
655 680 String[] sVectorjsonArray = sVectorjson.split(";");
656 681 for (String sVectorjsonOne : sVectorjsonArray) {
... ... @@ -666,8 +691,9 @@ public class MilvusServiceImpl implements MilvusService {
666 691 }else{
667 692 sText = value.toString();
668 693 }
669   - vectorText.append(" ").append(fieldArr[0]).append(sText);
  694 + vectorText.append(" | ").append(fieldArr[0]).append(":").append(sText);
670 695 }
  696 + vectorText.append(" ").append(sceneName);
671 697 }
672 698  
673 699  
... ...
src/main/java/com/xly/service/XlyErpService.java
1 1 package com.xly.service;
2 2  
3 3 import cn.hutool.core.collection.ListUtil;
4   -import cn.hutool.core.date.DatePattern;
5 4 import cn.hutool.core.date.DateUtil;
6   -import cn.hutool.core.util.IdUtil;
  5 +import cn.hutool.core.util.BooleanUtil;
7 6 import cn.hutool.core.util.ObjectUtil;
8 7 import cn.hutool.core.util.StrUtil;
  8 +import cn.hutool.json.JSONUtil;
9 9 import com.alibaba.fastjson2.JSON;
10 10 import com.alibaba.fastjson2.JSONObject;
11 11 import com.xly.agent.ChatiAgent;
... ... @@ -405,25 +405,48 @@ public class XlyErpService {
405 405 String sInputTabelName = session.getCurrentTool().getSInputTabelName();
406 406 String sVectorfiledAll = session.getCurrentTool().getSVectorfiledAll();
407 407 String sVectorfiledShow = session.getCurrentTool().getSVectorfiledShow();
408   - Map<String,Object> rMap = milvusService.getMilvusFiled(sVectorfiled,sVectorfiledAll,sVectorfiledShow);
  408 + String sVectorjson = session.getCurrentTool().getSVectorjson();
  409 + String sceneName = session.getCurrentTool().getSceneName();
  410 + String sMethodName = session.getCurrentTool().getSMethodName();
  411 + Map<String,Object> rMap = milvusService.getMilvusFiled(sVectorfiled,sVectorfiledAll,sVectorfiledShow,sVectorjson);
409 412 String sMilvusFiled = rMap.get("sMilvusFiled").toString();
  413 + String sMilvusFiledXl = rMap.get("sMilvusFiledXl").toString();
410 414 String sMilvusFiledDescription = rMap.get("sMilvusFiledDescription").toString();
  415 + String sMilvusFiledDescriptionXl = rMap.get("sMilvusFiledDescriptionXl").toString();
411 416 String sMilvusFiledDescriptionAll = rMap.get("sMilvusFiledDescriptionAll").toString();
412 417 List<String> filedsShow = (List<String>) rMap.get("filedsShow");
413 418 List<Map<String, String>> title = (List<Map<String, String>>) rMap.get("title");
414 419 String milvusFilter = StrUtil.EMPTY;
  420 + String vectorValue = StrUtil.EMPTY;
  421 + Boolean bMethodName = false;
415 422 if(!bCach){
416   - milvusFilter = aiAgent.getMilvusFilter(session.getUserId(),userInput, sMilvusFiled, sMilvusFiledDescription,DateUtil.now());
417   - log.info("查询向量库条件{}",milvusFilter);
418   - milvusFilter = milvusService.isValidMilvusFilter(milvusFilter)?milvusFilter : null;
419   - log.info("实际查询向量库条件{}",milvusFilter);
  423 + long sDateNow = System.currentTimeMillis() / 1000;
  424 + String milvusFilterOld = aiAgent.getMilvusFilter(session.getUserId(),userInput, sMilvusFiled, sMilvusFiledDescription,sMilvusFiledXl, sMilvusFiledDescriptionXl,sDateNow,sMethodName);
  425 + log.info("查询向量库条件{}",milvusFilterOld);
  426 + if(ObjectUtil.isNotEmpty(milvusFilterOld) && JSONUtil.isTypeJSON(milvusFilterOld)){
  427 + Map<String,Object> filterMap = JSONUtil.parseObj(milvusFilterOld);
  428 + if(ObjectUtil.isNotEmpty(filterMap.get("filterExpression"))){
  429 + milvusFilterOld = filterMap.get("filterExpression").toString();
  430 + }
  431 + if(ObjectUtil.isNotEmpty(filterMap.get("vectorValue"))){
  432 + vectorValue = filterMap.get("vectorValue").toString();
  433 + }
  434 + if(ObjectUtil.isNotEmpty(filterMap.get("sMethodName"))){
  435 + bMethodName = BooleanUtil.toBoolean(filterMap.get("sMethodName").toString());
  436 + }
  437 + }
  438 + Boolean milvusFilterCheck = milvusService.isStringFilterValid(milvusFilterOld,sInputTabelName);
  439 + milvusFilter = milvusFilterCheck?milvusFilterOld : null;
  440 + if(!bMethodName && ObjectUtil.isEmpty(vectorValue) && ObjectUtil.isEmpty(milvusFilter)){
  441 + return resultExplain;
  442 + }
420 443 }
421 444 Integer pageSize = 100;
422 445 if(ObjectUtil.isEmpty(milvusFilter)){
423 446 pageSize = 10;
424 447 }
425 448 // 待条件全查 不带 10条
426   - List<Map<String,Object>> data = milvusService.getDataToCollection(sInputTabelName, milvusFilter,userInput,pageSize,filedsShow);
  449 + List<Map<String,Object>> data = milvusService.getDataToCollection(sInputTabelName, milvusFilter,userInput,pageSize,filedsShow, vectorValue,sceneName);
427 450 //存储到历史问题库(带where条件了就不存)并且没有记录过缓存
428 451 if(!bCach && ObjectUtil.isEmpty(milvusFilter)){
429 452 //执行操作记录表
... ... @@ -436,13 +459,7 @@ public class XlyErpService {
436 459 }
437 460 }
438 461 //采用表格形式显示明细、...详情、...记录、...列表、...清单
439   - if(ObjectUtil.isEmpty(milvusFilter)
440   - || userInput.contains("明细")
441   - || userInput.contains("详情")
442   - || userInput.contains("记录")
443   - || userInput.contains("列表")
444   - || userInput.contains("清单")
445   - ){
  462 + if( retrunMarkdownType(userInput) ){
446 463 resultExplain = buildMarkdownTableWithStream(data, title);
447 464 }else{
448 465 resultExplain = aiAgent.explainMilvusResult(session.getUserId(),userInput,sMilvusFiledDescriptionAll,JSONObject.toJSONString(data));
... ... @@ -453,6 +470,21 @@ public class XlyErpService {
453 470 }
454 471 return resultExplain;
455 472 }
  473 +
  474 + /***
  475 + * @Author 钱豹
  476 + * @Date 19:48 2026/3/28
  477 + * @Param [userInput]
  478 + * @return java.lang.Boolean
  479 + * @Description 是否返回Markdown类型
  480 + **/
  481 + private Boolean retrunMarkdownType(String userInput){
  482 + return userInput.contains("明细")
  483 +// || userInput.contains("详情")
  484 +// || userInput.contains("记录")
  485 + || userInput.contains("列表")
  486 + || userInput.contains("清单");
  487 + }
456 488 /***
457 489 * @Author 钱豹
458 490 * @Date 13:19 2026/3/25
... ... @@ -640,38 +672,47 @@ public class XlyErpService {
640 672 if(Integer.valueOf(iErroCount)>0){
641 673 doAiSqlErrorHistoryThread(session, cleanSql, StrUtil.EMPTY, StrUtil.EMPTY,input);
642 674 }
643   - //插入常用操作
  675 + //插入常用操作 不包含where 条件
644 676 if(doAddSql){
645 677 //执行操作记录表
646 678 doAiUserAgentQuestion(session,input,cleanSql,"MYSQL",chatMessage);
647 679 }
648   - String sText = aiAgent.explainSqlResult(
649   - userId,
650   - userInput,
651   - cleanSql,
652   - tableStruct,
653   - resultJson
654   - );
655   - return sText;
  680 + //采用表格形式显示明细、...详情、...记录、...列表、...清单
  681 + String resultExplain = StrUtil.EMPTY;
  682 + if(retrunMarkdownType(userInput) ){
  683 + List<Map<String, String>> titles = getMarkdownTableTitleWithSql(sqlResult);
  684 + resultExplain = buildMarkdownTableWithStream(sqlResult, titles);
  685 + }else {
  686 + resultExplain = aiAgent.explainSqlResult(
  687 + userId,
  688 + userInput,
  689 + cleanSql,
  690 + tableStruct,
  691 + resultJson);
  692 + }
  693 + return resultExplain;
656 694 }
657 695  
658 696 /***
659 697 * @Author 钱豹
660   - * @Date 17:04 2026/3/19
661   - * @Param [session]
662   - * @return java.lang.String
663   - * @Description 获取动态SQL(历史中查询)
  698 + * @Date 19:55 2026/3/28
  699 + * @Param [sqlResult]
  700 + * @return java.util.List<java.util.Map<java.lang.String,java.lang.String>>
  701 + * @Description 动态SQL 返回Markdown 形式抬头
664 702 **/
665   - private Map<String,Object> getDynamicTableCach(UserSceneSession session,String input){
666   - try{
667   - String searchText = session.getCurrentScene().getSId()+"_"+session.getCurrentTool().getSId()+input;
668   - //根据问题查询向量库
669   - Map<String,Object> serMap = aiGlobalAgentQuestionSqlEmitterService.queryAiGlobalAgentQuestionSqlEmitter(searchText, "ai_global_agent_question_sql");
670   - return serMap;
671   - }catch (Exception e){
672   - log.error("取是否走缓存异常");
  703 + private List<Map<String, String>> getMarkdownTableTitleWithSql(List<Map<String, Object>> sqlResult){
  704 + if(ObjectUtil.isEmpty(sqlResult)){
  705 + return new ArrayList<>();
673 706 }
674   - return null;
  707 + Map<String, Object> one = sqlResult.get(0);
  708 + List<Map<String, String>> titleData = new ArrayList<>();
  709 + one.forEach((k,v)->{
  710 + Map<String, String> title = new HashMap<>();
  711 + title.put("sTitle",k);
  712 + title.put("sName",k);
  713 + titleData.add(title);
  714 + });
  715 + return titleData;
675 716 }
676 717  
677 718 /***
... ... @@ -681,22 +722,14 @@ public class XlyErpService {
681 722 * @return java.lang.String
682 723 * @Description 获取动态SQL(历史中查询)
683 724 **/
684   - private String getDynamicTableNl2Sql(UserSceneSession session,String input){
685   -// String sReidKey = SqlValidateUtil.getsKey( session.getCurrentScene().getSId(), session.getCurrentTool().getSId(), input);
686   -// Object sSql = redisService.get(sReidKey);
687   -// if(ObjectUtil.isNotEmpty(sSql)){
688   -// return sSql.toString();
689   -// }
  725 + private Map<String,Object> getDynamicTableCach(UserSceneSession session,String input){
690 726 try{
691 727 String searchText = session.getCurrentScene().getSId()+"_"+session.getCurrentTool().getSId()+input;
692   - //SqlValidateUtil.getsKey( session.getCurrentScene().getSId(), session.getCurrentTool().getSId(), SqlValidateUtil.getsQuestion(session.getSUserQuestionList()));
693 728 //根据问题查询向量库
694 729 Map<String,Object> serMap = aiGlobalAgentQuestionSqlEmitterService.queryAiGlobalAgentQuestionSqlEmitter(searchText, "ai_global_agent_question_sql");
695   - if(ObjectUtil.isNotEmpty(serMap)){
696   - return serMap.get("sSqlContent").toString();
697   - }
  730 + return serMap;
698 731 }catch (Exception e){
699   -
  732 + log.error("取是否走缓存异常");
700 733 }
701 734 return null;
702 735 }
... ...
src/main/java/com/xly/thread/AiUserAgentQuestionThread.java
... ... @@ -9,8 +9,8 @@ import com.xly.entity.UserSceneSession;
9 9 import com.xly.milvus.service.AiGlobalAgentQuestionSqlEmitterService;
10 10 import com.xly.service.DynamicExeDbService;
11 11 import com.xly.service.RedisService;
12   -import com.xly.util.MD5Util;
13 12 import com.xly.util.SqlValidateUtil;
  13 +import com.xly.util.SqlWhereHelper;
14 14 import dev.langchain4j.data.message.ChatMessage;
15 15 import dev.langchain4j.data.message.ChatMessageType;
16 16 import java.util.HashMap;
... ... @@ -54,17 +54,15 @@ public class AiUserAgentQuestionThread implements Runnable {
54 54 }
55 55 String sKey = sSceneId+"_"+sMethodId +"_"+sQuestion;
56 56 // SqlValidateUtil.getsKey( sSceneId, sMethodId, SqlValidateUtil.getsQuestion(session.getSUserQuestionList()));
57   - //存入向量库
58   - aiGlobalAgentQuestionSqlEmitterService.addAiGlobalAgentQuestionSqlEmitter(sKey,data,sQuestion,sSqlContent,cachType,"ai_global_agent_question_sql");
59   - //调用数据库插入数据库
  57 + //存入向量库 不包含where 条件
  58 + if(!SqlWhereHelper.hasWhereCondition(sSqlContent)){
  59 + aiGlobalAgentQuestionSqlEmitterService.addAiGlobalAgentQuestionSqlEmitter(sKey,data,sQuestion,sSqlContent,cachType,"ai_global_agent_question_sql");
  60 + }
  61 + //调用数据库插入数据库
60 62 Map<String, Object> searMap = dynamicExeDbService.getDoProMap(sProName, data);
61 63 dynamicExeDbService.getCallPro(searMap, sProName);
62 64 }
63 65  
64   -
65   -
66   -
67   -
68 66 //获取组ID
69 67 private String getQuestionGroupNo(){
70 68 String sQuestionGroupNo = userMessage.stream()
... ...
src/main/java/com/xly/util/SqlWhereHelper.java 0 → 100644
  1 +package com.xly.util;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import lombok.extern.slf4j.Slf4j;
  5 +import net.sf.jsqlparser.JSQLParserException;
  6 +import net.sf.jsqlparser.parser.CCJSqlParserUtil;
  7 +import net.sf.jsqlparser.statement.Statement;
  8 +import net.sf.jsqlparser.statement.delete.Delete;
  9 +import net.sf.jsqlparser.statement.select.Select;
  10 +import net.sf.jsqlparser.statement.update.Update;
  11 +
  12 +@Slf4j
  13 +public class SqlWhereHelper {
  14 +
  15 + /**
  16 + * 判断 SQL 是否包含 WHERE 条件(使用 JSqlParser)
  17 + */
  18 + public static boolean hasWhereCondition(String sql) {
  19 + if (StrUtil.isBlank(sql)) {
  20 + return false;
  21 + }
  22 + try {
  23 + Statement statement = CCJSqlParserUtil.parse(sql);
  24 + if (statement instanceof Select) {
  25 + Select select = (Select) statement;
  26 + return select.getPlainSelect() != null && select.getPlainSelect().getWhere() != null;
  27 + }
  28 + if (statement instanceof Update) {
  29 + Update update = (Update) statement;
  30 + return update.getWhere() != null;
  31 + }
  32 + if (statement instanceof Delete) {
  33 + Delete delete = (Delete) statement;
  34 + return delete.getWhere() != null;
  35 + }
  36 + } catch (JSQLParserException e) {
  37 + log.warn("SQL 解析失败,回退到简单匹配: {}", sql, e);
  38 + return hasWhereConditionSimple(sql);
  39 + }
  40 + return false;
  41 + }
  42 +
  43 + /**
  44 + * 简单判断是否有 WHERE
  45 + */
  46 + public static boolean hasWhereConditionSimple(String sql) {
  47 + if (StrUtil.isBlank(sql)) return false;
  48 + String s = sql.toUpperCase();
  49 + int whereIdx = s.indexOf("WHERE");
  50 + if (whereIdx == -1) return false;
  51 +
  52 + String before = s.substring(0, whereIdx).trim();
  53 + if (!before.matches(".*\\b(SELECT|UPDATE|DELETE)\\b.*")) return false;
  54 +
  55 + String after = s.substring(whereIdx + 5).trim();
  56 + return !after.isEmpty() && !after.matches("^(GROUP BY|ORDER BY|LIMIT).*");
  57 + }
  58 +
  59 + // ===================== 你要的新方法 =====================
  60 + /**
  61 + * 判断:
  62 + * 1. 有 WHERE
  63 + * 2. 条件中 不包含 = > < >= <= IN EXISTS
  64 + * → 满足返回 true
  65 + */
  66 + public static boolean hasWhereButNoCompareOperators(String sql) {
  67 + if (!hasWhereCondition(sql)) {
  68 + return false;
  69 + }
  70 +
  71 + String upper = sql.toUpperCase();
  72 +
  73 + // 禁止出现的条件符号/关键字
  74 + boolean hasEq = upper.contains("=");
  75 + boolean hasGt = upper.contains(">");
  76 + boolean hasLt = upper.contains("<");
  77 + boolean hasIn = upper.contains(" IN ");
  78 + boolean hasExists = upper.contains(" EXISTS ");
  79 +
  80 + // 只要有任何一个,就返回 false
  81 + if (hasEq || hasGt || hasLt || hasIn || hasExists) {
  82 + return false;
  83 + }
  84 +
  85 + // 有 WHERE 且 无等值/区间/大小/IN/EXISTS → 返回 true
  86 + return true;
  87 + }
  88 +
  89 + // ===================== 你原来的方法不动 =====================
  90 + public static int getWhereInsertPosition(String sql) {
  91 + if (hasWhereCondition(sql)) {
  92 + return -1;
  93 + }
  94 + String upperSql = sql.toUpperCase();
  95 + int fromIndex = upperSql.indexOf("FROM");
  96 + if (fromIndex == -1) return sql.length();
  97 +
  98 + int tableEndIndex = findTableEnd(sql, fromIndex + 4);
  99 + int groupIndex = upperSql.indexOf("GROUP BY", tableEndIndex);
  100 + int orderIndex = upperSql.indexOf("ORDER BY", tableEndIndex);
  101 + int limitIndex = upperSql.indexOf("LIMIT", tableEndIndex);
  102 +
  103 + int nextKeyword = sql.length();
  104 + if (groupIndex != -1) nextKeyword = Math.min(nextKeyword, groupIndex);
  105 + if (orderIndex != -1) nextKeyword = Math.min(nextKeyword, orderIndex);
  106 + if (limitIndex != -1) nextKeyword = Math.min(nextKeyword, limitIndex);
  107 + return nextKeyword;
  108 + }
  109 +
  110 + private static int findTableEnd(String sql, int startIndex) {
  111 + if (startIndex >= sql.length()) return sql.length();
  112 + String afterFrom = sql.substring(startIndex);
  113 + int endIndex = startIndex;
  114 + for (int i = 0; i < afterFrom.length(); i++) {
  115 + char c = afterFrom.charAt(i);
  116 + if (Character.isWhitespace(c) || c == ',' || c == '(' || c == ')') {
  117 + endIndex = startIndex + i;
  118 + break;
  119 + }
  120 + endIndex = startIndex + i + 1;
  121 + }
  122 + return endIndex;
  123 + }
  124 +
  125 + public static String addWhereCondition(String sql, String condition) {
  126 + if (StrUtil.isBlank(sql) || StrUtil.isBlank(condition)) return sql;
  127 + if (hasWhereCondition(sql)) {
  128 + return sql + " AND " + condition;
  129 + } else {
  130 + int insertPos = getWhereInsertPosition(sql);
  131 + return sql.substring(0, insertPos) + " WHERE " + condition + sql.substring(insertPos);
  132 + }
  133 + }
  134 +}
0 135 \ No newline at end of file
... ...