feat(agent): add intent prompt customization in agent editor

Allow per-intent system prompt overrides for non-retrieval intents in normal mode.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
helloandyzhang
2026-05-26 18:06:43 +08:00
committed by lyingbug
parent 09892ef763
commit e6a469631b
13 changed files with 322 additions and 29 deletions

View File

@@ -84,6 +84,8 @@ export interface CustomAgentConfig {
fallback_strategy?: 'fixed' | 'model'; // 兜底策略
fallback_response?: string; // 固定兜底回复
fallback_prompt?: string; // 兜底提示词(模型生成时)
// 意图提示词:非检索意图(问候、闲聊等)时覆盖主系统提示词
intent_prompts?: Record<string, string>;
// ===== 已废弃字段(保留兼容)=====
welcome_message?: string;

View File

@@ -48,6 +48,7 @@ export interface PromptTemplatesConfig {
keywords_extraction?: PromptTemplate[]
chat_summary?: PromptTemplate[]
agent_system_prompt?: PromptTemplate[]
intent_prompts?: PromptTemplate[]
}
export function getSystemInfo(): Promise<{ data: SystemInfo }> {

View File

@@ -14,6 +14,7 @@
</t-button>
<!-- 选择模板按钮 -->
<t-popup
v-if="showTemplatePicker"
trigger="click"
placement="top-right"
:visible="popupVisible"
@@ -77,13 +78,19 @@ import { getPromptTemplates, type PromptTemplate, type PromptTemplatesConfig } f
const { t } = useI18n();
const props = defineProps<{
type: 'systemPrompt' | 'contextTemplate' | 'rewrite' | 'fallback' | 'agentSystemPrompt';
const props = withDefaults(defineProps<{
type: 'systemPrompt' | 'contextTemplate' | 'rewrite' | 'fallback' | 'agentSystemPrompt' | 'intentPrompt';
hasKnowledgeBase?: boolean;
position?: 'inline' | 'corner'; // inline: 行内显示, corner: 输入框右下角
/** 用于 fallback 场景:区分固定回复和模型 prompt */
fallbackMode?: 'fixed' | 'model';
}>();
/** intent 场景:当前选中的 intent id对应 template.id */
intentId?: string;
/** 为 false 时只显示「恢复默认」,不显示「使用模板」 */
showTemplatePicker?: boolean;
}>(), {
showTemplatePicker: true,
});
const emit = defineEmits<{
(e: 'select', template: PromptTemplate): void;
@@ -143,6 +150,9 @@ const templates = computed<PromptTemplate[]>(() => {
case 'agentSystemPrompt':
list = templatesConfig.value.agent_system_prompt || [];
break;
case 'intentPrompt':
list = templatesConfig.value.intent_prompts || [];
break;
default:
list = [];
}
@@ -161,6 +171,15 @@ const findDefaultTemplate = (list: PromptTemplate[]): PromptTemplate | null => {
return defaultItem || list[0];
};
const resolveResetTemplate = (): PromptTemplate | null => {
const list = templates.value;
if (props.type === 'intentPrompt') {
if (!props.intentId) return null;
return list.find((t) => t.id === props.intentId) ?? null;
}
return findDefaultTemplate(list);
};
// Reset to default template content
const handleResetToDefault = async () => {
if (!templatesConfig.value) {
@@ -170,14 +189,13 @@ const handleResetToDefault = async () => {
templatesConfig.value = response.data;
} catch (error) {
console.error('Failed to load prompt templates:', error);
resettingDefault.value = false;
return;
} finally {
resettingDefault.value = false;
}
resettingDefault.value = false;
}
const templateList = templates.value;
const defaultTpl = findDefaultTemplate(templateList);
const defaultTpl = resolveResetTemplate();
if (defaultTpl) {
emit('reset-default', defaultTpl);
}

View File

@@ -3972,6 +3972,12 @@ export default {
clickToInsert: '(click to insert)',
hint: "(click to insert, or type {'{{'} to show list)",
},
intentPrompts: {
title: 'Intent Prompts',
intentLabel: 'Intent',
intentDescription: 'Select the intent-specific system prompt to edit',
promptPlaceholder: 'Enter a custom system prompt...',
},
selection: {
all: 'All',
selected: 'Selected',

View File

@@ -4035,6 +4035,12 @@ export default {
clickToInsert: '(클릭하여 삽입)',
hint: "(클릭하여 삽입, 또는 {'{{'} 입력으로 목록 표시)",
},
intentPrompts: {
title: '의도 프롬프트',
intentLabel: '의도',
intentDescription: '편집할 의도별 시스템 프롬프트를 선택하세요',
promptPlaceholder: '사용자 정의 시스템 프롬프트 입력...',
},
selection: {
all: '전체',
selected: '지정',

View File

@@ -3610,6 +3610,12 @@ export default {
clickToInsert: '(нажмите для вставки)',
hint: "(нажмите для вставки или введите {'{{'} для списка)"
},
intentPrompts: {
title: 'Промпты намерений',
intentLabel: 'Намерение',
intentDescription: 'Выберите системный промпт для редактирования',
promptPlaceholder: 'Введите пользовательский системный промпт...',
},
selection: {
all: 'Все',
selected: 'Выбранные',

View File

@@ -3967,6 +3967,12 @@ export default {
clickToInsert: "(点击插入)",
hint: "(点击插入,或输入 {'{{'} 唤起列表)",
},
intentPrompts: {
title: "意图提示词",
intentLabel: "意图",
intentDescription: "选择要编辑的意图专属系统提示词",
promptPlaceholder: "输入自定义系统提示词...",
},
selection: {
all: "全部",
selected: "指定",

View File

@@ -222,6 +222,68 @@
</div>
</div>
<!-- 意图提示词仅普通模式放在上下文模板下方 -->
<div v-if="!isAgentMode" class="setting-row setting-row-vertical">
<div class="setting-info">
<label>{{ $t('agentEditor.intentPrompts.title') }}</label>
</div>
<div class="setting-control setting-control-full">
<div class="intent-prompts-editor">
<div class="intent-selector-row">
<span class="intent-label">{{ $t('agentEditor.intentPrompts.intentLabel') }}</span>
<t-select v-model="selectedIntent" size="small"
:disabled="props.readOnly || intentPromptTemplates.length === 0" class="intent-select">
<t-option v-for="template in intentPromptTemplates" :key="template.id"
:value="template.id" :label="template.name || template.id">
{{ template.name || template.id }}
</t-option>
</t-select>
<span v-if="currentIntentTemplateDesc" class="intent-desc">{{ currentIntentTemplateDesc
}}</span>
</div>
<div v-if="placeholderData.system_prompt.length > 0" class="placeholder-tags">
<span class="placeholder-label">{{ $t('agentEditor.placeholders.available') }}</span>
<t-tooltip v-for="placeholder in placeholderData.system_prompt" :key="placeholder.name"
:content="placeholder.description + $t('agentEditor.placeholders.clickToInsert')"
placement="top">
<span class="placeholder-tag" @click="handlePlaceholderClick('intent', placeholder.name)"
v-text="'{{' + placeholder.name + '}}'" />
</t-tooltip>
<span class="placeholder-hint">{{ $t('agentEditor.placeholders.hint') }}</span>
</div>
<div class="textarea-with-template">
<t-textarea ref="intentPromptTextareaRef" v-model="intentEditorValue"
class="system-prompt-textarea" :autosize="{ minRows: 10, maxRows: 25 }"
:disabled="props.readOnly || !selectedIntent"
:placeholder="currentIntentTemplate?.content || $t('agentEditor.intentPrompts.promptPlaceholder')"
@input="handleIntentPromptInput" />
<PromptTemplateSelector type="intentPrompt" position="corner" :intent-id="selectedIntent"
:show-template-picker="false" @reset-default="resetCurrentIntentPrompt" />
</div>
<Teleport to="body">
<div v-if="intentPromptPopup.show && filteredIntentPlaceholders.length > 0"
class="placeholder-popup-wrapper" :style="intentPromptPopup.style">
<div class="placeholder-popup">
<div v-for="(placeholder, index) in filteredIntentPlaceholders" :key="placeholder.name"
class="placeholder-item"
:class="{ active: intentPromptPopup.selectedIndex === index }"
@mousedown.prevent="insertGenericPlaceholder('intent', placeholder.name, true)"
@mouseenter="intentPromptPopup.selectedIndex = index">
<div class="placeholder-name">
<code v-html="`{{${placeholder.name}}}`" />
</div>
<div class="placeholder-desc">{{ placeholder.description }}</div>
</div>
</div>
</div>
</Teleport>
</div>
</div>
</div>
</div>
</div>
@@ -1289,6 +1351,7 @@ const kbOptions = ref<{ label: string; value: string; type?: 'document' | 'faq';
const agentTypePresets = ref<AgentTypePreset[]>([]);
// Agent 系统提示词模板缓存(用于切换智能体类型时根据 system_prompt_id 解析出实际文本填入)
const agentSystemPromptTemplates = ref<PromptTemplate[]>([]);
const intentPromptTemplates = ref<PromptTemplate[]>([]);
const mcpOptions = ref<{ label: string; value: string }[]>([]);
const webSearchProviderList = ref<WebSearchProviderEntity[]>([]);
const skillOptions = ref<{ name: string; description: string }[]>([]);
@@ -1594,6 +1657,12 @@ const contextPlaceholderPrefix = ref('');
const contextPopupStyle = ref({ top: '0px', left: '0px' });
let contextPlaceholderPopupTimer: any = null;
// 意图提示词编辑相关
const selectedIntent = ref('');
const intentEditorValue = ref('');
const intentPromptsSyncing = ref(false);
const intentPromptTextareaRef = ref<any>(null);
// 通用占位符弹出相关(用于改写提示词和兜底提示词)
interface PlaceholderPopupState {
show: boolean;
@@ -1605,6 +1674,10 @@ interface PlaceholderPopupState {
placeholders: PlaceholderDefinition[];
}
const intentPromptPopup = ref<PlaceholderPopupState>({
show: false, selectedIndex: 0, prefix: '', style: { top: '0px', left: '0px' }, timer: null, fieldKey: 'intent_prompt', placeholders: []
});
const rewriteSystemPopup = ref<PlaceholderPopupState>({
show: false, selectedIndex: 0, prefix: '', style: { top: '0px', left: '0px' }, timer: null, fieldKey: 'rewrite_prompt_system', placeholders: []
});
@@ -1740,6 +1813,94 @@ const agentMode = computed({
const isAgentMode = computed(() => agentMode.value === 'smart-reasoning');
const currentIntentTemplate = computed(() =>
intentPromptTemplates.value.find((template) => template.id === selectedIntent.value),
);
const currentIntentTemplateDesc = computed(() =>
currentIntentTemplate.value?.description || t('agentEditor.intentPrompts.intentDescription'),
);
const filteredIntentPlaceholders = computed(() => {
if (!intentPromptPopup.value.prefix) {
return placeholderData.value.system_prompt;
}
const prefix = intentPromptPopup.value.prefix.toLowerCase();
return placeholderData.value.system_prompt.filter(p => p.name.toLowerCase().startsWith(prefix));
});
const syncIntentEditorFromSelection = () => {
const key = selectedIntent.value;
if (!key) {
intentEditorValue.value = '';
return;
}
const overrides = formData.value.config.intent_prompts || {};
intentEditorValue.value = overrides[key] ?? currentIntentTemplate.value?.content ?? '';
};
watch(selectedIntent, () => {
intentPromptPopup.value.show = false;
intentPromptPopup.value.prefix = '';
syncIntentEditorFromSelection();
});
watch(
() => intentPromptTemplates.value,
(templates) => {
if (!selectedIntent.value && templates.length > 0) {
selectedIntent.value = templates[0].id;
} else if (selectedIntent.value) {
syncIntentEditorFromSelection();
}
},
{ immediate: true },
);
watch(intentEditorValue, (value) => {
const key = selectedIntent.value;
if (!key || intentPromptsSyncing.value) return;
const defaultContent = currentIntentTemplate.value?.content || '';
const next = value.trim();
if (!next || next === defaultContent.trim()) {
if (formData.value.config.intent_prompts) {
const { [key]: _removed, ...rest } = formData.value.config.intent_prompts;
if (Object.keys(rest).length === 0) {
delete formData.value.config.intent_prompts;
} else {
formData.value.config.intent_prompts = rest;
}
}
return;
}
formData.value.config.intent_prompts = {
...(formData.value.config.intent_prompts || {}),
[key]: value,
};
});
watch(
() => formData.value.config.intent_prompts,
() => {
intentPromptsSyncing.value = true;
syncIntentEditorFromSelection();
intentPromptsSyncing.value = false;
},
{ deep: true },
);
const resetCurrentIntentPrompt = () => {
const key = selectedIntent.value;
if (!key || !formData.value.config.intent_prompts) return;
const { [key]: _removed, ...rest } = formData.value.config.intent_prompts;
if (Object.keys(rest).length === 0) {
delete formData.value.config.intent_prompts;
} else {
formData.value.config.intent_prompts = rest;
}
syncIntentEditorFromSelection();
};
// ============================================================================
// 智能体类型预设(仅 smart-reasoning 模式下可见)
// 选择类型后自动填充 system_prompt_id / allowed_tools 等;
@@ -2496,6 +2657,9 @@ const loadDependencies = async () => {
if (fixedFallback?.content) defaultFallbackResponse.value = fixedFallback.content;
const modelFallback = fallbackList.find(t => t.mode === 'model' && t.default) || fallbackList.find(t => t.mode === 'model');
if (modelFallback?.content) defaultFallbackPrompt.value = modelFallback.content;
if (Array.isArray(cfg?.intent_prompts)) {
intentPromptTemplates.value = cfg.intent_prompts;
}
} catch (e) {
console.warn('Failed to load prompt templates', e);
}
@@ -2523,7 +2687,7 @@ const loadDependencies = async () => {
// 加载占位符定义(从统一 API
try {
const placeholdersRes = await getPlaceholders();
if (placeholdersRes.data) {
if (placeholdersRes?.data) {
placeholderData.value = placeholdersRes.data;
}
} catch (e) {
@@ -2557,6 +2721,7 @@ const handleAddModel = (subSection: string) => {
const handleClose = () => {
showPlaceholderPopup.value = false;
showContextPlaceholderPopup.value = false;
intentPromptPopup.value.show = false;
rewriteSystemPopup.value.show = false;
rewriteUserPopup.value.show = false;
fallbackPromptPopup.value.show = false;
@@ -2928,12 +3093,34 @@ const insertContextPlaceholder = (placeholderName: string, fromPopup: boolean =
});
};
type GenericPlaceholderType = 'rewriteSystem' | 'rewriteUser' | 'fallback' | 'intent';
const genericPlaceholderFieldKeyMap: Record<Exclude<GenericPlaceholderType, 'intent'>, keyof typeof formData.value.config> = {
rewriteSystem: 'rewrite_prompt_system',
rewriteUser: 'rewrite_prompt_user',
fallback: 'fallback_prompt',
};
const getGenericPlaceholderFieldValue = (type: GenericPlaceholderType): string => {
if (type === 'intent') return intentEditorValue.value || '';
return String(formData.value.config[genericPlaceholderFieldKeyMap[type]] || '');
};
const setGenericPlaceholderFieldValue = (type: GenericPlaceholderType, value: string) => {
if (type === 'intent') {
intentEditorValue.value = value;
return;
}
(formData.value.config as any)[genericPlaceholderFieldKeyMap[type]] = value;
};
// 通用获取 textarea 元素
const getGenericTextareaElement = (type: 'rewriteSystem' | 'rewriteUser' | 'fallback'): HTMLTextAreaElement | null => {
const getGenericTextareaElement = (type: GenericPlaceholderType): HTMLTextAreaElement | null => {
const refMap = {
rewriteSystem: rewriteSystemTextareaRef,
rewriteUser: rewriteUserTextareaRef,
fallback: fallbackPromptTextareaRef,
intent: intentPromptTextareaRef,
};
const ref = refMap[type];
if (ref.value) {
@@ -2982,16 +3169,15 @@ const calculateGenericCursorPosition = (textarea: HTMLTextAreaElement, fieldValu
// 通用检查并显示占位符弹出
const checkAndShowGenericPlaceholderPopup = (
type: 'rewriteSystem' | 'rewriteUser' | 'fallback',
type: GenericPlaceholderType,
popup: typeof rewriteSystemPopup,
fieldKey: keyof typeof formData.value.config,
filteredPlaceholders: PlaceholderDefinition[]
) => {
const textarea = getGenericTextareaElement(type);
if (!textarea) return;
const cursorPos = textarea.selectionStart;
const fieldValue = String(formData.value.config[fieldKey] || '');
const fieldValue = getGenericPlaceholderFieldValue(type);
const textBeforeCursor = fieldValue.substring(0, cursorPos);
let lastOpenPos = -1;
@@ -3035,7 +3221,7 @@ const handleRewriteSystemInput = () => {
clearTimeout(rewriteSystemPopup.value.timer);
}
rewriteSystemPopup.value.timer = setTimeout(() => {
checkAndShowGenericPlaceholderPopup('rewriteSystem', rewriteSystemPopup, 'rewrite_prompt_system', filteredRewriteSystemPlaceholders.value);
checkAndShowGenericPlaceholderPopup('rewriteSystem', rewriteSystemPopup, filteredRewriteSystemPlaceholders.value);
}, 50);
};
@@ -3045,7 +3231,7 @@ const handleRewriteUserInput = () => {
clearTimeout(rewriteUserPopup.value.timer);
}
rewriteUserPopup.value.timer = setTimeout(() => {
checkAndShowGenericPlaceholderPopup('rewriteUser', rewriteUserPopup, 'rewrite_prompt_user', filteredRewriteUserPlaceholders.value);
checkAndShowGenericPlaceholderPopup('rewriteUser', rewriteUserPopup, filteredRewriteUserPlaceholders.value);
}, 50);
};
@@ -3055,12 +3241,22 @@ const handleFallbackPromptInput = () => {
clearTimeout(fallbackPromptPopup.value.timer);
}
fallbackPromptPopup.value.timer = setTimeout(() => {
checkAndShowGenericPlaceholderPopup('fallback', fallbackPromptPopup, 'fallback_prompt', filteredFallbackPlaceholders.value);
checkAndShowGenericPlaceholderPopup('fallback', fallbackPromptPopup, filteredFallbackPlaceholders.value);
}, 50);
};
// 处理意图提示词输入
const handleIntentPromptInput = () => {
if (intentPromptPopup.value.timer) {
clearTimeout(intentPromptPopup.value.timer);
}
intentPromptPopup.value.timer = setTimeout(() => {
checkAndShowGenericPlaceholderPopup('intent', intentPromptPopup, filteredIntentPlaceholders.value);
}, 50);
};
// 通用插入占位符
const insertGenericPlaceholder = (type: 'rewriteSystem' | 'rewriteUser' | 'fallback', placeholderName: string, fromPopup: boolean = false) => {
const insertGenericPlaceholder = (type: GenericPlaceholderType, placeholderName: string, fromPopup: boolean = false) => {
const textarea = getGenericTextareaElement(type);
if (!textarea) return;
@@ -3068,15 +3264,10 @@ const insertGenericPlaceholder = (type: 'rewriteSystem' | 'rewriteUser' | 'fallb
rewriteSystem: rewriteSystemPopup,
rewriteUser: rewriteUserPopup,
fallback: fallbackPromptPopup,
};
const fieldKeyMap: Record<string, keyof typeof formData.value.config> = {
rewriteSystem: 'rewrite_prompt_system',
rewriteUser: 'rewrite_prompt_user',
fallback: 'fallback_prompt',
intent: intentPromptPopup,
};
const popup = popupMap[type];
const fieldKey = fieldKeyMap[type];
popup.value.show = false;
popup.value.prefix = '';
@@ -3084,7 +3275,7 @@ const insertGenericPlaceholder = (type: 'rewriteSystem' | 'rewriteUser' | 'fallb
nextTick(() => {
const cursorPos = textarea.selectionStart;
const currentValue = String(formData.value.config[fieldKey] || '');
const currentValue = getGenericPlaceholderFieldValue(type);
const textBeforeCursor = currentValue.substring(0, cursorPos);
const textAfterCursor = currentValue.substring(cursorPos);
@@ -3101,7 +3292,7 @@ const insertGenericPlaceholder = (type: 'rewriteSystem' | 'rewriteUser' | 'fallb
if (lastOpenPos !== -1) {
const textBeforeOpen = currentValue.substring(0, lastOpenPos);
const newValue = textBeforeOpen + `{{${placeholderName}}}` + textAfterCursor;
(formData.value.config as any)[fieldKey] = newValue;
setGenericPlaceholderFieldValue(type, newValue);
nextTick(() => {
const newCursorPos = textBeforeOpen.length + placeholderName.length + 4;
@@ -3114,7 +3305,7 @@ const insertGenericPlaceholder = (type: 'rewriteSystem' | 'rewriteUser' | 'fallb
// 直接在光标位置插入完整占位符
const newValue = textBeforeCursor + `{{${placeholderName}}}` + textAfterCursor;
(formData.value.config as any)[fieldKey] = newValue;
setGenericPlaceholderFieldValue(type, newValue);
nextTick(() => {
const newCursorPos = cursorPos + placeholderName.length + 4;
@@ -3210,7 +3401,7 @@ const setupTextareaEventListeners = () => {
// 通用设置 textarea 事件监听
const setupGenericTextareaEventListeners = (
type: 'rewriteSystem' | 'rewriteUser' | 'fallback',
type: GenericPlaceholderType,
popup: typeof rewriteSystemPopup,
filteredPlaceholders: () => PlaceholderDefinition[]
) => {
@@ -3256,7 +3447,7 @@ const setupGenericTextareaEventListeners = (
};
// 处理点击占位符标签
const handlePlaceholderClick = (type: 'system' | 'context' | 'rewriteSystem' | 'rewriteUser' | 'fallback', placeholderName: string) => {
const handlePlaceholderClick = (type: 'system' | 'context' | 'rewriteSystem' | 'rewriteUser' | 'fallback' | 'intent', placeholderName: string) => {
if (type === 'system') {
insertPlaceholder(placeholderName);
} else if (type === 'context') {
@@ -3272,6 +3463,7 @@ watch(() => props.visible, (val) => {
nextTick(() => {
setupTextareaEventListeners();
setupContextTemplateEventListeners();
setupGenericTextareaEventListeners('intent', intentPromptPopup, () => filteredIntentPlaceholders.value);
setupGenericTextareaEventListeners('rewriteSystem', rewriteSystemPopup, () => filteredRewriteSystemPlaceholders.value);
setupGenericTextareaEventListeners('rewriteUser', rewriteUserPopup, () => filteredRewriteUserPlaceholders.value);
setupGenericTextareaEventListeners('fallback', fallbackPromptPopup, () => filteredFallbackPlaceholders.value);
@@ -3408,6 +3600,10 @@ const handleSave = async () => {
formData.value.config.suggested_prompts = formData.value.config.suggested_prompts.filter((p: string) => p.trim() !== '');
}
if (!formData.value.config.intent_prompts || Object.keys(formData.value.config.intent_prompts).length === 0) {
delete formData.value.config.intent_prompts;
}
saving.value = true;
try {
if (props.mode === 'create') {
@@ -4260,6 +4456,35 @@ const handleSave = async () => {
width: 100%;
}
.intent-prompts-editor {
width: 100%;
display: flex;
flex-direction: column;
gap: 8px;
}
.intent-selector-row {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.intent-label {
font-size: 12px;
color: var(--td-text-color-secondary);
}
.intent-select {
width: 150px;
}
.intent-desc {
font-size: 12px;
color: var(--td-text-color-placeholder);
line-height: 1.4;
}
// 系统提示词输入框样式
.system-prompt-textarea {
width: 100%;

View File

@@ -187,8 +187,16 @@ func (p *PluginQueryUnderstand) OnEvent(ctx context.Context,
// --- Apply intent-specific system prompt override ---
if !chatManage.NeedsRetrieval() {
if prompt, ok := p.config.Conversation.IntentSystemPrompts[string(chatManage.Intent)]; ok {
chatManage.SystemPromptOverride = prompt
intentKey := string(chatManage.Intent)
if chatManage.IntentPromptOverrides != nil {
chatManage.SystemPromptOverride = strings.TrimSpace(chatManage.IntentPromptOverrides[intentKey])
}
if chatManage.SystemPromptOverride == "" {
if prompt, ok := p.config.Conversation.IntentSystemPrompts[intentKey]; ok {
chatManage.SystemPromptOverride = prompt
}
}
if chatManage.SystemPromptOverride != "" {
pipelineInfo(ctx, "QueryUnderstand", "prompt_override", map[string]interface{}{
"session_id": chatManage.SessionID,
"intent": chatManage.Intent,

View File

@@ -210,6 +210,11 @@ func (s *sessionService) applyAgentOverridesToChatManage(
if cm.DataAnalysisEnabled {
logger.Infof(ctx, "Data analysis pipeline stage enabled by custom agent")
}
if len(customAgent.Config.IntentPrompts) > 0 {
cm.IntentPromptOverrides = customAgent.Config.IntentPrompts
logger.Infof(ctx, "Using custom agent's intent_prompts (%d overrides)", len(cm.IntentPromptOverrides))
}
}
// restrictMentionsToAgentScope filters user-provided @mention targets (KB IDs

View File

@@ -951,6 +951,7 @@ func (h *TenantHandler) GetPromptTemplates(c *gin.Context) {
GenerateSummary: templates.GenerateSummary,
KeywordsExtraction: templates.KeywordsExtraction,
AgentSystemPrompt: config.LocalizeTemplates(templates.AgentSystemPrompt, lang),
IntentPrompts: config.LocalizeTemplates(templates.IntentPrompts, lang),
}
c.JSON(http.StatusOK, gin.H{

View File

@@ -59,6 +59,10 @@ type PipelineRequest struct {
// File attachments support
Attachments MessageAttachments `json:"-"`
// IntentPromptOverrides holds agent-level intent prompt overrides for the
// query-understanding stage. Empty values fall back to tenant/global defaults.
IntentPromptOverrides map[string]string `json:"-"`
// Misc request-scoped config
TenantID uint64 `json:"-"`
WebSearchEnabled bool `json:"-"`
@@ -221,6 +225,7 @@ func (c *ChatManage) Clone() *ChatManage {
WebFetchEnabled: c.WebFetchEnabled,
WebFetchTopN: c.WebFetchTopN,
Language: c.Language,
IntentPromptOverrides: maps.Clone(c.IntentPromptOverrides),
},
PipelineState: PipelineState{
RewriteQuery: c.RewriteQuery,

View File

@@ -256,6 +256,10 @@ type CustomAgentConfig struct {
FallbackResponse string `yaml:"fallback_response" json:"fallback_response"`
// Fallback prompt (when FallbackStrategy is "model")
FallbackPrompt string `yaml:"fallback_prompt" json:"fallback_prompt"`
// IntentPrompts holds per-intent system prompt overrides for non-retrieval
// intents (greeting, chitchat, etc.). Empty values fall back to templates
// under config/prompt_templates/intent_prompts.yaml.
IntentPrompts map[string]string `yaml:"intent_prompts" json:"intent_prompts,omitempty"`
// ===== Suggested Prompts =====
// 推荐问题列表,用于在前端对话面板展示快捷提问