UNPKG

@openai/agents-core

Version:

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

197 lines 7.33 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"; /** * 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 hisstory 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 */ inputFilter; /** * 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, }; } 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 hasOnHandoff = !!config.onHandoff; const hasInputType = !!config.inputType; const hasBothOrNeitherHandoffAndInputType = hasOnHandoff === hasInputType; if (!hasBothOrNeitherHandoffAndInputType) { throw new UserError('You must provide either both `onHandoff` and `inputType` or neither.'); } 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'); } try { // verify that it's valid input but we don't care about the result const parsed = await parser(inputJsonString); if (config.onHandoff) { await config.onHandoff(context, parsed); } } catch (error) { addErrorToCurrentSpan({ message: `Invalid JSON provided`, data: {}, }); if (!logger.dontLogToolData) { logger.error(`Invalid JSON when parsing: ${inputJsonString}. Error: ${error}`); } throw new ModelBehaviorError('Invalid JSON provided'); } } else { await config.onHandoff?.(context); } return agent; } const handoff = new Handoff(agent, onInvokeHandoff); 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 (config.inputType) { const result = getSchemaAndParserFromInputType(config.inputType, handoff.toolName); 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) { 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