9 changed files with 290 additions and 17 deletions
Binary file not shown.
@ -0,0 +1,122 @@ |
|||||
|
import { z } from "zod"; |
||||
|
import type { JSONSchema7 } from "json-schema"; |
||||
|
import type { ToolExecutor, ToolResult } from "../../registry"; |
||||
|
import type { AgentInvokeToolConfig } from "./config"; |
||||
|
import { invokeAgent } from "../../../agent/collaboration"; |
||||
|
import { listAgents } from "../../../agent/agent"; |
||||
|
|
||||
|
export const agentInvokeInputSchema = z.object({ |
||||
|
agentSlug: z.string().min(1).max(100).describe("目标 agent 的 slug"), |
||||
|
input: z.string().min(1).max(50000).describe("传递给目标 agent 的任务描述/输入内容"), |
||||
|
}); |
||||
|
|
||||
|
type AgentInvokeInput = z.infer<typeof agentInvokeInputSchema>; |
||||
|
|
||||
|
export const agentInvokeExecutor: ToolExecutor<AgentInvokeToolConfig> = { |
||||
|
buildInputSchema(_config: AgentInvokeToolConfig): JSONSchema7 { |
||||
|
return { |
||||
|
type: "object", |
||||
|
properties: { |
||||
|
agentSlug: { |
||||
|
type: "string", |
||||
|
description: "目标 agent 的 slug(唯一标识符)", |
||||
|
}, |
||||
|
input: { |
||||
|
type: "string", |
||||
|
description: "传递给目标 agent 的任务描述或输入内容", |
||||
|
}, |
||||
|
}, |
||||
|
required: ["agentSlug", "input"], |
||||
|
}; |
||||
|
}, |
||||
|
|
||||
|
buildDescription(_config: AgentInvokeToolConfig): string { |
||||
|
return "调用另一个 agent 来处理子任务。目标 agent 必须已启用且标记为可调用(isCallable)。传入 agent slug 和任务描述,返回目标 agent 的回复内容。支持多级调用链,但有递归深度限制。"; |
||||
|
}, |
||||
|
|
||||
|
async buildDescriptionAsync(_config: AgentInvokeToolConfig, ctx?: { agentId?: number | null }): Promise<string> { |
||||
|
const base = this.buildDescription(_config); |
||||
|
const callableAgents = await listAgents(true); |
||||
|
const currentAgentId = ctx?.agentId ?? null; |
||||
|
const available = callableAgents.filter((a) => a.isCallable === 1 && a.id !== currentAgentId); |
||||
|
if (available.length === 0) { |
||||
|
return `${base}\n\n当前没有可调用的 Agent。请勿调用此工具。`; |
||||
|
} |
||||
|
const agentList = available.map((a) => `- slug: "${a.slug}" — ${a.name}: ${a.description ?? "无描述"}`).join("\n"); |
||||
|
return `${base}\n\n可调用的 Agent 列表(通过 agentSlug 参数指定):\n${agentList}`; |
||||
|
}, |
||||
|
|
||||
|
formatResult(result: ToolResult, _config: AgentInvokeToolConfig): string { |
||||
|
if (!result.success) { |
||||
|
return `Agent 调用失败: ${result.error ?? "未知错误"}`; |
||||
|
} |
||||
|
const meta = result.metadata; |
||||
|
const agentSlug = meta?.agentSlug ?? "unknown"; |
||||
|
const depth = meta?.recursionDepth ?? 0; |
||||
|
const duration = meta?.durationMs ?? 0; |
||||
|
const data = typeof result.data === "string" ? result.data : JSON.stringify(result.data); |
||||
|
return `[Agent "${agentSlug}" 调用完成 | 深度: ${depth} | 耗时: ${duration}ms]\n${data}`; |
||||
|
}, |
||||
|
|
||||
|
async execute( |
||||
|
input: unknown, |
||||
|
config: AgentInvokeToolConfig, |
||||
|
ctx: { userId: number | null; agentId?: number | null; recursionDepth?: number }, |
||||
|
): Promise<ToolResult> { |
||||
|
const start = Date.now(); |
||||
|
|
||||
|
const parsed = agentInvokeInputSchema.safeParse(input); |
||||
|
if (!parsed.success) { |
||||
|
return { |
||||
|
success: false, |
||||
|
data: null, |
||||
|
error: `参数校验失败: ${parsed.error.message}`, |
||||
|
metadata: { durationMs: Date.now() - start }, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
const data: AgentInvokeInput = parsed.data; |
||||
|
const currentDepth = ctx.recursionDepth ?? 0; |
||||
|
|
||||
|
const result = await invokeAgent( |
||||
|
{ |
||||
|
agentSlug: data.agentSlug, |
||||
|
input: data.input, |
||||
|
context: { |
||||
|
userId: ctx.userId, |
||||
|
callerAgentId: ctx.agentId ?? null, |
||||
|
recursionDepth: currentDepth + 1, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
maxRecursionDepth: config.maxRecursionDepth, |
||||
|
timeoutMs: config.timeoutMs, |
||||
|
maxOutputTokens: config.maxOutputTokens, |
||||
|
}, |
||||
|
); |
||||
|
|
||||
|
if (!result.ok) { |
||||
|
return { |
||||
|
success: false, |
||||
|
data: null, |
||||
|
error: result.error ?? `调用 agent ${data.agentSlug} 失败`, |
||||
|
metadata: { durationMs: Date.now() - start }, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
let output = result.output; |
||||
|
if (output.length > config.resultTruncateLength) { |
||||
|
output = output.slice(0, config.resultTruncateLength) + `\n...[结果已截断,原始长度 ${output.length} 字符]`; |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
success: true, |
||||
|
data: output, |
||||
|
metadata: { |
||||
|
durationMs: Date.now() - start, |
||||
|
agentSlug: data.agentSlug, |
||||
|
recursionDepth: currentDepth + 1, |
||||
|
}, |
||||
|
}; |
||||
|
}, |
||||
|
}; |
||||
@ -0,0 +1,25 @@ |
|||||
|
import { z } from "zod"; |
||||
|
|
||||
|
export const agentInvokeConfigSchema = z.object({ |
||||
|
maxRecursionDepth: z.number().int().positive().max(10).default(3), |
||||
|
resultTruncateLength: z.number().int().positive().max(50000).default(4000), |
||||
|
timeoutMs: z.number().int().positive().max(120000).default(30000), |
||||
|
maxOutputTokens: z.number().int().positive().max(32768).default(4096), |
||||
|
}); |
||||
|
|
||||
|
export type AgentInvokeToolConfig = z.infer<typeof agentInvokeConfigSchema>; |
||||
|
|
||||
|
export const DEFAULT_AGENT_INVOKE_CONFIG: AgentInvokeToolConfig = { |
||||
|
maxRecursionDepth: 3, |
||||
|
resultTruncateLength: 8000, |
||||
|
timeoutMs: 60000, |
||||
|
maxOutputTokens: 8192, |
||||
|
}; |
||||
|
|
||||
|
export function parseAgentInvokeConfig(raw: unknown): AgentInvokeToolConfig { |
||||
|
const parsed = agentInvokeConfigSchema.safeParse(raw); |
||||
|
if (!parsed.success) { |
||||
|
throw new Error(`Invalid agent-invoke config: ${parsed.error.message}`); |
||||
|
} |
||||
|
return parsed.data; |
||||
|
} |
||||
Loading…
Reference in new issue