Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/loud-bags-enter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@openai/agents-realtime': patch
'@openai/agents-core': patch
---

feat: #478 add isEnabled to handoffs & agents as tools
6 changes: 4 additions & 2 deletions examples/agent-patterns/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

# Agent Pattern Examples

This directory contains small scripts that demonstrate different agent patterns.
Expand All @@ -8,6 +7,10 @@ Run them with `pnpm` using the commands shown below.
```bash
pnpm examples:agents-as-tools
```
- `agents-as-tools-conditional.ts` – Enable language tools based on user preference.
```bash
pnpm examples:agents-as-tools-conditional
```
- `deterministic.ts` – Fixed agent flow with gating and quality checks.
```bash
pnpm examples:deterministic
Expand Down Expand Up @@ -52,4 +55,3 @@ Run them with `pnpm` using the commands shown below.
```bash
pnpm -F agent-patterns start:streaming-guardrails
```

132 changes: 132 additions & 0 deletions examples/agent-patterns/agents-as-tools-conditional.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { Agent, RunContext, run, withTrace } from '@openai/agents';
import { createInterface } from 'node:readline/promises';

const introduction =
`Agents-as-Tools with Conditional Enabling\n\n` +
'This demonstrates how language response tools are dynamically enabled based on user preferences.\n';

type LanguagePreference = 'spanish_only' | 'french_spanish' | 'european';

type AppContext = {
languagePreference: LanguagePreference;
};

function frenchSpanishEnabled({
runContext,
}: {
runContext: RunContext<AppContext>;
}) {
return (
runContext.context.languagePreference === 'french_spanish' ||
runContext.context.languagePreference === 'european'
);
}

function europeanEnabled({
runContext,
}: {
runContext: RunContext<AppContext>;
}) {
return runContext.context.languagePreference === 'european';
}

const spanishAgent = new Agent<AppContext>({
name: 'spanish_agent',
instructions:
"You respond in Spanish. Always reply to the user's question in Spanish.",
});

const frenchAgent = new Agent<AppContext>({
name: 'french_agent',
instructions:
"You respond in French. Always reply to the user's question in French.",
});

const italianAgent = new Agent<AppContext>({
name: 'italian_agent',
instructions:
"You respond in Italian. Always reply to the user's question in Italian.",
});

const orchestrator = new Agent<AppContext>({
name: 'orchestrator',
instructions: [
'You are a multilingual assistant. You use the tools given to you to respond to users.',
'You must call all available tools to provide responses in different languages.',
'You never respond in languages yourself, you always use the provided tools.',
].join(' '),
tools: [
spanishAgent.asTool({
toolName: 'respond_spanish',
toolDescription: "Respond to the user's question in Spanish.",
isEnabled: true,
}),
frenchAgent.asTool({
toolName: 'respond_french',
toolDescription: "Respond to the user's question in French.",
isEnabled: frenchSpanishEnabled,
}),
italianAgent.asTool({
toolName: 'respond_italian',
toolDescription: "Respond to the user's question in Italian.",
isEnabled: europeanEnabled,
}),
],
});

async function main() {
const rl = createInterface({ input: process.stdin, output: process.stdout });

try {
console.log(introduction);
console.log('Choose language preference:');
console.log('1. Spanish only (1 tool)');
console.log('2. French and Spanish (2 tools)');
console.log('3. European languages (3 tools)');

const choice = (await rl.question('\nSelect option (1-3): ')).trim();
const preferenceMap: Record<string, LanguagePreference> = {
'1': 'spanish_only',
'2': 'french_spanish',
'3': 'european',
};
const languagePreference = preferenceMap[choice] ?? 'spanish_only';

const runContext = new RunContext<AppContext>({ languagePreference });
const availableTools = await orchestrator.getAllTools(runContext);
const toolNames = availableTools.map((tool) => tool.name).join(', ');

console.log(`\nLanguage preference: ${languagePreference}`);
console.log(
toolNames
? `Available tools: ${toolNames} (the model sees ${availableTools.length} tools).`
: 'Available tools: none.',
);

const userRequest = await rl.question(
'\nAsk a question and see responses in the available languages:\n',
);

if (!userRequest.trim()) {
console.log('No question provided, exiting.');
return;
}

console.log('\nProcessing request...');
await withTrace('Conditional tool access', async () => {
const result = await run(orchestrator, userRequest, {
context: runContext,
});
console.log(`\nResponse:\n${result.finalOutput}`);
});
} finally {
await rl.close();
}
}

if (require.main === module) {
main().catch((error) => {
console.error('Error:', error);
process.exitCode = 1;
});
}
1 change: 1 addition & 0 deletions examples/agent-patterns/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"scripts": {
"build-check": "tsc --noEmit",
"start:agents-as-tools": "tsx agents-as-tools.ts",
"start:agents-as-tools-conditional": "tsx agents-as-tools-conditional.ts",
"start:deterministic": "tsx deterministic.ts",
"start:forcing-tool-use": "tsx forcing-tool-use.ts -t default",
"start:human-in-the-loop-stream": "tsx human-in-the-loop-stream.ts",
Expand Down
6 changes: 6 additions & 0 deletions examples/handoffs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,9 @@ pnpm -F handoffs start
```

`types.ts` demonstrates typed outputs. A triage agent inspects the message and hands off to either `firstAgent` or `secondAgent`, each with their own Zod schema for structured output. The script logs which agent produced the final result.

`is-enabled.ts` demonstrates gating handoffs with feature-like preferences. Run it with:

```bash
pnpm -F handoffs start:is-enabled
```
142 changes: 142 additions & 0 deletions examples/handoffs/is-enabled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { Agent, RunContext, handoff, run, withTrace } from '@openai/agents';
import { createInterface } from 'node:readline/promises';

const introduction =
`Handoffs with Conditional Enabling\n\n` +
'This routes conversations to language specialists based on user preferences.\n';

type LanguagePreference = 'spanish_only' | 'french_spanish' | 'european';

type AppContext = {
languagePreference: LanguagePreference;
};

function frenchSpanishEnabled({
runContext,
}: {
runContext: RunContext<AppContext>;
}) {
return (
runContext.context.languagePreference === 'french_spanish' ||
runContext.context.languagePreference === 'european'
);
}

function europeanEnabled({
runContext,
}: {
runContext: RunContext<AppContext>;
}) {
return runContext.context.languagePreference === 'european';
}

const spanishAgent = new Agent<AppContext>({
name: 'spanish_agent',
instructions:
"You respond in Spanish. Always reply to the user's question in Spanish.",
handoffDescription: 'Spanish speaking specialist.',
});

const frenchAgent = new Agent<AppContext>({
name: 'french_agent',
instructions:
"You respond in French. Always reply to the user's question in French.",
handoffDescription: 'French speaking specialist.',
});

const italianAgent = new Agent<AppContext>({
name: 'italian_agent',
instructions:
"You respond in Italian. Always reply to the user's question in Italian.",
handoffDescription: 'Italian speaking specialist.',
});

const spanishHandoff = handoff(spanishAgent, {
toolDescriptionOverride:
'Transfer the conversation to the Spanish specialist.',
});

const frenchHandoff = handoff(frenchAgent, {
isEnabled: frenchSpanishEnabled,
toolDescriptionOverride:
'Transfer the conversation to the French specialist.',
});

const italianHandoff = handoff(italianAgent, {
isEnabled: europeanEnabled,
toolDescriptionOverride:
'Transfer the conversation to the Italian specialist.',
});

const triageAgent = new Agent<AppContext>({
name: 'triage_agent',
instructions: [
'You triage multilingual conversations.',
'When a specialist handoff is available you immediately transfer the conversation.',
'If no specialist is available you answer in English and explain the limitation.',
].join(' '),
handoffs: [spanishHandoff, frenchHandoff, italianHandoff],
});

async function main() {
const rl = createInterface({ input: process.stdin, output: process.stdout });

try {
console.log(introduction);
console.log('Choose language preference:');
console.log('1. Spanish only (handoff to Spanish agent)');
console.log(
'2. French and Spanish (handoffs to Spanish and French agents)',
);
console.log('3. European languages (handoffs to all three agents)');

const choice = (await rl.question('\nSelect option (1-3): ')).trim();
const preferenceMap: Record<string, LanguagePreference> = {
'1': 'spanish_only',
'2': 'french_spanish',
'3': 'european',
};
const languagePreference = preferenceMap[choice] ?? 'spanish_only';

const runContext = new RunContext<AppContext>({ languagePreference });
const enabledHandoffs = await triageAgent.getEnabledHandoffs(runContext);
const handoffNames = enabledHandoffs
.map((handoffItem) => handoffItem.toolName)
.join(', ');

console.log(`\nLanguage preference: ${languagePreference}`);
console.log(
handoffNames
? `Available handoffs: ${handoffNames} (the model sees ${enabledHandoffs.length} options).`
: 'Available handoffs: none.',
);

const userRequest = await rl.question(
'\nAsk a question and see which specialist handles the conversation:\n',
);

if (!userRequest.trim()) {
console.log('No question provided, exiting.');
return;
}

console.log('\nProcessing request...');
await withTrace('Conditional handoff access', async () => {
const result = await run(triageAgent, userRequest, {
context: runContext,
});
const finalAgentName = result.lastAgent?.name ?? triageAgent.name;
console.log(`\nFinal agent: ${finalAgentName}`);
console.log(`\nResponse:\n${result.finalOutput}`);
});
} finally {
await rl.close();
}
}

if (require.main === module) {
main().catch((error) => {
console.error('Error:', error);
process.exitCode = 1;
});
}
3 changes: 2 additions & 1 deletion examples/handoffs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"scripts": {
"build-check": "tsc --noEmit",
"start": "tsx index.ts",
"start:types": "tsx types.ts"
"start:types": "tsx types.ts",
"start:is-enabled": "tsx is-enabled.ts"
}
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"lint:fix": "eslint --fix",
"examples:basic": "pnpm -F basic start",
"examples:agents-as-tools": "pnpm -F agent-patterns start:agents-as-tools",
"examples:agents-as-tools-conditional": "pnpm -F agent-patterns start:agents-as-tools-conditional",
"examples:deterministic": "pnpm -F agent-patterns start:deterministic",
"examples:parallelization": "pnpm -F agent-patterns start:parallelization",
"examples:human-in-the-loop": "pnpm -F agent-patterns start:human-in-the-loop",
Expand Down
Loading