mirror of
https://github.com/Tencent/WeKnora.git
synced 2026-10-30 19:33:05 +08:00
feat: Add system prompt template support and placeholder functionality
- Introduced a new PlaceholderDefinition interface to manage available placeholders for system prompts. - Enhanced AgentSettings.vue to allow users to configure system prompts with dynamic placeholders, improving user experience. - Updated the AgentConfig structure to include a system prompt field, enabling custom prompt templates. - Modified backend components to support the new system prompt template, ensuring compatibility with existing configurations. - Implemented logic for rendering placeholders in the system prompt, allowing for dynamic content generation based on user input.
This commit is contained in:
@@ -13,6 +13,12 @@ export interface ToolDefinition {
|
||||
description: string
|
||||
}
|
||||
|
||||
export interface PlaceholderDefinition {
|
||||
name: string
|
||||
label: string
|
||||
description: string
|
||||
}
|
||||
|
||||
export interface AgentConfig {
|
||||
enabled: boolean
|
||||
max_iterations: number
|
||||
@@ -22,7 +28,9 @@ export interface AgentConfig {
|
||||
thinking_model_id: string
|
||||
rerank_model_id: string
|
||||
knowledge_bases?: string[]
|
||||
system_prompt?: string // System prompt template with placeholders (optional)
|
||||
available_tools?: ToolDefinition[] // GET 响应中包含,POST/PUT 不需要
|
||||
available_placeholders?: PlaceholderDefinition[] // GET 响应中包含,POST/PUT 不需要
|
||||
}
|
||||
|
||||
export function getSystemInfo(): Promise<{ data: SystemInfo }> {
|
||||
|
||||
@@ -190,6 +190,68 @@
|
||||
</t-select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 系统 Prompt -->
|
||||
<div class="setting-row vertical">
|
||||
<div class="setting-info">
|
||||
<label>系统 Prompt</label>
|
||||
<p class="desc">配置 Agent 的系统提示词,支持占位符模板。占位符会在运行时自动替换为实际内容。</p>
|
||||
<div class="placeholder-hint">
|
||||
<p class="hint-title">可用占位符:</p>
|
||||
<ul class="placeholder-list">
|
||||
<li v-for="placeholder in availablePlaceholders" :key="placeholder.name">
|
||||
<code v-html="`{{${placeholder.name}}}`"></code> - {{ placeholder.label }}({{ placeholder.description }})
|
||||
</li>
|
||||
</ul>
|
||||
<p class="hint-tip">提示:输入 <code>{{</code> 时会自动显示可用占位符</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-control full-width" style="position: relative;">
|
||||
<div class="prompt-header">
|
||||
<t-button
|
||||
theme="default"
|
||||
variant="outline"
|
||||
size="small"
|
||||
@click="handleResetToDefault"
|
||||
:loading="isResettingPrompt"
|
||||
>
|
||||
恢复默认
|
||||
</t-button>
|
||||
</div>
|
||||
<div class="prompt-textarea-wrapper">
|
||||
<t-textarea
|
||||
ref="promptTextareaRef"
|
||||
v-model="localSystemPrompt"
|
||||
:autosize="{ minRows: 15, maxRows: 30 }"
|
||||
placeholder="请输入系统 Prompt,或留空使用默认 Prompt..."
|
||||
@blur="handleSystemPromptChange"
|
||||
@input="handlePromptInput"
|
||||
@keydown="handlePromptKeydown"
|
||||
style="width: 100%; font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-size: 13px;"
|
||||
/>
|
||||
</div>
|
||||
<!-- 占位符提示下拉框 -->
|
||||
<teleport to="body">
|
||||
<div v-if="showPlaceholderPopup && filteredPlaceholders.length > 0" class="placeholder-popup-wrapper" :style="popupStyle">
|
||||
<div class="placeholder-popup">
|
||||
<div
|
||||
v-for="(placeholder, index) in filteredPlaceholders"
|
||||
:key="placeholder.name"
|
||||
class="placeholder-item"
|
||||
:class="{ active: selectedPlaceholderIndex === index }"
|
||||
@mousedown.prevent="insertPlaceholder(placeholder.name)"
|
||||
@mouseenter="selectedPlaceholderIndex = index"
|
||||
>
|
||||
<div class="placeholder-name">
|
||||
<code v-html="`{{${placeholder.name}}}`"></code>
|
||||
</div>
|
||||
<div class="placeholder-desc">{{ placeholder.description }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</teleport>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -198,9 +260,9 @@
|
||||
import { ref, onMounted, watch, computed, nextTick } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useSettingsStore } from '@/stores/settings'
|
||||
import { MessagePlugin } from 'tdesign-vue-next'
|
||||
import { MessagePlugin, DialogPlugin } from 'tdesign-vue-next'
|
||||
import { listModels, type ModelConfig } from '@/api/model'
|
||||
import { getAgentConfig, updateAgentConfig, type AgentConfig, type ToolDefinition } from '@/api/system'
|
||||
import { getAgentConfig, updateAgentConfig, type AgentConfig, type ToolDefinition, type PlaceholderDefinition } from '@/api/system'
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const router = useRouter()
|
||||
@@ -211,6 +273,7 @@ const localTemperature = ref(0.7)
|
||||
const localThinkingModelId = ref('')
|
||||
const localRerankModelId = ref('')
|
||||
const localAllowedTools = ref<string[]>([])
|
||||
const localSystemPrompt = ref('')
|
||||
|
||||
// 计算 Agent 是否就绪
|
||||
const isAgentReady = computed(() => {
|
||||
@@ -248,12 +311,122 @@ const loadingModels = ref(false)
|
||||
|
||||
// 可用工具列表
|
||||
const availableTools = ref<ToolDefinition[]>([])
|
||||
// 可用占位符列表
|
||||
const availablePlaceholders = ref<PlaceholderDefinition[]>([])
|
||||
|
||||
// 配置加载状态
|
||||
const loadingConfig = ref(false)
|
||||
const configLoaded = ref(false) // 防止重复加载
|
||||
const isInitializing = ref(true) // 标记是否正在初始化,防止初始化时触发保存
|
||||
|
||||
// 保存的 Prompt 值,用于比较是否变化
|
||||
let savedSystemPrompt = ''
|
||||
|
||||
// 恢复默认 Prompt 的加载状态
|
||||
const isResettingPrompt = ref(false)
|
||||
|
||||
// 占位符提示相关状态
|
||||
const promptTextareaRef = ref<any>(null)
|
||||
const showPlaceholderPopup = ref(false)
|
||||
const selectedPlaceholderIndex = ref(0)
|
||||
let placeholderPopupTimer: any = null
|
||||
const placeholderPrefix = ref('') // 当前输入的前缀,用于过滤
|
||||
const popupStyle = ref({ top: '0px', left: '0px' }) // 提示框位置
|
||||
|
||||
// 设置 textarea 原生事件监听器
|
||||
const setupTextareaEventListeners = () => {
|
||||
nextTick(() => {
|
||||
const textarea = getTextareaElement()
|
||||
if (textarea) {
|
||||
// 添加原生 keydown 事件监听(使用 capture 阶段,确保优先处理)
|
||||
textarea.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||
// 如果正在显示占位符提示,优先处理占位符相关的按键
|
||||
if (showPlaceholderPopup.value && filteredPlaceholders.value.length > 0) {
|
||||
if (e.key === 'ArrowDown') {
|
||||
// 下箭头选择下一个
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
e.stopImmediatePropagation()
|
||||
if (selectedPlaceholderIndex.value < filteredPlaceholders.value.length - 1) {
|
||||
selectedPlaceholderIndex.value++
|
||||
} else {
|
||||
selectedPlaceholderIndex.value = 0 // 循环到第一个
|
||||
}
|
||||
return
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
// 上箭头选择上一个
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
e.stopImmediatePropagation()
|
||||
if (selectedPlaceholderIndex.value > 0) {
|
||||
selectedPlaceholderIndex.value--
|
||||
} else {
|
||||
selectedPlaceholderIndex.value = filteredPlaceholders.value.length - 1 // 循环到最后一个
|
||||
}
|
||||
return
|
||||
} else if (e.key === 'Enter') {
|
||||
// Enter 键插入选中的占位符
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
e.stopImmediatePropagation()
|
||||
const selected = filteredPlaceholders.value[selectedPlaceholderIndex.value]
|
||||
if (selected) {
|
||||
insertPlaceholder(selected.name)
|
||||
}
|
||||
return
|
||||
} else if (e.key === 'Escape') {
|
||||
// ESC 键关闭提示
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
e.stopImmediatePropagation()
|
||||
showPlaceholderPopup.value = false
|
||||
placeholderPrefix.value = ''
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 如果按下的是 { 键
|
||||
if (e.key === '{') {
|
||||
// 清除之前的定时器
|
||||
if (placeholderPopupTimer) {
|
||||
clearTimeout(placeholderPopupTimer)
|
||||
}
|
||||
|
||||
// 延迟检查,等待输入完成(连续输入两个 {)
|
||||
placeholderPopupTimer = setTimeout(() => {
|
||||
checkAndShowPlaceholderPopup()
|
||||
}, 150)
|
||||
}
|
||||
}, true) // 使用 capture 阶段
|
||||
|
||||
// 添加原生 input 事件监听(作为备用)
|
||||
textarea.addEventListener('input', () => {
|
||||
if (placeholderPopupTimer) {
|
||||
clearTimeout(placeholderPopupTimer)
|
||||
}
|
||||
placeholderPopupTimer = setTimeout(() => {
|
||||
checkAndShowPlaceholderPopup()
|
||||
}, 50)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取 textarea 元素的辅助函数
|
||||
const getTextareaElement = (): HTMLTextAreaElement | null => {
|
||||
if (promptTextareaRef.value) {
|
||||
if (promptTextareaRef.value.$el) {
|
||||
return promptTextareaRef.value.$el.querySelector('textarea')
|
||||
} else if (promptTextareaRef.value instanceof HTMLTextAreaElement) {
|
||||
return promptTextareaRef.value
|
||||
}
|
||||
}
|
||||
|
||||
// 如果还是找不到,尝试通过 DOM 查找
|
||||
const wrapper = document.querySelector('.setting-control.full-width')
|
||||
return wrapper?.querySelector('textarea') || null
|
||||
}
|
||||
|
||||
// 初始化加载
|
||||
onMounted(async () => {
|
||||
// 防止重复加载
|
||||
@@ -275,7 +448,13 @@ onMounted(async () => {
|
||||
localThinkingModelId.value = config.thinking_model_id
|
||||
localRerankModelId.value = config.rerank_model_id
|
||||
localAllowedTools.value = config.allowed_tools || []
|
||||
localSystemPrompt.value = config.system_prompt || ''
|
||||
savedSystemPrompt = config.system_prompt || '' // 记录已保存的值
|
||||
availableTools.value = config.available_tools || []
|
||||
availablePlaceholders.value = config.available_placeholders || []
|
||||
|
||||
// 调试信息
|
||||
console.log('加载的占位符列表:', availablePlaceholders.value)
|
||||
|
||||
// 统一加载所有模型(只调用一次API)
|
||||
if (config.thinking_model_id || config.rerank_model_id) {
|
||||
@@ -298,7 +477,10 @@ onMounted(async () => {
|
||||
// 再等待一帧,确保所有事件监听器都已设置好
|
||||
requestAnimationFrame(() => {
|
||||
// 初始化完成,现在可以允许保存操作
|
||||
isInitializing.value = false
|
||||
isInitializing.value = false
|
||||
|
||||
// 设置原生事件监听器(作为备用方案)
|
||||
setupTextareaEventListeners()
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('加载Agent配置失败:', error)
|
||||
@@ -380,7 +562,8 @@ const handleMaxIterationsChangeDebounced = (value: number) => {
|
||||
allowed_tools: localAllowedTools.value,
|
||||
temperature: localTemperature.value,
|
||||
thinking_model_id: localThinkingModelId.value,
|
||||
rerank_model_id: localRerankModelId.value
|
||||
rerank_model_id: localRerankModelId.value,
|
||||
system_prompt: localSystemPrompt.value
|
||||
}
|
||||
|
||||
await updateAgentConfig(config)
|
||||
@@ -443,7 +626,8 @@ const handleThinkingModelChange = async (value: string) => {
|
||||
allowed_tools: localAllowedTools.value,
|
||||
temperature: localTemperature.value,
|
||||
thinking_model_id: value,
|
||||
rerank_model_id: localRerankModelId.value
|
||||
rerank_model_id: localRerankModelId.value,
|
||||
system_prompt: localSystemPrompt.value
|
||||
}
|
||||
|
||||
await updateAgentConfig(config)
|
||||
@@ -476,7 +660,8 @@ const handleRerankModelChange = async (value: string) => {
|
||||
allowed_tools: localAllowedTools.value,
|
||||
temperature: localTemperature.value,
|
||||
thinking_model_id: localThinkingModelId.value,
|
||||
rerank_model_id: value
|
||||
rerank_model_id: value,
|
||||
system_prompt: localSystemPrompt.value
|
||||
}
|
||||
|
||||
await updateAgentConfig(config)
|
||||
@@ -556,7 +741,8 @@ const handleTemperatureChange = async (value: number) => {
|
||||
allowed_tools: localAllowedTools.value,
|
||||
temperature: value,
|
||||
thinking_model_id: localThinkingModelId.value,
|
||||
rerank_model_id: localRerankModelId.value
|
||||
rerank_model_id: localRerankModelId.value,
|
||||
system_prompt: localSystemPrompt.value
|
||||
}
|
||||
|
||||
await updateAgentConfig(config)
|
||||
@@ -581,7 +767,8 @@ const handleAllowedToolsChange = async (value: string[]) => {
|
||||
allowed_tools: value,
|
||||
temperature: localTemperature.value,
|
||||
thinking_model_id: localThinkingModelId.value,
|
||||
rerank_model_id: localRerankModelId.value
|
||||
rerank_model_id: localRerankModelId.value,
|
||||
system_prompt: localSystemPrompt.value
|
||||
}
|
||||
|
||||
await updateAgentConfig(config)
|
||||
@@ -595,6 +782,293 @@ const handleAllowedToolsChange = async (value: string[]) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 处理系统 Prompt 键盘事件(作为备用,主要逻辑在原生事件监听器中)
|
||||
const handlePromptKeydown = (e: KeyboardEvent) => {
|
||||
// 如果正在显示占位符提示,且输入的是字母、数字或下划线,实时更新过滤
|
||||
if (showPlaceholderPopup.value && /^[a-zA-Z0-9_]$/.test(e.key)) {
|
||||
// 延迟检查,等待字符输入完成
|
||||
if (placeholderPopupTimer) {
|
||||
clearTimeout(placeholderPopupTimer)
|
||||
}
|
||||
placeholderPopupTimer = setTimeout(() => {
|
||||
checkAndShowPlaceholderPopup()
|
||||
}, 50)
|
||||
}
|
||||
}
|
||||
|
||||
// 过滤后的占位符列表(根据前缀匹配)
|
||||
const filteredPlaceholders = computed(() => {
|
||||
if (!placeholderPrefix.value) {
|
||||
return availablePlaceholders.value
|
||||
}
|
||||
|
||||
const prefix = placeholderPrefix.value.toLowerCase()
|
||||
return availablePlaceholders.value.filter(p =>
|
||||
p.name.toLowerCase().startsWith(prefix)
|
||||
)
|
||||
})
|
||||
|
||||
// 计算光标在 textarea 中的像素位置
|
||||
const calculateCursorPosition = (textarea: HTMLTextAreaElement) => {
|
||||
const cursorPos = textarea.selectionStart
|
||||
const textBeforeCursor = localSystemPrompt.value.substring(0, cursorPos)
|
||||
|
||||
// 获取 textarea 的样式和位置
|
||||
const style = window.getComputedStyle(textarea)
|
||||
const textareaRect = textarea.getBoundingClientRect()
|
||||
|
||||
// 计算行数和当前行的文本
|
||||
const lines = textBeforeCursor.split('\n')
|
||||
const currentLine = lines.length - 1
|
||||
const lineText = lines[currentLine] || ''
|
||||
|
||||
// 获取行高
|
||||
const lineHeight = parseFloat(style.lineHeight) || parseFloat(style.fontSize) * 1.2
|
||||
|
||||
// 获取 padding
|
||||
const paddingTop = parseFloat(style.paddingTop) || 0
|
||||
const paddingLeft = parseFloat(style.paddingLeft) || 0
|
||||
|
||||
// 使用 canvas 测量当前行的文本宽度(更准确)
|
||||
const canvas = document.createElement('canvas')
|
||||
const context = canvas.getContext('2d')
|
||||
let textWidth = 0
|
||||
|
||||
if (context) {
|
||||
context.font = `${style.fontSize} ${style.fontFamily}`
|
||||
textWidth = context.measureText(lineText).width
|
||||
} else {
|
||||
// 回退方案:使用等宽字体估算(Monaco/Menlo 是等宽字体)
|
||||
const charWidth = parseFloat(style.fontSize) * 0.6 // 等宽字体字符宽度约为字体大小的 0.6 倍
|
||||
textWidth = lineText.length * charWidth
|
||||
}
|
||||
|
||||
// 计算光标位置的 top(考虑滚动)
|
||||
const scrollTop = textarea.scrollTop
|
||||
const top = textareaRect.top + paddingTop + (currentLine * lineHeight) - scrollTop + lineHeight + 4
|
||||
|
||||
// 计算光标位置的 left(考虑滚动)
|
||||
const scrollLeft = textarea.scrollLeft
|
||||
const left = textareaRect.left + paddingLeft + textWidth - scrollLeft
|
||||
|
||||
return { top, left }
|
||||
}
|
||||
|
||||
// 检查并显示占位符提示
|
||||
const checkAndShowPlaceholderPopup = () => {
|
||||
const textarea = getTextareaElement()
|
||||
|
||||
if (!textarea) {
|
||||
return
|
||||
}
|
||||
|
||||
const cursorPos = textarea.selectionStart
|
||||
const textBeforeCursor = localSystemPrompt.value.substring(0, cursorPos)
|
||||
|
||||
// 检查是否输入了 {{(从光标位置向前查找最近的 {{)
|
||||
// 需要找到光标前最近的 {{,且中间没有 }}
|
||||
let lastOpenPos = -1
|
||||
for (let i = cursorPos - 1; i >= 0; i--) {
|
||||
if (i > 0 && textBeforeCursor[i - 1] === '{' && textBeforeCursor[i] === '{') {
|
||||
// 找到了 {{
|
||||
const textAfterOpen = textBeforeCursor.substring(i + 1)
|
||||
// 检查是否已经包含 }}(说明占位符已完成)
|
||||
if (!textAfterOpen.includes('}}')) {
|
||||
lastOpenPos = i - 1
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lastOpenPos === -1) {
|
||||
// 没有找到有效的 {{,隐藏提示
|
||||
showPlaceholderPopup.value = false
|
||||
placeholderPrefix.value = ''
|
||||
return
|
||||
}
|
||||
|
||||
// 获取 {{ 之后到光标位置的内容作为前缀
|
||||
const textAfterOpen = textBeforeCursor.substring(lastOpenPos + 2)
|
||||
|
||||
// 更新前缀
|
||||
placeholderPrefix.value = textAfterOpen
|
||||
|
||||
// 根据前缀过滤占位符
|
||||
const filtered = filteredPlaceholders.value
|
||||
|
||||
if (filtered.length > 0) {
|
||||
// 有匹配的占位符,显示提示
|
||||
// 计算光标位置
|
||||
nextTick(() => {
|
||||
const position = calculateCursorPosition(textarea)
|
||||
popupStyle.value = {
|
||||
top: `${position.top}px`,
|
||||
left: `${position.left}px`
|
||||
}
|
||||
showPlaceholderPopup.value = true
|
||||
// 重置选中索引为第一个(默认选择第一个)
|
||||
selectedPlaceholderIndex.value = 0
|
||||
})
|
||||
} else {
|
||||
// 没有匹配的占位符,隐藏提示
|
||||
showPlaceholderPopup.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 处理系统 Prompt 输入
|
||||
const handlePromptInput = () => {
|
||||
// 清除之前的定时器
|
||||
if (placeholderPopupTimer) {
|
||||
clearTimeout(placeholderPopupTimer)
|
||||
}
|
||||
|
||||
// 延迟检查,避免频繁触发
|
||||
placeholderPopupTimer = setTimeout(() => {
|
||||
checkAndShowPlaceholderPopup()
|
||||
}, 50)
|
||||
}
|
||||
|
||||
// 插入占位符
|
||||
const insertPlaceholder = (placeholderName: string) => {
|
||||
const textarea = getTextareaElement()
|
||||
if (!textarea) {
|
||||
return
|
||||
}
|
||||
|
||||
// 先关闭提示,避免触发 blur 事件
|
||||
showPlaceholderPopup.value = false
|
||||
placeholderPrefix.value = ''
|
||||
selectedPlaceholderIndex.value = 0
|
||||
|
||||
// 延迟执行,确保提示框已关闭
|
||||
nextTick(() => {
|
||||
const cursorPos = textarea.selectionStart
|
||||
const textBeforeCursor = localSystemPrompt.value.substring(0, cursorPos)
|
||||
const textAfterCursor = localSystemPrompt.value.substring(cursorPos)
|
||||
|
||||
// 找到最后一个 {{ 的位置
|
||||
const lastOpenPos = textBeforeCursor.lastIndexOf('{{')
|
||||
if (lastOpenPos === -1) {
|
||||
// 如果没有找到 {{,直接插入完整的占位符
|
||||
const placeholder = `{{${placeholderName}}}`
|
||||
localSystemPrompt.value = textBeforeCursor + placeholder + textAfterCursor
|
||||
// 设置光标位置
|
||||
nextTick(() => {
|
||||
const newPos = cursorPos + placeholder.length
|
||||
textarea.setSelectionRange(newPos, newPos)
|
||||
textarea.focus()
|
||||
})
|
||||
} else {
|
||||
// 替换 {{ 到光标位置的内容为完整的占位符
|
||||
const beforePlaceholder = textBeforeCursor.substring(0, lastOpenPos)
|
||||
const placeholder = `{{${placeholderName}}}`
|
||||
localSystemPrompt.value = beforePlaceholder + placeholder + textAfterCursor
|
||||
// 设置光标位置
|
||||
nextTick(() => {
|
||||
const newPos = lastOpenPos + placeholder.length
|
||||
textarea.setSelectionRange(newPos, newPos)
|
||||
textarea.focus()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 恢复默认 Prompt
|
||||
const handleResetToDefault = async () => {
|
||||
const confirmDialog = DialogPlugin.confirm({
|
||||
header: '恢复默认 Prompt',
|
||||
body: '确定要恢复为默认 Prompt 吗?当前的自定义 Prompt 将被覆盖。',
|
||||
confirmBtn: '确定',
|
||||
cancelBtn: '取消',
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
isResettingPrompt.value = true
|
||||
|
||||
// 通过设置 system_prompt 为空字符串来获取默认值
|
||||
// 后端在 system_prompt 为空时会返回默认值
|
||||
const tempConfig: AgentConfig = {
|
||||
enabled: isAgentReady.value,
|
||||
max_iterations: localMaxIterations.value,
|
||||
reflection_enabled: false,
|
||||
allowed_tools: localAllowedTools.value,
|
||||
temperature: localTemperature.value,
|
||||
thinking_model_id: localThinkingModelId.value,
|
||||
rerank_model_id: localRerankModelId.value,
|
||||
system_prompt: '' // 空字符串表示使用默认
|
||||
}
|
||||
|
||||
await updateAgentConfig(tempConfig)
|
||||
|
||||
// 重新加载配置以获取默认 Prompt 的完整内容
|
||||
const res = await getAgentConfig()
|
||||
const defaultPrompt = res.data.system_prompt || ''
|
||||
|
||||
// 设置为默认 Prompt 的内容
|
||||
localSystemPrompt.value = defaultPrompt
|
||||
savedSystemPrompt = defaultPrompt
|
||||
|
||||
MessagePlugin.success('已恢复为默认 Prompt')
|
||||
confirmDialog.hide()
|
||||
} catch (error) {
|
||||
console.error('恢复默认 Prompt 失败:', error)
|
||||
MessagePlugin.error(getErrorMessage(error))
|
||||
} finally {
|
||||
isResettingPrompt.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 处理系统 Prompt 变化
|
||||
const handleSystemPromptChange = async (e?: FocusEvent) => {
|
||||
// 如果点击的是占位符提示框,不触发保存
|
||||
if (e?.relatedTarget) {
|
||||
const target = e.relatedTarget as HTMLElement
|
||||
if (target.closest('.placeholder-popup-wrapper')) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 延迟检查,避免点击占位符时立即触发
|
||||
await nextTick()
|
||||
|
||||
// 如果占位符提示框还在显示,说明用户点击了占位符,不触发保存
|
||||
if (showPlaceholderPopup.value) {
|
||||
return
|
||||
}
|
||||
|
||||
// 隐藏占位符提示
|
||||
placeholderPrefix.value = ''
|
||||
|
||||
// 如果正在初始化,不触发保存
|
||||
if (isInitializing.value) return
|
||||
|
||||
// 检查内容是否变化
|
||||
if (localSystemPrompt.value === savedSystemPrompt) {
|
||||
return // 内容没变,不调用接口
|
||||
}
|
||||
|
||||
try {
|
||||
const config: AgentConfig = {
|
||||
enabled: isAgentReady.value,
|
||||
max_iterations: localMaxIterations.value,
|
||||
reflection_enabled: false,
|
||||
allowed_tools: localAllowedTools.value,
|
||||
temperature: localTemperature.value,
|
||||
thinking_model_id: localThinkingModelId.value,
|
||||
rerank_model_id: localRerankModelId.value,
|
||||
system_prompt: localSystemPrompt.value
|
||||
}
|
||||
|
||||
await updateAgentConfig(config)
|
||||
savedSystemPrompt = localSystemPrompt.value // 更新已保存的值
|
||||
MessagePlugin.success('系统 Prompt 已保存')
|
||||
} catch (error) {
|
||||
console.error('保存系统 Prompt 失败:', error)
|
||||
MessagePlugin.error(getErrorMessage(error))
|
||||
}
|
||||
}
|
||||
|
||||
// 监听 Agent 就绪状态变化,同步到 store
|
||||
watch(isAgentReady, (newValue, oldValue) => {
|
||||
if (!isInitializing.value) {
|
||||
@@ -760,7 +1234,6 @@ watch(isAgentReady, (newValue, oldValue) => {
|
||||
.setting-info {
|
||||
flex: 1;
|
||||
max-width: 65%;
|
||||
padding-right: 24px;
|
||||
|
||||
label {
|
||||
font-size: 15px;
|
||||
@@ -831,5 +1304,111 @@ watch(isAgentReady, (newValue, oldValue) => {
|
||||
}
|
||||
}
|
||||
|
||||
.prompt-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.prompt-textarea-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.setting-control.full-width {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.placeholder-hint {
|
||||
margin-top: 12px;
|
||||
padding: 12px;
|
||||
background: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
|
||||
.hint-title {
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.placeholder-list {
|
||||
margin: 8px 0;
|
||||
padding-left: 20px;
|
||||
color: #666;
|
||||
|
||||
li {
|
||||
margin: 4px 0;
|
||||
|
||||
code {
|
||||
background: #fff;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 11px;
|
||||
color: #e83e8c;
|
||||
border: 1px solid #e1e8ed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hint-tip {
|
||||
margin: 8px 0 0 0;
|
||||
color: #999;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
.placeholder-popup-wrapper {
|
||||
position: fixed;
|
||||
z-index: 10001;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.placeholder-popup {
|
||||
background: #fff;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
max-width: 400px;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.placeholder-item {
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover,
|
||||
&.active {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
.placeholder-name {
|
||||
font-weight: 500;
|
||||
margin-bottom: 4px;
|
||||
|
||||
code {
|
||||
background: #f5f7fa;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 12px;
|
||||
color: #e83e8c;
|
||||
}
|
||||
}
|
||||
|
||||
.placeholder-desc {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
+13
-10
@@ -31,6 +31,7 @@ type AgentEngine struct {
|
||||
knowledgeBasesInfo []*KnowledgeBaseInfo // Detailed knowledge base information for prompt
|
||||
contextManager interfaces.ContextManager // Context manager for writing agent conversation to LLM context
|
||||
sessionID string // Session ID for context management
|
||||
systemPromptTemplate string // System prompt template (optional, uses default if empty)
|
||||
}
|
||||
|
||||
// listToolNames returns tool.function names for logging
|
||||
@@ -52,19 +53,21 @@ func NewAgentEngine(
|
||||
knowledgeBasesInfo []*KnowledgeBaseInfo,
|
||||
contextManager interfaces.ContextManager,
|
||||
sessionID string,
|
||||
systemPromptTemplate string,
|
||||
) *AgentEngine {
|
||||
if eventBus == nil {
|
||||
eventBus = event.NewEventBus()
|
||||
}
|
||||
return &AgentEngine{
|
||||
config: config,
|
||||
toolRegistry: toolRegistry,
|
||||
chatModel: chatModel,
|
||||
knowledgeService: knowledgeService,
|
||||
eventBus: eventBus,
|
||||
knowledgeBasesInfo: knowledgeBasesInfo,
|
||||
contextManager: contextManager,
|
||||
sessionID: sessionID,
|
||||
config: config,
|
||||
toolRegistry: toolRegistry,
|
||||
chatModel: chatModel,
|
||||
knowledgeService: knowledgeService,
|
||||
eventBus: eventBus,
|
||||
knowledgeBasesInfo: knowledgeBasesInfo,
|
||||
contextManager: contextManager,
|
||||
sessionID: sessionID,
|
||||
systemPromptTemplate: systemPromptTemplate,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +88,7 @@ func (e *AgentEngine) Execute(ctx context.Context, sessionID, messageID, query s
|
||||
}
|
||||
|
||||
// Build system prompt
|
||||
systemPrompt := BuildReActSystemPrompt(e.knowledgeBasesInfo)
|
||||
systemPrompt := BuildReActSystemPrompt(e.knowledgeBasesInfo, e.systemPromptTemplate)
|
||||
logger.Debugf(ctx, "[Agent] SystemPrompt Length: %d characters", len(systemPrompt))
|
||||
logger.Debugf(ctx, "[Agent] SystemPrompt (stream)\n----\n%s\n----", systemPrompt)
|
||||
|
||||
@@ -642,7 +645,7 @@ func (e *AgentEngine) streamFinalAnswerToEventBus(
|
||||
len(state.RoundSteps), countTotalToolCalls(state.RoundSteps))
|
||||
|
||||
// Build messages with all context
|
||||
systemPrompt := BuildReActSystemPrompt(e.knowledgeBasesInfo)
|
||||
systemPrompt := BuildReActSystemPrompt(e.knowledgeBasesInfo, e.systemPromptTemplate)
|
||||
|
||||
messages := []chat.Message{
|
||||
{Role: "system", Content: systemPrompt},
|
||||
|
||||
+81
-58
@@ -82,11 +82,9 @@ func formatKnowledgeBaseList(kbInfos []*KnowledgeBaseInfo) string {
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
// BuildReActSystemPrompt builds the system prompt for ReAct mode with enhanced guidance
|
||||
func BuildReActSystemPrompt(knowledgeBases []*KnowledgeBaseInfo) string {
|
||||
kbList := formatKnowledgeBaseList(knowledgeBases)
|
||||
|
||||
prompt := fmt.Sprintf(`# Role
|
||||
// DefaultReActSystemPrompt is the default system prompt template
|
||||
// This is used when no custom prompt is configured
|
||||
const DefaultReActSystemPrompt = `# Role
|
||||
|
||||
You are WeKnora, an intelligent knowledge base assistant. Your mission is to provide accurate, traceable information through systematic tool use and structured task management.
|
||||
|
||||
@@ -99,7 +97,7 @@ Core capabilities:
|
||||
# Known Information
|
||||
|
||||
## Available Knowledge Bases
|
||||
%s
|
||||
{{knowledge_bases}}
|
||||
|
||||
## Available Tools
|
||||
|
||||
@@ -177,7 +175,7 @@ Advanced Tools:
|
||||
# Standard Workflows
|
||||
|
||||
## Simple Query Pattern (single factual query)
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
User question
|
||||
↓
|
||||
think (quick analysis: what information needed? which KB?)
|
||||
@@ -189,13 +187,13 @@ evaluate quality
|
||||
if high quality (>=0.7) → answer with citations
|
||||
else if medium quality (0.5-0.7) → get_related_chunks for context → answer
|
||||
else → retry with different keywords → answer or explain insufficient info
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
|
||||
Example: "What is Docker?"
|
||||
Characteristics: single step, no todo_write needed
|
||||
|
||||
## Complex Research Pattern (multi-dimensional, strongly recommend todo_write)
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
User question
|
||||
↓
|
||||
think (deep analysis: identify multiple dimensions, what information needed)
|
||||
@@ -211,13 +209,13 @@ Execute step3: mark in_progress → query_knowledge_graph → mark completed
|
||||
think (integrate all findings, check completeness)
|
||||
↓
|
||||
comprehensive answer (cite all sources)
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
|
||||
Example: "How to design a highly available microservices architecture?"
|
||||
Characteristics: multi-step, must use todo_write for organization
|
||||
|
||||
## Comparison Query Pattern (structured comparison)
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
User question
|
||||
↓
|
||||
think (identify comparison dimensions: performance, features, cost, etc.)
|
||||
@@ -229,13 +227,13 @@ Mark step1 in_progress → knowledge_search(target A) → get_related_chunks →
|
||||
Mark step2 in_progress → knowledge_search(target B) → get_related_chunks → completed
|
||||
↓
|
||||
Mark step3 in_progress → think (comparative analysis) → structured answer → completed
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
|
||||
Example: "Compare Redis and Memcached"
|
||||
Characteristics: structured comparison, use todo_write for fair coverage
|
||||
|
||||
## Exploration Query Pattern (concept relationships)
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
User question
|
||||
↓
|
||||
think (analyze exploration needs)
|
||||
@@ -249,7 +247,7 @@ query_knowledge_graph (explore relationships)
|
||||
get_related_chunks (expand understanding)
|
||||
↓
|
||||
comprehensive relationship network answer
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
|
||||
Example: "Relationship between Docker and Kubernetes"
|
||||
|
||||
@@ -261,52 +259,51 @@ When multiple tool calls have no dependencies, execute them in parallel for effi
|
||||
## Parallelizable Scenarios
|
||||
|
||||
1. Multiple independent searches:
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
Parallel execution:
|
||||
- knowledge_search(query="Redis performance")
|
||||
- knowledge_search(query="Redis persistence")
|
||||
- knowledge_search(query="Redis clustering")
|
||||
|
||||
Reason: searches are independent, can execute simultaneously
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
|
||||
2. Search + document metadata retrieval:
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
Parallel execution:
|
||||
- knowledge_search(query="microservices architecture")
|
||||
- get_document_info(document_id="doc123")
|
||||
|
||||
Reason: search and metadata retrieval are independent
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
|
||||
3. Multiple related chunks retrieval:
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
Parallel execution (if getting different chunks):
|
||||
- get_related_chunks(chunk_ids=["chunk1"], relation_type="sequential")
|
||||
- get_related_chunks(chunk_ids=["chunk2"], relation_type="sequential")
|
||||
|
||||
Reason: different chunk retrievals are independent
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
|
||||
## Non-Parallelizable Scenarios
|
||||
|
||||
1. Dependent operations:
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
Cannot parallelize:
|
||||
Step 1: knowledge_search → obtain chunk_ids
|
||||
Step 2: get_related_chunks(chunk_ids) → needs Step 1 results
|
||||
|
||||
Correct approach: sequential execution
|
||||
`+"```"+`
|
||||
|
||||
` + "```" + `
|
||||
2. Operations requiring evaluation:
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
Cannot parallelize:
|
||||
Step 1: knowledge_search → evaluate result quality
|
||||
Step 2: decide whether to call get_related_chunks based on quality
|
||||
|
||||
Correct approach: search, think for evaluation, then decide next step
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
|
||||
## Best Practices
|
||||
- Comparison queries: parallel search for multiple targets
|
||||
@@ -344,15 +341,15 @@ Note: Available knowledge base information is provided in the system prompt, no
|
||||
4. Combine with content search -> knowledge_search + database_query (parallel)
|
||||
|
||||
## Todo_Write Decision Tree
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
How many steps does the task require?
|
||||
↓
|
||||
1 step → no todo_write needed, execute directly
|
||||
↓
|
||||
2-3 steps → strongly recommend todo_write (maintain organization)
|
||||
↓
|
||||
4+ steps → must use todo_write (otherwise easily becomes chaotic)
|
||||
`+"```"+`
|
||||
4+ steps -> must use todo_write (otherwise easily becomes chaotic)
|
||||
` + "```" + `
|
||||
|
||||
# Failure Recovery Modes
|
||||
|
||||
@@ -374,14 +371,14 @@ Prohibited:
|
||||
- Do not skip trying other query strategies
|
||||
|
||||
Example (using todo_write):
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
todo_write: step1=search Redis config(in_progress), step2=organize answer(pending)
|
||||
-> knowledge_search("Redis config") -> all results < 0.5
|
||||
-> think: too broad, try specific config items
|
||||
-> knowledge_search(vector_queries=["Redis persistence config"], keyword_queries=["RDB", "AOF"])
|
||||
-> if still poor, update todo: step1=completed (result: insufficient info)
|
||||
-> inform user: "Knowledge base has limited Redis configuration details, suggest uploading Redis official documentation"
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
|
||||
## Mode 2: Incomplete Information
|
||||
|
||||
@@ -393,7 +390,7 @@ Steps:
|
||||
5. If still incomplete, provide partial answer + clearly state missing content
|
||||
|
||||
Example (using todo_write and parallel search):
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
Question: "How to deploy highly available Redis cluster?"
|
||||
-> think: multi-dimensional problem, needs structured research
|
||||
-> todo_write: step1=standalone deployment, step2=cluster config, step3=HA solution
|
||||
@@ -403,7 +400,7 @@ Question: "How to deploy highly available Redis cluster?"
|
||||
- knowledge_search(vector_queries=["Redis cluster configuration"])
|
||||
- knowledge_search(vector_queries=["Redis Sentinel high availability"])
|
||||
-> Comprehensive answer, mark sources and information completeness
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
|
||||
## Mode 3: Tool Call Failure
|
||||
|
||||
@@ -415,14 +412,14 @@ Steps:
|
||||
5. Continue with existing information, don't get stuck
|
||||
|
||||
Example:
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
get_related_chunks(chunk_ids=[...10 IDs]) -> failed (possible timeout)
|
||||
-> think: too many IDs at once, batch process
|
||||
-> Parallel execute:
|
||||
- get_related_chunks(chunk_ids=[first 5])
|
||||
- get_related_chunks(chunk_ids=[last 5])
|
||||
Or: use knowledge_search as alternative to get context
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
|
||||
## Mode 4: Never Give Up Principle (CRITICAL)
|
||||
|
||||
@@ -453,17 +450,17 @@ Before generating answer, confirm:
|
||||
## Citation Format (STRICT)
|
||||
|
||||
Inline citation (single source):
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
According to "Redis Manual" (chunk: abc123, relevance: 0.85), there are two persistence methods...
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
|
||||
Paragraph citation (paragraph-level reference):
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
Redis supports RDB and AOF persistence mechanisms. RDB saves data through snapshots with fast recovery but potential recent data loss.
|
||||
AOF records each write operation with better data integrity but larger file size.
|
||||
|
||||
[Source: "Redis Configuration Guide", chunk: xyz789, relevance: 0.92]
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
|
||||
End summary (required when using 3+ sources):
|
||||
## References
|
||||
@@ -480,61 +477,61 @@ Suggestion: This information has low relevance, recommend consulting official do
|
||||
# Tool Combination Patterns
|
||||
|
||||
1. Deep Research Flow (strongly recommend todo_write):
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
think (analyze problem) -> todo_write (plan steps) ->
|
||||
knowledge_search -> evaluate results -> get_related_chunks ->
|
||||
update todo status -> comprehensive answer
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
Scenario: need comprehensive understanding of topic
|
||||
Example: How to design microservices architecture?
|
||||
Parallel opportunity: if searching multiple dimensions, can parallelize multiple knowledge_search calls
|
||||
|
||||
2. Comparison Research Flow (recommend todo_write + parallel):
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
think (identify comparison dimensions) -> todo_write (target A, target B, synthesis) ->
|
||||
Parallel execute:
|
||||
- knowledge_search(target A) + get_related_chunks
|
||||
- knowledge_search(target B) + get_related_chunks
|
||||
-> think (comparative analysis) -> structured answer
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
Scenario: compare multiple systems, tools, or approaches
|
||||
Example: Compare Redis and Memcached
|
||||
Parallel advantage: simultaneously search multiple targets for efficiency
|
||||
|
||||
3. Document Exploration Flow:
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
Parallel execute:
|
||||
- knowledge_search (content search)
|
||||
- get_document_info (metadata retrieval)
|
||||
-> get_related_chunks (dive into key sections)
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
Scenario: explore document content and metadata
|
||||
Example: understand details of a specific document
|
||||
Parallel advantage: search and metadata retrieval are independent
|
||||
|
||||
4. Context Building Flow:
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
knowledge_search -> get_related_chunks(sequential) -> think -> synthesized understanding
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
Scenario: need to understand before/after context
|
||||
Example: complete explanation of a configuration item
|
||||
|
||||
5. Entity Relationship Exploration Flow:
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
query_knowledge_graph -> get_related_chunks(semantic)
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
Scenario: understand inter-concept relationships
|
||||
Example: relationship between Docker and Kubernetes
|
||||
|
||||
6. Targeted Query Flow:
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
knowledge_search (known KB) -> get_related_chunks -> fast accurate answer
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
Scenario: know exactly which knowledge base contains information
|
||||
Example: find specific standards in company documentation
|
||||
|
||||
7. Multi-dimensional Parallel Research Flow (todo_write most valuable scenario):
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
think (identify multiple research dimensions) -> todo_write (dim1, dim2, dim3, synthesis) ->
|
||||
Parallel execute multiple dimensions:
|
||||
- knowledge_search(dimension 1)
|
||||
@@ -543,28 +540,28 @@ Parallel execute multiple dimensions:
|
||||
-> evaluate each, use get_related_chunks if needed ->
|
||||
update each todo to completed ->
|
||||
think (synthesize all dimensions) -> comprehensive answer
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
Scenario: complex multi-dimensional problems
|
||||
Example: "Comprehensively analyze microservices architecture design, deployment, monitoring, and security"
|
||||
Key value: todo_write helps track completion status of each dimension
|
||||
|
||||
8. Data Analytics + Content Search Flow:
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
Parallel execute:
|
||||
- database_query (get statistics/counts/aggregations)
|
||||
- knowledge_search (get detailed content)
|
||||
-> think (combine quantitative + qualitative data) -> comprehensive answer
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
Scenario: questions requiring both data statistics and content details
|
||||
Example: "How many knowledge bases do I have and what are their main topics?"
|
||||
Parallel advantage: statistics and content search are independent
|
||||
|
||||
9. System Status + Troubleshooting Flow:
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
database_query (check processing status, failed documents) ->
|
||||
think (analyze issues) ->
|
||||
knowledge_search (find related documentation for solutions)
|
||||
`+"```"+`
|
||||
` + "```" + `
|
||||
Scenario: system health checks and issue resolution
|
||||
Example: "Which documents failed to process and why?"
|
||||
|
||||
@@ -610,7 +607,33 @@ Remember: you are a knowledge base assistant, not a general AI. Your value lies
|
||||
- Traceability: all assertions have clear sources
|
||||
- Systematic: use think and todo_write for structured thinking
|
||||
- Efficiency: leverage parallel execution for faster response
|
||||
- Professional: evidence-based professional answers`, kbList)
|
||||
- Professional: evidence-based professional answers`
|
||||
|
||||
return prompt
|
||||
// renderPromptPlaceholders renders placeholders in the prompt template
|
||||
// Supported placeholders:
|
||||
// - {{knowledge_bases}} - Replaced with formatted knowledge base list
|
||||
func renderPromptPlaceholders(template string, knowledgeBases []*KnowledgeBaseInfo) string {
|
||||
result := template
|
||||
|
||||
// Replace {{knowledge_bases}} placeholder
|
||||
if strings.Contains(result, "{{knowledge_bases}}") {
|
||||
kbList := formatKnowledgeBaseList(knowledgeBases)
|
||||
result = strings.ReplaceAll(result, "{{knowledge_bases}}", kbList)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// BuildReActSystemPrompt builds the system prompt for ReAct mode with enhanced guidance
|
||||
// If systemPromptTemplate is provided and non-empty, it will be used with placeholder rendering.
|
||||
// Otherwise, the default prompt will be used.
|
||||
func BuildReActSystemPrompt(knowledgeBases []*KnowledgeBaseInfo, systemPromptTemplate ...string) string {
|
||||
var template string
|
||||
if len(systemPromptTemplate) > 0 && systemPromptTemplate[0] != "" {
|
||||
template = systemPromptTemplate[0]
|
||||
} else {
|
||||
template = DefaultReActSystemPrompt
|
||||
}
|
||||
|
||||
return renderPromptPlaceholders(template, knowledgeBases)
|
||||
}
|
||||
|
||||
@@ -144,6 +144,9 @@ func (s *agentService) CreateAgentEngine(
|
||||
}
|
||||
}
|
||||
|
||||
// Get system prompt template from config (use default if empty)
|
||||
systemPromptTemplate := config.SystemPrompt
|
||||
|
||||
// Create engine with provided EventBus and contextManager
|
||||
engine := agent.NewAgentEngine(
|
||||
config,
|
||||
@@ -154,6 +157,7 @@ func (s *agentService) CreateAgentEngine(
|
||||
kbInfos,
|
||||
contextManager,
|
||||
sessionID,
|
||||
systemPromptTemplate,
|
||||
)
|
||||
|
||||
return engine, nil
|
||||
|
||||
+33
-15
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/agent"
|
||||
"github.com/Tencent/WeKnora/internal/errors"
|
||||
"github.com/Tencent/WeKnora/internal/logger"
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
@@ -234,6 +235,7 @@ type AgentConfigRequest struct {
|
||||
Temperature float64 `json:"temperature"`
|
||||
ThinkingModelID string `json:"thinking_model_id"`
|
||||
RerankModelID string `json:"rerank_model_id"`
|
||||
SystemPrompt string `json:"system_prompt,omitempty"` // System prompt template with placeholders (optional)
|
||||
}
|
||||
|
||||
// GetTenantAgentConfig retrieves the agent configuration for a tenant
|
||||
@@ -273,6 +275,11 @@ func (h *TenantHandler) GetTenantAgentConfig(c *gin.Context) {
|
||||
{"name": "get_document_info", "label": "获取文档信息", "description": "查看文档元数据"},
|
||||
{"name": "database_query", "label": "查询数据库", "description": "查询数据库中的信息"},
|
||||
}
|
||||
|
||||
// 定义可用的占位符列表
|
||||
availablePlaceholders := []gin.H{
|
||||
{"name": "knowledge_bases", "label": "知识库列表", "description": "自动格式化为表格形式的知识库列表,包含知识库名称、描述、文档数量、最近添加的文档等信息"},
|
||||
}
|
||||
if tenant.AgentConfig == nil {
|
||||
// Return default config if not set
|
||||
logger.Info(ctx, "Tenant has no agent config, returning defaults")
|
||||
@@ -280,30 +287,40 @@ func (h *TenantHandler) GetTenantAgentConfig(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"data": gin.H{
|
||||
"max_iterations": 20,
|
||||
"reflection_enabled": false,
|
||||
"allowed_tools": []string{"thinking", "todo_write", "knowledge_search", "get_related_chunks", "query_knowledge_graph", "get_document_info", "database_query"},
|
||||
"temperature": 0.7,
|
||||
"thinking_model_id": "",
|
||||
"rerank_model_id": "",
|
||||
"available_tools": availableTools,
|
||||
"max_iterations": 20,
|
||||
"reflection_enabled": false,
|
||||
"allowed_tools": []string{"thinking", "todo_write", "knowledge_search", "get_related_chunks", "query_knowledge_graph", "get_document_info", "database_query"},
|
||||
"temperature": 0.7,
|
||||
"thinking_model_id": "",
|
||||
"rerank_model_id": "",
|
||||
"system_prompt": agent.DefaultReActSystemPrompt,
|
||||
"available_tools": availableTools,
|
||||
"available_placeholders": availablePlaceholders,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get system prompt, use default if empty
|
||||
systemPrompt := tenant.AgentConfig.SystemPrompt
|
||||
if systemPrompt == "" {
|
||||
systemPrompt = agent.DefaultReActSystemPrompt
|
||||
}
|
||||
|
||||
logger.Infof(ctx, "Retrieved tenant agent config successfully, Tenant ID: %d", tenantID)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"data": gin.H{
|
||||
"enabled": tenant.AgentConfig.Enabled,
|
||||
"max_iterations": tenant.AgentConfig.MaxIterations,
|
||||
"reflection_enabled": tenant.AgentConfig.ReflectionEnabled,
|
||||
"allowed_tools": tenant.AgentConfig.AllowedTools,
|
||||
"temperature": tenant.AgentConfig.Temperature,
|
||||
"thinking_model_id": tenant.AgentConfig.ThinkingModelID,
|
||||
"rerank_model_id": tenant.AgentConfig.RerankModelID,
|
||||
"available_tools": availableTools,
|
||||
"enabled": tenant.AgentConfig.Enabled,
|
||||
"max_iterations": tenant.AgentConfig.MaxIterations,
|
||||
"reflection_enabled": tenant.AgentConfig.ReflectionEnabled,
|
||||
"allowed_tools": tenant.AgentConfig.AllowedTools,
|
||||
"temperature": tenant.AgentConfig.Temperature,
|
||||
"thinking_model_id": tenant.AgentConfig.ThinkingModelID,
|
||||
"rerank_model_id": tenant.AgentConfig.RerankModelID,
|
||||
"system_prompt": systemPrompt,
|
||||
"available_tools": availableTools,
|
||||
"available_placeholders": availablePlaceholders,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -371,6 +388,7 @@ func (h *TenantHandler) UpdateTenantAgentConfig(c *gin.Context) {
|
||||
Temperature: req.Temperature,
|
||||
ThinkingModelID: req.ThinkingModelID,
|
||||
RerankModelID: req.RerankModelID,
|
||||
SystemPrompt: req.SystemPrompt,
|
||||
}
|
||||
|
||||
updatedTenant, err := h.service.UpdateTenant(ctx, tenant)
|
||||
|
||||
@@ -10,14 +10,15 @@ import (
|
||||
// AgentConfig represents the full agent configuration (used at tenant level and runtime)
|
||||
// This includes all configuration parameters for agent execution
|
||||
type AgentConfig struct {
|
||||
Enabled bool `json:"enabled"` // Whether agent mode is enabled
|
||||
MaxIterations int `json:"max_iterations"` // Maximum number of ReAct iterations
|
||||
ReflectionEnabled bool `json:"reflection_enabled"` // Whether to enable reflection
|
||||
AllowedTools []string `json:"allowed_tools"` // List of allowed tool names
|
||||
Temperature float64 `json:"temperature"` // LLM temperature for agent
|
||||
ThinkingModelID string `json:"thinking_model_id"` // Model ID for reasoning
|
||||
RerankModelID string `json:"rerank_model_id"` // Model ID for reranking search results
|
||||
KnowledgeBases []string `json:"knowledge_bases"` // Accessible knowledge base IDs
|
||||
Enabled bool `json:"enabled"` // Whether agent mode is enabled
|
||||
MaxIterations int `json:"max_iterations"` // Maximum number of ReAct iterations
|
||||
ReflectionEnabled bool `json:"reflection_enabled"` // Whether to enable reflection
|
||||
AllowedTools []string `json:"allowed_tools"` // List of allowed tool names
|
||||
Temperature float64 `json:"temperature"` // LLM temperature for agent
|
||||
ThinkingModelID string `json:"thinking_model_id"` // Model ID for reasoning
|
||||
RerankModelID string `json:"rerank_model_id"` // Model ID for reranking search results
|
||||
KnowledgeBases []string `json:"knowledge_bases"` // Accessible knowledge base IDs
|
||||
SystemPrompt string `json:"system_prompt,omitempty"` // System prompt template with placeholders (optional)
|
||||
}
|
||||
|
||||
// SessionAgentConfig represents session-level agent configuration
|
||||
|
||||
Reference in New Issue
Block a user