UNPKG

@openai/agents-core

Version:

The OpenAI Agents SDK is a lightweight yet powerful framework for building multi-agent workflows.

252 lines • 10.1 kB
import { ModelBehaviorError, UserError } from "./errors.mjs"; import { toFunctionToolName } from "./utils/tools.mjs"; import { getSchemaAndParserFromInputType } from "./utils/tools.mjs"; import { addErrorToCurrentSpan } from "./tracing/context.mjs"; import logger from "./logger.mjs"; import { isAbortError } from "./utils/abortSignals.mjs"; import { isAsyncStandardSchemaValidationError } from "./utils/standardSchema.mjs"; // Direct invocations retain their released parse error. Runner can propagate a fatal // validator failure without attaching its potentially sensitive cause to that error. const fatalHandoffInputErrors = new WeakMap(); /** @internal */ export function getFatalHandoffInputError(error) { return error instanceof ModelBehaviorError ? fatalHandoffInputErrors.get(error) : undefined; } /** * Generates the message that will be given as tool output to the model that requested the handoff. * * @param agent The agent to transfer to * @returns The message that will be given as tool output to the model that requested the handoff */ export function getTransferMessage(agent) { return JSON.stringify({ assistant: agent.name }); } /** * The default name of the tool that represents the handoff. * * @param agent The agent to transfer to * @returns The name of the tool that represents the handoff */ function defaultHandoffToolName(agent) { return `transfer_to_${toFunctionToolName(agent.name)}`; } /** * Generates the description of the tool that represents the handoff. * * @param agent The agent to transfer to * @returns The description of the tool that represents the handoff */ function defaultHandoffToolDescription(agent) { return `Handoff to the ${agent.name} agent to handle the request. ${agent.handoffDescription ?? ''}`; } export class Handoff { /** * The name of the tool that represents the handoff. */ toolName; /** * The description of the tool that represents the handoff. */ toolDescription; /** * The JSON schema for the handoff input. Can be empty if the handoff does not take an input */ inputJsonSchema = { type: 'object', properties: {}, required: [], additionalProperties: false, }; /** * Whether the input JSON schema is in strict mode. We **strongly** recommend setting this to * true, as it increases the likelihood of correct JSON input. */ strictJsonSchema = true; /** * The function that invokes the handoff. The parameters passed are: * 1. The handoff run context * 2. The arguments from the LLM, as a JSON string. Empty string if inputJsonSchema is empty. * * Must return an agent */ onInvokeHandoff; /** * The name of the agent that is being handed off to. */ agentName; /** * A function that filters the inputs that are passed to the next agent. By default, the new agent * sees the entire conversation history. In some cases, you may want to filter inputs e.g. to * remove older inputs, or remove tools from existing inputs. * * The function will receive the entire conversation history so far, including the input item * that triggered the handoff and a tool call output item representing the handoff tool's output. * * You are free to modify the input history or new items as you see fit. The next agent that runs * will receive `handoffInputData.allItems`. * Filtered handoffs cannot use `conversationId` or `previousResponseId`, including no-op filters. * Use client-managed history or a Session without either continuation option instead. */ inputFilter; /** * Lets Runner continue on the source agent after a failed invocation. Return only * application-approved feedback for the model, or throw to propagate the failure. * Omitting this callback propagates invocation errors. Cancellation, transfer hooks, * input filters and Realtime handoffs retain their existing behavior. Recovery * preserves any external effects already performed by the invocation. */ errorFunction; /** * The agent that is being handed off to. */ agent; /** * Returns a function tool definition that can be used to invoke the handoff. */ getHandoffAsFunctionTool() { return { type: 'function', name: this.toolName, description: this.toolDescription, parameters: this.inputJsonSchema, strict: this.strictJsonSchema, }; } /** * Makes a copy of this handoff, with the given fields changed. * * Tool name and description default to the original handoff's values so adapters can replace * the runtime target without changing the model-facing tool contract. */ clone(overrides = {}) { const agent = overrides.agent ?? this.agent; const onInvokeHandoff = overrides.onInvokeHandoff ?? (overrides.agent ? async (context, args) => { await this.onInvokeHandoff(context, args); return agent; } : this.onInvokeHandoff); const cloned = new Handoff(agent, onInvokeHandoff); cloned.agentName = overrides.agentName ?? (overrides.agent ? overrides.agent.name : this.agentName); cloned.toolName = overrides.toolName ?? this.toolName; cloned.toolDescription = overrides.toolDescription ?? this.toolDescription; cloned.inputJsonSchema = overrides.inputJsonSchema ?? this.inputJsonSchema; cloned.strictJsonSchema = overrides.strictJsonSchema ?? this.strictJsonSchema; cloned.inputFilter = overrides.inputFilter ?? this.inputFilter; cloned.errorFunction = overrides.errorFunction ?? this.errorFunction; cloned.isEnabled = overrides.isEnabled ?? this.isEnabled; return cloned; } isEnabled = async () => true; constructor(agent, onInvokeHandoff) { this.agentName = agent.name; this.onInvokeHandoff = onInvokeHandoff; this.toolName = defaultHandoffToolName(agent); this.toolDescription = defaultHandoffToolDescription(agent); this.agent = agent; } } /** * Creates a handoff from an agent. Handoffs are automatically created when you pass an agent * into the `handoffs` option of the `Agent` constructor. Alternatively, you can use this function * to create a handoff manually, giving you more control over configuration. * * @template TContext The context of the handoff * @template TOutput The output type of the handoff * @template TInputType The input type of the handoff */ export function handoff(agent, config = {}) { let parser = undefined; const inputType = config.inputType; const hasInputType = inputType != null; if (hasInputType && config.onHandoff == null) { throw new UserError('You must provide `onHandoff` when `inputType` is provided.'); } async function onInvokeHandoff(context, inputJsonString) { if (parser) { if (!inputJsonString) { addErrorToCurrentSpan({ message: `Handoff function expected non empty input but got: ${inputJsonString}`, data: { details: `input is empty`, }, }); throw new ModelBehaviorError('Handoff function expected non empty input'); } let parsed; try { parsed = await parser(inputJsonString); } catch (error) { addErrorToCurrentSpan({ message: `Invalid JSON provided`, data: {}, }); if (!logger.dontLogToolData) { logger.error(`Invalid JSON when parsing: ${inputJsonString}. Error: ${error}`); } const invalidInput = new ModelBehaviorError('Invalid JSON provided'); if (isAbortError(error) || isAsyncStandardSchemaValidationError(error)) { fatalHandoffInputErrors.set(invalidInput, error); } throw invalidInput; } if (config.onHandoff) { await config.onHandoff(context, parsed); } } else { await config.onHandoff?.(context); } return agent; } const handoff = new Handoff(agent, onInvokeHandoff); handoff.errorFunction = config.errorFunction; if (typeof config.isEnabled === 'function') { const predicate = config.isEnabled; handoff.isEnabled = async ({ runContext, agent }) => { const result = await predicate({ runContext, agent }); return Boolean(result); }; } else if (typeof config.isEnabled === 'boolean') { handoff.isEnabled = async () => config.isEnabled; } if (hasInputType) { const result = getSchemaAndParserFromInputType(inputType, handoff.toolName, { strict: true }); handoff.inputJsonSchema = result.schema; handoff.strictJsonSchema = true; parser = result.parser; } if (config.toolNameOverride) { handoff.toolName = config.toolNameOverride; } if (config.toolDescriptionOverride) { handoff.toolDescription = config.toolDescriptionOverride; } if (config.inputFilter != null) { handoff.inputFilter = config.inputFilter; } return handoff; } /** * Returns a handoff for the given agent. If the agent is already wrapped into a handoff, * it will be returned as is. Otherwise, a new handoff instance will be created. * * @template TContext The context of the handoff * @template TOutput The output type of the handoff */ export function getHandoff(agent) { if (agent instanceof Handoff) { return agent; } return handoff(agent); } //# sourceMappingURL=handoff.mjs.map