Skip to content

Commit 47c8d8e

Browse files
committed
fix: Add error handling for null output and improve agent handoff workflow
1 parent 3f6f953 commit 47c8d8e

File tree

4 files changed

+143
-23
lines changed

4 files changed

+143
-23
lines changed

examples/guardrail.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,30 @@ const minimumLengthGuardrail = outputGuardrail({
3333
name: "minimum_length_check",
3434
check: async (context, agent, output) => {
3535
const minimumLength = 20; // characters
36-
const isTooShort = output.length < minimumLength;
36+
37+
// Safely handle null/undefined output
38+
if (!output) {
39+
return {
40+
output_info: {
41+
checked_for: "minimum length",
42+
required_length: minimumLength,
43+
actual_length: 0,
44+
is_too_short: true,
45+
error: "Output is null or undefined"
46+
},
47+
tripwire_triggered: true
48+
};
49+
}
50+
51+
// Convert output to string if it isn't already
52+
const outputStr = String(output);
53+
const isTooShort = outputStr.length < minimumLength;
3754

3855
return {
3956
output_info: {
4057
checked_for: "minimum length",
4158
required_length: minimumLength,
42-
actual_length: output.length,
59+
actual_length: outputStr.length,
4360
is_too_short: isTooShort
4461
},
4562
tripwire_triggered: isTooShort

examples/handoffs.js

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,76 @@ const { Agent, Runner } = require('../src/index');
33
// Set your OpenAI API key
44
const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
55

6+
// Provider configuration
7+
const providerConfig = {
8+
apiKey: OPENAI_API_KEY
9+
};
10+
611
async function main() {
12+
// Create the Spanish agent with explicit configuration
713
const spanishAgent = new Agent({
8-
name: "Spanish Agent",
9-
instructions: "You only speak Spanish.",
14+
name: "Spanish_Agent", // Use underscore for consistency
15+
instructions: "You are a helpful assistant that only speaks Spanish. Always respond in Spanish to any questions or requests.",
16+
model: "gpt-4",
17+
provider: "openai",
18+
providerConfig,
19+
modelSettings: {
20+
temperature: 0.7,
21+
max_tokens: 500
22+
}
1023
});
1124

25+
// Create the English agent with explicit configuration
1226
const englishAgent = new Agent({
13-
name: "English Agent",
14-
instructions: "You only speak English",
27+
name: "English_Agent", // Use underscore for consistency
28+
instructions: "You are a helpful assistant that only speaks English. Always respond in English to any questions or requests.",
29+
model: "gpt-4",
30+
provider: "openai",
31+
providerConfig,
32+
modelSettings: {
33+
temperature: 0.7,
34+
max_tokens: 500
35+
}
1536
});
1637

38+
// Create the triage agent with explicit configuration
1739
const triageAgent = new Agent({
18-
name: "Triage Agent",
19-
instructions: "Handoff to the appropriate agent based on the language of the request.",
40+
name: "Triage_Agent", // Use underscore for consistency
41+
instructions: `You are a language triage agent. Your task is to analyze messages and route them to the appropriate language agent.
42+
43+
Rules:
44+
1. For Spanish messages -> Use transfer_to_Spanish_Agent
45+
2. For English messages -> Use transfer_to_English_Agent
46+
3. DO NOT respond to the user directly
47+
4. ALWAYS use the appropriate transfer function
48+
5. If you detect Spanish, IMMEDIATELY call transfer_to_Spanish_Agent
49+
6. If you detect English, IMMEDIATELY call transfer_to_English_Agent`,
2050
handoffs: [spanishAgent, englishAgent],
51+
model: "gpt-4",
52+
provider: "openai",
53+
providerConfig,
54+
modelSettings: {
55+
temperature: 0.7,
56+
max_tokens: 500
57+
}
2158
});
2259

23-
const result = await Runner.run(triageAgent, "Hola, ¿cómo estás?");
24-
console.log(result.finalOutput);
60+
try {
61+
console.log('Sending message to triage agent...');
62+
const result = await Runner.run(triageAgent, "Hola, ¿cómo estás?", {
63+
maxTurns: 3, // Allow for triage + response
64+
context: { debug: true }
65+
});
66+
67+
console.log('\nFinal response:', result.finalOutput);
68+
console.log('Last agent:', result.lastAgent.name);
69+
console.log('\nMessage history:');
70+
result.messages.forEach((msg, i) => {
71+
console.log(`[${i}] ${msg.role}: ${msg.content}`);
72+
});
73+
} catch (error) {
74+
console.error('Error:', error.message);
75+
}
2576
}
2677

2778
main().catch(console.error);

src/providers/OpenAIProvider.js

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,24 @@ class OpenAIProvider extends LLMProvider {
3838
* @returns {Array} - Formatted messages for OpenAI
3939
*/
4040
formatMessages(messages) {
41-
return messages.map(msg => ({
42-
role: msg.role || 'user',
43-
content: msg.content
44-
}));
41+
return messages.map(msg => {
42+
const formattedMsg = {
43+
role: msg.role || 'user',
44+
content: msg.content || null
45+
};
46+
47+
// Add tool calls if present
48+
if (msg.tool_calls) {
49+
formattedMsg.tool_calls = msg.tool_calls;
50+
}
51+
52+
// Add tool call id if present
53+
if (msg.tool_call_id) {
54+
formattedMsg.tool_call_id = msg.tool_call_id;
55+
}
56+
57+
return formattedMsg;
58+
});
4559
}
4660

4761
/**
@@ -54,13 +68,37 @@ class OpenAIProvider extends LLMProvider {
5468
throw new Error('Invalid response from OpenAI');
5569
}
5670

57-
return {
58-
content: response.choices[0].message.content,
59-
role: response.choices[0].message.role,
71+
const message = response.choices[0].message;
72+
const result = {
73+
role: message.role,
6074
usage: response.usage,
6175
model: response.model,
6276
provider: 'openai'
6377
};
78+
79+
// Handle content (can be null for tool calls)
80+
if (message.content !== undefined) {
81+
result.content = message.content;
82+
}
83+
84+
// Handle tool calls if present
85+
if (message.tool_calls) {
86+
result.tool_calls = message.tool_calls;
87+
}
88+
89+
// Handle function call (legacy format) if present
90+
if (message.function_call) {
91+
result.tool_calls = [{
92+
id: `call_${Date.now()}`,
93+
type: 'function',
94+
function: {
95+
name: message.function_call.name,
96+
arguments: message.function_call.arguments
97+
}
98+
}];
99+
}
100+
101+
return result;
64102
}
65103

66104
/**

src/runner.js

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,12 +110,26 @@ class Runner {
110110
// Perform handoff
111111
currentAgent = handoffAgent;
112112

113-
// Add a system message indicating the handoff
114-
messages.push({
115-
role: 'system',
116-
content: `Handoff to ${handoffAgent.name}`
117-
});
118-
break; // Exit the tool call loop to restart with new agent
113+
// Reset messages for the new agent, keeping only the user's input
114+
const userMessages = messages.filter(msg => msg.role === 'user');
115+
messages = [
116+
{ role: 'system', content: handoffAgent.instructions },
117+
...userMessages
118+
];
119+
120+
// Get immediate response from new agent
121+
const handoffResponse = await handoffAgent.llmProvider.complete(
122+
messages,
123+
handoffAgent.modelSettings
124+
);
125+
126+
// Set final output and add response to messages
127+
finalOutput = handoffResponse.content;
128+
messages.push(handoffResponse);
129+
130+
// Exit the main loop since we have a final response
131+
currentTurn = maxTurns;
132+
break;
119133
}
120134
} else {
121135
// Regular tool call

0 commit comments

Comments
 (0)