feat(chatbot): 添加子智能体支持和摘要中间件
This commit is contained in:
parent
403608fb7d
commit
3babd9be54
@ -11,6 +11,7 @@ from yuxi.agents.common import BaseAgent, load_chat_model
|
|||||||
from yuxi.agents.common.backends import create_agent_composite_backend
|
from yuxi.agents.common.backends import create_agent_composite_backend
|
||||||
from yuxi.agents.common.middlewares import (
|
from yuxi.agents.common.middlewares import (
|
||||||
RuntimeConfigMiddleware,
|
RuntimeConfigMiddleware,
|
||||||
|
SummaryOffloadMiddleware,
|
||||||
save_attachments_to_fs,
|
save_attachments_to_fs,
|
||||||
)
|
)
|
||||||
from yuxi.agents.common.middlewares.knowledge_base_middleware import KnowledgeBaseMiddleware
|
from yuxi.agents.common.middlewares.knowledge_base_middleware import KnowledgeBaseMiddleware
|
||||||
@ -38,10 +39,10 @@ class ChatbotAgent(BaseAgent):
|
|||||||
await get_tools_from_all_servers()
|
await get_tools_from_all_servers()
|
||||||
) # 因为异步加载,无法放在 RuntimeConfigMiddleware 的 __init__ 中
|
) # 因为异步加载,无法放在 RuntimeConfigMiddleware 的 __init__ 中
|
||||||
|
|
||||||
|
# subagents
|
||||||
subagents = await get_subagents_from_names(context.subagents)
|
subagents = await get_subagents_from_names(context.subagents)
|
||||||
subagents_model = load_chat_model(context.subagents_model)
|
|
||||||
subagents_middleware = SubAgentMiddleware(
|
subagents_middleware = SubAgentMiddleware(
|
||||||
default_model=subagents_model,
|
default_model=load_chat_model(context.subagents_model),
|
||||||
subagents=subagents,
|
subagents=subagents,
|
||||||
general_purpose_agent=True,
|
general_purpose_agent=True,
|
||||||
default_middleware=[
|
default_middleware=[
|
||||||
@ -50,6 +51,17 @@ class ChatbotAgent(BaseAgent):
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# summary middleware
|
||||||
|
# 主 Agent 上下文优化:90k tokens 触发压缩(128k context window 的 70%)
|
||||||
|
summary_middleware = SummaryOffloadMiddleware(
|
||||||
|
model=load_chat_model(fully_specified_name=context.model),
|
||||||
|
trigger=("tokens", getattr(context, "summary_threshold", 100) * 1024),
|
||||||
|
trim_tokens_to_summarize=4000,
|
||||||
|
summary_offload_threshold=500,
|
||||||
|
max_retention_ratio=0.5,
|
||||||
|
)
|
||||||
|
|
||||||
|
# all middlewares
|
||||||
middlewares = [
|
middlewares = [
|
||||||
save_attachments_to_fs, # 附件注入提示词
|
save_attachments_to_fs, # 附件注入提示词
|
||||||
FilesystemMiddleware(backend=_create_fs_backend), # 文件系统后端
|
FilesystemMiddleware(backend=_create_fs_backend), # 文件系统后端
|
||||||
@ -57,6 +69,7 @@ class ChatbotAgent(BaseAgent):
|
|||||||
RuntimeConfigMiddleware(extra_tools=all_mcp_tools), # 运行时配置应用(模型/工具/MCP/提示词)
|
RuntimeConfigMiddleware(extra_tools=all_mcp_tools), # 运行时配置应用(模型/工具/MCP/提示词)
|
||||||
SkillsMiddleware(), # Skills 中间件(提示词注入、依赖展开、动态激活)
|
SkillsMiddleware(), # Skills 中间件(提示词注入、依赖展开、动态激活)
|
||||||
subagents_middleware,
|
subagents_middleware,
|
||||||
|
summary_middleware,
|
||||||
ModelRetryMiddleware(), # 模型重试中间件
|
ModelRetryMiddleware(), # 模型重试中间件
|
||||||
TodoListMiddleware(),
|
TodoListMiddleware(),
|
||||||
PatchToolCallsMiddleware(),
|
PatchToolCallsMiddleware(),
|
||||||
|
|||||||
@ -34,12 +34,6 @@ class BaseContext:
|
|||||||
metadata={"name": "用户ID", "configurable": False, "description": "用来唯一标识一个用户"},
|
metadata={"name": "用户ID", "configurable": False, "description": "用来唯一标识一个用户"},
|
||||||
)
|
)
|
||||||
|
|
||||||
# 不需要了,使用 user_id 判断
|
|
||||||
# department_id: int | None = field(
|
|
||||||
# default=None,
|
|
||||||
# metadata={"name": "部门ID", "configurable": False, "description": "用来唯一标识一个部门"},
|
|
||||||
# )
|
|
||||||
|
|
||||||
system_prompt: Annotated[str, {"__template_metadata__": {"kind": "prompt"}}] = field(
|
system_prompt: Annotated[str, {"__template_metadata__": {"kind": "prompt"}}] = field(
|
||||||
default="You are a helpful assistant.",
|
default="You are a helpful assistant.",
|
||||||
metadata={"name": "系统提示词", "description": "用来描述智能体的角色和行为"},
|
metadata={"name": "系统提示词", "description": "用来描述智能体的角色和行为"},
|
||||||
@ -94,6 +88,33 @@ class BaseContext:
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
subagents_model: Annotated[str, {"__template_metadata__": {"kind": "llm"}}] = field(
|
||||||
|
default=sys_config.default_model,
|
||||||
|
metadata={
|
||||||
|
"name": "子智能体的默认模型",
|
||||||
|
"description": "子智能体的默认模型,会被子智能体的配置覆盖。",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
subagents: Annotated[list[str], {"__template_metadata__": {"kind": "subagents"}}] = field(
|
||||||
|
default_factory=list,
|
||||||
|
metadata={
|
||||||
|
"name": "子智能体",
|
||||||
|
"options": [],
|
||||||
|
"description": "可选子智能体列表。为空表示不启用任何 SubAgent。但依然会启用一个 general-purpose 的子智能体",
|
||||||
|
"type": "list",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
summary_threshold: int = field(
|
||||||
|
default=100,
|
||||||
|
metadata={
|
||||||
|
"name": "上下文摘要触发阈值 (KB)",
|
||||||
|
"description": "当上下文大小超过该值时,启用摘要功能以优化上下文使用。单位为 KB,默认值为 100KB。",
|
||||||
|
"type": "number",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_configurable_items(cls):
|
def get_configurable_items(cls):
|
||||||
"""实现一个可配置的参数列表,在 UI 上配置时使用"""
|
"""实现一个可配置的参数列表,在 UI 上配置时使用"""
|
||||||
|
|||||||
@ -431,6 +431,7 @@ import { useAgentStore } from '@/stores/agent'
|
|||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
import { useDatabaseStore } from '@/stores/database'
|
import { useDatabaseStore } from '@/stores/database'
|
||||||
import { skillApi } from '@/apis/skill_api'
|
import { skillApi } from '@/apis/skill_api'
|
||||||
|
import { subagentApi } from '@/apis/subagent_api'
|
||||||
import { toolApi } from '@/apis/tool_api'
|
import { toolApi } from '@/apis/tool_api'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
|
|
||||||
@ -461,6 +462,7 @@ watch(
|
|||||||
if (val) {
|
if (val) {
|
||||||
databaseStore.loadDatabases().catch(() => {})
|
databaseStore.loadDatabases().catch(() => {})
|
||||||
loadLiveSkillOptions().catch(() => {})
|
loadLiveSkillOptions().catch(() => {})
|
||||||
|
loadSubagentOptions().catch(() => {})
|
||||||
loadToolOptions().catch(() => {})
|
loadToolOptions().catch(() => {})
|
||||||
if (selectedAgentId.value) {
|
if (selectedAgentId.value) {
|
||||||
try {
|
try {
|
||||||
@ -496,6 +498,7 @@ const currentSystemPromptKey = ref(null)
|
|||||||
const systemPromptDraft = ref('')
|
const systemPromptDraft = ref('')
|
||||||
const activeTab = ref('basic')
|
const activeTab = ref('basic')
|
||||||
const liveSkillOptions = ref([])
|
const liveSkillOptions = ref([])
|
||||||
|
const liveSubagentOptions = ref([])
|
||||||
const toolOptionsFromApi = ref([])
|
const toolOptionsFromApi = ref([])
|
||||||
|
|
||||||
const isEmptyConfig = computed(() => {
|
const isEmptyConfig = computed(() => {
|
||||||
@ -518,7 +521,8 @@ const hasOtherConfigs = computed(() => {
|
|||||||
value.template_metadata?.kind === 'mcps' ||
|
value.template_metadata?.kind === 'mcps' ||
|
||||||
value.template_metadata?.kind === 'knowledges' ||
|
value.template_metadata?.kind === 'knowledges' ||
|
||||||
value.template_metadata?.kind === 'tools' ||
|
value.template_metadata?.kind === 'tools' ||
|
||||||
value.template_metadata?.kind === 'skills'
|
value.template_metadata?.kind === 'skills' ||
|
||||||
|
value.template_metadata?.kind === 'subagents'
|
||||||
|
|
||||||
return !isBasic && !isTools
|
return !isBasic && !isTools
|
||||||
})
|
})
|
||||||
@ -580,9 +584,30 @@ const loadToolOptions = async (force = false) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loadSubagentOptions = async (force = false) => {
|
||||||
|
if (!userStore.isAdmin) {
|
||||||
|
liveSubagentOptions.value = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!force && liveSubagentOptions.value.length > 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const result = await subagentApi.getSubAgents()
|
||||||
|
const rows = result?.data || []
|
||||||
|
liveSubagentOptions.value = rows.map((item) => ({
|
||||||
|
id: item.name,
|
||||||
|
name: item.name,
|
||||||
|
description: item.description || ''
|
||||||
|
}))
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('加载 Subagents 列表失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 判断是否为需要跳转的配置类型
|
// 判断是否为需要跳转的配置类型
|
||||||
const isToolsKind = (kind) => {
|
const isToolsKind = (kind) => {
|
||||||
return ['knowledges', 'tools', 'mcps', 'skills'].includes(kind)
|
return ['knowledges', 'tools', 'mcps', 'skills', 'subagents'].includes(kind)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 强制刷新对应配置项的选项列表
|
// 强制刷新对应配置项的选项列表
|
||||||
@ -601,6 +626,10 @@ const refreshConfigOptions = async (key, kind) => {
|
|||||||
await loadLiveSkillOptions(true)
|
await loadLiveSkillOptions(true)
|
||||||
message.success('Skills 列表已刷新')
|
message.success('Skills 列表已刷新')
|
||||||
break
|
break
|
||||||
|
case 'subagents':
|
||||||
|
await loadSubagentOptions(true)
|
||||||
|
message.success('Subagents 列表已刷新')
|
||||||
|
break
|
||||||
case 'mcps':
|
case 'mcps':
|
||||||
// MCP 没有前端 store,提示用户刷新页面
|
// MCP 没有前端 store,提示用户刷新页面
|
||||||
message.info('请在 MCP 管理页面刷新')
|
message.info('请在 MCP 管理页面刷新')
|
||||||
@ -631,6 +660,9 @@ const navigateToConfigPage = (kind) => {
|
|||||||
case 'skills':
|
case 'skills':
|
||||||
router.push({ path: '/extensions', query: { tab: 'skills' } })
|
router.push({ path: '/extensions', query: { tab: 'skills' } })
|
||||||
break
|
break
|
||||||
|
case 'subagents':
|
||||||
|
router.push({ path: '/extensions', query: { tab: 'subagents' } })
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}, 100)
|
}, 100)
|
||||||
}
|
}
|
||||||
@ -651,13 +683,16 @@ const getConfigOptions = (value) => {
|
|||||||
if (value?.template_metadata?.kind === 'skills') {
|
if (value?.template_metadata?.kind === 'skills') {
|
||||||
return liveSkillOptions.value.length > 0 ? liveSkillOptions.value : value?.options || []
|
return liveSkillOptions.value.length > 0 ? liveSkillOptions.value : value?.options || []
|
||||||
}
|
}
|
||||||
|
if (value?.template_metadata?.kind === 'subagents') {
|
||||||
|
return liveSubagentOptions.value.length > 0 ? liveSubagentOptions.value : value?.options || []
|
||||||
|
}
|
||||||
return value?.options || []
|
return value?.options || []
|
||||||
}
|
}
|
||||||
|
|
||||||
const isListConfig = (key, value) => {
|
const isListConfig = (key, value) => {
|
||||||
const isTools = value?.template_metadata?.kind === 'tools'
|
const isTools = value?.template_metadata?.kind === 'tools'
|
||||||
const isList = value?.type === 'list'
|
const isList = value?.type === 'list'
|
||||||
return isTools || isList || key === 'skills'
|
return isTools || isList || key === 'skills' || key === 'subagents'
|
||||||
}
|
}
|
||||||
|
|
||||||
const getOptionValue = (option) => {
|
const getOptionValue = (option) => {
|
||||||
@ -723,7 +758,9 @@ const shouldShowConfig = (key, value) => {
|
|||||||
value.template_metadata?.kind === 'knowledges' ||
|
value.template_metadata?.kind === 'knowledges' ||
|
||||||
value.template_metadata?.kind === 'tools' ||
|
value.template_metadata?.kind === 'tools' ||
|
||||||
value.template_metadata?.kind === 'skills' ||
|
value.template_metadata?.kind === 'skills' ||
|
||||||
key === 'skills'
|
value.template_metadata?.kind === 'subagents' ||
|
||||||
|
key === 'skills' ||
|
||||||
|
key === 'subagents'
|
||||||
|
|
||||||
if (activeTab.value === 'basic') {
|
if (activeTab.value === 'basic') {
|
||||||
// 基础:System Prompt, LLM Model
|
// 基础:System Prompt, LLM Model
|
||||||
@ -825,6 +862,9 @@ const openSelectionModal = async (key) => {
|
|||||||
if (configurableItems.value[key]?.template_metadata?.kind === 'skills') {
|
if (configurableItems.value[key]?.template_metadata?.kind === 'skills') {
|
||||||
await loadLiveSkillOptions()
|
await loadLiveSkillOptions()
|
||||||
}
|
}
|
||||||
|
if (configurableItems.value[key]?.template_metadata?.kind === 'subagents') {
|
||||||
|
await loadSubagentOptions()
|
||||||
|
}
|
||||||
const currentValues = agentConfig.value[key] || []
|
const currentValues = agentConfig.value[key] || []
|
||||||
tempSelectedValues.value = [...currentValues]
|
tempSelectedValues.value = [...currentValues]
|
||||||
selectionModalOpen.value = true
|
selectionModalOpen.value = true
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user