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