mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
feat(agent): add deep thinking toggle (RFC-001)
This commit is contained in:
parent
c60d637e33
commit
2a8b90365b
@ -103,20 +103,40 @@ public class AgentService {
|
||||
}
|
||||
|
||||
public Flux<StreamDelta> chatStructuredStream(Long agentId, String message, String conversationId) {
|
||||
return chatStructuredStream(agentId, message, conversationId, "");
|
||||
return chatStructuredStream(agentId, message, conversationId, "", null);
|
||||
}
|
||||
|
||||
public Flux<StreamDelta> chatStructuredStream(Long agentId, String message, String conversationId,
|
||||
String requesterId) {
|
||||
return chatStructuredStream(agentId, message, conversationId, requesterId, null);
|
||||
}
|
||||
|
||||
public Flux<StreamDelta> chatStructuredStream(Long agentId, String message, String conversationId,
|
||||
String requesterId, String thinkingLevel) {
|
||||
memoryRecallTracker.trackRecalls(agentId, message);
|
||||
BaseAgent agent = getOrBuildAgent(agentId);
|
||||
|
||||
// 设置请求级思考深度(通过 ThreadLocal 传递到 StateGraph 执行)
|
||||
if (thinkingLevel != null && !thinkingLevel.isBlank()) {
|
||||
ThinkingLevelHolder.set(thinkingLevel);
|
||||
} else {
|
||||
// 尝试从 Agent 默认配置读取
|
||||
AgentEntity entity = getAgent(agentId);
|
||||
if (entity != null && entity.getDefaultThinkingLevel() != null) {
|
||||
ThinkingLevelHolder.set(entity.getDefaultThinkingLevel());
|
||||
} else {
|
||||
ThinkingLevelHolder.clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (agent instanceof StructuredStreamCapable capable) {
|
||||
return capable.chatStructuredStream(message, conversationId,
|
||||
requesterId != null ? requesterId : "");
|
||||
requesterId != null ? requesterId : "")
|
||||
.doFinally(signal -> ThinkingLevelHolder.clear());
|
||||
}
|
||||
|
||||
// 降级:不支持结构化流的 Agent,包装为纯内容流
|
||||
ThinkingLevelHolder.clear();
|
||||
return agent.chatStream(message, conversationId)
|
||||
.map(chunk -> new StreamDelta(chunk, null));
|
||||
}
|
||||
|
||||
@ -0,0 +1,33 @@
|
||||
package vip.mate.agent;
|
||||
|
||||
/**
|
||||
* 请求级思考深度的 ThreadLocal 持有器。
|
||||
* <p>
|
||||
* 用于将前端选择的思考级别从 AgentService 传递到 ReasoningNode,
|
||||
* 避免修改 Agent 缓存实例或 StructuredStreamCapable 接口。
|
||||
* <p>
|
||||
* 支持的值:off / low / medium / high / max,null 表示跟随模型默认。
|
||||
*
|
||||
* @author MateClaw Team
|
||||
*/
|
||||
public final class ThinkingLevelHolder {
|
||||
|
||||
private static final ThreadLocal<String> HOLDER = new ThreadLocal<>();
|
||||
|
||||
private ThinkingLevelHolder() {}
|
||||
|
||||
public static void set(String level) {
|
||||
HOLDER.set(level);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前请求的思考级别,null 表示未设置(跟随模型默认)
|
||||
*/
|
||||
public static String get() {
|
||||
return HOLDER.get();
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
HOLDER.remove();
|
||||
}
|
||||
}
|
||||
@ -320,7 +320,10 @@ public class NodeStreamingChatHelper {
|
||||
return;
|
||||
}
|
||||
thinkingAccum.append(thinkingDelta);
|
||||
if (broadcast) {
|
||||
// thinkingLevel=off 时不广播 thinking(模型仍可能产生,但前端不展示)
|
||||
boolean suppressThinking = "off".equalsIgnoreCase(
|
||||
vip.mate.agent.ThinkingLevelHolder.get());
|
||||
if (broadcast && !suppressThinking) {
|
||||
broadcastDelta(conversationId, "thinking_delta", thinkingDelta);
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@ import org.springframework.ai.tool.ToolCallback;
|
||||
import org.springframework.util.StringUtils;
|
||||
import vip.mate.agent.AgentToolSet;
|
||||
import vip.mate.agent.GraphEventPublisher;
|
||||
import vip.mate.agent.ThinkingLevelHolder;
|
||||
import vip.mate.agent.graph.NodeStreamingChatHelper;
|
||||
import vip.mate.agent.context.ConversationWindowManager;
|
||||
import vip.mate.agent.context.RuntimeContextInjector;
|
||||
@ -177,11 +178,16 @@ public class ReasoningNode implements NodeAction {
|
||||
promptMessages.add(new UserMessage(RuntimeContextInjector.buildContextMessage(workspaceBasePath)));
|
||||
promptMessages.addAll(messages);
|
||||
|
||||
// 请求级思考深度覆盖(ThinkingLevelHolder 由 AgentService 设置)
|
||||
String effectiveReasoning = resolveEffectiveReasoningEffort();
|
||||
log.info("[ReasoningNode] thinkingLevel={}, effectiveReasoningEffort={}, nodeDefault={}",
|
||||
ThinkingLevelHolder.get(), effectiveReasoning, this.reasoningEffort);
|
||||
|
||||
ChatOptions options;
|
||||
if (StringUtils.hasText(reasoningEffort)) {
|
||||
if (StringUtils.hasText(effectiveReasoning)) {
|
||||
OpenAiChatOptions oaiOpts = OpenAiChatOptions.builder()
|
||||
.toolCallbacks(toolCallbacks)
|
||||
.reasoningEffort(reasoningEffort)
|
||||
.reasoningEffort(effectiveReasoning)
|
||||
.maxTokens(maxOutputTokens)
|
||||
.build();
|
||||
oaiOpts.setInternalToolExecutionEnabled(false);
|
||||
@ -373,4 +379,28 @@ public class ReasoningNode implements NodeAction {
|
||||
streamTracker.updatePhase(conversationId, phase);
|
||||
streamTracker.broadcastObject(conversationId, "phase", GraphEventPublisher.phase(phase, extra).data());
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析有效的 reasoningEffort。
|
||||
* 优先级:ThinkingLevelHolder(请求级) > 构造时的 reasoningEffort(Agent/模型默认)。
|
||||
* "off" 会清除 reasoningEffort(返回 null)。
|
||||
*/
|
||||
private String resolveEffectiveReasoningEffort() {
|
||||
String requestLevel = ThinkingLevelHolder.get();
|
||||
if (requestLevel != null) {
|
||||
if ("off".equalsIgnoreCase(requestLevel)) {
|
||||
return null;
|
||||
}
|
||||
// thinkingLevel → reasoningEffort 映射
|
||||
return switch (requestLevel.toLowerCase()) {
|
||||
case "low" -> "low";
|
||||
case "medium" -> "medium";
|
||||
case "high" -> "high";
|
||||
case "max" -> "high"; // OpenAI 最高支持 high
|
||||
default -> requestLevel; // 透传未知值
|
||||
};
|
||||
}
|
||||
// 无请求级覆盖,使用构造时的默认值
|
||||
return this.reasoningEffort;
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,6 +52,9 @@ public class AgentEntity {
|
||||
/** 所属工作区 ID(默认 1 = default) */
|
||||
private Long workspaceId;
|
||||
|
||||
/** 默认思考深度:off / low / medium / high / max,null 表示跟随模型默认 */
|
||||
private String defaultThinkingLevel;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
|
||||
@ -418,7 +418,7 @@ public class ChatController {
|
||||
));
|
||||
|
||||
streamTracker.incrementFlux(conversationId);
|
||||
Disposable disposable = agentService.chatStructuredStream(agentId, promptText, conversationId, username)
|
||||
Disposable disposable = agentService.chatStructuredStream(agentId, promptText, conversationId, username, request.getThinkingLevel())
|
||||
.doOnNext(delta -> {
|
||||
if (emitterDone.get()) return;
|
||||
try {
|
||||
@ -922,6 +922,8 @@ public class ChatController {
|
||||
private List<MessageContentPart> contentParts;
|
||||
/** true 表示断线重连,不发送新消息,只附着到已有的流 */
|
||||
private Boolean reconnect;
|
||||
/** 思考深度:off / low / medium / high / max,null 表示跟随 Agent 默认 */
|
||||
private String thinkingLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
-- V4: Add default_thinking_level to mate_agent
|
||||
-- Supports: off / low / medium / high / max (null = follow model default)
|
||||
ALTER TABLE mate_agent ADD COLUMN IF NOT EXISTS default_thinking_level VARCHAR(32) DEFAULT NULL;
|
||||
@ -29,6 +29,7 @@ CREATE TABLE IF NOT EXISTS mate_agent (
|
||||
icon VARCHAR(256),
|
||||
tags VARCHAR(256),
|
||||
workspace_id BIGINT NOT NULL DEFAULT 1,
|
||||
default_thinking_level VARCHAR(32) DEFAULT NULL,
|
||||
create_time DATETIME NOT NULL,
|
||||
update_time DATETIME NOT NULL,
|
||||
deleted INT NOT NULL DEFAULT 0
|
||||
|
||||
@ -136,6 +136,18 @@
|
||||
<el-icon><Paperclip /></el-icon>
|
||||
</button>
|
||||
|
||||
<!-- 深度思考开关 -->
|
||||
<button
|
||||
type="button"
|
||||
class="action-btn thinking-btn"
|
||||
:class="{ active: thinkingEnabled }"
|
||||
:disabled="disabled"
|
||||
@click="emit('toggle-thinking')"
|
||||
:title="thinkingEnabled ? t('chat.thinkingOn') : t('chat.thinkingOff')"
|
||||
>
|
||||
<el-icon><MagicStick /></el-icon>
|
||||
</button>
|
||||
|
||||
<!-- Talk Mode 按钮 -->
|
||||
<button
|
||||
v-if="enableTalkMode"
|
||||
@ -190,7 +202,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, nextTick, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { CloseBold, Microphone, Paperclip, Promotion, Select, Timer, WarningFilled } from '@element-plus/icons-vue'
|
||||
import { CloseBold, MagicStick, Microphone, Paperclip, Promotion, Select, Timer, WarningFilled } from '@element-plus/icons-vue'
|
||||
import type { ChatAttachment, PendingApprovalMeta, StreamPhase, QueuedMessage } from '@/types'
|
||||
|
||||
interface Props {
|
||||
@ -224,6 +236,8 @@ interface Props {
|
||||
queueSize?: number
|
||||
/** 是否启用 Talk Mode 按钮 */
|
||||
enableTalkMode?: boolean
|
||||
/** 深度思考开关状态 */
|
||||
thinkingEnabled?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@ -241,6 +255,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
queuedMessage: null,
|
||||
queueSize: 0,
|
||||
enableTalkMode: false,
|
||||
thinkingEnabled: false,
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
@ -253,6 +268,7 @@ const emit = defineEmits<{
|
||||
approve: [pendingId: string]
|
||||
deny: [pendingId: string]
|
||||
talk: []
|
||||
'toggle-thinking': []
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
@ -563,6 +579,28 @@ defineExpose({
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.thinking-btn {
|
||||
position: relative;
|
||||
}
|
||||
.thinking-btn.active {
|
||||
color: var(--el-color-primary, #409eff);
|
||||
}
|
||||
.thinking-btn.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 4px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
border-radius: 50%;
|
||||
background: var(--el-color-primary, #409eff);
|
||||
}
|
||||
.thinking-btn:hover:not(:disabled) {
|
||||
color: var(--el-color-primary, #409eff);
|
||||
background: var(--el-color-primary-light-9, rgba(64, 158, 255, 0.08));
|
||||
}
|
||||
|
||||
.talk-btn:hover:not(:disabled) {
|
||||
color: var(--mc-primary, #D97757);
|
||||
background: var(--mc-primary-light, rgba(217, 119, 87, 0.08));
|
||||
|
||||
@ -21,6 +21,8 @@ export interface UseChatOptions {
|
||||
baseUrl: string
|
||||
/** 认证 Token */
|
||||
token?: string
|
||||
/** 当前思考深度(响应式 ref),off 时抑制 thinking 展示 */
|
||||
thinkingLevel?: import('vue').Ref<string>
|
||||
/**
|
||||
* 统一回调:流结束后(done/error/stopped 都会触发)。
|
||||
* 前端应在此回调中做持久化历史收口(reconcile)。
|
||||
@ -86,10 +88,13 @@ export interface SendMessageOptions {
|
||||
attachments?: MessageContentPart[]
|
||||
/** 消息内容 */
|
||||
contentParts?: MessageContentPart[]
|
||||
/** 思考深度:off / low / medium / high / max */
|
||||
thinkingLevel?: string
|
||||
}
|
||||
|
||||
export function useChat(options: UseChatOptions): UseChatReturn {
|
||||
const { baseUrl, token, onStreamEnd } = options
|
||||
const thinkingLevelRef = options.thinkingLevel
|
||||
|
||||
/**
|
||||
* 带认证的 fetch 封装 — 从 localStorage 读取 token(与 useStream / http.ts 一致)
|
||||
@ -274,10 +279,12 @@ export function useChat(options: UseChatOptions): UseChatReturn {
|
||||
|
||||
stream.on('thinking_delta', (data) => {
|
||||
if (isStaleEvent(data)) return
|
||||
// thinkingLevel=off 时抑制 thinking 展示
|
||||
if (options.thinkingLevel?.value === 'off') return
|
||||
if (currentAssistantId.value) {
|
||||
appendMessageContent(currentAssistantId.value, data.delta || '', 'thinking')
|
||||
if (streamPhase.value !== 'summarizing_observations') {
|
||||
streamPhase.value = 'thinking'
|
||||
streamPhase.value = options.thinkingLevel?.value === 'off' ? 'streaming' : 'thinking'
|
||||
}
|
||||
// 分段:追加到当前 thinking segment 或创建新的
|
||||
const segs = currentSegments.value
|
||||
@ -818,7 +825,7 @@ export function useChat(options: UseChatOptions): UseChatReturn {
|
||||
const assistantMessage = createAssistantMessage('', convId2)
|
||||
;(assistantMessage as any)._turnId = activeTurnId
|
||||
currentAssistantId.value = assistantMessage.id as string
|
||||
streamPhase.value = 'thinking'
|
||||
streamPhase.value = options.thinkingLevel?.value === 'off' ? 'streaming' : 'thinking'
|
||||
phaseInfo.value = null
|
||||
})
|
||||
|
||||
@ -932,7 +939,7 @@ export function useChat(options: UseChatOptions): UseChatReturn {
|
||||
error.value = null
|
||||
errorFired = false
|
||||
streamConversationId = conversationId
|
||||
streamPhase.value = 'thinking'
|
||||
streamPhase.value = thinkingLevelRef?.value === 'off' ? 'streaming' : 'thinking'
|
||||
phaseInfo.value = null
|
||||
|
||||
try {
|
||||
@ -946,12 +953,16 @@ export function useChat(options: UseChatOptions): UseChatReturn {
|
||||
currentAssistantId.value = assistantMessage.id as string
|
||||
|
||||
// contentParts 已由 buildOutgoingParts 包含 file entries,不要重复合并 attachments
|
||||
await stream.connect({
|
||||
const body: Record<string, any> = {
|
||||
agentId,
|
||||
message: content,
|
||||
conversationId,
|
||||
contentParts,
|
||||
})
|
||||
}
|
||||
if (options.thinkingLevel) {
|
||||
body.thinkingLevel = options.thinkingLevel
|
||||
}
|
||||
await stream.connect(body)
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e : new Error(String(e))
|
||||
streamPhase.value = 'idle'
|
||||
@ -998,7 +1009,7 @@ export function useChat(options: UseChatOptions): UseChatReturn {
|
||||
const assistantMessage = createAssistantMessage('', conversationId)
|
||||
;(assistantMessage as any)._turnId = activeTurnId
|
||||
currentAssistantId.value = assistantMessage.id as string
|
||||
streamPhase.value = 'thinking'
|
||||
streamPhase.value = thinkingLevelRef?.value === 'off' ? 'streaming' : 'thinking'
|
||||
phaseInfo.value = null
|
||||
await stream.connect({
|
||||
agentId,
|
||||
|
||||
@ -146,6 +146,8 @@ export default {
|
||||
approvalWaiting: 'Awaiting confirmation below...',
|
||||
pendingApprovalPlaceholder: 'Type /approve or /deny to respond...',
|
||||
messagePlaceholder: 'Type a message... (Enter to send, Shift+Enter for new line)',
|
||||
thinkingOn: 'Deep thinking enabled',
|
||||
thinkingOff: 'Click to enable deep thinking',
|
||||
subtitle: 'Your intelligent AI assistant powered by Spring AI Alibaba',
|
||||
// Queue related
|
||||
queuedSending: 'Sending queued message...',
|
||||
@ -613,9 +615,18 @@ export default {
|
||||
description: 'Description',
|
||||
systemPrompt: 'System Prompt',
|
||||
maxIterations: 'Max Iterations',
|
||||
defaultThinkingLevel: 'Default Thinking Level',
|
||||
tags: 'Tags',
|
||||
enabled: 'Enabled',
|
||||
},
|
||||
thinkingLevels: {
|
||||
auto: 'Follow Model',
|
||||
off: 'Off',
|
||||
low: 'Low',
|
||||
medium: 'Standard',
|
||||
high: 'Deep',
|
||||
max: 'Maximum',
|
||||
},
|
||||
types: {
|
||||
react: 'ReAct (Tool Calling)',
|
||||
planExecute: 'Plan-and-Execute',
|
||||
|
||||
@ -146,6 +146,8 @@ export default {
|
||||
approvalWaiting: '等待输入框确认...',
|
||||
pendingApprovalPlaceholder: '输入 /approve 或 /deny 来回复...',
|
||||
messagePlaceholder: '输入消息... (Enter 发送, Shift+Enter 换行)',
|
||||
thinkingOn: '深度思考已开启',
|
||||
thinkingOff: '点击开启深度思考',
|
||||
subtitle: '基于 Spring AI Alibaba 的智能 AI 助手',
|
||||
// 排队相关
|
||||
queuedSending: '正在发送排队消息...',
|
||||
@ -613,9 +615,18 @@ export default {
|
||||
description: '描述',
|
||||
systemPrompt: '系统提示词',
|
||||
maxIterations: '最大迭代次数',
|
||||
defaultThinkingLevel: '默认思考深度',
|
||||
tags: '标签',
|
||||
enabled: '启用',
|
||||
},
|
||||
thinkingLevels: {
|
||||
auto: '跟随模型',
|
||||
off: '关闭',
|
||||
low: '低',
|
||||
medium: '标准',
|
||||
high: '深度',
|
||||
max: '极限',
|
||||
},
|
||||
types: {
|
||||
react: 'ReAct(工具调用)',
|
||||
planExecute: 'Plan-and-Execute',
|
||||
|
||||
@ -185,6 +185,17 @@
|
||||
<label class="form-label">{{ t('agents.fields.maxIterations') }}</label>
|
||||
<input v-model.number="form.maxIterations" type="number" min="1" max="50" class="form-input" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ t('agents.fields.defaultThinkingLevel') }}</label>
|
||||
<select v-model="form.defaultThinkingLevel" class="form-input">
|
||||
<option :value="null">{{ t('agents.thinkingLevels.auto') }}</option>
|
||||
<option value="off">{{ t('agents.thinkingLevels.off') }}</option>
|
||||
<option value="low">{{ t('agents.thinkingLevels.low') }}</option>
|
||||
<option value="medium">{{ t('agents.thinkingLevels.medium') }}</option>
|
||||
<option value="high">{{ t('agents.thinkingLevels.high') }}</option>
|
||||
<option value="max">{{ t('agents.thinkingLevels.max') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group full-width">
|
||||
<label class="form-label">{{ t('agents.fields.description') }}</label>
|
||||
<input v-model="form.description" class="form-input" :placeholder="t('agents.placeholders.description')" />
|
||||
@ -297,7 +308,7 @@ const filterTabs = [
|
||||
{ key: 'agents.tabs.disabled', value: 'disabled' },
|
||||
]
|
||||
|
||||
const defaultForm = (): Partial<Agent> & { name: string } => ({
|
||||
const defaultForm = (): Partial<Agent> & { name: string; defaultThinkingLevel: string | null } => ({
|
||||
name: '',
|
||||
description: '',
|
||||
agentType: 'react',
|
||||
@ -306,6 +317,7 @@ const defaultForm = (): Partial<Agent> & { name: string } => ({
|
||||
icon: '🤖',
|
||||
tags: '',
|
||||
enabled: true,
|
||||
defaultThinkingLevel: null,
|
||||
})
|
||||
|
||||
const form = ref(defaultForm())
|
||||
@ -402,6 +414,7 @@ async function openEditModal(agent: Agent) {
|
||||
icon: agent.icon || '🤖',
|
||||
tags: agent.tags || '',
|
||||
enabled: agent.enabled,
|
||||
defaultThinkingLevel: (agent as any).defaultThinkingLevel || null,
|
||||
}
|
||||
modalTab.value = 'basic'
|
||||
showModal.value = true
|
||||
|
||||
@ -233,6 +233,8 @@
|
||||
@approve="handleApprove"
|
||||
@deny="handleDeny"
|
||||
:enable-talk-mode="!!selectedAgentId"
|
||||
:thinking-enabled="thinkingEnabled"
|
||||
@toggle-thinking="thinkingEnabled = !thinkingEnabled"
|
||||
@talk="showTalkMode = true"
|
||||
/>
|
||||
</div>
|
||||
@ -345,6 +347,11 @@ const activeModels = ref<ActiveModelsInfo | null>(null)
|
||||
const pendingAttachments = ref<ChatAttachment[]>([])
|
||||
const uploadingAttachment = ref(false)
|
||||
|
||||
// 思考模式:只有两个状态 — 开或关
|
||||
const thinkingEnabled = ref(localStorage.getItem('mateclaw_thinking') !== 'off')
|
||||
const thinkingLevel = computed(() => thinkingEnabled.value ? 'high' : 'off')
|
||||
watch(thinkingEnabled, (v) => localStorage.setItem('mateclaw_thinking', v ? 'on' : 'off'))
|
||||
|
||||
// Dropdowns & menus
|
||||
const agentDropdownOpen = ref(false)
|
||||
|
||||
@ -550,6 +557,7 @@ const {
|
||||
resetForNewConversation,
|
||||
} = useChat({
|
||||
baseUrl: '',
|
||||
thinkingLevel,
|
||||
onStreamEnd: async (meta) => {
|
||||
// 流结束后刷新会话列表(更新 lastActiveTime / 标题等)
|
||||
await loadConversations()
|
||||
@ -1040,6 +1048,7 @@ async function handleSendMessage(content: string) {
|
||||
conversationId: currentConversationId.value,
|
||||
agentId: selectedAgentId.value,
|
||||
contentParts,
|
||||
thinkingLevel: thinkingLevel.value,
|
||||
attachments: outgoingAttachments.map(a => ({
|
||||
type: 'file' as const,
|
||||
fileUrl: a.url,
|
||||
@ -1220,7 +1229,10 @@ function normalizeMessage(raw: Message): Message {
|
||||
if (msg.contentParts.length === 0 && msg.content) {
|
||||
if (msg.role === 'assistant') {
|
||||
const parsed = parseThinkingContent(msg.content)
|
||||
if (parsed.thinking) msg.contentParts.push({ type: 'thinking', text: parsed.thinking })
|
||||
// thinkingLevel=off 时不展示 thinking 内容,直接剥离 <think> 标签
|
||||
if (parsed.thinking && thinkingLevel.value !== 'off') {
|
||||
msg.contentParts.push({ type: 'thinking', text: parsed.thinking })
|
||||
}
|
||||
if (parsed.content) msg.contentParts.push({ type: 'text', text: parsed.content })
|
||||
msg.content = parsed.content
|
||||
} else {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user