Commit d72de0c4fe4d50edf536174f6b7f6fa97d18ac96
1 parent
b9b48f2e
添加向量库
Showing
8 changed files
with
581 additions
and
206 deletions
pom.xml
| @@ -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 |