fix(agent): parse employee drafts after reasoning blocks (#648)

This commit is contained in:
taobig 2026-09-24 03:14:24 -04:00
parent 894c945fab
commit 6bbd3ac4da
2 changed files with 94 additions and 0 deletions

View File

@ -295,6 +295,14 @@ public class AgentGenerationService {
private JsonNode parseJson(String response) { private JsonNode parseJson(String response) {
if (response == null || response.isBlank()) return null; if (response == null || response.isBlank()) return null;
String cleaned = response.trim(); String cleaned = response.trim();
// Some reasoning models prepend thinking to content on non-streaming
// calls. Strip only leading blocks: tags inside JSON string values are
// legitimate persona text, and JSON examples in thinking are not drafts.
while (cleaned.startsWith("<think>")) {
int end = cleaned.indexOf("</think>", "<think>".length());
if (end < 0) return null; // Incomplete reasoning is not a final answer.
cleaned = cleaned.substring(end + "</think>".length()).trim();
}
if (cleaned.startsWith("```json")) cleaned = cleaned.substring(7); if (cleaned.startsWith("```json")) cleaned = cleaned.substring(7);
else if (cleaned.startsWith("```")) cleaned = cleaned.substring(3); else if (cleaned.startsWith("```")) cleaned = cleaned.substring(3);
if (cleaned.endsWith("```")) cleaned = cleaned.substring(0, cleaned.length() - 3); if (cleaned.endsWith("```")) cleaned = cleaned.substring(0, cleaned.length() - 3);

View File

@ -0,0 +1,86 @@
package vip.mate.agent.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
import org.springframework.ai.chat.prompt.Prompt;
import vip.mate.agent.AgentGraphBuilder;
import vip.mate.exception.MateClawException;
import vip.mate.llm.model.ModelConfigEntity;
import vip.mate.llm.service.ModelConfigService;
import vip.mate.skill.service.SkillService;
import vip.mate.tool.service.AvailableToolService;
import vip.mate.wiki.service.WikiKnowledgeBaseService;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
class AgentGenerationServiceTest {
private ChatModel model;
private AgentGenerationService service;
@BeforeEach
void setUp() {
var configs = mock(ModelConfigService.class);
var builder = mock(AgentGraphBuilder.class);
var tools = mock(AvailableToolService.class);
var skills = mock(SkillService.class);
var kbs = mock(WikiKnowledgeBaseService.class);
var config = new ModelConfigEntity();
model = mock(ChatModel.class);
when(configs.getDefaultModel()).thenReturn(config);
when(builder.buildRuntimeChatModel(config)).thenReturn(model);
when(tools.listAvailable()).thenReturn(List.of());
when(skills.listEnabledSkills(1L)).thenReturn(List.of());
when(kbs.listByWorkspace(1L)).thenReturn(List.of());
service = new AgentGenerationService(configs, builder, new ObjectMapper(), tools, skills, kbs);
}
private void response(String text) {
when(model.call(any(Prompt.class))).thenReturn(new ChatResponse(
List.of(new Generation(new AssistantMessage(text)))));
}
@ParameterizedTest
@ValueSource(strings = {
"{\"name\":\"周报助手\"}",
"```json\n{\"name\":\"周报助手\"}\n```",
"<think>先考虑配置</think>\n{\"name\":\"周报助手\"}",
"<think>{\"name\":\"思考中的示例\"}</think>\n```json\n{\"name\":\"周报助手\"}\n```",
" \n<think>第一步</think>\n<think>第二步</think>\n{\"name\":\"周报助手\"}"
})
void acceptsFinalJsonAfterOptionalThinking(String text) {
response(text);
assertEquals("周报助手", service.generateDraft("跨部门周报汇总", 1L).getName());
}
@ParameterizedTest
@ValueSource(strings = {
"{\"name\":\"助手\",\"systemPrompt\":\"保留 <think>示例</think> 原文\"}",
"<think>规划</think>\n{\"name\":\"助手\",\"systemPrompt\":\"保留 <think>示例</think> 原文\"}"
})
void preservesLiteralTagsInsideJsonStrings(String text) {
response(text);
assertEquals("保留 <think>示例</think> 原文", service.generateDraft("助手", 1L).getSystemPrompt());
}
@ParameterizedTest
@ValueSource(strings = {
"", "<think>{\"name\":\"未完成思考\"}",
"<think>{\"name\":\"只有思考\"}</think>",
"<think>规划</think>\n{\"name\":", "[]",
"说明文字 {\"name\":\"不应猜测提取\"}"
})
void rejectsMissingOrMalformedFinalObject(String text) {
response(text);
assertThrows(MateClawException.class, () -> service.generateDraft("助手", 1L));
}
}