254 lines
5.9 KiB
Python
254 lines
5.9 KiB
Python
"""Generic AG-UI event creation utilities for any graph."""
|
|
|
|
from typing import Any
|
|
from uuid import uuid4
|
|
|
|
from pydantic import BaseModel
|
|
|
|
from haiku.rag.graph.agui.state import compute_state_delta
|
|
|
|
# Type aliases for AG-UI events (actual types from ag_ui.core will be used at runtime)
|
|
AGUIEvent = dict[str, Any]
|
|
|
|
|
|
def emit_run_started(
|
|
thread_id: str, run_id: str, input_data: str | None = None
|
|
) -> dict[str, Any]:
|
|
"""Create a RunStarted event.
|
|
|
|
Args:
|
|
thread_id: Unique identifier for the conversation thread
|
|
run_id: Unique identifier for this run
|
|
input_data: Optional input that started the run
|
|
|
|
Returns:
|
|
RunStarted event dict
|
|
"""
|
|
event: dict[str, Any] = {
|
|
"type": "RUN_STARTED",
|
|
"threadId": thread_id,
|
|
"runId": run_id,
|
|
}
|
|
if input_data:
|
|
event["input"] = input_data
|
|
return event
|
|
|
|
|
|
def emit_run_finished(thread_id: str, run_id: str, result: Any) -> dict[str, Any]:
|
|
"""Create a RunFinished event.
|
|
|
|
Args:
|
|
thread_id: Unique identifier for the conversation thread
|
|
run_id: Unique identifier for this run
|
|
result: The final result of the run
|
|
|
|
Returns:
|
|
RunFinished event dict
|
|
"""
|
|
# Convert result to dict if it's a Pydantic model
|
|
if hasattr(result, "model_dump"):
|
|
result = result.model_dump()
|
|
|
|
return {
|
|
"type": "RUN_FINISHED",
|
|
"threadId": thread_id,
|
|
"runId": run_id,
|
|
"result": result,
|
|
}
|
|
|
|
|
|
def emit_run_error(message: str, code: str | None = None) -> dict[str, Any]:
|
|
"""Create a RunError event.
|
|
|
|
Args:
|
|
message: Error message
|
|
code: Optional error code
|
|
|
|
Returns:
|
|
RunError event dict
|
|
"""
|
|
event: dict[str, Any] = {
|
|
"type": "RUN_ERROR",
|
|
"message": message,
|
|
}
|
|
if code:
|
|
event["code"] = code
|
|
return event
|
|
|
|
|
|
def emit_step_started(step_name: str) -> dict[str, Any]:
|
|
"""Create a StepStarted event.
|
|
|
|
Args:
|
|
step_name: Name of the step being started
|
|
|
|
Returns:
|
|
StepStarted event dict
|
|
"""
|
|
return {
|
|
"type": "STEP_STARTED",
|
|
"stepName": step_name,
|
|
}
|
|
|
|
|
|
def emit_step_finished(step_name: str) -> dict[str, Any]:
|
|
"""Create a StepFinished event.
|
|
|
|
Args:
|
|
step_name: Name of the step that finished
|
|
|
|
Returns:
|
|
StepFinished event dict
|
|
"""
|
|
return {
|
|
"type": "STEP_FINISHED",
|
|
"stepName": step_name,
|
|
}
|
|
|
|
|
|
def emit_text_message(content: str, role: str = "assistant") -> dict[str, Any]:
|
|
"""Create a TextMessageChunk event (convenience wrapper).
|
|
|
|
This creates a complete text message in one event.
|
|
|
|
Args:
|
|
content: The message content
|
|
role: The role of the sender (default: assistant)
|
|
|
|
Returns:
|
|
TextMessageChunk event dict
|
|
"""
|
|
message_id = str(uuid4())
|
|
return {
|
|
"type": "TEXT_MESSAGE_CHUNK",
|
|
"messageId": message_id,
|
|
"role": role,
|
|
"delta": content,
|
|
}
|
|
|
|
|
|
def emit_text_message_start(message_id: str, role: str = "assistant") -> dict[str, Any]:
|
|
"""Create a TextMessageStart event.
|
|
|
|
Args:
|
|
message_id: Unique identifier for this message
|
|
role: The role of the sender
|
|
|
|
Returns:
|
|
TextMessageStart event dict
|
|
"""
|
|
return {
|
|
"type": "TEXT_MESSAGE_START",
|
|
"messageId": message_id,
|
|
"role": role,
|
|
}
|
|
|
|
|
|
def emit_text_message_content(message_id: str, delta: str) -> dict[str, Any]:
|
|
"""Create a TextMessageContent event.
|
|
|
|
Args:
|
|
message_id: Identifier for the message being streamed
|
|
delta: Content chunk to append
|
|
|
|
Returns:
|
|
TextMessageContent event dict
|
|
"""
|
|
return {
|
|
"type": "TEXT_MESSAGE_CONTENT",
|
|
"messageId": message_id,
|
|
"delta": delta,
|
|
}
|
|
|
|
|
|
def emit_text_message_end(message_id: str) -> dict[str, Any]:
|
|
"""Create a TextMessageEnd event.
|
|
|
|
Args:
|
|
message_id: Identifier for the message being completed
|
|
|
|
Returns:
|
|
TextMessageEnd event dict
|
|
"""
|
|
return {
|
|
"type": "TEXT_MESSAGE_END",
|
|
"messageId": message_id,
|
|
}
|
|
|
|
|
|
def emit_state_snapshot(state: BaseModel) -> dict[str, Any]:
|
|
"""Create a StateSnapshot event.
|
|
|
|
Args:
|
|
state: The complete state to snapshot (any Pydantic BaseModel)
|
|
|
|
Returns:
|
|
StateSnapshot event dict
|
|
"""
|
|
return {
|
|
"type": "STATE_SNAPSHOT",
|
|
"snapshot": state.model_dump(),
|
|
}
|
|
|
|
|
|
def emit_state_delta(old_state: BaseModel, new_state: BaseModel) -> dict[str, Any]:
|
|
"""Create a StateDelta event with JSON Patch operations.
|
|
|
|
Args:
|
|
old_state: Previous state (any Pydantic BaseModel)
|
|
new_state: Current state (same type as old_state)
|
|
|
|
Returns:
|
|
StateDelta event dict
|
|
"""
|
|
delta = compute_state_delta(old_state, new_state)
|
|
return {
|
|
"type": "STATE_DELTA",
|
|
"delta": delta,
|
|
}
|
|
|
|
|
|
def emit_activity(
|
|
message_id: str,
|
|
activity_type: str,
|
|
content: dict[str, Any],
|
|
) -> dict[str, Any]:
|
|
"""Create an ActivitySnapshot event.
|
|
|
|
Args:
|
|
message_id: Message ID to associate activity with (required)
|
|
activity_type: Type of activity (e.g., "planning", "searching")
|
|
content: Structured payload representing the activity state
|
|
|
|
Returns:
|
|
ActivitySnapshot event dict
|
|
"""
|
|
return {
|
|
"type": "ACTIVITY_SNAPSHOT",
|
|
"messageId": message_id,
|
|
"activityType": activity_type,
|
|
"content": content,
|
|
}
|
|
|
|
|
|
def emit_activity_delta(
|
|
message_id: str,
|
|
activity_type: str,
|
|
patch: list[dict[str, Any]],
|
|
) -> dict[str, Any]:
|
|
"""Create an ActivityDelta event with JSON Patch operations.
|
|
|
|
Args:
|
|
message_id: Message ID of the activity being updated
|
|
activity_type: Type of activity being updated
|
|
patch: JSON Patch operations to apply
|
|
|
|
Returns:
|
|
ActivityDelta event dict
|
|
"""
|
|
return {
|
|
"type": "ACTIVITY_DELTA",
|
|
"messageId": message_id,
|
|
"activityType": activity_type,
|
|
"patch": patch,
|
|
}
|