Skip to content

Commit cc4ff80

Browse files
ninjuddhntrl
andauthored
fix: enable handleLLMNewToken events for useResponsesApi=true (#8578)
Co-authored-by: Hunter Lovell <[email protected]>
1 parent 15eb26d commit cc4ff80

File tree

2 files changed

+72
-2
lines changed

2 files changed

+72
-2
lines changed

libs/langchain-openai/src/chat_models.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1544,7 +1544,8 @@ export class ChatOpenAIResponses<
15441544

15451545
async *_streamResponseChunks(
15461546
messages: BaseMessage[],
1547-
options: this["ParsedCallOptions"]
1547+
options: this["ParsedCallOptions"],
1548+
runManager?: CallbackManagerForLLMRun
15481549
): AsyncGenerator<ChatGenerationChunk> {
15491550
const streamIterable = await this.completionWithRetry(
15501551
{
@@ -1559,6 +1560,17 @@ export class ChatOpenAIResponses<
15591560
const chunk = this._convertResponsesDeltaToBaseMessageChunk(data);
15601561
if (chunk == null) continue;
15611562
yield chunk;
1563+
await runManager?.handleLLMNewToken(
1564+
chunk.text || "",
1565+
{
1566+
prompt: options.promptIndex ?? 0,
1567+
completion: 0,
1568+
},
1569+
undefined,
1570+
undefined,
1571+
undefined,
1572+
{ chunk }
1573+
);
15621574
}
15631575
}
15641576

@@ -3377,7 +3389,8 @@ export class ChatOpenAI<
33773389
if (this._useResponsesApi(options)) {
33783390
yield* this.responses._streamResponseChunks(
33793391
messages,
3380-
this._combineCallOptions(options)
3392+
this._combineCallOptions(options),
3393+
runManager
33813394
);
33823395
return;
33833396
}

libs/langchain-openai/src/tests/chat_models_responses.int.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -766,3 +766,60 @@ describe("reasoning summaries", () => {
766766
expect(response).toBeDefined();
767767
});
768768
});
769+
770+
// https://github.com/langchain-ai/langchainjs/issues/8577
771+
test("useResponsesApi=true should emit handleLLMNewToken events during streaming", async () => {
772+
// This test demonstrates that when useResponsesApi=true is enabled,
773+
// the ChatOpenAI class properly passes the runManager parameter to
774+
// ChatOpenAIResponses._streamResponseChunks, allowing handleLLMNewToken
775+
// events to be emitted during streaming.
776+
777+
const model = new ChatOpenAI({
778+
model: "gpt-4o-mini",
779+
useResponsesApi: true,
780+
});
781+
782+
const messages = [new HumanMessage("Say 'Hello world' in 3 words.")];
783+
784+
// Track handleLLMNewToken events
785+
const newTokenEvents: string[] = [];
786+
let handleLLMNewTokenCalled = false;
787+
788+
const stream = model.streamEvents(messages, {
789+
version: "v2",
790+
callbacks: [
791+
{
792+
handleLLMNewToken(token: string) {
793+
handleLLMNewTokenCalled = true;
794+
newTokenEvents.push(token);
795+
},
796+
},
797+
],
798+
});
799+
800+
// Collect all events
801+
const events = [];
802+
for await (const event of stream) {
803+
events.push(event);
804+
}
805+
806+
// Verify that handleLLMNewToken was called with individual tokens
807+
expect(handleLLMNewTokenCalled).toBe(true);
808+
expect(newTokenEvents.length).toBeGreaterThan(0);
809+
810+
// Verify we have streaming events
811+
const streamingEvents = events.filter(
812+
(event) => event.event === "on_chat_model_stream"
813+
);
814+
expect(streamingEvents.length).toBeGreaterThan(0);
815+
816+
// Verify we have the start and end events
817+
const startEvents = events.filter(
818+
(event) => event.event === "on_chat_model_start"
819+
);
820+
const endEvents = events.filter(
821+
(event) => event.event === "on_chat_model_end"
822+
);
823+
expect(startEvents.length).toBeGreaterThan(0);
824+
expect(endEvents.length).toBeGreaterThan(0);
825+
});

0 commit comments

Comments
 (0)