Skip to content

Commit 3ea9945

Browse files
authored
feat: return tool call events for models that does not stream tool calls (#392)
1 parent ba89b81 commit 3ea9945

File tree

4 files changed

+62
-1
lines changed

4 files changed

+62
-1
lines changed

typescript-sdk/integrations/langgraph/python/ag_ui_langgraph/agent.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ async def _handle_stream_events(self, input: RunAgentInput) -> AsyncGenerator[st
113113
"thread_id": thread_id,
114114
"thinking_process": None,
115115
"node_name": None,
116+
"has_function_streaming": False,
116117
}
117118
self.active_run = INITIAL_ACTIVE_RUN
118119

@@ -485,6 +486,9 @@ async def _handle_single_event(self, event: Any, state: State) -> AsyncGenerator
485486
is_tool_call_args_event = has_current_stream and current_stream.get("tool_call_id") and tool_call_data and tool_call_data.get("args")
486487
is_tool_call_end_event = has_current_stream and current_stream.get("tool_call_id") and not tool_call_data
487488

489+
if is_tool_call_start_event or is_tool_call_end_event or is_tool_call_args_event:
490+
self.active_run["has_function_streaming"] = True
491+
488492
reasoning_data = resolve_reasoning_content(event["data"]["chunk"]) if event["data"]["chunk"] else None
489493
message_content = resolve_message_content(event["data"]["chunk"].content) if event["data"]["chunk"] and event["data"]["chunk"].content else None
490494
is_message_content_event = tool_call_data is None and message_content
@@ -649,6 +653,35 @@ async def _handle_single_event(self, event: Any, state: State) -> AsyncGenerator
649653
CustomEvent(type=EventType.CUSTOM, name=event["name"], value=event["data"], raw_event=event)
650654
)
651655

656+
elif event_type == LangGraphEventTypes.OnToolEnd:
657+
if self.active_run["has_function_streaming"]:
658+
return
659+
tool_call_output = event["data"]["output"]
660+
yield self._dispatch_event(
661+
ToolCallStartEvent(
662+
type=EventType.TOOL_CALL_START,
663+
tool_call_id=tool_call_output.tool_call_id,
664+
tool_call_name=tool_call_output.name,
665+
parent_message_id=tool_call_output.id,
666+
raw_event=event,
667+
)
668+
)
669+
yield self._dispatch_event(
670+
ToolCallArgsEvent(
671+
type=EventType.TOOL_CALL_ARGS,
672+
tool_call_id=tool_call_output.tool_call_id,
673+
delta=json.dumps(event["data"]["input"]),
674+
raw_event=event
675+
)
676+
)
677+
yield self._dispatch_event(
678+
ToolCallEndEvent(
679+
type=EventType.TOOL_CALL_END,
680+
tool_call_id=tool_call_output.tool_call_id,
681+
raw_event=event
682+
)
683+
)
684+
652685
def handle_thinking_event(self, reasoning_data: LangGraphReasoning) -> Generator[str, Any, str | None]:
653686
if not reasoning_data or "type" not in reasoning_data or "text" not in reasoning_data:
654687
return ""

typescript-sdk/integrations/langgraph/python/ag_ui_langgraph/types.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ class CustomEventNames(str, Enum):
4848
"exiting_node": NotRequired[bool],
4949
"manually_emitted_state": NotRequired[Optional[State]],
5050
"thread_id": NotRequired[Optional[ThinkingProcess]],
51-
"thinking_process": NotRequired[Optional[str]]
51+
"thinking_process": NotRequired[Optional[str]],
52+
"has_function_streaming": NotRequired[bool],
5253
})
5354

5455
MessagesInProgressRecord = Dict[str, Optional[MessageInProgress]]

typescript-sdk/integrations/langgraph/src/agent.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ export class LangGraphAgent extends AbstractAgent {
164164
this.activeRun = {
165165
id: input.runId,
166166
threadId: input.threadId,
167+
hasFunctionStreaming: false,
167168
};
168169
this.subscriber = subscriber;
169170
if (!this.assistant) {
@@ -571,6 +572,10 @@ export class LangGraphAgent extends AbstractAgent {
571572
hasCurrentStream && currentStream?.toolCallId && toolCallData?.args;
572573
const isToolCallEndEvent = hasCurrentStream && currentStream?.toolCallId && !toolCallData;
573574

575+
if (isToolCallEndEvent || isToolCallArgsEvent || isToolCallStartEvent) {
576+
this.activeRun!.hasFunctionStreaming = true;
577+
}
578+
574579
const reasoningData = resolveReasoningContent(event.data);
575580
const messageContent = resolveMessageContent(event.data.chunk.content);
576581
const isMessageContentEvent = Boolean(!toolCallData && messageContent);
@@ -768,6 +773,27 @@ export class LangGraphAgent extends AbstractAgent {
768773
rawEvent: event,
769774
});
770775
break;
776+
case LangGraphEventTypes.OnToolEnd:
777+
if (this.activeRun!.hasFunctionStreaming) break;
778+
const toolCallOutput = event.data.output
779+
this.dispatchEvent({
780+
type: EventType.TOOL_CALL_START,
781+
toolCallId: toolCallOutput.tool_call_id,
782+
toolCallName: toolCallOutput.name,
783+
parentMessageId: toolCallOutput.id,
784+
rawEvent: event,
785+
})
786+
this.dispatchEvent({
787+
type: EventType.TOOL_CALL_ARGS,
788+
toolCallId: toolCallOutput.tool_call_id,
789+
delta: JSON.stringify(event.data.input),
790+
rawEvent: event,
791+
});
792+
this.dispatchEvent({
793+
type: EventType.TOOL_CALL_END,
794+
toolCallId: toolCallOutput.tool_call_id,
795+
rawEvent: event,
796+
});
771797
}
772798
}
773799

typescript-sdk/integrations/langgraph/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export interface RunMetadata {
6363
manuallyEmittedState?: State | null;
6464
threadId?: string;
6565
graphInfo?: AssistantGraph
66+
hasFunctionStreaming?: boolean;
6667
}
6768

6869
export type MessagesInProgressRecord = Record<string, MessageInProgress | null>;

0 commit comments

Comments
 (0)