Commit d72de0c4fe4d50edf536174f6b7f6fa97d18ac96

Authored by qianbao
1 parent b9b48f2e

添加向量库

@@ -105,6 +105,12 @@ @@ -105,6 +105,12 @@
105 <groupId>org.springframework.boot</groupId> 105 <groupId>org.springframework.boot</groupId>
106 <artifactId>spring-boot-starter-webflux</artifactId> 106 <artifactId>spring-boot-starter-webflux</artifactId>
107 </dependency> 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 <!-- Tess4J OCR --> 115 <!-- Tess4J OCR -->
110 <dependency> 116 <dependency>
src/main/java/com/xly/agent/ErpAiAgent.java
@@ -93,24 +93,18 @@ public interface ErpAiAgent { @@ -93,24 +93,18 @@ public interface ErpAiAgent {
93 93
94 @SystemMessage(""" 94 @SystemMessage("""
95 你是一个智能查询路由专家。请根据【用户需求】,只返回 true 或 false。 95 你是一个智能查询路由专家。请根据【用户需求】,只返回 true 或 false。
96 -  
97 【最高优先级规则 - 必须首先判断】 96 【最高优先级规则 - 必须首先判断】
98 如果用户需求包含以下任一关键词,**直接返回 false**,不再进行其他判断: 97 如果用户需求包含以下任一关键词,**直接返回 false**,不再进行其他判断:
99 - - 明细、详情、详细信息、详细内容、具体内容  
100 - - 查询...明细、...详情、...记录、...列表、...清单  
101 - 98 + - 明细、列表、清单
  99 + - ...明细、...列表、...清单
102 重要:只要出现以上关键词,说明用户需要的是明细数据查询,而非统计分析。 100 重要:只要出现以上关键词,说明用户需要的是明细数据查询,而非统计分析。
103 -  
104 【统计类关键词 - 仅在满足最高优先级规则后才判断】 101 【统计类关键词 - 仅在满足最高优先级规则后才判断】
105 只有当用户需求不包含上述明细类关键词时,才检查是否包含以下关键词: 102 只有当用户需求不包含上述明细类关键词时,才检查是否包含以下关键词:
106 统计、求和、汇总、排名、TopN、平均、数量、总额、最高、最低、占比、分组 103 统计、求和、汇总、排名、TopN、平均、数量、总额、最高、最低、占比、分组
107 -  
108 - 如果包含,返回 true 104 - 如果包含,返回 true
109 - 否则返回 false 105 - 否则返回 false
110 -  
111 【判断示例】 106 【判断示例】
112 - \"查询中科精工集团的彩盒类产品的报价单明细\" → false(包含\"明细\") 107 - \"查询中科精工集团的彩盒类产品的报价单明细\" → false(包含\"明细\")
113 - - \"统计各产品销售额\" → true(包含\"统计\",且无明细关键词)  
114 - \"查询客户张三信息\" → false(无统计关键词,无明细关键词) 108 - \"查询客户张三信息\" → false(无统计关键词,无明细关键词)
115 - \"销售额排名前10的产品\" → true(包含\"排名\",且无明细关键词) 109 - \"销售额排名前10的产品\" → true(包含\"排名\",且无明细关键词)
116 - \"查看销售订单明细\" → false(包含\"明细\") 110 - \"查看销售订单明细\" → false(包含\"明细\")
@@ -119,90 +113,264 @@ public interface ErpAiAgent { @@ -119,90 +113,264 @@ public interface ErpAiAgent {
119 【用户需求】 113 【用户需求】
120 {{userInput}} 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 Boolean routeQuery(@MemoryId String userId, @V("userInput") String userInput); 126 Boolean routeQuery(@MemoryId String userId, @V("userInput") String userInput);
123 127
124 /** 128 /**
125 * 生成 Milvus 过滤条件(适配 Milvus v2.3.9) 129 * 生成 Milvus 过滤条件(适配 Milvus v2.3.9)
126 */ 130 */
127 @SystemMessage(""" 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 String getMilvusFilter(@MemoryId String userId, 367 String getMilvusFilter(@MemoryId String userId,
203 @V("userInput") String userInput, 368 @V("userInput") String userInput,
204 @V("sMilvusFiled") String sMilvusFiled, 369 @V("sMilvusFiled") String sMilvusFiled,
205 @V("sMilvusFiledDescription") String sMilvusFiledDescription, 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,7 +36,7 @@ public interface MilvusService {
36 * @return long 36 * @return long
37 * @Description 批量插入数据 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 * @Author 钱豹 42 * @Author 钱豹
@@ -45,7 +45,7 @@ public interface MilvusService { @@ -45,7 +45,7 @@ public interface MilvusService {
45 * @return 45 * @return
46 * @Description 向量库查询 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 * @Author 钱豹 51 * @Author 钱豹
@@ -54,7 +54,7 @@ public interface MilvusService { @@ -54,7 +54,7 @@ public interface MilvusService {
54 * @return java.util.Map<java.lang.String,java.lang.Object> 54 * @return java.util.Map<java.lang.String,java.lang.Object>
55 * @Description 获取配置 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 \ No newline at end of file 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,6 +8,7 @@ import com.google.gson.JsonObject;
8 import com.xly.milvus.service.AiGlobalAgentQuestionSqlEmitterService; 8 import com.xly.milvus.service.AiGlobalAgentQuestionSqlEmitterService;
9 import com.xly.milvus.service.VectorizationService; 9 import com.xly.milvus.service.VectorizationService;
10 import com.xly.milvus.util.MapToJsonConverter; 10 import com.xly.milvus.util.MapToJsonConverter;
  11 +import com.xly.service.DynamicExeDbService;
11 import io.milvus.v2.client.MilvusClientV2; 12 import io.milvus.v2.client.MilvusClientV2;
12 import io.milvus.v2.common.ConsistencyLevel; 13 import io.milvus.v2.common.ConsistencyLevel;
13 import io.milvus.v2.common.DataType; 14 import io.milvus.v2.common.DataType;
@@ -37,11 +38,15 @@ public class AiGlobalAgentQuestionSqlEmitterServiceImpl implements AiGlobalAgent @@ -37,11 +38,15 @@ public class AiGlobalAgentQuestionSqlEmitterServiceImpl implements AiGlobalAgent
37 38
38 private final MilvusClientV2 milvusClient; 39 private final MilvusClientV2 milvusClient;
39 private final VectorizationService vectorizationService; 40 private final VectorizationService vectorizationService;
  41 + private final DynamicExeDbService dynamicExeDbService;
  42 + private final String sProName ="Sp_Ai_AiGlobalAgentQuestionThread";
  43 +
40 44
41 // 或者从配置文件读取 45 // 或者从配置文件读取
42 @Value("${milvus.vector.dimension:384}") 46 @Value("${milvus.vector.dimension:384}")
43 private int VECTOR_DIM; 47 private int VECTOR_DIM;
44 48
  49 +
45 // 缓存已加载的集合 50 // 缓存已加载的集合
46 private final Set<String> loadedCollections = new ConcurrentHashSet<>(); 51 private final Set<String> loadedCollections = new ConcurrentHashSet<>();
47 52
@@ -78,6 +83,11 @@ public class AiGlobalAgentQuestionSqlEmitterServiceImpl implements AiGlobalAgent @@ -78,6 +83,11 @@ public class AiGlobalAgentQuestionSqlEmitterServiceImpl implements AiGlobalAgent
78 .build(); 83 .build();
79 84
80 InsertResp insertResp = milvusClient.insert(insertReq); 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 System.out.println("成功插入 " + insertResp.getInsertCnt() + " 条数据"); 91 System.out.println("成功插入 " + insertResp.getInsertCnt() + " 条数据");
82 System.out.println(" - 数据预览:"); 92 System.out.println(" - 数据预览:");
83 } 93 }
@@ -121,7 +131,7 @@ public class AiGlobalAgentQuestionSqlEmitterServiceImpl implements AiGlobalAgent @@ -121,7 +131,7 @@ public class AiGlobalAgentQuestionSqlEmitterServiceImpl implements AiGlobalAgent
121 .annsField("vector") // 向量字段名 131 .annsField("vector") // 向量字段名
122 .topK(10) // 返回最相似的10条 132 .topK(10) // 返回最相似的10条
123 .metricType(IndexParam.MetricType.IP) // 内积相似度 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 .searchParams(searchParams) 135 .searchParams(searchParams)
126 .build(); 136 .build();
127 // 5. 执行搜索 137 // 5. 执行搜索
src/main/java/com/xly/milvus/service/impl/MilvusServiceImpl.java
@@ -16,6 +16,10 @@ import com.xly.milvus.util.MapToJsonConverter; @@ -16,6 +16,10 @@ import com.xly.milvus.util.MapToJsonConverter;
16 import com.xly.milvus.util.MilvusTimeUtil; 16 import com.xly.milvus.util.MilvusTimeUtil;
17 import com.xly.service.DynamicExeDbService; 17 import com.xly.service.DynamicExeDbService;
18 import com.xly.tts.bean.TTSResponseDTO; 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 import io.milvus.v2.client.MilvusClientV2; 23 import io.milvus.v2.client.MilvusClientV2;
20 import io.milvus.v2.common.ConsistencyLevel; 24 import io.milvus.v2.common.ConsistencyLevel;
21 import io.milvus.v2.common.DataType; 25 import io.milvus.v2.common.DataType;
@@ -23,9 +27,11 @@ import io.milvus.v2.common.IndexParam; @@ -23,9 +27,11 @@ import io.milvus.v2.common.IndexParam;
23 import io.milvus.v2.service.collection.request.*; 27 import io.milvus.v2.service.collection.request.*;
24 import io.milvus.v2.service.vector.request.DeleteReq; 28 import io.milvus.v2.service.vector.request.DeleteReq;
25 import io.milvus.v2.service.vector.request.InsertReq; 29 import io.milvus.v2.service.vector.request.InsertReq;
  30 +import io.milvus.v2.service.vector.request.QueryReq;
26 import io.milvus.v2.service.vector.request.SearchReq; 31 import io.milvus.v2.service.vector.request.SearchReq;
27 import io.milvus.v2.service.vector.request.data.FloatVec; 32 import io.milvus.v2.service.vector.request.data.FloatVec;
28 import io.milvus.v2.service.vector.response.InsertResp; 33 import io.milvus.v2.service.vector.response.InsertResp;
  34 +import io.milvus.v2.service.vector.response.QueryResp;
29 import io.milvus.v2.service.vector.response.SearchResp; 35 import io.milvus.v2.service.vector.response.SearchResp;
30 import lombok.RequiredArgsConstructor; 36 import lombok.RequiredArgsConstructor;
31 import lombok.extern.slf4j.Slf4j; 37 import lombok.extern.slf4j.Slf4j;
@@ -94,10 +100,17 @@ public class MilvusServiceImpl implements MilvusService { @@ -94,10 +100,17 @@ public class MilvusServiceImpl implements MilvusService {
94 .message("向量化内容JSON") 100 .message("向量化内容JSON")
95 .build(); 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 String sInputTabelName = reqMap.get("sInputTabelName").toString(); 110 String sInputTabelName = reqMap.get("sInputTabelName").toString();
99 String sVectorfiled = reqMap.get("sVectorfiled").toString(); 111 String sVectorfiled = reqMap.get("sVectorfiled").toString();
100 String sVectorjson = reqMap.get("sVectorjson").toString(); 112 String sVectorjson = reqMap.get("sVectorjson").toString();
  113 + String sceneName = reqMap.get("sceneName").toString();
101 //创建集合 114 //创建集合
102 createCollectionIfNotExists(sInputTabelName, sVectorfiled, sVectorjson,true); 115 createCollectionIfNotExists(sInputTabelName, sVectorfiled, sVectorjson,true);
103 String tUpdateDate = DateUtil.now(); 116 String tUpdateDate = DateUtil.now();
@@ -106,7 +119,7 @@ public class MilvusServiceImpl implements MilvusService { @@ -106,7 +119,7 @@ public class MilvusServiceImpl implements MilvusService {
106 List<Map<String,Object>> data = getAddData(sInputTabelName,tUpdateDate, tUpdateDateUp); 119 List<Map<String,Object>> data = getAddData(sInputTabelName,tUpdateDate, tUpdateDateUp);
107 if(ObjectUtil.isNotEmpty(data)){ 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 addAiMilvusVectorRecord(sInputTabelName,tUpdateDate, tUpdateDateUp); 124 addAiMilvusVectorRecord(sInputTabelName,tUpdateDate, tUpdateDateUp);
112 return TTSResponseDTO.builder() 125 return TTSResponseDTO.builder()
@@ -202,7 +215,7 @@ public class MilvusServiceImpl implements MilvusService { @@ -202,7 +215,7 @@ public class MilvusServiceImpl implements MilvusService {
202 * @Description 新增数据集合 215 * @Description 新增数据集合
203 **/ 216 **/
204 @Override 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 // 1. 参数校验(防止空参数导致崩溃) 220 // 1. 参数校验(防止空参数导致崩溃)
208 if (ObjectUtil.isEmpty(collectionName) || CollUtil.isEmpty(data)) { 221 if (ObjectUtil.isEmpty(collectionName) || CollUtil.isEmpty(data)) {
@@ -210,7 +223,7 @@ public class MilvusServiceImpl implements MilvusService { @@ -210,7 +223,7 @@ public class MilvusServiceImpl implements MilvusService {
210 } 223 }
211 224
212 // 1. 转换为Milvus格式 225 // 1. 转换为Milvus格式
213 - List<JsonObject> rows = convertToMilvusRow(data, sVectorfiled, sVectorjson); 226 + List<JsonObject> rows = convertToMilvusRow(data, sVectorfiled, sVectorjson,sceneName);
214 if (CollUtil.isEmpty(rows)) { 227 if (CollUtil.isEmpty(rows)) {
215 return 0l; // 无数据直接返回 228 return 0l; // 无数据直接返回
216 } 229 }
@@ -264,7 +277,7 @@ public class MilvusServiceImpl implements MilvusService { @@ -264,7 +277,7 @@ public class MilvusServiceImpl implements MilvusService {
264 * @Description 返回组装动态内容 277 * @Description 返回组装动态内容
265 **/ 278 **/
266 @Override 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 List<String> sFileds = new ArrayList<>(); 281 List<String> sFileds = new ArrayList<>();
269 List<String> filedsShow = new ArrayList<>(); 282 List<String> filedsShow = new ArrayList<>();
270 List<String> sFiledDescriptions = new ArrayList<>(); 283 List<String> sFiledDescriptions = new ArrayList<>();
@@ -299,17 +312,32 @@ public class MilvusServiceImpl implements MilvusService { @@ -299,17 +312,32 @@ public class MilvusServiceImpl implements MilvusService {
299 String formattedDesc =String.format("%s: %s", sName, sDescriptions); 312 String formattedDesc =String.format("%s: %s", sName, sDescriptions);
300 sFiledDescriptionsAll.add(formattedDesc); 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 Map<String,Object> rMap = new HashMap<>(); 328 Map<String,Object> rMap = new HashMap<>();
303 rMap.put("sMilvusFiled", String.join(",", sFileds)); 329 rMap.put("sMilvusFiled", String.join(",", sFileds));
304 rMap.put("sMilvusFiledDescription", String.join(",", sFiledDescriptions)); 330 rMap.put("sMilvusFiledDescription", String.join(",", sFiledDescriptions));
305 rMap.put("sMilvusFiledDescriptionAll", String.join(",", sFiledDescriptionsAll)); 331 rMap.put("sMilvusFiledDescriptionAll", String.join(",", sFiledDescriptionsAll));
  332 + rMap.put("sMilvusFiledXl", String.join(",", sFiledsXl));
  333 + rMap.put("sMilvusFiledDescriptionXl", String.join(",", sFiledDescriptionsXl));
306 rMap.put("filedsShow", filedsShow); 334 rMap.put("filedsShow", filedsShow);
307 rMap.put("title", titleList); 335 rMap.put("title", titleList);
308 return rMap; 336 return rMap;
309 } 337 }
310 338
311 @Override 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 log.info("开始相似度查询: collection={}, searchText={}", collectionName, searchText); 341 log.info("开始相似度查询: collection={}, searchText={}", collectionName, searchText);
314 // 2. 设置范围搜索参数 342 // 2. 设置范围搜索参数
315 Map<String, Object> searchParams = new HashMap<>(); 343 Map<String, Object> searchParams = new HashMap<>();
@@ -317,10 +345,24 @@ public class MilvusServiceImpl implements MilvusService { @@ -317,10 +345,24 @@ public class MilvusServiceImpl implements MilvusService {
317 // 对于 IP 度量,相似度范围在 [minScore, maxScore] 345 // 对于 IP 度量,相似度范围在 [minScore, maxScore]
318 searchParams.put("radius", 0.9); // 最小相似度 346 searchParams.put("radius", 0.9); // 最小相似度
319 searchParams.put("range_filter", 1); // 最大相似度 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 // 1. 向量化搜索文本 364 // 1. 向量化搜索文本
323 - List<Float> vectorList = vectorizationService.textToVector(searchText); 365 + List<Float> vectorList = vectorizationService.textToVector(vectorValue);
324 if (vectorList == null || vectorList.isEmpty()) { 366 if (vectorList == null || vectorList.isEmpty()) {
325 throw new RuntimeException("向量化失败"); 367 throw new RuntimeException("向量化失败");
326 } 368 }
@@ -329,25 +371,27 @@ public class MilvusServiceImpl implements MilvusService { @@ -329,25 +371,27 @@ public class MilvusServiceImpl implements MilvusService {
329 for (int i = 0; i < vectorList.size(); i++) { 371 for (int i = 0; i < vectorList.size(); i++) {
330 floatArray[i] = vectorList.get(i); 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 // 3. 创建 Milvus FloatVec 对象 374 // 3. 创建 Milvus FloatVec 对象
338 FloatVec floatVec = new FloatVec(floatArray); 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 // 4. 构建搜索请求 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 // .searchParams(searchParams) 390 // .searchParams(searchParams)
348 - .filter(milvusFilter)  
349 - .build(); 391 +// .filter(milvusFilter)
  392 +// .build();
350 // 5. 执行搜索 393 // 5. 执行搜索
  394 + SearchReq searchReq = builder.build();
351 SearchResp searchResp = milvusClient.search(searchReq); 395 SearchResp searchResp = milvusClient.search(searchReq);
352 396
353 // 6. 处理结果 397 // 6. 处理结果
@@ -357,45 +401,26 @@ public class MilvusServiceImpl implements MilvusService { @@ -357,45 +401,26 @@ public class MilvusServiceImpl implements MilvusService {
357 401
358 /** 402 /**
359 * 判断 Milvus 过滤条件是否有效(支持 TEXT_MATCH 全文检索) 403 * 判断 Milvus 过滤条件是否有效(支持 TEXT_MATCH 全文检索)
360 - * @param milvusFilter 过滤条件字符串  
361 * @return true: 有效条件, false: 无效条件 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 return true; 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,14 +608,14 @@ public class MilvusServiceImpl implements MilvusService {
583 /** 608 /**
584 * 从实体对象构建Milvus插入数据 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 List<JsonObject> rows = new ArrayList<>(); 612 List<JsonObject> rows = new ArrayList<>();
588 if (CollUtil.isEmpty(data)) { 613 if (CollUtil.isEmpty(data)) {
589 return rows; 614 return rows;
590 } 615 }
591 // 批量遍历,逐个转换 616 // 批量遍历,逐个转换
592 for (Map<String, Object> map : data) { 617 for (Map<String, Object> map : data) {
593 - JsonObject jsonObject = convertToMilvusRowOne(map, sVectorfiled, sVectorjson); 618 + JsonObject jsonObject = convertToMilvusRowOne(map, sVectorfiled, sVectorjson,sceneName);
594 rows.add(jsonObject); 619 rows.add(jsonObject);
595 } 620 }
596 return rows; 621 return rows;
@@ -603,12 +628,12 @@ public class MilvusServiceImpl implements MilvusService { @@ -603,12 +628,12 @@ public class MilvusServiceImpl implements MilvusService {
603 * @return com.google.gson.JsonObject 628 * @return com.google.gson.JsonObject
604 * @Description 单个转换 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 // ====================== 修复 1:使用真实的向量化文本 ====================== 633 // ====================== 修复 1:使用真实的向量化文本 ======================
609 // 从 sVectorjson 或 data 中获取要向量化的字段值 634 // 从 sVectorjson 或 data 中获取要向量化的字段值
610 StringBuffer vectorText = new StringBuffer(); 635 StringBuffer vectorText = new StringBuffer();
611 - getVectorText(data, vectorText, sVectorjson); 636 + getVectorText(data, vectorText, sVectorjson,sceneName);
612 // 向量化 637 // 向量化
613 List<Float> vector = vectorizationService.textToVector(vectorText.toString()); 638 List<Float> vector = vectorizationService.textToVector(vectorText.toString());
614 if (vector == null || vector.isEmpty()) { 639 if (vector == null || vector.isEmpty()) {
@@ -650,7 +675,7 @@ public class MilvusServiceImpl implements MilvusService { @@ -650,7 +675,7 @@ public class MilvusServiceImpl implements MilvusService {
650 return row; 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 String[] sVectorjsonArray = sVectorjson.split(";"); 680 String[] sVectorjsonArray = sVectorjson.split(";");
656 for (String sVectorjsonOne : sVectorjsonArray) { 681 for (String sVectorjsonOne : sVectorjsonArray) {
@@ -666,8 +691,9 @@ public class MilvusServiceImpl implements MilvusService { @@ -666,8 +691,9 @@ public class MilvusServiceImpl implements MilvusService {
666 }else{ 691 }else{
667 sText = value.toString(); 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 package com.xly.service; 1 package com.xly.service;
2 2
3 import cn.hutool.core.collection.ListUtil; 3 import cn.hutool.core.collection.ListUtil;
4 -import cn.hutool.core.date.DatePattern;  
5 import cn.hutool.core.date.DateUtil; 4 import cn.hutool.core.date.DateUtil;
6 -import cn.hutool.core.util.IdUtil; 5 +import cn.hutool.core.util.BooleanUtil;
7 import cn.hutool.core.util.ObjectUtil; 6 import cn.hutool.core.util.ObjectUtil;
8 import cn.hutool.core.util.StrUtil; 7 import cn.hutool.core.util.StrUtil;
  8 +import cn.hutool.json.JSONUtil;
9 import com.alibaba.fastjson2.JSON; 9 import com.alibaba.fastjson2.JSON;
10 import com.alibaba.fastjson2.JSONObject; 10 import com.alibaba.fastjson2.JSONObject;
11 import com.xly.agent.ChatiAgent; 11 import com.xly.agent.ChatiAgent;
@@ -405,25 +405,48 @@ public class XlyErpService { @@ -405,25 +405,48 @@ public class XlyErpService {
405 String sInputTabelName = session.getCurrentTool().getSInputTabelName(); 405 String sInputTabelName = session.getCurrentTool().getSInputTabelName();
406 String sVectorfiledAll = session.getCurrentTool().getSVectorfiledAll(); 406 String sVectorfiledAll = session.getCurrentTool().getSVectorfiledAll();
407 String sVectorfiledShow = session.getCurrentTool().getSVectorfiledShow(); 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 String sMilvusFiled = rMap.get("sMilvusFiled").toString(); 412 String sMilvusFiled = rMap.get("sMilvusFiled").toString();
  413 + String sMilvusFiledXl = rMap.get("sMilvusFiledXl").toString();
410 String sMilvusFiledDescription = rMap.get("sMilvusFiledDescription").toString(); 414 String sMilvusFiledDescription = rMap.get("sMilvusFiledDescription").toString();
  415 + String sMilvusFiledDescriptionXl = rMap.get("sMilvusFiledDescriptionXl").toString();
411 String sMilvusFiledDescriptionAll = rMap.get("sMilvusFiledDescriptionAll").toString(); 416 String sMilvusFiledDescriptionAll = rMap.get("sMilvusFiledDescriptionAll").toString();
412 List<String> filedsShow = (List<String>) rMap.get("filedsShow"); 417 List<String> filedsShow = (List<String>) rMap.get("filedsShow");
413 List<Map<String, String>> title = (List<Map<String, String>>) rMap.get("title"); 418 List<Map<String, String>> title = (List<Map<String, String>>) rMap.get("title");
414 String milvusFilter = StrUtil.EMPTY; 419 String milvusFilter = StrUtil.EMPTY;
  420 + String vectorValue = StrUtil.EMPTY;
  421 + Boolean bMethodName = false;
415 if(!bCach){ 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 Integer pageSize = 100; 444 Integer pageSize = 100;
422 if(ObjectUtil.isEmpty(milvusFilter)){ 445 if(ObjectUtil.isEmpty(milvusFilter)){
423 pageSize = 10; 446 pageSize = 10;
424 } 447 }
425 // 待条件全查 不带 10条 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 //存储到历史问题库(带where条件了就不存)并且没有记录过缓存 450 //存储到历史问题库(带where条件了就不存)并且没有记录过缓存
428 if(!bCach && ObjectUtil.isEmpty(milvusFilter)){ 451 if(!bCach && ObjectUtil.isEmpty(milvusFilter)){
429 //执行操作记录表 452 //执行操作记录表
@@ -436,13 +459,7 @@ public class XlyErpService { @@ -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 resultExplain = buildMarkdownTableWithStream(data, title); 463 resultExplain = buildMarkdownTableWithStream(data, title);
447 }else{ 464 }else{
448 resultExplain = aiAgent.explainMilvusResult(session.getUserId(),userInput,sMilvusFiledDescriptionAll,JSONObject.toJSONString(data)); 465 resultExplain = aiAgent.explainMilvusResult(session.getUserId(),userInput,sMilvusFiledDescriptionAll,JSONObject.toJSONString(data));
@@ -453,6 +470,21 @@ public class XlyErpService { @@ -453,6 +470,21 @@ public class XlyErpService {
453 } 470 }
454 return resultExplain; 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 * @Author 钱豹 489 * @Author 钱豹
458 * @Date 13:19 2026/3/25 490 * @Date 13:19 2026/3/25
@@ -640,38 +672,47 @@ public class XlyErpService { @@ -640,38 +672,47 @@ public class XlyErpService {
640 if(Integer.valueOf(iErroCount)>0){ 672 if(Integer.valueOf(iErroCount)>0){
641 doAiSqlErrorHistoryThread(session, cleanSql, StrUtil.EMPTY, StrUtil.EMPTY,input); 673 doAiSqlErrorHistoryThread(session, cleanSql, StrUtil.EMPTY, StrUtil.EMPTY,input);
642 } 674 }
643 - //插入常用操作 675 + //插入常用操作 不包含where 条件
644 if(doAddSql){ 676 if(doAddSql){
645 //执行操作记录表 677 //执行操作记录表
646 doAiUserAgentQuestion(session,input,cleanSql,"MYSQL",chatMessage); 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 * @Author 钱豹 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,22 +722,14 @@ public class XlyErpService {
681 * @return java.lang.String 722 * @return java.lang.String
682 * @Description 获取动态SQL(历史中查询) 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 try{ 726 try{
691 String searchText = session.getCurrentScene().getSId()+"_"+session.getCurrentTool().getSId()+input; 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 Map<String,Object> serMap = aiGlobalAgentQuestionSqlEmitterService.queryAiGlobalAgentQuestionSqlEmitter(searchText, "ai_global_agent_question_sql"); 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 }catch (Exception e){ 731 }catch (Exception e){
699 - 732 + log.error("取是否走缓存异常");
700 } 733 }
701 return null; 734 return null;
702 } 735 }
src/main/java/com/xly/thread/AiUserAgentQuestionThread.java
@@ -9,8 +9,8 @@ import com.xly.entity.UserSceneSession; @@ -9,8 +9,8 @@ import com.xly.entity.UserSceneSession;
9 import com.xly.milvus.service.AiGlobalAgentQuestionSqlEmitterService; 9 import com.xly.milvus.service.AiGlobalAgentQuestionSqlEmitterService;
10 import com.xly.service.DynamicExeDbService; 10 import com.xly.service.DynamicExeDbService;
11 import com.xly.service.RedisService; 11 import com.xly.service.RedisService;
12 -import com.xly.util.MD5Util;  
13 import com.xly.util.SqlValidateUtil; 12 import com.xly.util.SqlValidateUtil;
  13 +import com.xly.util.SqlWhereHelper;
14 import dev.langchain4j.data.message.ChatMessage; 14 import dev.langchain4j.data.message.ChatMessage;
15 import dev.langchain4j.data.message.ChatMessageType; 15 import dev.langchain4j.data.message.ChatMessageType;
16 import java.util.HashMap; 16 import java.util.HashMap;
@@ -54,17 +54,15 @@ public class AiUserAgentQuestionThread implements Runnable { @@ -54,17 +54,15 @@ public class AiUserAgentQuestionThread implements Runnable {
54 } 54 }
55 String sKey = sSceneId+"_"+sMethodId +"_"+sQuestion; 55 String sKey = sSceneId+"_"+sMethodId +"_"+sQuestion;
56 // SqlValidateUtil.getsKey( sSceneId, sMethodId, SqlValidateUtil.getsQuestion(session.getSUserQuestionList())); 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 Map<String, Object> searMap = dynamicExeDbService.getDoProMap(sProName, data); 62 Map<String, Object> searMap = dynamicExeDbService.getDoProMap(sProName, data);
61 dynamicExeDbService.getCallPro(searMap, sProName); 63 dynamicExeDbService.getCallPro(searMap, sProName);
62 } 64 }
63 65
64 -  
65 -  
66 -  
67 -  
68 //获取组ID 66 //获取组ID
69 private String getQuestionGroupNo(){ 67 private String getQuestionGroupNo(){
70 String sQuestionGroupNo = userMessage.stream() 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 \ No newline at end of file 135 \ No newline at end of file