Skip to content
Merged
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
7 changes: 7 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,13 @@ Examples are documentation, not scratch space. Each example must:
- Be invoked by `.github/workflows/examples.yml` so the examples workflow
exercises it.

A `# #region docs:<id>` block must show every helper it calls that the
example file defines, methods and closures included. Move SDK work (stream
parsing, snapshot recovery, text accumulation) inside the region, before the
code that uses it; stand in for the reader's own app code (`render`,
`save_agent_binding`, ...) with a minimal stub inside the region under a
one-line `# Your app's code: ...` comment.

## Linting and formatting

```shell
Expand Down
42 changes: 18 additions & 24 deletions examples/introspection_examples/patterns/add_to_your_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,26 +44,6 @@ class AgentBinding:
user_id: str


class Orders:
"""Stands in for your database: which task works on which order."""

def __init__(self) -> None:
self.bindings: dict[str, AgentBinding] = {}

def save_agent_binding(
self, order_id: str, *, task_id: str, user_id: str
) -> None:
self.bindings[order_id] = AgentBinding(task_id, user_id)


orders = Orders()


def send_event_to_browser(event: AGUIEvent) -> None:
"""Stands in for your server-sent events or WebSocket push."""
print(f" -> browser: {event.type}")


def open_runner(
runtime: str, user_id: str
) -> tuple[IntrospectionClient, IntrospectionLogs, Runner]:
Expand All @@ -85,23 +65,38 @@ def open_runner(
return client, outcomes, runner


def start_task(runner: Runner, order_id: str, user_id: str) -> None:
def start_task(runner: Runner, order_id: str, user_id: str) -> AgentBinding:
# #region docs:patterns/add-to-your-app/start-task
# Your app's code: store which task works on which order.
def save_agent_binding(
order_id: str, *, task_id: str, user_id: str
) -> None:
print(f" saved task {task_id} for order {order_id} ({user_id})")

# Your app's code: push the event over server-sent events or a WebSocket.
def send_event_to_browser(event: AGUIEvent) -> None:
print(f" -> browser: {event.type}")

handle = runner.tasks.start(
prompt="Review order 1842 against the refund policy and draft the next response.",
metadata={"order_id": "1842"},
)
orders.save_agent_binding(
save_agent_binding(
order_id, task_id=str(handle.run.task_id), user_id=user_id
)

for event in handle.stream():
send_event_to_browser(event)
# #endregion docs:patterns/add-to-your-app/start-task
return AgentBinding(str(handle.run.task_id), user_id)


def reconnect(runner: Runner, binding: AgentBinding) -> None:
# #region docs:patterns/add-to-your-app/reconnect
# Your app's code: push the event over server-sent events or a WebSocket.
def send_event_to_browser(event: AGUIEvent) -> None:
print(f" -> browser: {event.type}")

task = runner.tasks.get(binding.task_id)
metadata = task.metadata or {}
run_id = metadata.get("active_run_id") or metadata["latest_run_id"]
Expand Down Expand Up @@ -144,8 +139,7 @@ def main() -> None:

client, outcomes, runner = open_runner(runtime, user_id)
try:
start_task(runner, "1842", user_id)
binding = orders.bindings["1842"]
binding = start_task(runner, "1842", user_id)
print(f"task {binding.task_id} saved for order 1842")

# After a reload: check the record belongs to the signed-in user.
Expand Down
26 changes: 25 additions & 1 deletion examples/introspection_examples/patterns/structured_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,43 @@
import sys
from typing import Literal

from ag_ui.core import ToolCallArgsEvent, ToolCallStartEvent
from ag_ui.core import (
AssistantMessage,
MessagesSnapshotEvent,
ToolCallArgsEvent,
ToolCallStartEvent,
)
from dotenv import load_dotenv
from introspection_sdk import IntrospectionClient, Runner
from pydantic import BaseModel, Field, ValidationError


def read_stream(runner: Runner) -> tuple[str, str | None]:
# #region docs:patterns/structured-output/read-stream
def from_snapshot(
event: MessagesSnapshotEvent, args: dict[str, str]
) -> str | None:
"""A reconnect behind the replay buffer sends the run so far instead of
the frames, so replace the collected arguments with its tool calls and
return the ``submit_result`` call's id."""
args.clear()
submit_id = None
for message in event.messages:
if not isinstance(message, AssistantMessage):
continue
for call in message.tool_calls or []:
args[call.id] = call.function.arguments
if call.function.name == "submit_result":
submit_id = call.id
return submit_id

handle = runner.tasks.start(prompt="Triage ticket 4411: ...")
args: dict[str, str] = {}
submit_id: str | None = None

for event in handle.stream():
if isinstance(event, MessagesSnapshotEvent):
submit_id = from_snapshot(event, args)
if (
isinstance(event, ToolCallStartEvent)
and event.tool_call_name == "submit_result"
Expand Down
8 changes: 4 additions & 4 deletions examples/introspection_examples/reference/python_sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@
from introspection_sdk.schemas.agui import AGUIEvent, RunFinishedEvent


def render(event: AGUIEvent) -> None:
print(event.model_dump_json(by_alias=True, exclude_none=True))


# The regions keep the docs' line lengths, not the formatter's.
# fmt: off
async def reattach(runner: AsyncRunner, task_id: str) -> None:
# #region docs:reference/python/reattach
# Your app's code: show the event in your UI.
def render(event: AGUIEvent) -> None:
print(event.model_dump_json(by_alias=True, exclude_none=True))

task = await runner.tasks.get(task_id)
metadata = task.metadata or {}
run_id = metadata.get("active_run_id") or metadata["latest_run_id"]
Expand Down
Loading