Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions examples/agui_stream.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
"""
Example: ``launchdarkly_ai_server.agui.stream`` — one AI Config turn as AG-UI events.

``config().stream()`` still yields ``chunk`` then ``done``. This reader is the
opt-in entry: ``RUN_STARTED``, the text and tool events the handler yields,
then ``RUN_FINISHED``. The SDK still runs the tool loop. The stream only
reports it.

Text deltas go to stdout. Run and tool events go to stderr, the same split as
``examples/graph_streaming.py``. The generator is built inside
``conversation_id`` and iterated outside it, the same shape as
``examples/streaming.py``.

``TOOL_CALL_ARGS`` deltas and ``TOOL_CALL_RESULT`` content are empty unless
``capture_content`` is on, through ``configure(options=SDKOptions(capture_content=True))``
or the handler factory's ``capture_content=True``. The tool name and call id are
always sent.

Usage (via main.py):
python main.py agui-stream <flag-key> "<user input>"
"""

from __future__ import annotations

import json
import sys
from uuid import uuid4

import examples.register # noqa: F401 – side-effect: populate global_registry
from examples.utils import new_context, new_conversation_id
from launchdarkly_ai_server import config, conversation_id, global_registry
from launchdarkly_ai_server.agui import stream


async def run(key: str, user_input: str) -> None:
conversation = new_conversation_id("agui-stream-example")
thread_id = f"thread-{uuid4().hex[:8]}"
run_id = f"run-{uuid4().hex[:8]}"
print(f"[conversation] {conversation}", file=sys.stderr)
print(f"[thread] {thread_id} [run] {run_id}", file=sys.stderr)

with conversation_id(conversation):
events = stream(
config(key=key, registry=global_registry),
user_input,
new_context(),
thread_id=thread_id,
run_id=run_id,
)

async for event in events:
kind = event["type"]
if kind == "TEXT_MESSAGE_CONTENT":
sys.stdout.write(event.get("delta") or "")
sys.stdout.flush()
elif kind == "RUN_STARTED":
print(
f"[RUN_STARTED] thread={event['threadId']} run={event['runId']}",
file=sys.stderr,
)
elif kind == "TOOL_CALL_START":
print(
f"[tool] start {event['toolCallName']} id={event['toolCallId']}",
file=sys.stderr,
)
elif kind == "TOOL_CALL_ARGS":
print(
f"[tool] args id={event['toolCallId']} {event.get('delta') or ''}",
file=sys.stderr,
)
elif kind == "TOOL_CALL_END":
print(f"[tool] end id={event['toolCallId']}", file=sys.stderr)
elif kind == "TOOL_CALL_RESULT":
failed = (
(event.get("metadata") or {}).get("launchdarkly", {}).get("toolFailed")
)
print(
f"[tool] result id={event['toolCallId']} failed={bool(failed)}",
file=sys.stderr,
)
elif kind == "RUN_FINISHED":
sys.stdout.write("\n\n")
print(
"Usage:",
json.dumps(event.get("usage"), indent=2, default=str),
)
judges = (
(event.get("metadata") or {})
.get("launchdarkly", {})
.get("judgeResults")
)
if judges:
print("Judge results:", json.dumps(judges, indent=2, default=str))
sys.stdout.write("\n")
1 change: 1 addition & 0 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"agent": "examples.agent",
"capture-content": "examples.capture_content",
"streaming": "examples.streaming",
"agui-stream": "examples.agui_stream",
"graph": "examples.graph_example",
"graph-history": "examples.graph_history",
"graph-streaming": "examples.graph_streaming",
Expand Down
Loading