|
|
@@ -42,8 +42,8 @@ import org.apache.poi.hwpf.usermodel.Range;
|
|
42
|
42
|
import org.apache.poi.xwpf.usermodel.*;
|
|
43
|
43
|
import org.apache.xmlbeans.XmlCursor;
|
|
44
|
44
|
import org.noear.solon.ai.AiUsage;
|
|
|
45
|
+import org.noear.solon.ai.agent.simple.SimpleAgent;
|
|
45
|
46
|
import org.noear.solon.ai.chat.ChatModel;
|
|
46
|
|
-import org.noear.solon.ai.chat.ChatResponse;
|
|
47
|
47
|
import org.noear.solon.ai.chat.ChatSession;
|
|
48
|
48
|
import org.noear.solon.ai.chat.message.ChatMessage;
|
|
49
|
49
|
import org.noear.solon.ai.chat.prompt.Prompt;
|
|
|
@@ -138,7 +138,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
138
|
138
|
@Override
|
|
139
|
139
|
public String getOpening(String agentName) {
|
|
140
|
140
|
String content = "";
|
|
141
|
|
- if (agentName.contains("技术"))
|
|
|
141
|
+ if (agentName.contains("技术标书"))
|
|
142
|
142
|
content = "我是投标文件写作助手,我将助您完成技术文件部分撰写。请上传招标询价文件,分析后将依招标服务要求,运用技术方案知识库,为您提供参考。";
|
|
143
|
143
|
else if (agentName.contains("检查"))
|
|
144
|
144
|
content = "我是文档检查助手,我将助您完成错别字检查。请上传文件。";
|
|
|
@@ -152,7 +152,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
152
|
152
|
* @return 结果
|
|
153
|
153
|
*/
|
|
154
|
154
|
@Override
|
|
155
|
|
- public JSONObject uploadDocument(MultipartFile file, String agentName) throws IOException {
|
|
|
155
|
+ public JSONObject uploadDocument(MultipartFile file, String agentName) throws Throwable {
|
|
156
|
156
|
processValue = "";
|
|
157
|
157
|
String prefixPath = "/upload/agent/" + agentName;
|
|
158
|
158
|
File profilePath = new File(RuoYiConfig.getProfile() + prefixPath);
|
|
|
@@ -176,7 +176,10 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
176
|
176
|
CmcChat cmcChat = new CmcChat();
|
|
177
|
177
|
cmcChat.setChatId(jsonObject.getString("chatId"));
|
|
178
|
178
|
cmcChat.setInputTime(new Date());
|
|
179
|
|
- cmcChat.setInput("招标文件地址:" + prefixPath + "/" + file.getOriginalFilename());
|
|
|
179
|
+ if (agentName.contains("技术设计书"))
|
|
|
180
|
+ cmcChat.setInput("项目任务书地址:" + prefixPath + "/" + file.getOriginalFilename());
|
|
|
181
|
+ else
|
|
|
182
|
+ cmcChat.setInput("招标文件地址:" + prefixPath + "/" + file.getOriginalFilename());
|
|
180
|
183
|
cmcChat.setUserId(SecurityUtils.getUserId());
|
|
181
|
184
|
cmcChatMapper.insertCmcChat(cmcChat);
|
|
182
|
185
|
if (agentName.contains("技术标书")) {
|
|
|
@@ -197,7 +200,9 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
197
|
200
|
jsonObject.put("projectOverview", projectOverview);
|
|
198
|
201
|
|
|
199
|
202
|
// 分析评分标准
|
|
200
|
|
- String scoringRequirements = analyzeScoringRequirements(segments, agentName);
|
|
|
203
|
+ String scoringRequirements = "";
|
|
|
204
|
+ if (agentName.contains("技术标书"))
|
|
|
205
|
+ scoringRequirements = analyzeScoringRequirements(segments, agentName);
|
|
201
|
206
|
jsonObject.put("scoringRequirements", scoringRequirements);
|
|
202
|
207
|
|
|
203
|
208
|
// 提取输出文件名(不含路径)
|
|
|
@@ -209,10 +214,16 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
209
|
214
|
jsonObject.put("detailedDirectory", detailedDirectoryTree);
|
|
210
|
215
|
jsonObject.put("directoryText", detailedDirectory);
|
|
211
|
216
|
|
|
212
|
|
- message = "好的,我已经收到您上传的招标文件。以下为根据招标文件生成的章节大纲:\n\n" + detailedDirectory + "\n\n" +
|
|
213
|
|
- "若您对章节标题有异议,请打开" + "【<a href='/profile" + outputFilename + "'> 技术文件 " + "</a>】"
|
|
214
|
|
- + "进行修改,后续将根据修改后的章节标题,帮您生成对应章节内容。\n\n" +
|
|
215
|
|
- "思考时间可能较长,请耐心等待!\n";
|
|
|
217
|
+ if (agentName.contains("技术标书"))
|
|
|
218
|
+ message = "好的,我已经收到您上传的招标文件。以下为根据招标文件生成的章节大纲:\n\n" + detailedDirectory + "\n\n" +
|
|
|
219
|
+ "若您对章节标题有异议,请打开" + "【<a href='/profile" + outputFilename + "'> 技术文件 " + "</a>】"
|
|
|
220
|
+ + "进行修改,后续将根据修改后的章节标题,帮您生成对应章节内容。\n\n" +
|
|
|
221
|
+ "思考时间可能较长,请耐心等待!\n";
|
|
|
222
|
+ else
|
|
|
223
|
+ message = "好的,我已经收到您上传的项目任务书。以下为根据项目任务书生成的章节大纲:\n\n" + detailedDirectory + "\n\n" +
|
|
|
224
|
+ "若您对章节标题有异议,请打开" + "【<a href='/profile" + outputFilename + "'> 项目任务书 " + "</a>】"
|
|
|
225
|
+ + "进行修改,后续将根据修改后的章节标题,帮您生成对应章节内容。\n\n" +
|
|
|
226
|
+ "思考时间可能较长,请耐心等待!\n";
|
|
216
|
227
|
|
|
217
|
228
|
// 返回输出文件名,以便前端在保存目录时使用
|
|
218
|
229
|
jsonObject.put("filename", file.getOriginalFilename()
|
|
|
@@ -244,7 +255,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
244
|
255
|
StringBuilder output = new StringBuilder();
|
|
245
|
256
|
output.append("【项目概况】\n").append(projectOverview).append("\n\n");
|
|
246
|
257
|
// 当agentname包含"技术"且包含"询价"时,使用"应价人须知",否则使用"评分标准"
|
|
247
|
|
- String scoringTitle = agentName.contains("技术") && agentName.contains("询价") ? "【应价人须知】" : "【评分标准】";
|
|
|
258
|
+ String scoringTitle = agentName.contains("技术标书") && agentName.contains("询价") ? "【应价人须知】" : "【评分标准】";
|
|
248
|
259
|
output.append(scoringTitle).append("\n").append(scoringRequirements).append("\n\n");
|
|
249
|
260
|
output.append("【详细目录】\n").append(detailedDirectory);
|
|
250
|
261
|
|
|
|
@@ -269,7 +280,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
269
|
280
|
*/
|
|
270
|
281
|
@Override
|
|
271
|
282
|
public JSONObject uploadModifyFile(String topicId, MultipartFile file, String agentName, String selectedNode)
|
|
272
|
|
- throws IOException, InterruptedException, ExecutionException {
|
|
|
283
|
+ throws IOException, Throwable {
|
|
273
|
284
|
processValue = "";
|
|
274
|
285
|
String prefixPath = "/upload/agent/" + agentName;
|
|
275
|
286
|
File profilePath = new File(RuoYiConfig.getProfile() + prefixPath);
|
|
|
@@ -332,7 +343,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
332
|
343
|
}
|
|
333
|
344
|
|
|
334
|
345
|
String message = "";
|
|
335
|
|
- if (agentName.contains("技术")) {
|
|
|
346
|
+ if (agentName.contains("技术标书")) {
|
|
336
|
347
|
String docPath = profilePath + "/" + file.getOriginalFilename();
|
|
337
|
348
|
// 优先从 Word 文档提取段落 text 格式的叶子标题
|
|
338
|
349
|
List<String> allTitles = extractSubTitles(docPath, "技术文件");
|
|
|
@@ -463,7 +474,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
463
|
474
|
*/
|
|
464
|
475
|
private Map<String, String> generateChaptersContent(String inputPath, String templatePath,
|
|
465
|
476
|
List<String> targetTitles, String projectOverview, String scoringRequirements, String agentName)
|
|
466
|
|
- throws IOException, InterruptedException, ExecutionException {
|
|
|
477
|
+ throws Throwable {
|
|
467
|
478
|
// 构建招标文件内容的嵌入向量存储(inputPath 是招标文件路径,通过 topicId 查询得到)
|
|
468
|
479
|
File tenderFile = new File(inputPath);
|
|
469
|
480
|
InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
|
|
|
@@ -527,7 +538,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
527
|
538
|
titleContentMap.put(title, chapterContent);
|
|
528
|
539
|
successTitles.add(title);
|
|
529
|
540
|
|
|
530
|
|
- } catch (Exception e) {
|
|
|
541
|
+ } catch (Throwable e) {
|
|
531
|
542
|
failureTitles.add(title);
|
|
532
|
543
|
System.err.println("生成章节 " + title + " 内容时出错: " + e.getMessage());
|
|
533
|
544
|
titleContentMap.put(title, "该章节内容生成失败,请手动填写。");
|
|
|
@@ -633,7 +644,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
633
|
644
|
/**
|
|
634
|
645
|
* 调用LLM生成回答 - 检查文档内容
|
|
635
|
646
|
*/
|
|
636
|
|
- public String generateAnswerWithDocumentContent(String documentPath) throws IOException {
|
|
|
647
|
+ public String generateAnswerWithDocumentContent(String documentPath) throws Throwable {
|
|
637
|
648
|
File profilePath = new File(documentPath);
|
|
638
|
649
|
List<TextSegment> segments = splitDocument(profilePath, 10000, 0);
|
|
639
|
650
|
StringBuilder sb = new StringBuilder();
|
|
|
@@ -695,8 +706,9 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
695
|
706
|
|
|
696
|
707
|
// 评分标准/应价人须知
|
|
697
|
708
|
if (scoringRequirements != null && !scoringRequirements.isEmpty()) {
|
|
698
|
|
- // 当agentname包含"技术"且包含"询价"时,使用"应价人须知",否则使用"评分标准"
|
|
699
|
|
- String scoringTitle = agentName != null && agentName.contains("技术") && agentName.contains("询价") ? "【应价人须知】"
|
|
|
709
|
+ // 当agentname包含"技术标书"且包含"询价"时,使用"应价人须知",否则使用"评分标准"
|
|
|
710
|
+ String scoringTitle = agentName != null && agentName.contains("技术标书") && agentName.contains("询价")
|
|
|
711
|
+ ? "【应价人须知】"
|
|
700
|
712
|
: "【评分标准】";
|
|
701
|
713
|
prompt.append(scoringTitle).append(":\n");
|
|
702
|
714
|
prompt.append(scoringRequirements).append("\n\n");
|
|
|
@@ -735,7 +747,12 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
735
|
747
|
prompt.append("【输出格式】:\n");
|
|
736
|
748
|
prompt.append("请直接输出章节的正文内容(不要包含三级标题),正文内容为总-分结构,别写成总-分-总结构。");
|
|
737
|
749
|
prompt.append("【标题编号规范】:\n");
|
|
738
|
|
- prompt.append("如果需要在章节内添加子标题,必须严格遵循层级编号格式,最多到第六级标题(如6.1,6.1.1,6.1.1.1,6.1.1.1.1,6.1.1.1.1.1,6.1.1.1.1.1.1),且子标题后应添加换行符。\n");
|
|
|
750
|
+ if (agentName.contains("技术设计书"))
|
|
|
751
|
+ prompt.append(
|
|
|
752
|
+ "如果需要在章节内添加子标题,必须严格遵循层级编号格式,最多到第六级标题(如1.1,1.1.1,1.1.1.1,1.1.1.1.1,1.1.1.1.1.1,1.1.1.1.1.1),且子标题后应添加换行符。\\n");
|
|
|
753
|
+ else
|
|
|
754
|
+ prompt.append(
|
|
|
755
|
+ "如果需要在章节内添加子标题,必须严格遵循层级编号格式,最多到第六级标题(如6.1,6.1.1,6.1.1.1,6.1.1.1.1,6.1.1.1.1.1,6.1.1.1.1.1.1),且子标题后应添加换行符。\n");
|
|
739
|
756
|
prompt.append("各级标题下,正文文字叙述部份的分点说明序号分为六级,\n");
|
|
740
|
757
|
prompt.append("第一级按阿拉伯数字序号编排,如1,2,3\n");
|
|
741
|
758
|
prompt.append("第二级按带右圆括号的阿拉伯数字序号编排,如1),2),3)\n");
|
|
|
@@ -864,33 +881,52 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
864
|
881
|
* 调用LLM生成目录结构
|
|
865
|
882
|
*/
|
|
866
|
883
|
public JSONArray generateDetailedDirectory(String fileName, String agentName, List<TextSegment> segments)
|
|
867
|
|
- throws IOException {
|
|
868
|
|
- StringBuilder sb = new StringBuilder("招标文件内容:\n\n");
|
|
869
|
|
- InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
|
|
870
|
|
- List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
|
|
871
|
|
- embeddingStore.addAll(embeddings, segments);
|
|
872
|
|
- Embedding queryEmbedding = embeddingModel.embed("技术文件工作大纲要求").content();
|
|
873
|
|
- EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
|
|
874
|
|
- .queryEmbedding(queryEmbedding)
|
|
875
|
|
- .maxResults(5)
|
|
876
|
|
- .minScore(0.7)
|
|
877
|
|
- .build();
|
|
878
|
|
- List<EmbeddingMatch<TextSegment>> results = embeddingStore.search(embeddingSearchRequest).matches();
|
|
879
|
|
- results.sort(Comparator.comparingDouble(EmbeddingMatch<TextSegment>::score).reversed());
|
|
880
|
|
- for (EmbeddingMatch<TextSegment> embeddingMatch : results) {
|
|
881
|
|
- String requests = embeddingMatch.embedded().toString();
|
|
882
|
|
- sb.append(requests).append("\n\n");
|
|
|
884
|
+ throws Throwable {
|
|
|
885
|
+ StringBuilder sb = new StringBuilder("");
|
|
|
886
|
+ if (agentName.contains("技术设计书")) {
|
|
|
887
|
+ sb.append("项目设计书内容:\n\n");
|
|
|
888
|
+ for (TextSegment segment : segments) {
|
|
|
889
|
+ sb.append(segment.toString()).append("\n\n");
|
|
|
890
|
+ }
|
|
|
891
|
+ } else if (agentName.contains("技术标书")) {
|
|
|
892
|
+ sb.append("招标文件内容:\n\n");
|
|
|
893
|
+
|
|
|
894
|
+ InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
|
|
|
895
|
+ List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
|
|
|
896
|
+ embeddingStore.addAll(embeddings, segments);
|
|
|
897
|
+ Embedding queryEmbedding = embeddingModel.embed("技术文件工作大纲要求").content();
|
|
|
898
|
+ EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
|
|
|
899
|
+ .queryEmbedding(queryEmbedding)
|
|
|
900
|
+ .maxResults(5)
|
|
|
901
|
+ .minScore(0.7)
|
|
|
902
|
+ .build();
|
|
|
903
|
+ List<EmbeddingMatch<TextSegment>> results = embeddingStore.search(embeddingSearchRequest).matches();
|
|
|
904
|
+ results.sort(Comparator.comparingDouble(EmbeddingMatch<TextSegment>::score).reversed());
|
|
|
905
|
+ for (EmbeddingMatch<TextSegment> embeddingMatch : results) {
|
|
|
906
|
+ String requests = embeddingMatch.embedded().toString();
|
|
|
907
|
+ sb.append(requests).append("\n\n");
|
|
|
908
|
+ }
|
|
883
|
909
|
}
|
|
884
|
910
|
// 根据招标文件工作大纲要求生成二级标题
|
|
885
|
|
- sb.append("请基于上述招标文件中提到的")
|
|
886
|
|
- .append("技术文件工作大纲要求")
|
|
887
|
|
- .append(",先列出二级章节标题,严格按以下格式,仅输出标题列表:\n")
|
|
888
|
|
- .append("6.1 XX\n" +
|
|
889
|
|
- "6.2 XX\n" +
|
|
890
|
|
- "6.3 XX\n" +
|
|
891
|
|
- "......\n" +
|
|
892
|
|
- "6.n-1 XX\n" +
|
|
893
|
|
- "6.n XX\n");
|
|
|
911
|
+ if (agentName.contains("技术标书"))
|
|
|
912
|
+ sb.append("请基于上述招标文件中提到的")
|
|
|
913
|
+ .append("技术文件工作大纲要求")
|
|
|
914
|
+ .append(",先列出二级章节标题,严格按以下格式,仅输出标题列表:\n")
|
|
|
915
|
+ .append("6.1 XX\n" +
|
|
|
916
|
+ "6.2 XX\n" +
|
|
|
917
|
+ "6.3 XX\n" +
|
|
|
918
|
+ "......\n" +
|
|
|
919
|
+ "6.n-1 XX\n" +
|
|
|
920
|
+ "6.n XX\n");
|
|
|
921
|
+ else if (agentName.contains("技术设计书"))
|
|
|
922
|
+ sb.append("请基于上述项目设计书内容")
|
|
|
923
|
+ .append(",先列出二级章节标题,严格按以下格式,仅输出标题列表:\n")
|
|
|
924
|
+ .append("1.1 XX\n" +
|
|
|
925
|
+ "1.2 XX\n" +
|
|
|
926
|
+ "1.3 XX\n" +
|
|
|
927
|
+ "......\n" +
|
|
|
928
|
+ "1.n-1 XX\n" +
|
|
|
929
|
+ "1.n XX\n");
|
|
894
|
930
|
String directoryContent = writeChapters(sb.toString(), fileName, agentName);
|
|
895
|
931
|
return parseDirectoryToTree(directoryContent);
|
|
896
|
932
|
}
|
|
|
@@ -907,7 +943,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
907
|
943
|
String[] lines = directoryContent.split("\n");
|
|
908
|
944
|
for (String line : lines) {
|
|
909
|
945
|
line = line.trim().replace("*", "").replace("#", "").replace("-", "");
|
|
910
|
|
- if (!line.contains("6.") || line.isEmpty()) {
|
|
|
946
|
+ if ((!line.startsWith("6.") && !line.startsWith("1.")) || line.isEmpty()) {
|
|
911
|
947
|
continue;
|
|
912
|
948
|
}
|
|
913
|
949
|
|
|
|
@@ -992,7 +1028,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
992
|
1028
|
* @return 分析结果
|
|
993
|
1029
|
*/
|
|
994
|
1030
|
private String analyzeDocumentContent(List<TextSegment> segments, String query, String analysisPrompt,
|
|
995
|
|
- String progressStartMsg, String progressFoundMsg, String progressEndMsg) throws IOException {
|
|
|
1031
|
+ String progressStartMsg, String progressFoundMsg, String progressEndMsg) throws Throwable {
|
|
996
|
1032
|
processValue = progressStartMsg;
|
|
997
|
1033
|
StringBuilder sb = new StringBuilder("招标文件内容:\n\n");
|
|
998
|
1034
|
InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
|
|
|
@@ -1026,7 +1062,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
1026
|
1062
|
/**
|
|
1027
|
1063
|
* 分析项目概况
|
|
1028
|
1064
|
*/
|
|
1029
|
|
- public String analyzeProjectOverview(String uploadFilePath, List<TextSegment> segments) throws IOException {
|
|
|
1065
|
+ public String analyzeProjectOverview(String uploadFilePath, List<TextSegment> segments) throws Throwable {
|
|
1030
|
1066
|
String query = uploadFilePath.split("/")[uploadFilePath.split("/").length - 1] + "项目概况";
|
|
1031
|
1067
|
String analysisPrompt = "请基于上述招标文件内容,分析项目概况。\n" +
|
|
1032
|
1068
|
"请以清晰的结构输出分析结果。\n";
|
|
|
@@ -1040,13 +1076,13 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
1040
|
1076
|
* 分析评分标准/应价人须知
|
|
1041
|
1077
|
*/
|
|
1042
|
1078
|
public String analyzeScoringRequirements(List<TextSegment> segments, String agentName)
|
|
1043
|
|
- throws IOException {
|
|
|
1079
|
+ throws Throwable {
|
|
1044
|
1080
|
String query = "评分标准、评标办法、打分规则";
|
|
1045
|
1081
|
String analysisPrompt = "请基于上述招标文件内容,分析评分标准。\n" +
|
|
1046
|
1082
|
"请以清晰的结构输出分析结果,包括各项的具体分值和评分细则。\n";
|
|
1047
|
1083
|
|
|
1048
|
|
- // 当agentname包含"技术"且包含"询价"时,使用"应价人须知"相关查询
|
|
1049
|
|
- if (agentName.contains("技术") && agentName.contains("询价")) {
|
|
|
1084
|
+ // 当agentname包含"技术标书"且包含"询价"时,使用"应价人须知"相关查询
|
|
|
1085
|
+ if (agentName.contains("技术标书") && agentName.contains("询价")) {
|
|
1050
|
1086
|
query = "应价人须知、报价要求、响应要求、投标报价";
|
|
1051
|
1087
|
analysisPrompt = "请基于上述招标文件内容,分析应价人须知。\n" +
|
|
1052
|
1088
|
"请以清晰的结构输出分析结果,包括报价要求、响应要求等内容。\n";
|
|
|
@@ -1054,7 +1090,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
1054
|
1090
|
"分析应价人须知中: 0%",
|
|
1055
|
1091
|
"已查到应价人须知: 50%",
|
|
1056
|
1092
|
"分析应价人须知中: 100%");
|
|
1057
|
|
- } else if (agentName.contains("技术")) {
|
|
|
1093
|
+ } else if (agentName.contains("技术标书")) {
|
|
1058
|
1094
|
query = "技术部分评分标准、评标办法、打分规则";
|
|
1059
|
1095
|
} else if (agentName.contains("商务")) {
|
|
1060
|
1096
|
query = "商务部分评分标准、评标办法、打分规则";
|
|
|
@@ -1071,7 +1107,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
1071
|
1107
|
*
|
|
1072
|
1108
|
* @return
|
|
1073
|
1109
|
*/
|
|
1074
|
|
- public String writeChapters(String prompt, String fileName, String agentName) throws IOException {
|
|
|
1110
|
+ public String writeChapters(String prompt, String fileName, String agentName) throws Throwable {
|
|
1075
|
1111
|
String chapters2 = generateAnswer(prompt);
|
|
1076
|
1112
|
|
|
1077
|
1113
|
// 根据技术文档知识库生成二级标题下三级标题
|
|
|
@@ -1079,7 +1115,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
1079
|
1115
|
List<String> chapter2List = new ArrayList<>();
|
|
1080
|
1116
|
String[] contentLines = chapters2.split("\n");
|
|
1081
|
1117
|
for (String line : contentLines) {
|
|
1082
|
|
- if (line.contains("6."))
|
|
|
1118
|
+ if (line.startsWith("6.") || line.startsWith("1."))
|
|
1083
|
1119
|
chapter2List.add(line.replace("*", "").replace("#", "").replace("-", ""));
|
|
1084
|
1120
|
}
|
|
1085
|
1121
|
processValue = "已生成二级标题:100%";
|
|
|
@@ -1103,90 +1139,61 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
1103
|
1139
|
}
|
|
1104
|
1140
|
String sb = "二级标题如下:\n" + chapters2 +
|
|
1105
|
1141
|
"\n 三级标题如下:" + chapter3 +
|
|
1106
|
|
- "请将上述二级与对应的三级标题进行合并,严格按以下格式输出完整大纲,仅输出标题:\n" +
|
|
1107
|
|
- "6 技术文件\n" +
|
|
1108
|
|
- "6.1 XX\n" +
|
|
1109
|
|
- "6.1.1 XX\n" +
|
|
1110
|
|
- "6.1.2 XX\n" +
|
|
1111
|
|
- "6.2 XX\n" +
|
|
1112
|
|
- "6.2.1 XX\n" +
|
|
1113
|
|
- "6.2.2 XX\n" +
|
|
1114
|
|
- "6.3 XX\n" +
|
|
1115
|
|
- "6.3.1 XX\n" +
|
|
1116
|
|
- "6.3.2 XX\n" +
|
|
1117
|
|
- "...... \n" +
|
|
1118
|
|
- "6.n XX\n" +
|
|
1119
|
|
- "6.n.1 XX\n" +
|
|
1120
|
|
- "6.n 2 XX\n";
|
|
|
1142
|
+ "请将上述二级与对应的三级标题进行合并,严格按以下格式输出完整大纲,仅输出标题:\n";
|
|
|
1143
|
+ if (agentName.contains("技术标书"))
|
|
|
1144
|
+ sb += "6 技术文件\n" +
|
|
|
1145
|
+ "6.1 XX\n" +
|
|
|
1146
|
+ "6.1.1 XX\n" +
|
|
|
1147
|
+ "6.1.2 XX\n" +
|
|
|
1148
|
+ "6.2 XX\n" +
|
|
|
1149
|
+ "6.2.1 XX\n" +
|
|
|
1150
|
+ "6.2.2 XX\n" +
|
|
|
1151
|
+ "6.3 XX\n" +
|
|
|
1152
|
+ "6.3.1 XX\n" +
|
|
|
1153
|
+ "6.3.2 XX\n" +
|
|
|
1154
|
+ "...... \n" +
|
|
|
1155
|
+ "6.n XX\n" +
|
|
|
1156
|
+ "6.n.1 XX\n" +
|
|
|
1157
|
+ "6.n 2 XX\n";
|
|
|
1158
|
+ if (agentName.contains("技术设计书"))
|
|
|
1159
|
+ sb += "1 技术设计书\n" +
|
|
|
1160
|
+ "1.1 XX\n" +
|
|
|
1161
|
+ "1.1.1 XX\n" +
|
|
|
1162
|
+ "1.1.2 XX\n" +
|
|
|
1163
|
+ "1.2 XX\n" +
|
|
|
1164
|
+ "1.2.1 XX\n" +
|
|
|
1165
|
+ "1.2.2 XX\n" +
|
|
|
1166
|
+ "1.3 XX\n" +
|
|
|
1167
|
+ "1.3.1 XX\n" +
|
|
|
1168
|
+ "1.3.2 XX\n" +
|
|
|
1169
|
+ "...... \n" +
|
|
|
1170
|
+ "1.n XX\n" +
|
|
|
1171
|
+ "1.n.1 XX\n" +
|
|
|
1172
|
+ "1.n 2 XX\n";
|
|
1121
|
1173
|
String content = generateAnswer(sb);
|
|
1122
|
1174
|
writeTitles(content, fileName, agentName);
|
|
1123
|
1175
|
return content;
|
|
1124
|
1176
|
}
|
|
1125
|
1177
|
|
|
1126
|
|
- // /**
|
|
1127
|
|
- // * 调用LLM生成回答
|
|
1128
|
|
- // *
|
|
1129
|
|
- // * @return
|
|
1130
|
|
- // */
|
|
1131
|
|
- // public String generateAnswer(String prompt) throws IOException {
|
|
1132
|
|
- // ChatModel chatModel =
|
|
1133
|
|
- // ChatModel.of("https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions")
|
|
1134
|
|
- // .model("qwen3-vl-32b-instruct")
|
|
1135
|
|
- // .apiKey("sk-750a17cc723847f28b31fa1bc17c255c")
|
|
1136
|
|
- // .timeout(Duration.ofSeconds(240))
|
|
1137
|
|
- // .build();
|
|
1138
|
|
-
|
|
1139
|
|
- // List<ChatMessage> messages = new ArrayList<>();
|
|
1140
|
|
- // messages.add(ChatMessage.ofUser(prompt));
|
|
1141
|
|
- // ChatSession chatSession =
|
|
1142
|
|
- // InMemoryChatSession.builder().messages(messages).build();
|
|
1143
|
|
-
|
|
1144
|
|
- // Prompt prompt1 = Prompt.of(prompt).attrPut("session", chatSession);
|
|
1145
|
|
- // ChatResponse response = chatModel.prompt(prompt1).call();
|
|
1146
|
|
- // String content = response.lastChoice().getMessage().getContent();
|
|
1147
|
|
- // return content;
|
|
1148
|
|
- // }
|
|
1149
|
|
-
|
|
1150
|
1178
|
/**
|
|
1151
|
1179
|
* 调用LLM生成回答
|
|
1152
|
1180
|
*
|
|
1153
|
1181
|
* @return
|
|
1154
|
1182
|
*/
|
|
1155
|
|
- public String generateAnswer(String prompt) throws IOException {
|
|
|
1183
|
+ public String generateAnswer(String prompt) throws Throwable, IOException {
|
|
1156
|
1184
|
ChatModel chatModel = ChatModel.of(llmServiceUrl)
|
|
1157
|
1185
|
.model("Qwen")
|
|
1158
|
1186
|
.timeout(Duration.ofSeconds(240))
|
|
1159
|
1187
|
.build();
|
|
1160
|
|
-
|
|
1161
|
|
- List<ChatMessage> messages = new ArrayList<>();
|
|
1162
|
|
- messages.add(ChatMessage.ofUser(prompt));
|
|
1163
|
|
- ChatSession chatSession = InMemoryChatSession.builder().messages(messages).build();
|
|
1164
|
|
-
|
|
1165
|
|
- Prompt prompt1 = Prompt.of(prompt).attrPut("session", chatSession);
|
|
1166
|
|
- ChatResponse response = chatModel.prompt(prompt1).call();
|
|
1167
|
|
- String content = response.lastChoice().getMessage().getContent();
|
|
1168
|
|
- return content;
|
|
1169
|
|
- }
|
|
1170
|
|
-
|
|
1171
|
|
- /**
|
|
1172
|
|
- * 流式调用LLM生成回答 - 使用Flux
|
|
1173
|
|
- *
|
|
1174
|
|
- * @param prompt 提示词
|
|
1175
|
|
- * @return Flux<String> 流式输出(原始内容)
|
|
1176
|
|
- */
|
|
1177
|
|
- public Flux<String> generateAnswerFlux(String prompt) {
|
|
1178
|
|
- ChatModel chatModel = ChatModel.of(llmServiceUrl)
|
|
1179
|
|
- .model("Qwen")
|
|
1180
|
|
- .timeout(Duration.ofSeconds(240))
|
|
|
1188
|
+ SimpleAgent robot = SimpleAgent.of(chatModel)
|
|
1181
|
1189
|
.build();
|
|
1182
|
1190
|
List<ChatMessage> messages = new ArrayList<>();
|
|
1183
|
1191
|
messages.add(ChatMessage.ofUser(prompt));
|
|
1184
|
1192
|
ChatSession chatSession = InMemoryChatSession.builder().messages(messages).build();
|
|
1185
|
1193
|
|
|
1186
|
1194
|
Prompt prompt1 = Prompt.of(prompt).attrPut("session", chatSession);
|
|
1187
|
|
- return chatModel.prompt(prompt1).stream()
|
|
1188
|
|
- .map(resp -> resp.getContent())
|
|
1189
|
|
- .filter(content -> content != null && !content.isEmpty());
|
|
|
1195
|
+ String content = robot.prompt(prompt1).call().getContent();
|
|
|
1196
|
+ return content;
|
|
1190
|
1197
|
}
|
|
1191
|
1198
|
|
|
1192
|
1199
|
/**
|
|
|
@@ -1202,6 +1209,8 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
1202
|
1209
|
.model("Qwen")
|
|
1203
|
1210
|
.timeout(Duration.ofSeconds(240))
|
|
1204
|
1211
|
.build();
|
|
|
1212
|
+ SimpleAgent robot = SimpleAgent.of(chatModel)
|
|
|
1213
|
+ .build();
|
|
1205
|
1214
|
List<ChatMessage> messages = new ArrayList<>();
|
|
1206
|
1215
|
messages.add(ChatMessage.ofUser(prompt));
|
|
1207
|
1216
|
ChatSession chatSession = InMemoryChatSession.builder().messages(messages).build();
|
|
|
@@ -1213,34 +1222,34 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
1213
|
1222
|
tokenUsage[0].set((long) prompt.length());
|
|
1214
|
1223
|
}
|
|
1215
|
1224
|
|
|
1216
|
|
- return chatModel.prompt(prompt1).stream()
|
|
|
1225
|
+ return robot.prompt(prompt1).stream()
|
|
1217
|
1226
|
.doOnNext(resp -> {
|
|
1218
|
1227
|
if (tokenUsage != null && tokenUsage.length >= 3) {
|
|
1219
|
1228
|
try {
|
|
1220
|
1229
|
// 优先使用vLLM返回的usage信息(在最后一个chunk中)
|
|
1221
|
|
- AiUsage usage = resp.getUsage();
|
|
1222
|
|
- if (usage != null) {
|
|
1223
|
|
- Long pt = usage.promptTokens();
|
|
1224
|
|
- Long ct = usage.completionTokens();
|
|
1225
|
|
- Long tt = usage.totalTokens();
|
|
1226
|
|
-
|
|
1227
|
|
- if (pt > 0)
|
|
1228
|
|
- tokenUsage[0].set(pt);
|
|
1229
|
|
- if (ct > 0)
|
|
1230
|
|
- tokenUsage[1].set(ct);
|
|
1231
|
|
- if (tt > 0)
|
|
1232
|
|
- tokenUsage[2].set(tt);
|
|
1233
|
|
- } else {
|
|
1234
|
|
- // 如果没有usage信息(中间chunk),估算completion tokens
|
|
1235
|
|
- String content = resp.getContent();
|
|
1236
|
|
- if (content != null && !content.isEmpty()) {
|
|
1237
|
|
- // 中文大约1个字符=1个token,实时累计
|
|
1238
|
|
- long currentCompletion = tokenUsage[1].get();
|
|
1239
|
|
- long increment = content.length();
|
|
1240
|
|
- tokenUsage[1].set(currentCompletion + increment);
|
|
1241
|
|
- tokenUsage[2].set(tokenUsage[0].get() + tokenUsage[1].get());
|
|
1242
|
|
- }
|
|
|
1230
|
+ // AiUsage usage = resp.getUsage();
|
|
|
1231
|
+ // if (usage != null) {
|
|
|
1232
|
+ // Long pt = usage.promptTokens();
|
|
|
1233
|
+ // Long ct = usage.completionTokens();
|
|
|
1234
|
+ // Long tt = usage.totalTokens();
|
|
|
1235
|
+
|
|
|
1236
|
+ // if (pt > 0)
|
|
|
1237
|
+ // tokenUsage[0].set(pt);
|
|
|
1238
|
+ // if (ct > 0)
|
|
|
1239
|
+ // tokenUsage[1].set(ct);
|
|
|
1240
|
+ // if (tt > 0)
|
|
|
1241
|
+ // tokenUsage[2].set(tt);
|
|
|
1242
|
+ // } else {
|
|
|
1243
|
+ // 如果没有usage信息(中间chunk),估算completion tokens
|
|
|
1244
|
+ String content = resp.getContent();
|
|
|
1245
|
+ if (content != null && !content.isEmpty()) {
|
|
|
1246
|
+ // 中文大约1个字符=1个token,实时累计
|
|
|
1247
|
+ long currentCompletion = tokenUsage[1].get();
|
|
|
1248
|
+ long increment = content.length();
|
|
|
1249
|
+ tokenUsage[1].set(currentCompletion + increment);
|
|
|
1250
|
+ tokenUsage[2].set(tokenUsage[0].get() + tokenUsage[1].get());
|
|
1243
|
1251
|
}
|
|
|
1252
|
+ // }
|
|
1244
|
1253
|
} catch (Exception e) {
|
|
1245
|
1254
|
// 如果获取usage失败,也尝试估算
|
|
1246
|
1255
|
try {
|
|
|
@@ -1333,7 +1342,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
1333
|
1342
|
List<String> chapters = new ArrayList<>();
|
|
1334
|
1343
|
String[] contentLines = content.split("\n");
|
|
1335
|
1344
|
for (String line : contentLines) {
|
|
1336
|
|
- if (line.contains("6."))
|
|
|
1345
|
+ if (line.startsWith("6.") || line.startsWith("1."))
|
|
1337
|
1346
|
chapters.add(line.replace("*", "").replace("#", "").replace("-", ""));
|
|
1338
|
1347
|
}
|
|
1339
|
1348
|
String prefixPath = "/upload/agent/" + agentName;
|
|
|
@@ -1628,7 +1637,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
1628
|
1637
|
|
|
1629
|
1638
|
for (String line : lines) {
|
|
1630
|
1639
|
line = line.trim().replace("*", "").replace("#", "").replace("-", "");
|
|
1631
|
|
- if (!line.contains("6.") || line.isEmpty()) {
|
|
|
1640
|
+ if (!line.startsWith("6.") && !line.startsWith("1.") || line.isEmpty()) {
|
|
1632
|
1641
|
continue;
|
|
1633
|
1642
|
}
|
|
1634
|
1643
|
|
|
|
@@ -1846,7 +1855,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
1846
|
1855
|
* 分割文档
|
|
1847
|
1856
|
*/
|
|
1848
|
1857
|
private List<TextSegment> splitDocument(File transferFile, int maxSegmentSizeInChars, int maxOverlapSizeInChars)
|
|
1849
|
|
- throws IOException {
|
|
|
1858
|
+ throws Throwable {
|
|
1850
|
1859
|
String filename = transferFile.getName().toLowerCase();
|
|
1851
|
1860
|
try (InputStream fileInputStream = new FileInputStream(transferFile)) {
|
|
1852
|
1861
|
if (filename.endsWith(".docx")) {
|
|
|
@@ -2026,7 +2035,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
2026
|
2035
|
// 当前行按普通段落处理
|
|
2027
|
2036
|
blocks.add(new Block("para", line));
|
|
2028
|
2037
|
} else if (inTable) {
|
|
2029
|
|
- tableLines.add(line);
|
|
|
2038
|
+ tableLines.add(line);
|
|
2030
|
2039
|
} else {
|
|
2031
|
2040
|
// 匹配格式:1. **文本**(数字前缀+加粗)
|
|
2032
|
2041
|
Pattern numberedBoldPattern = Pattern.compile("^(\\d+\\.\\s+)\\*\\*([^*]+)\\*\\*(.*)$");
|
|
|
@@ -2282,7 +2291,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
2282
|
2291
|
*/
|
|
2283
|
2292
|
@Override
|
|
2284
|
2293
|
public SseEmitter streamGenerateChapters(String topicId, MultipartFile file, String agentName, String selectedNode)
|
|
2285
|
|
- throws IOException {
|
|
|
2294
|
+ throws Throwable {
|
|
2286
|
2295
|
// 设置SSE超时时间为10分钟
|
|
2287
|
2296
|
SseEmitter emitter = new SseEmitter(600000L);
|
|
2288
|
2297
|
|
|
|
@@ -2494,7 +2503,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
2494
|
2503
|
|
|
2495
|
2504
|
emitter.complete();
|
|
2496
|
2505
|
|
|
2497
|
|
- } catch (Exception e) {
|
|
|
2506
|
+ } catch (Throwable e) {
|
|
2498
|
2507
|
try {
|
|
2499
|
2508
|
emitter.send(SseEmitter.event()
|
|
2500
|
2509
|
.name("error")
|