Skip to content

Commit 4734e27

Browse files
Add usage data from stream on response (#652)
1 parent 0a808d2 commit 4734e27

File tree

3 files changed

+70
-0
lines changed

3 files changed

+70
-0
lines changed

.changeset/shaky-bananas-check.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@openai/agents-openai': patch
3+
---
4+
5+
Export usage data from Chat Completions response for trace

packages/agents-openai/src/openaiChatCompletionsModel.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,18 @@ export class OpenAIChatCompletionsModel implements Model {
214214
response,
215215
stream,
216216
)) {
217+
if (
218+
event.type === 'response_done' &&
219+
response.usage?.total_tokens === 0
220+
) {
221+
response.usage = {
222+
prompt_tokens: event.response.usage.inputTokens,
223+
completion_tokens: event.response.usage.outputTokens,
224+
total_tokens: event.response.usage.totalTokens,
225+
prompt_tokens_details: event.response.usage.inputTokensDetails,
226+
completion_tokens_details: event.response.usage.outputTokensDetails,
227+
};
228+
}
217229
yield event;
218230
}
219231

packages/agents-openai/test/openaiChatCompletionsModel.test.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,4 +511,57 @@ describe('OpenAIChatCompletionsModel', () => {
511511
expect(convertChatCompletionsStreamToResponses).toHaveBeenCalled();
512512
expect(events).toEqual([{ type: 'first' }, { type: 'second' }]);
513513
});
514+
515+
it('populates usage from response_done event when initial usage is zero', async () => {
516+
// override the original implementation to add the response_done event.
517+
vi.mocked(convertChatCompletionsStreamToResponses).mockImplementationOnce(
518+
async function* () {
519+
yield { type: 'first' } as any;
520+
yield { type: 'second' } as any;
521+
yield {
522+
type: 'response_done',
523+
response: {
524+
usage: {
525+
inputTokens: 10,
526+
outputTokens: 5,
527+
totalTokens: 15,
528+
inputTokensDetails: { cached_tokens: 2 },
529+
outputTokensDetails: { reasoning_tokens: 3 },
530+
},
531+
},
532+
} as any;
533+
},
534+
);
535+
536+
const client = new FakeClient();
537+
async function* fakeStream() {
538+
yield { id: 'c' } as any;
539+
}
540+
client.chat.completions.create.mockResolvedValue(fakeStream());
541+
542+
const model = new OpenAIChatCompletionsModel(client as any, 'gpt');
543+
const req: any = {
544+
input: 'hi',
545+
modelSettings: {},
546+
tools: [],
547+
outputType: 'text',
548+
handoffs: [],
549+
tracing: false,
550+
};
551+
const events: any[] = [];
552+
await withTrace('t', async () => {
553+
for await (const e of model.getStreamedResponse(req)) {
554+
events.push(e);
555+
}
556+
});
557+
558+
expect(client.chat.completions.create).toHaveBeenCalledWith(
559+
expect.objectContaining({ stream: true }),
560+
{ headers: HEADERS, signal: undefined },
561+
);
562+
expect(convertChatCompletionsStreamToResponses).toHaveBeenCalled();
563+
const responseDone = events.find((e) => e.type === 'response_done');
564+
expect(responseDone).toBeDefined();
565+
expect(responseDone.response.usage.totalTokens).toBe(15);
566+
});
514567
});

0 commit comments

Comments
 (0)