style: code format
This commit is contained in:
parent
0fdc0acf91
commit
3b35493db3
@ -15,9 +15,9 @@ from langgraph.types import Command
|
||||
|
||||
from yuxi import config as sys_config
|
||||
from yuxi.agents.context import BaseContext, resolve_agent_resource_options
|
||||
from yuxi.utils.subagent_thread_utils import make_child_thread_id
|
||||
from yuxi.storage.postgres.manager import pg_manager
|
||||
from yuxi.utils import logger
|
||||
from yuxi.utils.subagent_thread_utils import make_child_thread_id
|
||||
|
||||
|
||||
def _json_safe(value: Any) -> Any:
|
||||
@ -79,9 +79,7 @@ def _subagent_route_for_namespace(
|
||||
return None
|
||||
|
||||
|
||||
async def _collect_subagent_routes(
|
||||
run, parent_thread_id: str, routes: dict[tuple[str, ...], dict[str, str]]
|
||||
) -> None:
|
||||
async def _collect_subagent_routes(run, parent_thread_id: str, routes: dict[tuple[str, ...], dict[str, str]]) -> None:
|
||||
subagents = getattr(run, "subagents", None)
|
||||
if subagents is None:
|
||||
return
|
||||
|
||||
@ -10,7 +10,7 @@ class ChatBotContext(BaseContext):
|
||||
metadata={
|
||||
"name": "子智能体",
|
||||
"options": [],
|
||||
"description": "可选子智能体列表,只有被选中的子智能体会作为 task 工具候选。为空表示启用当前用户可见的全部子智能体。",
|
||||
"description": "可选子智能体列表,为空表示启用当前用户可见的全部子智能体。",
|
||||
"type": "list",
|
||||
"kind": "subagents",
|
||||
},
|
||||
|
||||
@ -15,7 +15,6 @@ from yuxi.agents.middlewares.knowledge_base import KnowledgeBaseMiddleware
|
||||
from yuxi.agents.middlewares.skills import SkillsMiddleware
|
||||
from yuxi.agents.toolkits.service import resolve_configured_runtime_tools
|
||||
|
||||
|
||||
_SUBAGENT_DISABLED_TOOLS = frozenset({"present_artifacts", "ask_user_question"})
|
||||
|
||||
|
||||
|
||||
@ -15,13 +15,13 @@ from langgraph.prebuilt.tool_node import ToolRuntime
|
||||
from langgraph.types import Command
|
||||
|
||||
from yuxi.agents.context import build_agent_input_context
|
||||
from yuxi.utils.subagent_thread_utils import make_child_thread_id
|
||||
from yuxi.repositories.agent_repository import SUB_AGENT_BACKEND_ID, AgentRepository
|
||||
from yuxi.repositories.agent_run_repository import AgentRunRepository
|
||||
from yuxi.repositories.user_repository import UserRepository
|
||||
from yuxi.storage.postgres.manager import pg_manager
|
||||
from yuxi.storage.postgres.models_business import Agent
|
||||
from yuxi.utils.datetime_utils import utc_isoformat
|
||||
from yuxi.utils.subagent_thread_utils import make_child_thread_id
|
||||
|
||||
_CHILD_STATE_INHERIT_KEYS: frozenset[str] = frozenset()
|
||||
_TERMINAL_RUN_STATUSES = {"completed", "failed", "cancelled", "interrupted"}
|
||||
@ -195,11 +195,7 @@ def _state_for_child(
|
||||
skills_thread_id: str,
|
||||
continuing: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
state = (
|
||||
{}
|
||||
if continuing
|
||||
else {key: runtime.state[key] for key in _CHILD_STATE_INHERIT_KEYS if key in runtime.state}
|
||||
)
|
||||
state = {} if continuing else {key: runtime.state[key] for key in _CHILD_STATE_INHERIT_KEYS if key in runtime.state}
|
||||
state.update(
|
||||
{
|
||||
"parent_thread_id": parent_thread_id,
|
||||
|
||||
@ -20,18 +20,19 @@ DEFAULT_SHARE_CONFIG = {"access_level": "global", "department_ids": [], "user_ui
|
||||
WEB_SEARCH_AGENT_SLUG = "web-search"
|
||||
WEB_SEARCH_AGENT_NAME = "网页检索"
|
||||
WEB_SEARCH_AGENT_DESCRIPTION = "围绕检索目标持续搜索网页,返回带引用来源的摘要资料。"
|
||||
WEB_SEARCH_SYSTEM_PROMPT = """你是「网页检索」子智能体,由主智能体通过 task 工具调用,专注于面向目标的网页信息检索。
|
||||
WEB_SEARCH_SYSTEM_PROMPT = """你是「网页检索」子智能体,专注于面向目标的网页信息检索。
|
||||
|
||||
你的职责:围绕调用方给定的检索目标,使用网页搜索工具持续检索,直到收集到足以回答目标的信息。
|
||||
|
||||
工作方式:
|
||||
1. 拆解目标,确定需要检索的关键问题与检索词。
|
||||
2. 多轮调用网页搜索工具:依据上一轮结果调整检索词、补充遗漏角度、交叉验证关键事实,直到信息充分或确认无法获取更多有效信息。
|
||||
2. 多轮调用搜索工具:依据上一轮结果调整检索词、补充遗漏角度、交叉验证关键事实,直到信息充分或确认无法获取更多有效信息。
|
||||
3. 优先采信权威、时效性强且彼此印证的来源;对存在冲突的信息要说明分歧。
|
||||
|
||||
输出要求:
|
||||
- 返回一份结构化的摘要资料,按主题或要点组织。
|
||||
- 每条关键结论后使用 <cite source="$URL" type="url">$INDEX</cite> 标注引用来源,$INDEX 从 1 开始递增。不单独成行,直接跟在结论后面。
|
||||
- 每条关键结论后使用 <cite source="$URL" type="url">$INDEX</cite> 标注引用来源,$INDEX 从 1 开始递增。
|
||||
- 引用不单独成行,直接跟在结论后面。
|
||||
- 在结尾汇总「参考来源」列表,逐条列出标题与 URL。
|
||||
- 不要编造来源或链接;无法验证的信息要明确标注。"""
|
||||
ACCESS_LEVELS = {"global", "department", "user"}
|
||||
|
||||
@ -29,6 +29,7 @@ from yuxi.utils.question_utils import (
|
||||
normalize_questions as _normalize_interrupt_questions,
|
||||
)
|
||||
|
||||
|
||||
def _build_state_files(attachments: list[dict]) -> dict:
|
||||
"""将附件列表转换为 StateBackend 格式的 files 字典
|
||||
|
||||
|
||||
@ -2,7 +2,6 @@ from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
|
||||
|
||||
_CHILD_THREAD_ID_PREFIX = "subagent_"
|
||||
_CHILD_THREAD_ID_DIGEST_LENGTH = 64 - len(_CHILD_THREAD_ID_PREFIX)
|
||||
|
||||
|
||||
@ -45,7 +45,10 @@
|
||||
<div class="chat-box">
|
||||
<template v-for="row in conversationRows" :key="row.key">
|
||||
<div v-if="row.type === 'conversation'" class="conv-box">
|
||||
<template v-for="(displayItem, itemIndex) in row.displayItems" :key="displayItem.key">
|
||||
<template
|
||||
v-for="(displayItem, itemIndex) in row.displayItems"
|
||||
:key="displayItem.key"
|
||||
>
|
||||
<AgentMessageComponent
|
||||
v-if="displayItem.type === 'message'"
|
||||
:message="displayItem.message"
|
||||
@ -547,7 +550,9 @@ const getArtifactMetaLabel = (path) => {
|
||||
|
||||
const getSubagentRunName = (run) => {
|
||||
const subagentType = run?.subagent_type ? String(run.subagent_type) : ''
|
||||
return run?.subagent_name || currentSubagentOptionBySlug.value.get(subagentType)?.name || '子智能体'
|
||||
return (
|
||||
run?.subagent_name || currentSubagentOptionBySlug.value.get(subagentType)?.name || '子智能体'
|
||||
)
|
||||
}
|
||||
|
||||
const getSubagentAgent = (run) => {
|
||||
@ -824,7 +829,9 @@ const currentThreadConfigNotice = computed(() => {
|
||||
const shouldSuppressRefsForApproval = () =>
|
||||
approvalState.showModal ||
|
||||
Boolean(
|
||||
approvalState.threadId && chatState.currentThreadId === approvalState.threadId && isProcessing.value
|
||||
approvalState.threadId &&
|
||||
chatState.currentThreadId === approvalState.threadId &&
|
||||
isProcessing.value
|
||||
)
|
||||
|
||||
// 计算是否显示Refs组件的条件
|
||||
@ -911,7 +918,9 @@ const runningSubagentRunsFromStream = computed(() => {
|
||||
return ongoingTaskCalls.value
|
||||
.filter((call) => activeIds.has(call.id))
|
||||
.map((call) => {
|
||||
const option = call.subagentType ? currentSubagentOptionBySlug.value.get(call.subagentType) : null
|
||||
const option = call.subagentType
|
||||
? currentSubagentOptionBySlug.value.get(call.subagentType)
|
||||
: null
|
||||
return {
|
||||
id: call.id,
|
||||
subagent_type: call.subagentType,
|
||||
|
||||
@ -569,7 +569,9 @@ const ensureArray = (key) => {
|
||||
if (!config[key] || !Array.isArray(config[key])) {
|
||||
return []
|
||||
}
|
||||
const validValues = new Set(getConfigOptions(configItem).map((option) => String(getOptionValue(option))))
|
||||
const validValues = new Set(
|
||||
getConfigOptions(configItem).map((option) => String(getOptionValue(option)))
|
||||
)
|
||||
if (validValues.size === 0) return config[key]
|
||||
return config[key].filter((value) => validValues.has(String(value)))
|
||||
}
|
||||
|
||||
@ -34,10 +34,15 @@ const props = defineProps({
|
||||
}
|
||||
})
|
||||
|
||||
const conversations = computed(() => MessageProcessor.convertServerHistoryToMessages(props.messages))
|
||||
const conversations = computed(() =>
|
||||
MessageProcessor.convertServerHistoryToMessages(props.messages)
|
||||
)
|
||||
|
||||
const getDisplayItems = (conv) =>
|
||||
getConversationDisplayItems(conv, props.enrichToolCalls ? { enrichToolCalls: props.enrichToolCalls } : {})
|
||||
getConversationDisplayItems(
|
||||
conv,
|
||||
props.enrichToolCalls ? { enrichToolCalls: props.enrichToolCalls } : {}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
@ -58,10 +58,10 @@ export const isHiddenToolCall = (toolCall) => HIDDEN_TOOL_CALL_IDS.includes(getT
|
||||
export const isValidToolCall = (toolCall) => {
|
||||
return Boolean(
|
||||
toolCall &&
|
||||
(toolCall.id || toolCall.name || toolCall.function?.name) &&
|
||||
(toolCall.args !== undefined ||
|
||||
toolCall.function?.arguments !== undefined ||
|
||||
toolCall.tool_call_result !== undefined)
|
||||
(toolCall.id || toolCall.name || toolCall.function?.name) &&
|
||||
(toolCall.args !== undefined ||
|
||||
toolCall.function?.arguments !== undefined ||
|
||||
toolCall.tool_call_result !== undefined)
|
||||
)
|
||||
}
|
||||
|
||||
@ -76,15 +76,21 @@ export const parseToolCallArgs = (toolCall) => {
|
||||
}
|
||||
}
|
||||
|
||||
export const enrichTaskToolCall = (toolCall, { subagentRunById, subagentRunByThreadId, subagentOptionBySlug } = {}) => {
|
||||
export const enrichTaskToolCall = (
|
||||
toolCall,
|
||||
{ subagentRunById, subagentRunByThreadId, subagentOptionBySlug } = {}
|
||||
) => {
|
||||
if (getToolCallId(toolCall) !== 'task') return toolCall
|
||||
|
||||
const args = parseToolCallArgs(toolCall)
|
||||
const subagentRun =
|
||||
(toolCall.id ? subagentRunById?.get?.(String(toolCall.id)) : null) ||
|
||||
(args.thread_id ? subagentRunByThreadId?.get?.(String(args.thread_id)) : null)
|
||||
const subagentOption = args.subagent_type ? subagentOptionBySlug?.get?.(String(args.subagent_type)) : null
|
||||
const displayLabel = subagentRun?.subagent_name || subagentOption?.name || subagentRun?.subagent_type || undefined
|
||||
const subagentOption = args.subagent_type
|
||||
? subagentOptionBySlug?.get?.(String(args.subagent_type))
|
||||
: null
|
||||
const displayLabel =
|
||||
subagentRun?.subagent_name || subagentOption?.name || subagentRun?.subagent_type || undefined
|
||||
|
||||
return {
|
||||
...toolCall,
|
||||
@ -105,6 +111,8 @@ export const normalizeToolCalls = (toolCalls, { includeHidden = false, mapToolCa
|
||||
}
|
||||
|
||||
export const enrichTaskToolCalls = (toolCalls, options = {}) =>
|
||||
normalizeToolCalls(toolCalls, { mapToolCall: (toolCall) => enrichTaskToolCall(toolCall, options) })
|
||||
normalizeToolCalls(toolCalls, {
|
||||
mapToolCall: (toolCall) => enrichTaskToolCall(toolCall, options)
|
||||
})
|
||||
|
||||
export const getToolIcon = (toolId) => TOOL_ICON_MAP[toolId] || null
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
<template>
|
||||
<BaseToolCall :tool-call="toolCall" :status="baseStatus" :force-show-result="Boolean(displayResult)">
|
||||
<BaseToolCall
|
||||
:tool-call="toolCall"
|
||||
:status="baseStatus"
|
||||
:force-show-result="Boolean(displayResult)"
|
||||
>
|
||||
<template #header>
|
||||
<div class="sep-header">
|
||||
<span class="note">{{ subagentDisplayName }}</span>
|
||||
@ -67,9 +71,13 @@ const childThreadId = computed(
|
||||
(getSubagentThreadIdByToolCall ? getSubagentThreadIdByToolCall(props.toolCall.id) : '') ||
|
||||
''
|
||||
)
|
||||
const hasToolResult = computed(() => Boolean(props.toolCall.tool_call_result || props.toolCall.result))
|
||||
const hasToolResult = computed(() =>
|
||||
Boolean(props.toolCall.tool_call_result || props.toolCall.result)
|
||||
)
|
||||
// 是否为当前真正在执行的子智能体调用(同一子线程的多次 steer 中只有最后一个为活跃)。
|
||||
const isActiveRun = computed(() => Boolean(activeSubagentToolCallIds?.value?.has(String(props.toolCall.id))))
|
||||
const isActiveRun = computed(() =>
|
||||
Boolean(activeSubagentToolCallIds?.value?.has(String(props.toolCall.id)))
|
||||
)
|
||||
const runStatus = computed(() => {
|
||||
if (props.toolCall.status === 'error') return 'failed'
|
||||
// ongoing 期间工具结果不流式:有结果说明是历史/已落库,按结果展示;
|
||||
@ -114,7 +122,9 @@ const shortDescription = computed(() => {
|
||||
const isRunning = computed(() => runStatus.value === 'running')
|
||||
|
||||
const truncate = (text, limit = 50) => {
|
||||
const value = String(text || '').replace(/\s+/g, ' ').trim()
|
||||
const value = String(text || '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim()
|
||||
if (!value) return ''
|
||||
return value.length > limit ? value.slice(0, limit) + '...' : value
|
||||
}
|
||||
@ -122,7 +132,8 @@ const truncate = (text, limit = 50) => {
|
||||
const formatToolCall = (toolCall) => {
|
||||
const name = toolCall?.name || toolCall?.function?.name || 'tool'
|
||||
const rawArgs = toolCall?.args ?? toolCall?.function?.arguments
|
||||
const args = rawArgs && typeof rawArgs === 'object' ? JSON.stringify(rawArgs) : String(rawArgs ?? '')
|
||||
const args =
|
||||
rawArgs && typeof rawArgs === 'object' ? JSON.stringify(rawArgs) : String(rawArgs ?? '')
|
||||
return `Call(${name}): ${args}`
|
||||
}
|
||||
|
||||
|
||||
@ -826,7 +826,6 @@ onMounted(async () => {
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.user-info-content {
|
||||
|
||||
@ -141,7 +141,12 @@
|
||||
{{ formatExtensionCardTitle(previewSkill.name) }}
|
||||
</div>
|
||||
<div class="skill-preview-meta">
|
||||
<span>{{ sourceTypeLabel(previewSkill.sourceType || previewSkill.source_type) }} Skill</span>
|
||||
<span
|
||||
>{{
|
||||
sourceTypeLabel(previewSkill.sourceType || previewSkill.source_type)
|
||||
}}
|
||||
Skill</span
|
||||
>
|
||||
<span v-if="previewSkill.enabled === false" class="skill-preview-disabled-tag">
|
||||
已禁用
|
||||
</span>
|
||||
@ -525,7 +530,17 @@
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import { RefreshCw, Upload, Computer, BookMarked, History, Trash2, Check, Plus, Minus } from 'lucide-vue-next'
|
||||
import {
|
||||
RefreshCw,
|
||||
Upload,
|
||||
Computer,
|
||||
BookMarked,
|
||||
History,
|
||||
Trash2,
|
||||
Check,
|
||||
Plus,
|
||||
Minus
|
||||
} from 'lucide-vue-next'
|
||||
import { skillApi } from '@/apis/skill_api'
|
||||
import ExtensionCardGrid from './ExtensionCardGrid.vue'
|
||||
import InfoCard from '@/components/shared/InfoCard.vue'
|
||||
@ -601,7 +616,10 @@ const filteredDeletableSkills = computed(() =>
|
||||
)
|
||||
)
|
||||
const canDeletePreviewSkill = computed(
|
||||
() => !!previewSkill.value && canManageSkill(previewSkill.value) && previewSkill.value.sourceType !== 'builtin'
|
||||
() =>
|
||||
!!previewSkill.value &&
|
||||
canManageSkill(previewSkill.value) &&
|
||||
previewSkill.value.sourceType !== 'builtin'
|
||||
)
|
||||
|
||||
// 仓库拉取的技能列表过滤
|
||||
|
||||
@ -402,7 +402,9 @@ const isBuiltinInstalledSkill = computed(() => {
|
||||
})
|
||||
const canManageCurrentSkill = computed(() => currentSkill.value?.can_manage !== false)
|
||||
const isReadOnlySkill = computed(() => isInstalledSkill.value && !canManageCurrentSkill.value)
|
||||
const canEditSkillFiles = computed(() => canManageCurrentSkill.value && !isBuiltinInstalledSkill.value)
|
||||
const canEditSkillFiles = computed(
|
||||
() => canManageCurrentSkill.value && !isBuiltinInstalledSkill.value
|
||||
)
|
||||
const canEditSkillDependencies = computed(
|
||||
() => canManageCurrentSkill.value && !isBuiltinInstalledSkill.value
|
||||
)
|
||||
|
||||
@ -617,7 +617,6 @@ defineExpose({
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
|
||||
.agent-card-icon-image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
|
||||
@ -81,7 +81,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="$slots.tags || (normalizedTags && normalizedTags.length > 0)" class="info-card-tags">
|
||||
<div
|
||||
v-if="$slots.tags || (normalizedTags && normalizedTags.length > 0)"
|
||||
class="info-card-tags"
|
||||
>
|
||||
<slot name="tags">
|
||||
<span
|
||||
v-for="(tag, idx) in normalizedTags"
|
||||
|
||||
@ -171,7 +171,12 @@ export function useAgentRunStream({
|
||||
}
|
||||
}
|
||||
|
||||
const finalizeRunStream = (threadId, runId, touchedThreadIds, { delay = 200, scroll = false } = {}) => {
|
||||
const finalizeRunStream = (
|
||||
threadId,
|
||||
runId,
|
||||
touchedThreadIds,
|
||||
{ delay = 200, scroll = false } = {}
|
||||
) => {
|
||||
const ts = getThreadState(threadId)
|
||||
if (!ts || ts.activeRunId !== runId) return
|
||||
touchedThreadIds.forEach((id) => streamSmoother?.flushThread(id))
|
||||
@ -276,7 +281,11 @@ export function useAgentRunStream({
|
||||
})
|
||||
touchedThreadIds.add(routeThreadId)
|
||||
handleStreamChunk(
|
||||
{ ...payload.chunk, run_id: payload.chunk.run_id || data.run_id || runId, thread_id: routeThreadId },
|
||||
{
|
||||
...payload.chunk,
|
||||
run_id: payload.chunk.run_id || data.run_id || runId,
|
||||
thread_id: routeThreadId
|
||||
},
|
||||
routeThreadId
|
||||
)
|
||||
}
|
||||
@ -307,7 +316,10 @@ export function useAgentRunStream({
|
||||
scheduleRunReconnect(threadId, runId)
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Run SSE closed before terminal event; reconnecting after status check failed:', e)
|
||||
console.warn(
|
||||
'Run SSE closed before terminal event; reconnecting after status check failed:',
|
||||
e
|
||||
)
|
||||
scheduleRunReconnect(threadId, runId)
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,8 +32,16 @@ const run = () => {
|
||||
}
|
||||
|
||||
{
|
||||
assert.throws(() => generatePixelAvatar(''), /requires an id/, 'Empty ID should be treated as invalid data')
|
||||
assert.throws(() => generatePixelAvatar(null), /requires an id/, 'Null ID should be treated as invalid data')
|
||||
assert.throws(
|
||||
() => generatePixelAvatar(''),
|
||||
/requires an id/,
|
||||
'Empty ID should be treated as invalid data'
|
||||
)
|
||||
assert.throws(
|
||||
() => generatePixelAvatar(null),
|
||||
/requires an id/,
|
||||
'Null ID should be treated as invalid data'
|
||||
)
|
||||
console.log('T5 Missing ID fails: PASS')
|
||||
}
|
||||
|
||||
|
||||
@ -7,17 +7,20 @@ const hasVisibleAssistantBody = (message) => {
|
||||
const { content, reasoningContent } = MessageProcessor.parseAssistantMessageBody(message)
|
||||
return Boolean(
|
||||
content ||
|
||||
reasoningContent ||
|
||||
message.error_type ||
|
||||
message.extra_metadata?.error_type ||
|
||||
message.isStoppedByUser
|
||||
reasoningContent ||
|
||||
message.error_type ||
|
||||
message.extra_metadata?.error_type ||
|
||||
message.isStoppedByUser
|
||||
)
|
||||
}
|
||||
|
||||
const defaultEnrichToolCalls = (message) => enrichTaskToolCalls(message?.tool_calls)
|
||||
|
||||
// 将 AI 消息拆成“正文块”和“工具块”,再跨消息合并相邻工具块。
|
||||
export const getConversationDisplayItems = (conv, { enrichToolCalls = defaultEnrichToolCalls } = {}) => {
|
||||
export const getConversationDisplayItems = (
|
||||
conv,
|
||||
{ enrichToolCalls = defaultEnrichToolCalls } = {}
|
||||
) => {
|
||||
if (!Array.isArray(conv?.messages) || conv.messages.length === 0) return []
|
||||
|
||||
const items = []
|
||||
|
||||
@ -419,7 +419,6 @@ export class MessageProcessor {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default MessageProcessor
|
||||
|
||||
Loading…
Reference in New Issue
Block a user