ソースを参照

Merge branch 'master' of http://oa.sccehui.com:6101/cmc-coding/cmc-oa

余思翰 1週間前
コミット
15cfd6fb9e

+ 4
- 4
oa-back/ruoyi-admin/src/main/resources/application.yml ファイルの表示

@@ -8,16 +8,16 @@ cmc:
8 8
   copyrightYear: 2023
9 9
   # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
10 10
 #  profile: /home/cmc/Java-server/综合办公
11
-#  profile: /home/cmc/projects/cmc-oa
12
-  profile: D:/ruoyi/uploadPath
11
+  profile: /home/cmc/projects/cmc-oa
12
+#  profile: D:/ruoyi/uploadPath
13 13
   # 获取ip地址开关
14 14
   addressEnabled: false
15 15
   # 验证码类型 math 数字计算 char 字符验证
16 16
   captchaType: math
17 17
   llmService:
18
-    url: http://192.168.28.196:8000/v1/chat/completions
18
+    url: http://192.168.31.11:8000/v1/chat/completions
19 19
   milvusService:
20
-    url: http://192.168.28.196:19530
20
+    url: http://192.168.31.11:19530 
21 21
 
22 22
 # 开发环境配置
23 23
 server:

+ 5
- 15
oa-back/ruoyi-agent/pom.xml ファイルの表示

@@ -128,26 +128,16 @@
128 128
             </plugin>
129 129
 
130 130
             <plugin>
131
-                <groupId>org.apache.maven.plugins</groupId>
132
-                <artifactId>maven-assembly-plugin</artifactId>
131
+                <groupId>org.springframework.boot</groupId>
132
+                <artifactId>spring-boot-maven-plugin</artifactId>
133
+                <version>2.7.18</version>
133 134
                 <configuration>
134
-                    <finalName>${project.name}</finalName>
135
-                    <appendAssemblyId>false</appendAssemblyId>
136
-                    <descriptorRefs>
137
-                        <descriptorRef>jar-with-dependencies</descriptorRef>
138
-                    </descriptorRefs>
139
-                    <archive>
140
-                        <manifest>
141
-                            <mainClass>webapp.HelloApp</mainClass>
142
-                        </manifest>
143
-                    </archive>
135
+                    <mainClass>com.ruoyi.agent.RuoYiAgentApplication</mainClass>
144 136
                 </configuration>
145 137
                 <executions>
146 138
                     <execution>
147
-                        <id>make-assembly</id>
148
-                        <phase>package</phase>
149 139
                         <goals>
150
-                            <goal>single</goal>
140
+                            <goal>repackage</goal>
151 141
                         </goals>
152 142
                     </execution>
153 143
                 </executions>

+ 104
- 0
oa-back/ruoyi-agent/src/main/java/com/ruoyi/agent/service/impl/McpServiceImpl.java ファイルの表示

@@ -126,6 +126,24 @@ public class McpServiceImpl implements IMcpService {
126 126
             return executeQuery(sqlString);
127 127
     }
128 128
 
129
+    /**
130
+      * 获取所有表名列表
131
+      */
132
+     @ToolMapping(description = "获取数据库中所有表名列表")
133
+     public JSONArray GetAllTableNames()
134
+     {
135
+             return getAllTableNames();
136
+     }
137
+
138
+     /**
139
+      * 获取指定表的结构信息
140
+      */
141
+     @ToolMapping(description = "获取指定表的结构信息")
142
+     public JSONObject GetTableStructure(@Param(description = "表名") String tableName)
143
+     {
144
+             return getTableStructure(tableName);
145
+     }
146
+
129 147
     public JSONArray executeQuery(String sql) {
130 148
         JSONArray result = new JSONArray();
131 149
 
@@ -720,4 +738,90 @@ public class McpServiceImpl implements IMcpService {
720 738
         }
721 739
     }
722 740
 
741
+    /**
742
+     * 获取表结构信息
743
+     */
744
+    private JSONObject getTableStructure(String tableName) {
745
+        JSONObject result = new JSONObject();
746
+        
747
+        try {
748
+            Class.forName("com.mysql.cj.jdbc.Driver");
749
+        } catch (ClassNotFoundException e) {
750
+            throw new RuntimeException("MySQL 驱动未找到", e);
751
+        }
752
+
753
+        try (Connection conn = DriverManager.getConnection(Solon.cfg().getProperty("cmc.mysqlService.jdbcUrl"),Solon.cfg().getProperty("cmc.mysqlService.username"), Solon.cfg().getProperty("cmc.mysqlService.password"))) {
754
+            
755
+            DatabaseMetaData metaData = conn.getMetaData();
756
+            
757
+            ResultSet columns = metaData.getColumns(null, null, tableName, null);
758
+            JSONArray columnsInfo = new JSONArray();
759
+            
760
+            while (columns.next()) {
761
+                JSONObject column = new JSONObject();
762
+                column.put("columnName", columns.getString("COLUMN_NAME"));
763
+                column.put("dataType", columns.getString("TYPE_NAME"));
764
+                column.put("columnSize", columns.getString("COLUMN_SIZE"));
765
+                column.put("isNullable", columns.getString("IS_NULLABLE"));
766
+                column.put("columnComment", columns.getString("REMARKS"));
767
+                columnsInfo.add(column);
768
+            }
769
+            columns.close();
770
+            
771
+            ResultSet primaryKeys = metaData.getPrimaryKeys(null, null, tableName);
772
+            JSONArray primaryKeyColumns = new JSONArray();
773
+            
774
+            while (primaryKeys.next()) {
775
+                primaryKeyColumns.add(primaryKeys.getString("COLUMN_NAME"));
776
+            }
777
+            primaryKeys.close();
778
+            
779
+            result.put("tableName", tableName);
780
+            result.put("columns", columnsInfo);
781
+            result.put("primaryKey", primaryKeyColumns);
782
+            
783
+        } catch (SQLException e) {
784
+            throw new RuntimeException("获取表结构失败: " + tableName, e);
785
+        }
786
+        
787
+        return result;
788
+    }
789
+
790
+    /**
791
+     * 获取所有表名列表(只包含cmc、sys和files开头的表)
792
+     */
793
+    private JSONArray getAllTableNames() {
794
+        JSONArray tableNames = new JSONArray();
795
+        
796
+        try {
797
+            Class.forName("com.mysql.cj.jdbc.Driver");
798
+        } catch (ClassNotFoundException e) {
799
+            throw new RuntimeException("MySQL 驱动未找到", e);
800
+        }
801
+
802
+        try (Connection conn = DriverManager.getConnection(Solon.cfg().getProperty("cmc.mysqlService.jdbcUrl"),Solon.cfg().getProperty("cmc.mysqlService.username"), Solon.cfg().getProperty("cmc.mysqlService.password"))) {
803
+            
804
+            DatabaseMetaData metaData = conn.getMetaData();
805
+            ResultSet tables = metaData.getTables(null, null, null, new String[]{"TABLE"});
806
+            
807
+            while (tables.next()) {
808
+                String tableName = tables.getString("TABLE_NAME");
809
+                String tableComment = tables.getString("REMARKS");
810
+                // 只获取cmc、sys和files开头的表
811
+                if (tableName.startsWith("cmc") || tableName.startsWith("sys") || tableName.startsWith("files")) {
812
+                    JSONObject tableInfo = new JSONObject();
813
+                    tableInfo.put("tableName", tableName);
814
+                    tableInfo.put("tableComment", tableComment != null ? tableComment : "");
815
+                    tableNames.add(tableInfo);
816
+                }
817
+            }
818
+            tables.close();
819
+            
820
+        } catch (SQLException e) {
821
+            throw new RuntimeException("获取表名列表失败", e);
822
+        }
823
+        
824
+        return tableNames;
825
+    }
826
+
723 827
 }

+ 2
- 2
oa-back/ruoyi-agent/src/main/resources/application.yml ファイルの表示

@@ -4,9 +4,9 @@ cmc:
4 4
   #  profile: /home/cmc/projects/cmc-llm
5 5
   profile: /home/cmc/projects/cmc-oa
6 6
   llmService:
7
-    url: http://192.168.28.196:8000/v1/chat/completions
7
+    url: http://192.168.31.11:8000/v1/chat/completions
8 8
   milvusService:
9
-    url: http://192.168.28.196:19530
9
+    url: http://192.168.31.11:19530 
10 10
   mysqlService:
11 11
     jdbcUrl: jdbc:mysql://localhost:3306/cmc_oa?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
12 12
     username: root

+ 168
- 165
oa-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/service/impl/SessionServiceImpl.java ファイルの表示

@@ -1,7 +1,6 @@
1 1
 package com.ruoyi.web.llm.service.impl;
2 2
 
3 3
 import com.alibaba.fastjson2.JSONObject;
4
-import com.ruoyi.common.config.RuoYiConfig;
5 4
 import com.ruoyi.llm.domain.CmcChat;
6 5
 import com.ruoyi.llm.service.ICmcChatService;
7 6
 import com.ruoyi.web.llm.service.ILangChainMilvusService;
@@ -20,10 +19,6 @@ import org.springframework.beans.factory.annotation.Value;
20 19
 import org.springframework.stereotype.Service;
21 20
 import reactor.core.publisher.Flux;
22 21
 
23
-import java.io.BufferedReader;
24
-import java.io.File;
25
-import java.io.FileReader;
26
-import java.io.IOException;
27 22
 import java.util.ArrayList;
28 23
 import java.util.List;
29 24
 
@@ -42,7 +37,7 @@ public class SessionServiceImpl implements ISessionService {
42 37
     @Override
43 38
     public Flux<String> answer(String topicId, String question) {
44 39
         McpClientProvider clientProvider = McpClientProvider.builder()
45
-                .channel(McpChannel.STREAMABLE_STATELESS )
40
+                .channel(McpChannel.STREAMABLE_STATELESS)
46 41
                 .url("http://localhost:8087/mcp/sse")
47 42
                 .build();
48 43
         ChatModel chatModel = ChatModel.of(llmServiceUrl)
@@ -59,122 +54,192 @@ public class SessionServiceImpl implements ISessionService {
59 54
             messages.add(ChatMessage.ofAssistant(chat.getOutput()));
60 55
         }
61 56
 
62
-        // 读取SQL文件
63
-        StringBuilder sqlContent = new StringBuilder();
64
-        File sqlStructure = new File(RuoYiConfig.getProfile() + "/cmc_oa.sql");
65
-        try (BufferedReader br = new BufferedReader(new FileReader(sqlStructure))) {
66
-            String line;
67
-            while ((line = br.readLine()) != null) {
68
-                sqlContent.append(line).append("\n");
69
-            }
70
-        } catch (IOException e) {
71
-            System.out.println("读取SQL文件失败");
72
-        }
57
+        // 第一步:调用 GetAllTableNames 获取所有表名
58
+        String step1Prompt = "你是一个MySQL专家。请调用 GetAllTableNames 工具获取数据库中所有表名列表。";
59
+        messages.add(ChatMessage.ofUser(step1Prompt));
60
+        ChatSession session1 = InMemoryChatSession.builder().messages(messages).build();
61
+        Prompt prompt1 = Prompt.of(step1Prompt).attrPut("session", session1);
73 62
 
74
-        // 优化prompt:明确要求不输出任何中间标记
75
-        String prompt = "你是一个MySQL专家。根据以下OA系统表结构信息:  \n" +
76
-                sqlContent + " \n" +
77
-                "用户查询:  \n"+
78
-                question  + " \n"+
79
-                "你可以使用SQLQuery工具来查询OA系统数据。  \n" +
80
-                "SQLQuery工具功能:执行SQL查询语句,获取OA系统数据  \n" +
81
-                "工具参数:sqlString(需要执行的SQL语句,格式为[SQL语句])  \n" +
82
-                "要求:  \n" +
83
-                "1. 根据用户查询需求,决定是否需要查询数据  \n" +
84
-                "2. 如果需要查询数据,调用SQLQuery工具并传入正确的SQL语句  \n" +
85
-                "3. 生成标准MYSQL查询语句,根据语义和字段类型使用COUNT/SUM/AVG等聚合函数  \n" +
86
-                "4. 如果单表字段不满足查询需求,根据OA系统表结构信息进行多张表连接查询  \n" +
87
-                "5. 如果字段值由逗号隔开,根据OA系统表结构信息进行多张表连接查询,采用concat方式多值匹配  \n" +
88
-                "6. 给生成的字段取一个简短的中文名称  \n" +
89
-                "7. **重要:输出格式要求**  \n" +
90
-                "   - 不要输出任何思考过程  \n" +
91
-                "   - 所有输出必须是完整的句子  \n" +
92
-                "   - 不要输出'查询结果'、'正在生成回答'等中间提示  \n" +
93
-                "   - 直接输出最终回答内容  \n" +
94
-                "   - 如果使用工具,工具调用和结果处理对用户透明,用户只看到最终答案  \n";
63
+        Flux<ChatResponse> chatResponse = chatModel.prompt(prompt1).stream();
64
+        Flux<String> contentFlux = chatResponse
65
+                .concatMap(resp -> {
66
+                    try {
67
+                        // 执行第一步:获取所有表名
68
+                        String toolCall1 = "{\"name\": \"GetAllTableNames\", \"arguments\": {}}";
69
+                        String toolResult1 = clientProvider.callTool("GetAllTableNames", new JSONObject()).getContent();
95 70
 
96
-        messages.add(ChatMessage.ofUser(prompt));
97
-        ChatSession chatSession = InMemoryChatSession.builder().messages(messages).build();
98
-        Prompt wholePrompt = Prompt.of(prompt).attrPut("session", chatSession);
71
+                        // 第二步:模型分析表名,找到相关表
72
+                        List<ChatMessage> messages2 = new ArrayList<>(messages);
73
+                        messages2.add(ChatMessage.ofAssistant("<tool_call>" + toolCall1 + "</tool_call>"));
74
+                        messages2.add(ChatMessage.ofUser(toolResult1));
99 75
 
100
-        Flux<ChatResponse> chatResponse = chatModel.prompt(wholePrompt).stream();
76
+                        String step2Prompt = "请分析以下表名列表,找出与" + question + "相关的表。\n\n" +
77
+                        "**输出格式要求**:\n" +
78
+                        "- 如果需要数据库查询,请以固定格式输出:以\"相关表:\" 开头,中间用英文逗号隔开,以英文句号结尾\n" +
79
+                        "- 如果不需要进行数据库查询,请直接回答。\n\n" + toolResult1;
80
+                        messages2.add(ChatMessage.ofUser(step2Prompt));
81
+                        ChatSession session2 = InMemoryChatSession.builder().messages(messages2).build();
101 82
 
102
-        // 使用StringBuilder缓存完整响应,用于检测工具调用
103
-        StringBuilder fullResponseBuilder = new StringBuilder();
104
-        // 标记是否已经处理过工具调用
105
-        boolean[] toolCallProcessed = {false};
106
-        // 输出过滤状态:用于抑制 tool_call 区块(对用户不可见)
107
-        StreamFilterState filterState = new StreamFilterState();
83
+                        // 生成第二步的响应
84
+                        return chatModel.prompt(Prompt.of(step2Prompt).attrPut("session", session2))
85
+                                .stream()
86
+                                .concatMap(resp1 -> {
87
+                                    try {
88
+                                        String content2 = resp1.getContent();
108 89
 
109
-        Flux<String> contentFlux = chatResponse
110
-                .concatMap(resp -> {
111
-                    String content = resp.getContent();
90
+                                        // 提取相关表名(从模型响应中提取)
91
+                                        String relevantTables = extractRelevantTables(content2);
112 92
 
113
-                    // 先累积原始内容,用于 tool_call 检测
114
-                    fullResponseBuilder.append(content);
115
-                    String currentFullContent = fullResponseBuilder.toString();
93
+                                        // 检查是否需要继续执行(如果包含工具调用或明确需要数据库查询)
94
+                                        if (relevantTables.equals("")) {
95
+                                            // 不需要数据库查询,直接返回最终回答
96
+                                            return Flux.just(content2)
97
+                                                    .map(this::toSseDataFrame)
98
+                                                    .concatWith(Flux.just("data: [DONE]\n\n"));
99
+                                        }
116 100
 
117
-                    // 1) 优先处理工具调用:检测到闭合的 <tool_call>...</tool_call> 后,静默调用工具并流式输出最终答案
118
-                    if (!toolCallProcessed[0]
119
-                            && currentFullContent.contains("<tool_call>")
120
-                            && currentFullContent.contains("</tool_call>")) {
121
-                        toolCallProcessed[0] = true;
122
-                        // 进入“最终回答阶段”前重置过滤状态
123
-                        filterState.reset();
124
-                        try {
125
-                            // 提取工具调用内容
126
-                            int start = currentFullContent.indexOf("<tool_call>");
127
-                            int end = currentFullContent.indexOf("</tool_call>");
128
-                            if (start < 0 || end < 0 || end <= start) {
129
-                                throw new IllegalStateException("未找到完整的 <tool_call>...</tool_call> 区块");
130
-                            }
131
-                            String toolContent = currentFullContent.substring(start + "<tool_call>".length(), end).trim();
132
-                            JSONObject jsonObject = JSONObject.parseObject(toolContent);
133
-                            String name = jsonObject.getString("name");
134
-                            JSONObject arguments = jsonObject.getJSONObject("arguments");
135
-                            String toolResult = "";
136
-                            try {
137
-                                // 调用工具
138
-                                toolResult = clientProvider.callTool(name, arguments).getContent();
139
-                            } catch (Exception e) {
140
-                                return Flux.just("抱歉,系统执行查询时遇到问题,请稍后重试。");
141
-                            } finally {
142
-                                clientProvider.close();
143
-                            }
101
+                                        // 第三步:调用 GetTableStructure 获取相关表的结构
102
+                                        List<ChatMessage> messages3 = new ArrayList<>(messages2);
103
+                                        messages3.add(ChatMessage.ofAssistant(content2));
144 104
 
145
-                            // 生成最终回答 - 不添加任何中间提示
146
-                            String finalPrompt = "基于以下查询结果,直接回答用户问题:\n" +
147
-                                    "查询结果:\n" + toolResult + "\n\n" +
148
-                                    "用户问题:\n" + question + "\n\n" +
149
-                                    "要求:\n" +
150
-                                    "1. 直接给出最终答案,不要输出任何中间过程\n" +
151
-                                    "2. 不要输出'查询结果'、'根据查询结果'等前缀\n" +
152
-                                    "3. 用完整的中文句子回答,语言自然流畅\n" +
153
-                                    "4. 如果查询结果为空,如实说明";
105
+                                        // 执行第三步:获取表结构
106
+                                        StringBuilder tableStructures = new StringBuilder();
107
+                                        for (String tableName : relevantTables.split(",")) {
108
+                                            tableName = tableName.trim();
109
+                                            if (!tableName.isEmpty()) {
110
+                                                JSONObject args = new JSONObject();
111
+                                                args.put("tableName", tableName);
112
+                                                String toolResult3 = clientProvider.callTool("GetTableStructure", args)
113
+                                                        .getContent();
114
+                                                tableStructures.append("表:").append(tableName).append("\n")
115
+                                                        .append(toolResult3)
116
+                                                        .append("\n\n");
117
+                                            }
118
+                                        }
154 119
 
155
-                            return chatModel.prompt(finalPrompt).stream()
156
-                                    .map(ChatResponse::getContent)
157
-                                    .filter(answer -> answer != null && !answer.isEmpty());
120
+                                        // 第四步:模型生成 SQL 查询
121
+                                        List<ChatMessage> messages4 = new ArrayList<>(messages3);
122
+                                        messages4.add(ChatMessage.ofAssistant(
123
+                                                "<tool_call>{\"name\": \"GetTableStructure\", \"arguments\": {\"tableName\": \"employee\"}}</tool_call>"));
124
+                                        messages4.add(ChatMessage.ofUser(tableStructures.toString()));
158 125
 
159
-                        } catch (Exception e) {
160
-                            return Flux.just("抱歉,系统执行查询时遇到问题,请稍后重试。");
161
-                        }
162
-                    }
126
+                                        String step4Prompt = "请根据以下表结构,生成" + question + "的SQL语句:\n\n"
127
+                                                + tableStructures.toString();
128
+                                        messages4.add(ChatMessage.ofUser(step4Prompt));
163 129
 
164
-                    // 2) 对用户可见输出:统一用“容错状态机”剥离<tool_call> 区块
165
-                    //    不再使用“未闭合 tool_call 就整段屏蔽”的策略,避免吞掉真正回答。
166
-                    String visible = filterVisibleChunk(content, filterState);
167
-                    return visible.isEmpty() ? Flux.empty() : Flux.just(visible);
168
-                });
130
+                                        // 生成第四步的响应
131
+                                        return chatModel
132
+                                                .prompt(Prompt.of(step4Prompt).attrPut("session",
133
+                                                        InMemoryChatSession.builder().messages(messages4).build()))
134
+                                                .stream()
135
+                                                .concatMap(resp2 -> {
136
+                                                    try {
137
+                                                        String content4 = resp2.getContent();
138
+
139
+                                                        // 提取 SQL 语句
140
+                                                        String sqlQuery = extractSqlFromContent(content4);
141
+
142
+                                                        // 第五步:调用 SQLQuery 执行查询
143
+                                                        List<ChatMessage> messages5 = new ArrayList<>(messages4);
144
+                                                        messages5.add(ChatMessage.ofAssistant(content4));
145
+
146
+                                                        // 执行第五步:执行 SQL 查询
147
+                                                        JSONObject sqlArgs = new JSONObject();
148
+                                                        sqlArgs.put("sqlString", sqlQuery);
149
+                                                        String toolResult5 = clientProvider
150
+                                                                .callTool("SQLQuery", sqlArgs)
151
+                                                                .getContent();
152
+
153
+                                                        // 第六步:模型生成最终回答
154
+                                                        List<ChatMessage> messages6 = new ArrayList<>(messages5);
155
+                                                        messages6.add(ChatMessage.ofAssistant(
156
+                                                                "<tool_call>{\"name\": \"SQLQuery\", \"arguments\": {\"sqlString\": \""
157
+                                                                        + sqlQuery + "\"}}</tool_call>"));
158
+                                                        messages6.add(ChatMessage.ofUser(toolResult5));
169 159
 
160
+                                                        String step6Prompt = "请根据以下查询结果,生成关于" + question + "的最终回答:\n\n"
161
+                                                                + toolResult5;
162
+                                                        messages6.add(ChatMessage.ofUser(step6Prompt));
163
+
164
+                                                        // 生成第六步的响应(流式)
165
+                                                        return chatModel
166
+                                                                .prompt(Prompt.of(step6Prompt).attrPut("session",
167
+                                                                        InMemoryChatSession.builder()
168
+                                                                                .messages(messages6)
169
+                                                                                .build()))
170
+                                                                .stream()
171
+                                                                .map(resp3 -> resp3.getContent())
172
+                                                                .filter(answer -> answer != null && !answer.isEmpty());
173
+                                                    } catch (Exception e) {
174
+                                                        return Flux.just("抱歉,系统执行查询时遇到问题,请稍后重试。")
175
+                                                                .map(this::toSseDataFrame)
176
+                                                                .concatWith(Flux.just("data: [DONE]\n\n"));
177
+                                                    }
178
+                                                });
179
+                                    } catch (Exception e) {
180
+                                        return Flux.just("抱歉,系统执行查询时遇到问题,请稍后重试。")
181
+                                                .map(this::toSseDataFrame)
182
+                                                .concatWith(Flux.just("data: [DONE]\n\n"));
183
+                                    }
184
+                                });
185
+                    } catch (Exception e) {
186
+                        return Flux.just("抱歉,系统执行查询时遇到问题,请稍后重试。")
187
+                                .map(this::toSseDataFrame)
188
+                                .concatWith(Flux.just("data: [DONE]\n\n"));
189
+                    }
190
+                });
170 191
         // 手工拼接 SSE 帧,绕过 ServerSentEvent 编码差异;并在末尾发送 [DONE]
171 192
         Flux<String> sseFlux = contentFlux
172 193
                 .map(this::toSseDataFrame)
173 194
                 .concatWith(Flux.just("data: [DONE]\n\n"));
174
-
175 195
         return sseFlux;
176 196
     }
177 197
 
198
+    /**
199
+     * 从模型响应中提取相关表名
200
+     */
201
+    private String extractRelevantTables(String content) {
202
+        // 简化处理,实际应该根据模型输出格式提取
203
+        // 假设模型会返回类似 "相关表:employee, department"
204
+        if (content.contains("相关表:")) {
205
+            int start = content.indexOf("相关表:") + 4;
206
+            int end = content.indexOf(".", start);
207
+            if (end > start) {
208
+                String tables = content.substring(start, end).trim();
209
+                // 限制相关表最多只保留10个
210
+                String[] tableNames = tables.split(",");
211
+                if (tableNames.length > 10) {
212
+                    StringBuilder limitedTables = new StringBuilder();
213
+                    for (int i = 0; i < 10; i++) {
214
+                        if (i > 0) {
215
+                            limitedTables.append(",");
216
+                        }
217
+                        limitedTables.append(tableNames[i].trim());
218
+                    }
219
+                    return limitedTables.toString();
220
+                }
221
+                return tables;
222
+            }
223
+        }
224
+        // 默认返回可能相关的表
225
+        return "";
226
+    }
227
+
228
+    /**
229
+     * 从模型响应中提取 SQL 语句
230
+     */
231
+    private String extractSqlFromContent(String content) {
232
+        // 简化处理,实际应该根据模型输出格式提取
233
+        if (content.contains("SELECT")) {
234
+            int start = content.indexOf("SELECT");
235
+            int end = content.indexOf(";", start);
236
+            if (end > start) {
237
+                return content.substring(start, end + 1);
238
+            }
239
+        }
240
+        return "";
241
+    }
242
+
178 243
     @Override
179 244
     public Flux<AssistantMessage> answerWithDocument(String topicId, String chatId, String question) throws Exception {
180 245
         return langChainMilvusService.generateAnswerWithDocument(topicId, chatId, question);
@@ -204,66 +269,4 @@ public class SessionServiceImpl implements ISessionService {
204 269
         return sb.toString();
205 270
     }
206 271
 
207
-    private static final class StreamFilterState {
208
-        boolean inToolCall = false;
209
-
210
-        void reset() {
211
-            inToolCall = false;
212
-        }
213
-    }
214
-
215
-    /**
216
-     * 对用户可见输出过滤:
217
-     * - 丢弃 <tool_call>...</tool_call> 内容(容错:缺失 </tool_call> 时,遇到空行分隔则自动退出 tool_call)
218
-     * 说明:工具调用检测/执行仍基于原始累积文本,不依赖该过滤器。
219
-     */
220
-    private String filterVisibleChunk(String chunk, StreamFilterState st) {
221
-        if (chunk == null || chunk.isEmpty()) {
222
-            return "";
223
-        }
224
-        String s = chunk.replace("\r\n", "\n");
225
-        StringBuilder out = new StringBuilder(s.length());
226
-        int i = 0;
227
-        while (i < s.length()) {
228
-            // 在 tool_call 内:优先寻找 </tool_call>;找不到则容错以空行分隔退出
229
-            if (st.inToolCall) {
230
-                int end = s.indexOf("</tool_call>", i);
231
-                if (end >= 0) {
232
-                    st.inToolCall = false;
233
-                    i = end + "</tool_call>".length();
234
-                    continue;
235
-                }
236
-                int sep = s.indexOf("\n\n", i);
237
-                if (sep >= 0) {
238
-                    st.inToolCall = false;
239
-                    i = sep + 2;
240
-                    continue;
241
-                }
242
-                // 剩余都认为在 tool_call 内
243
-                break;
244
-            }
245
-
246
-            // 不在任何区块内:找下一个起始标签
247
-            int nextTool = s.indexOf("<tool_call>", i);
248
-            int next;
249
-            if (nextTool == -1) {
250
-                out.append(s.substring(i));
251
-                break;
252
-            } else {
253
-                next = nextTool;
254
-            }
255
-
256
-            if (next > i) {
257
-                out.append(s, i, next);
258
-            }
259
-            st.inToolCall = true;
260
-            i = next + "<tool_call>".length();
261
-        }
262
-
263
-        // 兜底移除残留标签字面量(避免显示到前端)
264
-        return out.toString()
265
-                .replace("<tool_call>", "")
266
-                .replace("</tool_call>", "");
267
-    }
268
-
269 272
 }

読み込み中…
キャンセル
保存