feat: 全新智能体功能

- 基于先进智能体框架,增加智能体编排功能
- 增加智能体聊天,并对接持久化
This commit is contained in:
2026-05-25 11:42:48 +08:00
parent 6c3d98eaac
commit 72df00f25b
168 changed files with 22045 additions and 400 deletions

View File

@@ -0,0 +1,371 @@
import type {
AgentCapabilityKind,
AgentDraftState,
AgentInfo,
AgentKnowledgeBinding,
AgentToolBinding,
AgentValidationIssue,
} from '../types';
import {computed, reactive} from 'vue';
const BASE_NODE_ID = 'agent-base';
const SAFE_TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
function createLocalId(prefix: string) {
return `${prefix}-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
}
function isSafeToolName(name?: string) {
return SAFE_TOOL_NAME_PATTERN.test(String(name || ''));
}
function buildFallbackToolName(prefix: string, resource?: Record<string, any>) {
const id = resource?.id ? String(resource.id) : createLocalId(prefix);
return `${prefix}_${id}`;
}
function resolveToolName(
kind: Exclude<AgentCapabilityKind, 'knowledge'>,
resource?: Record<string, any>,
) {
if (isSafeToolName(resource?.englishName)) {
return String(resource?.englishName);
}
if (isSafeToolName(resource?.name)) {
return String(resource?.name);
}
return buildFallbackToolName(
kind === 'workflow' ? 'workflow' : 'plugin',
resource,
);
}
function normalizeBindingToolName(binding: AgentToolBinding) {
if (isSafeToolName(binding.toolName)) {
return String(binding.toolName);
}
const kind =
String(binding.toolType || '').toUpperCase() === 'WORKFLOW'
? 'workflow'
: 'plugin';
const resource = {
...(binding.resourceSnapshot || {}),
...(binding.resourceSummary || {}),
id:
binding.targetId ||
binding.resourceSummary?.id ||
binding.resourceSnapshot?.id,
};
return resolveToolName(kind, resource);
}
export function createEmptyAgent(): AgentInfo {
return {
name: '未命名智能体',
description: '',
avatar: '',
categoryId: '',
modelId: '',
promptConfigJson: { systemPrompt: '' },
memoryConfigJson: {
compressionParameter: {
enabled: true,
msgThreshold: 12,
lastKeep: 8,
minCompressionTokenThreshold: 6000,
},
},
status: 1,
};
}
function normalizeAgent(agent?: AgentInfo): AgentInfo {
const source = agent || {};
const memoryConfig = source.memoryConfigJson || {};
const compressionParameter = memoryConfig.compressionParameter || {};
const defaultCompressionParameter =
createEmptyAgent().memoryConfigJson?.compressionParameter || {};
const legacyMsgThreshold = memoryConfig.maxAttachedMessageCount;
return {
...createEmptyAgent(),
...source,
promptConfigJson: {
systemPrompt: '',
...source.promptConfigJson,
},
memoryConfigJson: {
...memoryConfig,
compressionParameter: {
enabled: true,
...compressionParameter,
msgThreshold:
compressionParameter.msgThreshold ??
legacyMsgThreshold ??
defaultCompressionParameter.msgThreshold,
lastKeep:
compressionParameter.lastKeep ?? defaultCompressionParameter.lastKeep,
minCompressionTokenThreshold:
compressionParameter.minCompressionTokenThreshold ??
defaultCompressionParameter.minCompressionTokenThreshold,
},
},
};
}
function normalizeKnowledgeBinding(
binding: AgentKnowledgeBinding,
index: number,
): AgentKnowledgeBinding {
return {
...binding,
enabled: binding.enabled !== false,
localId:
binding.localId || String(binding.id || createLocalId('knowledge')),
optionsJson: {
limit: 5,
scoreThreshold: 0.5,
...binding.optionsJson,
},
retrievalMode: binding.retrievalMode || 'HYBRID',
sortNo: binding.sortNo ?? index + 1,
};
}
function normalizeToolBinding(
binding: AgentToolBinding,
index: number,
): AgentToolBinding {
return {
...binding,
enabled: binding.enabled !== false,
hitlEnabled: Boolean(binding.hitlEnabled),
localId:
binding.localId ||
String(
binding.id ||
createLocalId(String(binding.toolType || 'tool').toLowerCase()),
),
toolName: normalizeBindingToolName(binding),
optionsJson: binding.optionsJson || {},
sortNo: binding.sortNo ?? index + 1,
};
}
export function useAgentDesignerState() {
const state = reactive<AgentDraftState>({
agent: createEmptyAgent(),
knowledgeBindings: [],
toolBindings: [],
selectedNodeId: BASE_NODE_ID,
panelMode: 'base',
dirty: false,
});
const selectedCapability = computed(() => {
if (state.selectedNodeId.startsWith('knowledge:')) {
const localId = state.selectedNodeId.slice('knowledge:'.length);
return {
kind: 'knowledge' as AgentCapabilityKind,
binding: state.knowledgeBindings.find(
(item) => item.localId === localId,
),
};
}
if (state.selectedNodeId.startsWith('tool:')) {
const localId = state.selectedNodeId.slice('tool:'.length);
const binding = state.toolBindings.find(
(item) => item.localId === localId,
);
return {
kind:
String(binding?.toolType || '').toUpperCase() === 'WORKFLOW'
? ('workflow' as AgentCapabilityKind)
: ('plugin' as AgentCapabilityKind),
binding,
};
}
return undefined;
});
function markDirty() {
state.dirty = true;
}
function reset(agent?: AgentInfo) {
state.agent = normalizeAgent(agent);
state.knowledgeBindings = (agent?.knowledgeBindings || []).map(
(binding, index) => normalizeKnowledgeBinding(binding, index),
);
state.toolBindings = (agent?.toolBindings || []).map((binding, index) =>
normalizeToolBinding(binding, index),
);
state.selectedNodeId = BASE_NODE_ID;
state.panelMode = 'base';
state.dirty = false;
}
function selectBase() {
state.selectedNodeId = BASE_NODE_ID;
state.panelMode = 'base';
}
function selectNode(nodeId: string) {
state.selectedNodeId = nodeId;
state.panelMode = nodeId === BASE_NODE_ID ? 'base' : 'capability';
}
function openTryout() {
state.panelMode = 'tryout';
}
function addKnowledgeNode(resource?: Record<string, any>) {
const binding = normalizeKnowledgeBinding(
{
knowledgeId: resource?.id ? String(resource.id) : '',
resourceSummary: resource || {},
},
state.knowledgeBindings.length,
);
state.knowledgeBindings.push(binding);
state.selectedNodeId = `knowledge:${binding.localId}`;
state.panelMode = 'capability';
markDirty();
}
function addToolNode(
kind: Exclude<AgentCapabilityKind, 'knowledge'>,
resource?: Record<string, any>,
) {
const toolType = kind === 'workflow' ? 'WORKFLOW' : 'PLUGIN';
const binding = normalizeToolBinding(
{
toolType,
targetId: resource?.id ? String(resource.id) : '',
toolName: resolveToolName(kind, resource),
resourceSummary: resource || {},
},
state.toolBindings.length,
);
state.toolBindings.push(binding);
state.selectedNodeId = `tool:${binding.localId}`;
state.panelMode = 'capability';
markDirty();
}
function removeSelectedCapability() {
const selected = selectedCapability.value;
if (!selected?.binding?.localId) return;
if (selected.kind === 'knowledge') {
state.knowledgeBindings = state.knowledgeBindings.filter(
(item) => item.localId !== selected.binding?.localId,
);
} else {
state.toolBindings = state.toolBindings.filter(
(item) => item.localId !== selected.binding?.localId,
);
}
selectBase();
markDirty();
}
function validate(): AgentValidationIssue[] {
const issues: AgentValidationIssue[] = [];
if (!String(state.agent.name || '').trim()) {
issues.push({
nodeId: BASE_NODE_ID,
field: 'name',
message: '请填写 Agent 名称',
});
}
if (!state.agent.modelId) {
issues.push({
nodeId: BASE_NODE_ID,
field: 'modelId',
message: '请选择模型',
});
}
state.knowledgeBindings.forEach((binding) => {
if (!binding.knowledgeId) {
issues.push({
nodeId: `knowledge:${binding.localId}`,
field: 'knowledgeId',
message: '请选择知识库',
});
}
});
state.toolBindings.forEach((binding) => {
const nodeId = `tool:${binding.localId}`;
if (!binding.targetId) {
issues.push({ nodeId, field: 'targetId', message: '请选择能力资源' });
}
if (!String(binding.toolName || '').trim()) {
issues.push({ nodeId, field: 'toolName', message: '请填写工具名称' });
} else if (!isSafeToolName(binding.toolName)) {
issues.push({
nodeId,
field: 'toolName',
message: '工具名称只能包含英文、数字、下划线或中划线',
});
}
});
return issues;
}
function buildPayloadAgent(): AgentInfo {
const memoryConfigJson = state.agent.memoryConfigJson || {};
const compressionParameter = memoryConfigJson.compressionParameter || {};
const { maxAttachedMessageCount, ...restMemoryConfigJson } =
memoryConfigJson;
return {
...state.agent,
memoryConfigJson: {
...restMemoryConfigJson,
compressionParameter: {
...compressionParameter,
enabled: true,
},
},
status: state.agent.status ?? 1,
};
}
function buildKnowledgePayload(agentId?: number | string) {
return state.knowledgeBindings.map((binding, index) => ({
...binding,
agentId,
enabled: binding.enabled !== false,
sortNo: index + 1,
}));
}
function buildToolPayload(agentId?: number | string) {
return state.toolBindings.map((binding, index) => ({
...binding,
agentId,
enabled: binding.enabled !== false,
hitlEnabled: Boolean(binding.hitlEnabled),
sortNo: index + 1,
}));
}
reset();
return {
BASE_NODE_ID,
state,
selectedCapability,
addKnowledgeNode,
addToolNode,
buildKnowledgePayload,
buildPayloadAgent,
buildToolPayload,
markDirty,
openTryout,
removeSelectedCapability,
reset,
selectBase,
selectNode,
validate,
};
}