Skip to content

Commit 37ea6db

Browse files
authored
Merge branch 'main' into realtime-agent-validation
2 parents 3ea5bf7 + d7fd8dc commit 37ea6db

File tree

15 files changed

+96
-40
lines changed

15 files changed

+96
-40
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@openai/agents-core': patch
3+
---
4+
5+
Export CURRENT_SCHEMA_VERSION constant and use it when serializing run state.

.changeset/wicked-masks-tell.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@openai/agents-openai': minor
3+
---
4+
5+
Fix incorrect handling of chat completions mode for handoff

.changeset/wise-results-mate.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@openai/agents-realtime': patch
3+
---
4+
5+
fix: avoid realtime guardrail race condition and detect ongoing response

examples/docs/voice-agents/delegationAgent.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,5 @@ const agent = new RealtimeAgent({
2727
name: 'Customer Support',
2828
instructions:
2929
'You are a customer support agent. If you receive any requests for refunds, you need to delegate to your supervisor.',
30+
tools: [refundSupervisor],
3031
});

examples/realtime-next/src/app/websocket/page.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
TransportEvent,
88
RealtimeItem,
99
OutputGuardrailTripwireTriggered,
10+
RealtimeOutputGuardrail,
1011
} from '@openai/agents/realtime';
1112
import { useEffect, useRef, useState } from 'react';
1213
import { z } from 'zod';
@@ -26,6 +27,21 @@ const refundBackchannel = tool({
2627
},
2728
});
2829

30+
const guardrails: RealtimeOutputGuardrail[] = [
31+
{
32+
name: 'No mention of Dom',
33+
execute: async ({ agentOutput }) => {
34+
const domInOutput = agentOutput.includes('Dom');
35+
return {
36+
tripwireTriggered: domInOutput,
37+
outputInfo: {
38+
domInOutput,
39+
},
40+
};
41+
},
42+
},
43+
];
44+
2945
const agent = new RealtimeAgent({
3046
name: 'Greeter',
3147
instructions:
@@ -48,6 +64,7 @@ export default function Home() {
4864
useEffect(() => {
4965
session.current = new RealtimeSession(agent, {
5066
transport: 'websocket',
67+
outputGuardrails: guardrails,
5168
});
5269
recorder.current = new WavRecorder({ sampleRate: 24000 });
5370
player.current = new WavStreamPlayer({ sampleRate: 24000 });
@@ -87,6 +104,7 @@ export default function Home() {
87104
async function connect() {
88105
if (isConnected) {
89106
await session.current?.close();
107+
await player.current?.interrupt();
90108
await recorder.current?.end();
91109
setIsConnected(false);
92110
} else {

packages/agents-core/src/runState.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import { safeExecute } from './utils/safeExecute';
3737
* run state is compatible with the current version of the SDK.
3838
* If anything in this schema changes, the version will have to be incremented.
3939
*/
40-
const CURRENT_SCHEMA_VERSION = '1.0' as const;
40+
export const CURRENT_SCHEMA_VERSION = '1.0' as const;
4141
const $schemaVersion = z.literal(CURRENT_SCHEMA_VERSION);
4242

4343
const serializedAgentSchema = z.object({
@@ -349,7 +349,7 @@ export class RunState<TContext, TAgent extends Agent<any, any>> {
349349
*/
350350
toJSON(): z.infer<typeof SerializedRunState> {
351351
const output = {
352-
$schemaVersion: '1.0',
352+
$schemaVersion: CURRENT_SCHEMA_VERSION,
353353
currentTurn: this._currentTurn,
354354
currentAgent: {
355355
name: this._currentAgent.name,

packages/agents-core/test/runState.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
buildAgentMap,
55
deserializeModelResponse,
66
deserializeItem,
7+
CURRENT_SCHEMA_VERSION,
78
} from '../src/runState';
89
import { RunContext } from '../src/runContext';
910
import { Agent } from '../src/agent';
@@ -35,7 +36,7 @@ describe('RunState', () => {
3536
const agent = new Agent({ name: 'Agent1' });
3637
const state = new RunState(context, 'input1', agent, 2);
3738
const json = state.toJSON();
38-
expect(json.$schemaVersion).toBe('1.0');
39+
expect(json.$schemaVersion).toBe(CURRENT_SCHEMA_VERSION);
3940
expect(json.currentTurn).toBe(0);
4041
expect(json.currentAgent).toEqual({ name: 'Agent1' });
4142
expect(json.originalInput).toEqual('input1');
@@ -65,7 +66,7 @@ describe('RunState', () => {
6566
expect(
6667
async () => await RunState.fromString(agent, JSON.stringify(jsonVersion)),
6768
).rejects.toThrow(
68-
'Run state schema version 0.1 is not supported. Please use version 1.0',
69+
`Run state schema version 0.1 is not supported. Please use version ${CURRENT_SCHEMA_VERSION}`,
6970
);
7071
});
7172

packages/agents-openai/src/openaiChatCompletionsModel.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export class OpenAIChatCompletionsModel implements Model {
6767
const output: protocol.OutputModelItem[] = [];
6868
if (response.choices && response.choices[0]) {
6969
const message = response.choices[0].message;
70-
if (message.content !== undefined) {
70+
if (message.content !== undefined && message.content !== null) {
7171
const { content, ...rest } = message;
7272
output.push({
7373
id: response.id,

packages/agents-realtime/src/openaiRealtimeBase.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ export abstract class OpenAIRealtimeBase
256256
type: 'transcript_delta',
257257
delta: parsed.delta,
258258
itemId: parsed.item_id,
259+
responseId: parsed.response_id,
259260
});
260261
}
261262
// no support for partial transcripts yet.

packages/agents-realtime/src/openaiRealtimeEvents.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,18 @@ import type { MessageEvent as WebSocketMessageEvent } from 'ws';
66
// provide better runtime validation when parsing events from the server.
77

88
export const realtimeResponse = z.object({
9-
id: z.string().optional(),
10-
conversation_id: z.string().optional(),
11-
max_output_tokens: z.number().or(z.literal('inf')).optional(),
9+
id: z.string().optional().nullable(),
10+
conversation_id: z.string().optional().nullable(),
11+
max_output_tokens: z.number().or(z.literal('inf')).optional().nullable(),
1212
metadata: z.record(z.string(), z.any()).optional().nullable(),
13-
modalities: z.array(z.string()).optional(),
14-
object: z.literal('realtime.response').optional(),
15-
output: z.array(z.any()).optional(),
16-
output_audio_format: z.string().optional(),
17-
status: z.enum(['completed', 'incomplete', 'failed', 'cancelled']).optional(),
13+
modalities: z.array(z.string()).optional().nullable(),
14+
object: z.literal('realtime.response').optional().nullable(),
15+
output: z.array(z.any()).optional().nullable(),
16+
output_audio_format: z.string().optional().nullable(),
17+
status: z
18+
.enum(['completed', 'incomplete', 'failed', 'cancelled', 'in_progress'])
19+
.optional()
20+
.nullable(),
1821
status_details: z.record(z.string(), z.any()).optional().nullable(),
1922
usage: z
2023
.object({
@@ -26,8 +29,9 @@ export const realtimeResponse = z.object({
2629
.optional()
2730
.nullable(),
2831
})
29-
.optional(),
30-
voice: z.string().optional(),
32+
.optional()
33+
.nullable(),
34+
voice: z.string().optional().nullable(),
3135
});
3236

3337
// Basic content schema used by ConversationItem.
@@ -315,7 +319,6 @@ export const responseDoneEventSchema = z.object({
315319
type: z.literal('response.done'),
316320
event_id: z.string(),
317321
response: realtimeResponse,
318-
test: z.boolean(),
319322
});
320323

321324
export const responseFunctionCallArgumentsDeltaEventSchema = z.object({

0 commit comments

Comments
 (0)