Skip to content
2 changes: 1 addition & 1 deletion examples/fastapi-vite/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ to suspend execution whenever the LLM wants to call a tool. The flow is:

1. LLM emits a tool call
2. Backend calls `await ai.hook(...)` with `payload=ai.ToolApproval`
3. The runtime emits a `role="signal"` message containing a pending `HookPart`
3. The runtime emits a `role="internal"` message containing a pending `HookPart`
4. The frontend renders Approve / Reject buttons via the
`<Confirmation>` component (from AI Elements)
5. When the user clicks a button, `addToolApprovalResponse()` patches
Expand Down
2 changes: 1 addition & 1 deletion examples/multiagent-textual/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ The current implementation uses:
- `ai.agent(...)` for each branch and the orchestrator
- `await ai.hook(...)` for branch-specific approvals
- `ai.yield_from(...)` to forward nested agent output into the outer run
- `role="signal"` messages for hook state updates over the WebSocket
- `role="internal"` messages for hook state updates over the WebSocket

## Setup

Expand Down
22 changes: 11 additions & 11 deletions examples/multiagent-textual/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ async def run_websocket(self) -> None:
# ------------------------------------------------------------------

def _handle_message(self, msg: ai.Message) -> None:
label = msg.label or "unknown"
label = msg.source_label or "unknown"

if (hook_part := msg.get_hook_part()) is not None:
if hook_part.status == "pending":
Expand All @@ -176,21 +176,21 @@ def _handle_message(self, msg: ai.Message) -> None:
if panel.status == "idle":
panel.status = "streaming..."

# Text deltas
if msg.text_delta:
panel.append_text(msg.text_delta)
if msg.reasoning_delta:
panel.append_text(msg.reasoning_delta, style="dim")

# Tool argument deltas
for delta in msg.tool_deltas:
panel.append_text(delta.args_delta, style="dim")
# Text / reasoning / tool-arg deltas
for ev in msg.deltas:
match ev.part:
case ai.TextPart():
panel.append_text(ev.chunk)
case ai.ReasoningPart():
panel.append_text(ev.chunk, style="dim")
case ai.ToolCallPart():
panel.append_text(ev.chunk, style="dim")

# Completed message — show tool calls and results
if msg.is_done:
for part in msg.parts:
match part:
case ai.ToolCallPart(tool_name=name, tool_args=args, state="done"):
case ai.ToolCallPart(tool_name=name, tool_args=args):
panel.append_line(f"> {name}({args})")
case ai.ToolResultPart(tool_name=name, result=result):
panel.append_line(f"< {name} = {result}")
Expand Down
2 changes: 1 addition & 1 deletion examples/multiagent-textual/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ async def multiagent_loop(context: ai.Context) -> AsyncGenerator[ai.Message]:
],
)
async for msg in s:
yield msg.model_copy(update={"label": "summary"})
yield msg.model_copy(update={"agent": "summary"})


# ---------------------------------------------------------------------------
Expand Down
5 changes: 3 additions & 2 deletions examples/samples/agent_custom_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,9 @@ async def custom(context: ai.Context) -> AsyncGenerator[ai.Message]:
model,
[ai.user_message("Compare the weather and population of New York and Tokyo.")],
):
if msg.text_delta:
print(msg.text_delta, end="", flush=True)
for ev in msg.deltas:
if isinstance(ev.part, ai.TextPart):
print(ev.chunk, end="", flush=True)
print()


Expand Down
11 changes: 6 additions & 5 deletions examples/samples/agent_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Demonstrates the function-based hook API:
- await hook("label", payload=Model) to suspend inside the loop
- resolve_hook("label", data) to unblock from outside
- Hook messages arrive with role="signal"
- Hook messages arrive with role="internal"
"""

import asyncio
Expand Down Expand Up @@ -76,8 +76,8 @@ async def with_approval(context: ai.Context) -> AsyncGenerator[ai.Message]:
]

async for msg in my_agent.run(model, messages):
# Hook signals arrive with role="signal"
if msg.role == "signal":
# Hook signals arrive with role="internal"
if msg.role == "internal":
hook_part = msg.get_hook_part()
if hook_part and hook_part.status == "pending":
answer = input(f"Approve {hook_part.hook_id}? [y/n] ")
Expand All @@ -90,8 +90,9 @@ async def with_approval(context: ai.Context) -> AsyncGenerator[ai.Message]:
)
continue

if msg.text_delta:
print(msg.text_delta, end="", flush=True)
for ev in msg.deltas:
if isinstance(ev.part, ai.TextPart):
print(ev.chunk, end="", flush=True)
print()


Expand Down
16 changes: 10 additions & 6 deletions examples/samples/agent_hooks_serverless.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,18 @@ async def with_confirmation(context: ai.Context) -> AsyncGenerator[ai.Message]:

durability = ai.EventLogProvider()
async for msg in my_agent.run(model, messages, durability=durability):
if msg.role == "signal":
if msg.role == "internal":
hook_part = msg.get_hook_part()
if hook_part and hook_part.status == "pending":
pending_hook_labels.append(hook_part.hook_id)
print(
f" Hook pending: {hook_part.hook_id}"
f" (metadata={hook_part.metadata})"
)
elif msg.text_delta:
print(msg.text_delta, end="", flush=True)
else:
for ev in msg.deltas:
if isinstance(ev.part, ai.TextPart):
print(ev.chunk, end="", flush=True)

saved_checkpoint = durability.checkpoint()
print(f"\n Checkpoint saved: {len(saved_checkpoint.steps)} steps\n")
Expand All @@ -108,12 +110,14 @@ async def with_confirmation(context: ai.Context) -> AsyncGenerator[ai.Message]:

durability = ai.EventLogProvider(saved_checkpoint)
async for msg in my_agent.run(model, messages, durability=durability):
if msg.role == "signal":
if msg.role == "internal":
hook_part = msg.get_hook_part()
if hook_part:
print(f" Hook {hook_part.status}: {hook_part.hook_id}")
elif msg.text_delta:
print(msg.text_delta, end="", flush=True)
else:
for ev in msg.deltas:
if isinstance(ev.part, ai.TextPart):
print(ev.chunk, end="", flush=True)
print()


Expand Down
7 changes: 4 additions & 3 deletions examples/samples/agent_nested.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import ai

model = ai.model("ai-gateway", "anthropic/claude-sonnet-4")
model = ai.ai_gateway("anthropic/claude-sonnet-4")


@ai.tool
Expand Down Expand Up @@ -45,8 +45,9 @@ async def main() -> None:
]

async for msg in orchestrator.run(model, messages):
if msg.text_delta:
print(msg.text_delta, end="", flush=True)
for ev in msg.deltas:
if isinstance(ev.part, ai.TextPart):
print(ev.chunk, end="", flush=True)
print()


Expand Down
7 changes: 4 additions & 3 deletions examples/samples/agent_simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ async def get_weather(city: str) -> str:


async def main() -> None:
model = ai.model("ai-gateway", "anthropic/claude-sonnet-4")
model = ai.ai_gateway("anthropic/claude-sonnet-4")

my_agent = ai.agent(tools=[get_weather])

Expand All @@ -22,8 +22,9 @@ async def main() -> None:
]

async for msg in my_agent.run(model, messages):
if msg.text_delta:
print(msg.text_delta, end="", flush=True)
for ev in msg.deltas:
if isinstance(ev.part, ai.TextPart):
print(ev.chunk, end="", flush=True)
print()


Expand Down
5 changes: 3 additions & 2 deletions examples/samples/explicit_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@
async def main() -> None:
try:
async for msg in await ai.models.stream(model, messages):
if msg.text_delta:
print(msg.text_delta, end="", flush=True)
for ev in msg.deltas:
if isinstance(ev.part, ai.TextPart):
print(ev.chunk, end="", flush=True)
print()
finally:
await client.aclose()
Expand Down
2 changes: 1 addition & 1 deletion examples/samples/image_edit.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

import ai

model = ai.model("ai-gateway", "openai/gpt-image-1")
model = ai.ai_gateway("openai/gpt-image-1")


async def main() -> None:
Expand Down
7 changes: 4 additions & 3 deletions examples/samples/inline_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

import ai

model = ai.model("ai-gateway", "google/gemini-3-pro-image")
model = ai.ai_gateway("google/gemini-3-pro-image")

messages = [
ai.system_message(
Expand All @@ -26,8 +26,9 @@ async def main() -> None:

# Stream — text deltas arrive as usual, images arrive as FileParts
async for msg in await ai.stream(model, messages):
if msg.text_delta:
print(msg.text_delta, end="", flush=True)
for ev in msg.deltas:
if isinstance(ev.part, ai.TextPart):
print(ev.chunk, end="", flush=True)
last_msg = msg

print()
Expand Down
7 changes: 4 additions & 3 deletions examples/samples/mcp_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@


async def main() -> None:
model = ai.model("ai-gateway", "anthropic/claude-sonnet-4")
model = ai.ai_gateway("anthropic/claude-sonnet-4")

context7_tools: list[ai.Tool[..., Any]] = await ai.mcp.get_http_tools(
"https://mcp.context7.com/mcp",
Expand All @@ -26,8 +26,9 @@ async def main() -> None:
]

async for msg in my_agent.run(model, messages):
if msg.text_delta:
print(msg.text_delta, end="", flush=True)
for ev in msg.deltas:
if isinstance(ev.part, ai.TextPart):
print(ev.chunk, end="", flush=True)
print()


Expand Down
5 changes: 3 additions & 2 deletions examples/samples/middleware_simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,9 @@ async def main() -> None:

print("--- starting agent run ---\n")
async for msg in my_agent.run(model, messages, middleware=[PrintMiddleware()]):
if msg.text_delta:
print(msg.text_delta, end="", flush=True)
for ev in msg.deltas:
if isinstance(ev.part, ai.TextPart):
print(ev.chunk, end="", flush=True)
print("\n\n--- done ---")


Expand Down
7 changes: 4 additions & 3 deletions examples/samples/multimodal_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import ai

model = ai.model("ai-gateway", "anthropic/claude-sonnet-4")
model = ai.ai_gateway("anthropic/claude-sonnet-4")

# Load a local image file (replace with your own path).
image_path = pathlib.Path("sample_image.jpg")
Expand All @@ -21,8 +21,9 @@

async def main() -> None:
async for msg in await ai.stream(model, messages):
if msg.text_delta:
print(msg.text_delta, end="", flush=True)
for ev in msg.deltas:
if isinstance(ev.part, ai.TextPart):
print(ev.chunk, end="", flush=True)
print()


Expand Down
5 changes: 3 additions & 2 deletions examples/samples/stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@

async def main() -> None:
async for msg in await ai.stream(model, messages):
if msg.text_delta:
print(msg.text_delta, end="", flush=True)
for ev in msg.deltas:
if isinstance(ev.part, ai.TextPart):
print(ev.chunk, end="", flush=True)
print()


Expand Down
16 changes: 9 additions & 7 deletions examples/samples/streaming_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,20 @@ async def talk_to_mothership(question: str) -> AsyncGenerator[ai.Message]:
for step in ["Connecting...", "Transmitting...", "Awaiting response..."]:
yield ai.Message(
role="assistant",
parts=[ai.TextPart(text=step, state="done")],
label="tool_progress",
parts=[ai.TextPart(text=step)],
source_label="tool_progress",
)
await asyncio.sleep(0.3)

# The final yielded message's text is returned as the tool result.
yield ai.Message(
role="assistant",
parts=[ai.TextPart(text="The mothership says: Soon.", state="done")],
parts=[ai.TextPart(text="The mothership says: Soon.")],
)


async def main() -> None:
model = ai.model("ai-gateway", "anthropic/claude-sonnet-4")
model = ai.ai_gateway("anthropic/claude-sonnet-4")

my_agent = ai.agent(tools=[talk_to_mothership])

Expand All @@ -40,10 +40,12 @@ async def main() -> None:
]

async for msg in my_agent.run(model, messages):
if msg.label == "tool_progress":
if msg.source_label == "tool_progress":
print(f" [{msg.text}]")
elif msg.text_delta:
print(msg.text_delta, end="", flush=True)
else:
for ev in msg.deltas:
if isinstance(ev.part, ai.TextPart):
print(ev.chunk, end="", flush=True)
print()


Expand Down
7 changes: 4 additions & 3 deletions examples/samples/structured_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import ai

model = ai.model("ai-gateway", "anthropic/claude-sonnet-4")
model = ai.ai_gateway("anthropic/claude-sonnet-4")


class Recipe(pydantic.BaseModel):
Expand All @@ -22,8 +22,9 @@ class Recipe(pydantic.BaseModel):
async def main() -> None:
# Stream with structured output — watch JSON arrive, get validated at the end
async for msg in await ai.stream(model, messages, output_type=Recipe):
if msg.text_delta:
print(msg.text_delta, end="", flush=True)
for ev in msg.deltas:
if isinstance(ev.part, ai.TextPart):
print(ev.chunk, end="", flush=True)
if msg.output:
recipe: Recipe = msg.output
print(f"\n\nParsed recipe: {recipe.name}")
Expand Down
11 changes: 6 additions & 5 deletions examples/samples/tools_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import ai

model = ai.model("ai-gateway", "anthropic/claude-sonnet-4")
model = ai.ai_gateway("anthropic/claude-sonnet-4")

# Define a tool schema — anything matching the ToolLike protocol works.
get_weather = ai.ToolSchema(
Expand All @@ -26,11 +26,12 @@
async def main() -> None:
# Stream with tools — the model may emit tool calls
async for msg in await ai.stream(model, messages, tools=[get_weather]):
if msg.text_delta:
print(msg.text_delta, end="", flush=True)
for ev in msg.deltas:
if isinstance(ev.part, ai.TextPart):
print(ev.chunk, end="", flush=True)

for tc in msg.tool_calls:
if tc.state == "done":
if msg.is_done:
for tc in msg.tool_calls:
print(f"\nTool call: {tc.tool_name}({tc.tool_args})")
print()

Expand Down
Loading
Loading