From a879dc2ff1b0c8d2ddbaecea68b2b7e394797c31 Mon Sep 17 00:00:00 2001 From: Nelson <1475262689@qq.com> Date: Fri, 17 Jul 2026 14:05:54 +0800 Subject: [PATCH] feat(agent): control reasoning visibility --- .../src/components/AgentManagementView.vue | 41 +++++- .../components/StreamingMarkdownMessage.vue | 33 +++-- .../apps/admin-web/src/services/api.ts | 7 + .../apps/admin-web/src/styles.css | 31 +++++ .../apps/admin-web/src/types/api.ts | 1 + .../apps/backend/app/api/admin_settings.py | 9 +- .../apps/backend/app/api/chat.py | 22 +++- .../apps/backend/app/schemas/admin.py | 1 + .../app/services/agent_debug_service.py | 17 ++- .../app/services/reasoning_policy_service.py | 124 ++++++++++++++++++ .../tests/test_agent_runtime_config.py | 43 ++++-- .../tests/test_production_readiness.py | 1 + .../backend/tests/test_reasoning_policy.py | 18 +++ .../apps/user-client/src/App.vue | 12 +- .../src/components/ChatMessage.vue | 37 ++++-- .../src/components/MessageList.vue | 4 + .../apps/user-client/src/services/api.ts | 15 ++- .../apps/user-client/src/styles.css | 3 + 18 files changed, 368 insertions(+), 51 deletions(-) create mode 100644 ai_knowledge_base_v2/apps/backend/app/services/reasoning_policy_service.py create mode 100644 ai_knowledge_base_v2/apps/backend/tests/test_reasoning_policy.py diff --git a/ai_knowledge_base_v2/apps/admin-web/src/components/AgentManagementView.vue b/ai_knowledge_base_v2/apps/admin-web/src/components/AgentManagementView.vue index caa9d63..393ce4e 100644 --- a/ai_knowledge_base_v2/apps/admin-web/src/components/AgentManagementView.vue +++ b/ai_knowledge_base_v2/apps/admin-web/src/components/AgentManagementView.vue @@ -30,7 +30,13 @@ const historyDetailOpen = ref(false); const selectedHistory = ref(null); const historyDetailLoading = ref(false); const agentDebugTrace = ref[]>([]); -const agentPreviewMessages = ref<{ role: "user" | "assistant" | "system"; content: string; streaming?: boolean }[]>([ +const agentPreviewMessages = ref<{ + role: "user" | "assistant" | "system"; + content: string; + reasoning?: string; + showReasoning?: boolean; + streaming?: boolean; +}[]>([ { role: "assistant", content: "选择模型和知识库后,可以在这里调试 Agent 的真实问答效果。" }, ]); @@ -54,6 +60,7 @@ const runtimeForm = reactive({ frequencyPenalty: null as number | null, maxToken: 8192 as number | null, streamEnabled: 1, + reasoningVisible: 0, }); const promptDirty = computed(() => promptContent.value !== savedPromptContent.value); @@ -109,6 +116,7 @@ function applyRuntimeConfig(value: AgentRuntimeConfig) { frequencyPenalty: value.frequencyPenalty, maxToken: value.maxToken, streamEnabled: value.streamEnabled, + reasoningVisible: value.reasoningVisible, }); } @@ -138,6 +146,7 @@ async function saveRuntimeConfig() { frequencyPenalty: runtimeForm.frequencyPenalty, maxToken: runtimeForm.maxToken, streamEnabled: runtimeForm.streamEnabled, + reasoningVisible: runtimeForm.reasoningVisible, }); applyRuntimeConfig(saved); const model = models.value.find((item) => item.id === saved.modelId); @@ -289,6 +298,14 @@ async function debugAgent() { currentAssistant().content += chunk; scrollAgentPreview(); }, + (chunk) => { + currentAssistant().reasoning = (currentAssistant().reasoning || "") + chunk; + scrollAgentPreview(); + }, + (status) => { + currentAssistant().showReasoning = status.reasoningVisible; + scrollAgentPreview(); + }, (result) => { agentDebugTrace.value = result.retrievalTrace || []; ElMessage.success(result.message || "Agent 调试完成"); @@ -410,6 +427,21 @@ function errorMessage(error: unknown, fallback: string) { :disabled="!runtimeConfig?.modelId" /> + +
+ 展示思考过程 + 开启后,用户端和后台调试预览可展开查看模型返回的思考内容;关闭时后端不会下发该内容。 +
+ +
@@ -475,7 +507,12 @@ function errorMessage(error: unknown, fallback: string) {
{{ message.role === "user" ? "你" : "Agent" }}
{{ message.content }}
diff --git a/ai_knowledge_base_v2/apps/admin-web/src/components/StreamingMarkdownMessage.vue b/ai_knowledge_base_v2/apps/admin-web/src/components/StreamingMarkdownMessage.vue index 8b41498..18ce47d 100644 --- a/ai_knowledge_base_v2/apps/admin-web/src/components/StreamingMarkdownMessage.vue +++ b/ai_knowledge_base_v2/apps/admin-web/src/components/StreamingMarkdownMessage.vue @@ -4,47 +4,60 @@ import { computed } from "vue"; const props = defineProps<{ content: string; + reasoning?: string; + showReasoning?: boolean; streaming?: boolean; }>(); const markdown = new MarkdownIt({ breaks: true, html: false, linkify: true }); -const answer = computed(() => stripStreamingReasoning(props.content).replace(/^\s+/, "")); +const parsed = computed(() => splitReasoning(props.content)); +const answer = computed(() => parsed.value.answer.replace(/^\s+/, "")); +const reasoning = computed(() => props.reasoning || parsed.value.reasoning); const renderedContent = computed(() => answer.value.trim() ? markdown.render(answer.value) : ""); -function stripStreamingReasoning(content: string) { +function splitReasoning(content: string) { const lower = content.toLowerCase(); - let visible = ""; + let answer = ""; + let reasoning = ""; let cursor = 0; - let reasoningDepth = 0; + let depth = 0; while (cursor < content.length) { const tagStart = content.indexOf("<", cursor); if (tagStart === -1) { - if (reasoningDepth === 0) visible += content.slice(cursor); + if (depth) reasoning += content.slice(cursor); + else answer += content.slice(cursor); break; } - if (reasoningDepth === 0) visible += content.slice(cursor, tagStart); + const text = content.slice(cursor, tagStart); + if (depth) reasoning += text; + else answer += text; const tail = lower.slice(tagStart); const openTag = tail.match(/^]*)?>/); if (openTag) { - reasoningDepth += 1; + depth += 1; cursor = tagStart + openTag[0].length; continue; } const closeTag = tail.match(/^<\/think\s*>/); if (closeTag) { - reasoningDepth = Math.max(0, reasoningDepth - 1); + depth = Math.max(0, depth - 1); cursor = tagStart + closeTag[0].length; continue; } if ("".startsWith(tail)) break; - if (reasoningDepth === 0) visible += "<"; + if (depth) reasoning += "<"; + else answer += "<"; cursor = tagStart + 1; } - return visible; + return { answer, reasoning }; }