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