mirror of
https://github.com/Tencent/WeKnora.git
synced 2026-09-01 14:53:07 +08:00
feat(agent): keep max completion tokens as default or a custom cap
Unset (0) now resolves at call time (2048 / 4096 / 24576 with sandbox) so the editor can offer Default vs Custom without silently rewriting a saved value.
This commit is contained in:
@@ -27,7 +27,7 @@ builtin_agents:
|
||||
system_prompt_id: "default_kb"
|
||||
context_template_id: "default_context"
|
||||
temperature: 0.7
|
||||
max_completion_tokens: 2048
|
||||
max_completion_tokens: 0
|
||||
web_search_enabled: true
|
||||
web_search_max_results: 5
|
||||
multi_turn_enabled: true
|
||||
@@ -70,7 +70,7 @@ builtin_agents:
|
||||
agent_type: "rag-qa"
|
||||
system_prompt: ""
|
||||
temperature: 0.7
|
||||
max_completion_tokens: 32768
|
||||
max_completion_tokens: 0
|
||||
max_iterations: 50
|
||||
kb_selection_mode: "all"
|
||||
retrieve_kb_only_when_mentioned: false
|
||||
@@ -118,7 +118,7 @@ builtin_agents:
|
||||
agent_type: "data-analysis"
|
||||
system_prompt_id: "data_analyst"
|
||||
temperature: 0.3
|
||||
max_completion_tokens: 32768
|
||||
max_completion_tokens: 0
|
||||
max_iterations: 30
|
||||
kb_selection_mode: "all"
|
||||
retrieve_kb_only_when_mentioned: false
|
||||
@@ -163,7 +163,7 @@ builtin_agents:
|
||||
agent_type: "wiki-qa"
|
||||
system_prompt_id: "wiki_researcher"
|
||||
temperature: 0.7
|
||||
max_completion_tokens: 32768
|
||||
max_completion_tokens: 0
|
||||
max_iterations: 30
|
||||
kb_selection_mode: "all"
|
||||
retrieve_kb_only_when_mentioned: false
|
||||
@@ -207,7 +207,7 @@ builtin_agents:
|
||||
agent_type: "custom"
|
||||
system_prompt_id: "wiki_fixer"
|
||||
temperature: 0.7
|
||||
max_completion_tokens: 32768
|
||||
max_completion_tokens: 0
|
||||
max_iterations: 30
|
||||
kb_selection_mode: "selected"
|
||||
retrieve_kb_only_when_mentioned: false
|
||||
@@ -257,7 +257,7 @@ builtin_agents:
|
||||
agent_type: "custom"
|
||||
system_prompt_id: "skill_installer"
|
||||
temperature: 0.2
|
||||
max_completion_tokens: 32768
|
||||
max_completion_tokens: 24576
|
||||
max_iterations: 30
|
||||
kb_selection_mode: "none"
|
||||
retrieve_kb_only_when_mentioned: false
|
||||
|
||||
@@ -44,7 +44,7 @@ export interface CustomAgentConfig {
|
||||
model_id?: string;
|
||||
rerank_model_id?: string; // ReRank 模型 ID
|
||||
temperature?: number;
|
||||
max_completion_tokens?: number; // 最大生成 token 数;智能推理 ReAct 轮次会抬到至少 32768,避免工具 JSON 被网关默认 4096 截断
|
||||
max_completion_tokens?: number; // 0 = 跟随系统默认(快速问答 2048;智能推理 4096,绑沙箱可写文件时 24576)。大于 0 为自定义上限
|
||||
thinking?: boolean; // 是否启用思考模式(支持扩展思考的模型)
|
||||
citation_enabled?: boolean; // 是否在最终回答中输出知识库/网页来源引用(默认开启)
|
||||
|
||||
|
||||
@@ -1008,6 +1008,8 @@ export default {
|
||||
rewritePromptUser: 'Rewrite User Prompt',
|
||||
rewritePromptUserPlaceholder: 'Leave empty to use default prompt',
|
||||
maxCompletionTokens: 'Max Completion Tokens',
|
||||
maxCompletionTokensDefault: 'Default',
|
||||
maxCompletionTokensCustom: 'Custom',
|
||||
fallbackStrategy: 'Fallback Strategy',
|
||||
fallbackResponse: 'Fixed Response',
|
||||
fallbackResponsePlaceholder: 'Sorry, I cannot answer this question.',
|
||||
@@ -5529,7 +5531,8 @@ export default {
|
||||
contextTemplate: 'Define how retrieved content is formatted before passing to the model',
|
||||
model: 'Select the LLM used by the agent',
|
||||
temperature: 'Control output randomness, 0 is most deterministic, 1 is most random',
|
||||
maxTokens: 'Maximum number of tokens for model-generated responses',
|
||||
maxTokens: 'Maximum tokens for the model reply. Default is 2048. Custom values are saved as entered.',
|
||||
maxTokensAgent: 'Maximum tokens generated in each reasoning round, including tool-call JSON. Default is 4096 without a sandbox, or 24576 when a sandbox can write or edit files. A custom value is saved as entered and is not changed later.',
|
||||
thinking: 'Enable extended thinking capability (requires model support)',
|
||||
conversationSection: 'Configure multi-turn conversation and query rewriting parameters',
|
||||
conversationSectionAgent: 'How much earlier conversation each turn carries. Smart reasoning is always multi-turn',
|
||||
|
||||
@@ -1100,7 +1100,8 @@ export default {
|
||||
contextTemplate: '검색된 콘텐츠를 모델에 전달하기 전에 형식을 정의합니다',
|
||||
model: '에이전트가 사용할 대규모 언어 모델을 선택하세요',
|
||||
temperature: '출력의 무작위성을 제어합니다. 0이 가장 확정적, 1이 가장 무작위',
|
||||
maxTokens: '모델이 생성하는 응답의 최대 토큰 수',
|
||||
maxTokens: '모델 응답의 최대 토큰 수. 「기본값」은 2048입니다. 「사용자 지정」은 입력한 값을 그대로 저장합니다.',
|
||||
maxTokensAgent: '각 추론 라운드에서 생성할 최대 토큰 수(도구 호출 JSON 포함). 「기본값」은 샌드박스 없으면 4096, 파일 쓰기/편집이 가능하면 24576입니다. 「사용자 지정」은 입력한 값을 그대로 저장합니다.',
|
||||
thinking: '모델의 확장 사고 기능 활성화 (모델 지원 필요)',
|
||||
conversationSection: '다중 턴 대화 및 질문 재작성 관련 매개변수 설정',
|
||||
conversationSectionAgent: '매 턴에 실어 보낼 이전 대화 분량 설정 (스마트 추론은 항상 다중 턴)',
|
||||
@@ -5737,6 +5738,8 @@ export default {
|
||||
rewritePromptUser: '사용자 프롬프트 다시 작성',
|
||||
rewritePromptUserPlaceholder: '시스템 기본 프롬프트를 사용하려면 비워 두세요.',
|
||||
maxCompletionTokens: '생성된 토큰의 최대 수',
|
||||
maxCompletionTokensDefault: '기본값',
|
||||
maxCompletionTokensCustom: '사용자 지정',
|
||||
fallbackStrategy: '폴백 전략',
|
||||
fallbackResponse: '고정 응답 내용',
|
||||
fallbackResponsePlaceholder: '죄송합니다. 이 질문에는 답변해 드릴 수 없습니다.',
|
||||
|
||||
@@ -1100,7 +1100,8 @@ export default {
|
||||
contextTemplate: 'Определите формат передачи найденного контента модели',
|
||||
model: 'Выберите LLM, используемую агентом',
|
||||
temperature: 'Контроль случайности выхода: 0 — наиболее детерминированный, 1 — наиболее случайный',
|
||||
maxTokens: 'Максимальное количество токенов в ответе модели',
|
||||
maxTokens: 'Максимум токенов в ответе. «По умолчанию» — 2048. «Своё» сохраняет введённое число.',
|
||||
maxTokensAgent: 'Максимум токенов за один раунд рассуждения, включая JSON вызовов инструментов. «По умолчанию»: 4096 без песочницы, 24576 с записью/правкой файлов. «Своё» сохраняет введённое число и больше не меняется.',
|
||||
thinking: 'Включить расширенное мышление модели (требуется поддержка модели)',
|
||||
conversationSection: 'Настройка параметров многооборотного диалога и перефразирования вопросов',
|
||||
conversationSectionAgent: 'Объём предыдущего диалога в каждом ходе. Умные рассуждения всегда многооборотные',
|
||||
@@ -5737,6 +5738,8 @@ export default {
|
||||
rewritePromptUser: 'Rewrite User Prompt',
|
||||
rewritePromptUserPlaceholder: 'Leave empty to use default prompt',
|
||||
maxCompletionTokens: 'Max Completion Tokens',
|
||||
maxCompletionTokensDefault: 'По умолчанию',
|
||||
maxCompletionTokensCustom: 'Своё',
|
||||
fallbackStrategy: 'Fallback Strategy',
|
||||
fallbackResponse: 'Fixed Response',
|
||||
fallbackResponsePlaceholder: 'Sorry, I cannot answer this question.',
|
||||
|
||||
@@ -1102,7 +1102,8 @@ export default {
|
||||
contextTemplate: '定义如何将检索到的内容格式化后传递给模型',
|
||||
model: '选择智能体使用的大语言模型',
|
||||
temperature: '控制输出的随机性,0 最确定,1 最随机',
|
||||
maxTokens: '模型生成回复的最大Token数量',
|
||||
maxTokens: '模型生成回复的最大 Token 数。选「默认」时为 2048;选「自定义」后按你填的数保存。',
|
||||
maxTokensAgent: '每一轮推理的最大生成 Token(含工具调用 JSON)。选「默认」时,未绑沙箱为 4096,绑了沙箱(可写/改文件)为 24576。选「自定义」后按你填的数保存,不再自动改。',
|
||||
thinking: '启用模型的扩展思考能力(需要模型支持)',
|
||||
conversationSection: '配置多轮对话开关与问题改写开关(改写提示词见「提示词」)',
|
||||
conversationSectionAgent: '配置每轮携带多少历史对话。智能推理始终为多轮模式。',
|
||||
@@ -5739,6 +5740,8 @@ export default {
|
||||
rewritePromptUser: '改写用户提示词',
|
||||
rewritePromptUserPlaceholder: '留空使用系统默认提示词',
|
||||
maxCompletionTokens: '最大生成Token数',
|
||||
maxCompletionTokensDefault: '默认',
|
||||
maxCompletionTokensCustom: '自定义',
|
||||
fallbackStrategy: '兜底策略',
|
||||
fallbackResponse: '固定回复内容',
|
||||
fallbackResponsePlaceholder: '抱歉,我无法回答这个问题。',
|
||||
|
||||
@@ -610,14 +610,25 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 最大生成Token数(仅普通模式) -->
|
||||
<div v-if="!isAgentMode" class="setting-row">
|
||||
<!-- 最大生成Token数:0 表示跟随系统默认;自定义后按输入值保存 -->
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
<label>{{ $t('agent.editor.maxCompletionTokens') }}</label>
|
||||
<p class="desc">{{ $t('agentEditor.desc.maxTokens') }}</p>
|
||||
<p class="desc">{{ isAgentMode ? $t('agentEditor.desc.maxTokensAgent') :
|
||||
$t('agentEditor.desc.maxTokens') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<t-input-number v-model="formData.config.max_completion_tokens" :min="100" :max="100000"
|
||||
<div class="setting-control max-tokens-control">
|
||||
<t-radio-group v-model="maxCompletionTokensMode">
|
||||
<t-radio-button value="default">{{ $t('agent.editor.maxCompletionTokensDefault')
|
||||
}}</t-radio-button>
|
||||
<t-radio-button value="custom">{{ $t('agent.editor.maxCompletionTokensCustom')
|
||||
}}</t-radio-button>
|
||||
</t-radio-group>
|
||||
<span v-if="maxCompletionTokensMode === 'default'" class="max-tokens-value">
|
||||
{{ effectiveDefaultMaxCompletionTokens }}
|
||||
</span>
|
||||
<t-input-number v-else v-model="formData.config.max_completion_tokens" :min="100" :max="100000"
|
||||
:step="100" theme="column" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -2202,9 +2213,20 @@ const defaultKeywordThreshold = ref(0.3);
|
||||
const defaultVectorThreshold = ref(0.5);
|
||||
const defaultRerankTopK = ref(5);
|
||||
const defaultRerankThreshold = ref(0.5);
|
||||
const defaultMaxCompletionTokens = ref(2048);
|
||||
const defaultQuickAnswerMaxCompletionTokens = 2048;
|
||||
const defaultSmartReasoningMaxCompletionTokens = 4096;
|
||||
const defaultSandboxWriteMaxCompletionTokens = 24576;
|
||||
const defaultTemperature = ref(0.7);
|
||||
|
||||
const defaultMaxCompletionTokensFor = (mode: string, sandboxConfigId?: string) => {
|
||||
if (mode === 'smart-reasoning') {
|
||||
return sandboxConfigId
|
||||
? defaultSandboxWriteMaxCompletionTokens
|
||||
: defaultSmartReasoningMaxCompletionTokens;
|
||||
}
|
||||
return defaultQuickAnswerMaxCompletionTokens;
|
||||
};
|
||||
|
||||
// 知识库相关工具列表(用于 watch(hasKnowledgeBase) 从"无"变"有"时 seed 默认工具)
|
||||
const knowledgeBaseTools = ['grep_chunks', 'knowledge_search', 'list_knowledge_chunks', 'query_knowledge_graph', 'get_document_info', 'database_query'];
|
||||
|
||||
@@ -2575,7 +2597,7 @@ const defaultFormData = {
|
||||
model_id: '',
|
||||
rerank_model_id: '',
|
||||
temperature: 0.7,
|
||||
max_completion_tokens: 2048,
|
||||
max_completion_tokens: 0,
|
||||
thinking: false, // 默认禁用思考模式
|
||||
citation_enabled: true, // 默认输出知识库/网页来源引用
|
||||
// Agent模式设置
|
||||
@@ -2719,6 +2741,23 @@ const agentMode = computed({
|
||||
|
||||
const isAgentMode = computed(() => agentMode.value === 'smart-reasoning');
|
||||
|
||||
const effectiveDefaultMaxCompletionTokens = computed(() =>
|
||||
defaultMaxCompletionTokensFor(agentMode.value, formData.value.config.sandbox_config_id),
|
||||
);
|
||||
|
||||
const maxCompletionTokensMode = computed({
|
||||
get: () => (formData.value.config.max_completion_tokens > 0 ? 'custom' : 'default'),
|
||||
set: (mode: 'default' | 'custom') => {
|
||||
if (mode === 'default') {
|
||||
formData.value.config.max_completion_tokens = 0;
|
||||
return;
|
||||
}
|
||||
if (!formData.value.config.max_completion_tokens) {
|
||||
formData.value.config.max_completion_tokens = effectiveDefaultMaxCompletionTokens.value;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const currentIntentTemplate = computed(() =>
|
||||
intentPromptTemplates.value.find((template) => template.id === selectedIntent.value),
|
||||
);
|
||||
@@ -3250,6 +3289,7 @@ watch(() => props.visible, async (val) => {
|
||||
// 附件解析调优字段:旧数据缺省时置 0(表示使用全局默认)
|
||||
if (agentData.config.attachment_ocr_max_pages == null) agentData.config.attachment_ocr_max_pages = 0;
|
||||
if (agentData.config.attachment_parse_wait_timeout_sec == null) agentData.config.attachment_parse_wait_timeout_sec = 0;
|
||||
if (agentData.config.max_completion_tokens == null) agentData.config.max_completion_tokens = 0;
|
||||
// 长期记忆:后端用 omitempty,跟随空间设置的智能体不带这个字段。
|
||||
// 不补成 true 的话开关会显示为"关",用户随手一存就真的把记忆关了。
|
||||
if (agentData.config.memory_enabled == null) agentData.config.memory_enabled = true;
|
||||
@@ -3285,7 +3325,7 @@ watch(() => props.visible, async (val) => {
|
||||
newFormData.config.vector_threshold = defaultVectorThreshold.value;
|
||||
newFormData.config.rerank_top_k = defaultRerankTopK.value;
|
||||
newFormData.config.rerank_threshold = defaultRerankThreshold.value;
|
||||
newFormData.config.max_completion_tokens = defaultMaxCompletionTokens.value;
|
||||
newFormData.config.max_completion_tokens = 0;
|
||||
newFormData.config.temperature = defaultTemperature.value;
|
||||
// 应用系统默认提示词(根据模式填充)
|
||||
const isAgent = newFormData.config.agent_mode === 'smart-reasoning';
|
||||
@@ -5126,6 +5166,16 @@ const handleSave = async () => {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
&.max-tokens-control {
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 8px;
|
||||
|
||||
:deep(.t-input-number) {
|
||||
width: 140px;
|
||||
}
|
||||
}
|
||||
|
||||
// 让 select 和 input 占满控件区域
|
||||
:deep(.t-select),
|
||||
:deep(.t-input),
|
||||
@@ -5479,6 +5529,13 @@ const handleSave = async () => {
|
||||
color: var(--td-text-color-primary);
|
||||
}
|
||||
|
||||
.max-tokens-value {
|
||||
font-family: var(--app-font-family-mono);
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-size: 12px;
|
||||
color: var(--td-text-color-placeholder);
|
||||
}
|
||||
|
||||
// 推荐问题列表
|
||||
.suggested-prompts-list {
|
||||
display: flex;
|
||||
|
||||
@@ -85,14 +85,16 @@ func (e *AgentEngine) getLLMCallTimeout() time.Duration {
|
||||
}
|
||||
|
||||
// getCompletionTokenBudget is the max_tokens / max_completion_tokens sent on
|
||||
// each ReAct LLM round. Leaving it unset lets providers default to 4096 and
|
||||
// truncate write_sandbox_file (and similar) tool-call JSON.
|
||||
// each ReAct LLM round. Unset without a sandbox is 4096; unset with a
|
||||
// sandbox (write_sandbox_file / edit_sandbox_file) is 24576.
|
||||
func (e *AgentEngine) getCompletionTokenBudget() int {
|
||||
configured := 0
|
||||
sandboxID := ""
|
||||
if e.config != nil {
|
||||
configured = e.config.MaxCompletionTokens
|
||||
sandboxID = e.config.SandboxConfigID
|
||||
}
|
||||
return types.AgentRoundMaxCompletionTokens(configured)
|
||||
return types.AgentRoundMaxCompletionTokensFor(configured, sandboxID)
|
||||
}
|
||||
|
||||
// generateEventID generates a unique event ID with type suffix for better traceability
|
||||
|
||||
@@ -446,6 +446,23 @@ func TestStreamThinkingToEventBus_SetsCompletionTokenBudget(t *testing.T) {
|
||||
[]chat.Message{{Role: "user", Content: "test"}}, nil, 0, "sess-1")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, mock.opts, 1)
|
||||
assert.Equal(t, types.DefaultSmartReasoningMaxCompletionTokens, mock.opts[0].MaxTokens)
|
||||
assert.Equal(t, types.DefaultSmartReasoningMaxCompletionTokens, mock.opts[0].MaxCompletionTokens)
|
||||
})
|
||||
|
||||
t.Run("defaults to the write-file budget when a sandbox is bound", func(t *testing.T) {
|
||||
mock := &mockChat{
|
||||
responses: []mockResponse{
|
||||
{chunks: []types.StreamResponse{{Content: "ok", Done: true, FinishReason: "stop"}}},
|
||||
},
|
||||
}
|
||||
engine := newTestEngine(t, mock, func(cfg *types.AgentConfig) {
|
||||
cfg.SandboxConfigID = "cfg-a"
|
||||
})
|
||||
_, err := engine.streamThinkingToEventBus(context.Background(),
|
||||
[]chat.Message{{Role: "user", Content: "test"}}, nil, 0, "sess-1")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, mock.opts, 1)
|
||||
assert.Equal(t, types.DefaultAgentMaxCompletionTokens, mock.opts[0].MaxTokens)
|
||||
assert.Equal(t, types.DefaultAgentMaxCompletionTokens, mock.opts[0].MaxCompletionTokens)
|
||||
})
|
||||
|
||||
+46
-13
@@ -11,22 +11,54 @@ import (
|
||||
// DefaultMaxContextTokens is the default context window budget for agent conversations (200k).
|
||||
const DefaultMaxContextTokens = 200000
|
||||
|
||||
// DefaultAgentMaxCompletionTokens is the per-round completion budget for
|
||||
// ReAct / tool-calling agents. When MaxTokens is omitted, OpenAI-compatible
|
||||
// providers (and many Chinese gateways) default to 4096 — large tool-call JSON
|
||||
// such as write_sandbox_file is then truncated mid-stream (finish_reason=length),
|
||||
// which surfaces as missing required arguments. 32768 matches wiki ingest (#2604).
|
||||
const DefaultAgentMaxCompletionTokens = 32768
|
||||
// DefaultSmartReasoningMaxCompletionTokens is the per-round budget when the
|
||||
// agent cannot emit write_sandbox_file / edit_sandbox_file. 4096 matches
|
||||
// typical OpenAI-compatible provider defaults and is enough for ordinary
|
||||
// tool-call JSON.
|
||||
const DefaultSmartReasoningMaxCompletionTokens = 4096
|
||||
|
||||
// DefaultAgentMaxCompletionTokens is the per-round budget when the agent
|
||||
// can write or edit sandbox files. Those tool calls carry the file body in
|
||||
// JSON; a 4096 cap truncates mid-stream (finish_reason=length).
|
||||
const DefaultAgentMaxCompletionTokens = 24576
|
||||
|
||||
// DefaultQuickAnswerMaxCompletionTokens is the RAG answer budget.
|
||||
const DefaultQuickAnswerMaxCompletionTokens = 2048
|
||||
|
||||
// NeedsSandboxWriteCompletionBudget reports whether this agent may register
|
||||
// write_sandbox_file / edit_sandbox_file. Those tools follow the bound
|
||||
// sandbox, not the allowed_tools checklist.
|
||||
func NeedsSandboxWriteCompletionBudget(sandboxConfigID string) bool {
|
||||
return strings.TrimSpace(sandboxConfigID) != ""
|
||||
}
|
||||
|
||||
// DefaultMaxCompletionTokens is the unset-config default for one agent:
|
||||
// quick-answer 2048, smart-reasoning 4096, smart-reasoning with a sandbox 24576.
|
||||
func DefaultMaxCompletionTokens(agentMode, sandboxConfigID string) int {
|
||||
if agentMode == AgentModeSmartReasoning {
|
||||
if NeedsSandboxWriteCompletionBudget(sandboxConfigID) {
|
||||
return DefaultAgentMaxCompletionTokens
|
||||
}
|
||||
return DefaultSmartReasoningMaxCompletionTokens
|
||||
}
|
||||
return DefaultQuickAnswerMaxCompletionTokens
|
||||
}
|
||||
|
||||
// AgentRoundMaxCompletionTokens returns the completion-token budget for one
|
||||
// ReAct LLM round. Zero (unset) uses DefaultAgentMaxCompletionTokens. An
|
||||
// explicit configured value is honored as-is so a user who capped a small
|
||||
// local model at 4096 is not silently sent 32768.
|
||||
// ReAct LLM round when no sandbox is bound. Zero (unset) uses
|
||||
// DefaultSmartReasoningMaxCompletionTokens. An explicit configured value is
|
||||
// honored as-is.
|
||||
func AgentRoundMaxCompletionTokens(configured int) int {
|
||||
if configured <= 0 {
|
||||
return DefaultAgentMaxCompletionTokens
|
||||
return AgentRoundMaxCompletionTokensFor(configured, "")
|
||||
}
|
||||
|
||||
// AgentRoundMaxCompletionTokensFor is AgentRoundMaxCompletionTokens with
|
||||
// the agent's sandbox: unset + sandbox uses the large write-file budget.
|
||||
func AgentRoundMaxCompletionTokensFor(configured int, sandboxConfigID string) int {
|
||||
if configured > 0 {
|
||||
return configured
|
||||
}
|
||||
return configured
|
||||
return DefaultMaxCompletionTokens(AgentModeSmartReasoning, sandboxConfigID)
|
||||
}
|
||||
|
||||
// AgentConfig represents the full agent configuration (used at tenant level and runtime)
|
||||
@@ -90,7 +122,8 @@ type AgentConfig struct {
|
||||
LLMCallTimeout int `json:"llm_call_timeout,omitempty"`
|
||||
|
||||
// Maximum completion tokens for each ReAct LLM round. Zero means
|
||||
// DefaultAgentMaxCompletionTokens. Explicit values are sent as-is.
|
||||
// DefaultMaxCompletionTokens for this agent's mode and sandbox.
|
||||
// Explicit values are sent as-is.
|
||||
MaxCompletionTokens int `json:"max_completion_tokens,omitempty"`
|
||||
|
||||
// Maximum character length for tool output (default: 16000).
|
||||
|
||||
@@ -77,8 +77,11 @@ func TestUseCustomSystemPromptFlag(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAgentRoundMaxCompletionTokens(t *testing.T) {
|
||||
if got := AgentRoundMaxCompletionTokens(0); got != DefaultAgentMaxCompletionTokens {
|
||||
t.Fatalf("zero configured = %d, want %d", got, DefaultAgentMaxCompletionTokens)
|
||||
if got := AgentRoundMaxCompletionTokens(0); got != DefaultSmartReasoningMaxCompletionTokens {
|
||||
t.Fatalf("zero configured = %d, want %d", got, DefaultSmartReasoningMaxCompletionTokens)
|
||||
}
|
||||
if got := AgentRoundMaxCompletionTokensFor(0, "cfg-a"); got != DefaultAgentMaxCompletionTokens {
|
||||
t.Fatalf("unset with sandbox = %d, want %d", got, DefaultAgentMaxCompletionTokens)
|
||||
}
|
||||
if got := AgentRoundMaxCompletionTokens(2048); got != 2048 {
|
||||
t.Fatalf("explicit 2048 = %d, want 2048", got)
|
||||
@@ -86,6 +89,9 @@ func TestAgentRoundMaxCompletionTokens(t *testing.T) {
|
||||
if got := AgentRoundMaxCompletionTokens(4096); got != 4096 {
|
||||
t.Fatalf("explicit 4096 = %d, want 4096", got)
|
||||
}
|
||||
if got := AgentRoundMaxCompletionTokensFor(4096, "cfg-a"); got != 4096 {
|
||||
t.Fatalf("explicit 4096 with sandbox must stay 4096, got %d", got)
|
||||
}
|
||||
if got := AgentRoundMaxCompletionTokens(64000); got != 64000 {
|
||||
t.Fatalf("explicit high value = %d, want 64000", got)
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ type CustomAgentConfig struct {
|
||||
Temperature float64 `yaml:"temperature" json:"temperature"`
|
||||
// Maximum completion tokens. Quick-answer uses this for the RAG answer.
|
||||
// Smart-reasoning ReAct rounds send this value as-is (zero becomes
|
||||
// DefaultAgentMaxCompletionTokens at call time).
|
||||
// DefaultMaxCompletionTokens at call time: 4096, or 24576 with a sandbox).
|
||||
MaxCompletionTokens int `yaml:"max_completion_tokens" json:"max_completion_tokens"`
|
||||
// Whether to enable thinking mode (for models that support extended thinking)
|
||||
Thinking *bool `yaml:"thinking" json:"thinking"`
|
||||
@@ -525,13 +525,9 @@ func (a *CustomAgent) EnsureDefaults() {
|
||||
if a.Config.FallbackStrategy == "" {
|
||||
a.Config.FallbackStrategy = "model"
|
||||
}
|
||||
if a.Config.MaxCompletionTokens == 0 {
|
||||
if a.Config.AgentMode == AgentModeSmartReasoning {
|
||||
a.Config.MaxCompletionTokens = DefaultAgentMaxCompletionTokens
|
||||
} else {
|
||||
a.Config.MaxCompletionTokens = 2048
|
||||
}
|
||||
}
|
||||
// MaxCompletionTokens 0 means "use DefaultMaxCompletionTokens at call
|
||||
// time". Do not materialize a number here — that would make the editor
|
||||
// treat a chosen default as a custom cap.
|
||||
// Agent mode should always enable multi-turn conversation
|
||||
if a.Config.AgentMode == AgentModeSmartReasoning {
|
||||
a.Config.MultiTurnEnabled = true
|
||||
|
||||
@@ -43,15 +43,16 @@ func TestEnsureDefaults_ThinkingPreservesTrue(t *testing.T) {
|
||||
func TestEnsureDefaults_MaxCompletionTokensByMode(t *testing.T) {
|
||||
qa := &CustomAgent{Config: CustomAgentConfig{AgentMode: AgentModeQuickAnswer}}
|
||||
qa.EnsureDefaults()
|
||||
if qa.Config.MaxCompletionTokens != 2048 {
|
||||
t.Fatalf("quick-answer default MaxCompletionTokens = %d, want 2048", qa.Config.MaxCompletionTokens)
|
||||
if qa.Config.MaxCompletionTokens != 0 {
|
||||
t.Fatalf("unset max_completion_tokens must stay 0 (follow default at call time), got %d",
|
||||
qa.Config.MaxCompletionTokens)
|
||||
}
|
||||
|
||||
sr := &CustomAgent{Config: CustomAgentConfig{AgentMode: AgentModeSmartReasoning}}
|
||||
sr.EnsureDefaults()
|
||||
if sr.Config.MaxCompletionTokens != DefaultAgentMaxCompletionTokens {
|
||||
t.Fatalf("smart-reasoning default MaxCompletionTokens = %d, want %d",
|
||||
sr.Config.MaxCompletionTokens, DefaultAgentMaxCompletionTokens)
|
||||
if sr.Config.MaxCompletionTokens != 0 {
|
||||
t.Fatalf("unset smart-reasoning max_completion_tokens must stay 0, got %d",
|
||||
sr.Config.MaxCompletionTokens)
|
||||
}
|
||||
|
||||
explicit := &CustomAgent{Config: CustomAgentConfig{
|
||||
|
||||
Reference in New Issue
Block a user