diff --git a/examples/fastapi-vite/backend/main.py b/examples/fastapi-vite/backend/main.py index 5886f964..a38fd2ab 100644 --- a/examples/fastapi-vite/backend/main.py +++ b/examples/fastapi-vite/backend/main.py @@ -1,17 +1,17 @@ """FastAPI application entry point.""" -from fastapi import FastAPI -from fastapi.middleware.cors import CORSMiddleware +import fastapi +import fastapi.middleware.cors from .routes import chat -app = FastAPI( +app = fastapi.FastAPI( title="py-ai-fastapi-chat", description="Chat demo using Python Vercel AI SDK", ) app.add_middleware( - CORSMiddleware, + fastapi.middleware.cors.CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], diff --git a/examples/fastapi-vite/backend/routes/chat.py b/examples/fastapi-vite/backend/routes/chat.py index b62f4847..afd0d109 100644 --- a/examples/fastapi-vite/backend/routes/chat.py +++ b/examples/fastapi-vite/backend/routes/chat.py @@ -2,63 +2,58 @@ from __future__ import annotations -from fastapi import APIRouter -from fastapi.responses import StreamingResponse -from pydantic import BaseModel +import fastapi +import fastapi.responses +import pydantic import vercel_ai_sdk as ai -from vercel_ai_sdk.ai_sdk_ui import ( - UI_MESSAGE_STREAM_HEADERS, - UIMessage, - to_messages, - to_sse_stream, -) +import vercel_ai_sdk.ai_sdk_ui -from ..agent import TOOLS, get_llm, graph -from ..storage import FileStorage +from .. import agent +from .. import storage -router = APIRouter() -storage = FileStorage() +router = fastapi.APIRouter() +file_storage = storage.FileStorage() -class ChatRequest(BaseModel): +class ChatRequest(pydantic.BaseModel): """Request body for the chat endpoint.""" - messages: list[UIMessage] + messages: list[ai.ai_sdk_ui.UIMessage] session_id: str | None = None @router.post("/chat") async def chat(request: ChatRequest): """Handle chat requests and stream responses.""" - messages = to_messages(request.messages) + messages = ai.ai_sdk_ui.to_messages(request.messages) session_id = request.session_id or "default" checkpoint_key = f"checkpoint:{session_id}" - llm = get_llm() + llm = agent.get_llm() # Checkpoints resume an *interrupted* run (e.g. a hook that needed # user input in serverless mode). Each normal chat turn is a fresh # run — the frontend carries the full message history — so we only # load a checkpoint when one was saved from a previous incomplete run. - saved = await storage.get(checkpoint_key) + saved = await file_storage.get(checkpoint_key) checkpoint = ai.Checkpoint.deserialize(saved) if saved else None - result = ai.run(graph, llm, messages, TOOLS, checkpoint=checkpoint) + result = ai.run(agent.graph, llm, messages, agent.TOOLS, checkpoint=checkpoint) async def stream_response(): - async for chunk in to_sse_stream(result): + async for chunk in ai.ai_sdk_ui.to_sse_stream(result): yield chunk # If the run completed (no pending hooks), clear the checkpoint # so the next request starts fresh. If hooks are pending, save # the checkpoint so the next request can resume from here. if result.pending_hooks: - await storage.put(checkpoint_key, result.checkpoint.serialize()) + await file_storage.put(checkpoint_key, result.checkpoint.serialize()) else: - await storage.delete(checkpoint_key) + await file_storage.delete(checkpoint_key) - return StreamingResponse( + return fastapi.responses.StreamingResponse( stream_response(), - headers=UI_MESSAGE_STREAM_HEADERS, + headers=ai.ai_sdk_ui.UI_MESSAGE_STREAM_HEADERS, ) diff --git a/examples/fastapi-vite/backend/storage.py b/examples/fastapi-vite/backend/storage.py index 990f7614..2bd4c59d 100644 --- a/examples/fastapi-vite/backend/storage.py +++ b/examples/fastapi-vite/backend/storage.py @@ -10,7 +10,7 @@ from __future__ import annotations import json -from pathlib import Path +import pathlib from typing import Any, Protocol, runtime_checkable @@ -31,11 +31,11 @@ class FileStorage: local development; replace with a real database for production. """ - def __init__(self, directory: str | Path = "./data") -> None: - self._dir = Path(directory) + def __init__(self, directory: str | pathlib.Path = "./data") -> None: + self._dir = pathlib.Path(directory) self._dir.mkdir(parents=True, exist_ok=True) - def _path(self, key: str) -> Path: + def _path(self, key: str) -> pathlib.Path: # Sanitise the key so it's safe as a filename safe = key.replace("/", "__").replace(":", "_") return self._dir / f"{safe}.json" diff --git a/examples/multiagent-textual/client.py b/examples/multiagent-textual/client.py index 5f2bfde8..0c25c5b3 100644 --- a/examples/multiagent-textual/client.py +++ b/examples/multiagent-textual/client.py @@ -12,13 +12,13 @@ import asyncio import json +import textual +import textual.app +import textual.containers +import textual.widgets +import textual.worker +import rich.text import websockets -from rich.text import Text -from textual import work -from textual.app import App, ComposeResult -from textual.containers import VerticalScroll -from textual.widgets import Input, Static -from textual.worker import get_current_worker import vercel_ai_sdk as ai @@ -29,7 +29,7 @@ # --------------------------------------------------------------------------- -class AgentPanel(VerticalScroll): +class AgentPanel(textual.containers.VerticalScroll): """Scrolling panel for one agent's output stream.""" DEFAULT_CSS = """ @@ -47,15 +47,15 @@ def __init__(self, agent_id: str, title: str) -> None: super().__init__(id=agent_id) self._title = title self._status = "idle" - self._content = Text() + self._content = rich.text.Text() self._update_border_title() - def compose(self) -> ComposeResult: - yield Static(id=f"{self.id}-text") + def compose(self) -> textual.app.ComposeResult: + yield textual.widgets.Static(id=f"{self.id}-text") @property - def text_widget(self) -> Static: - return self.query_one(f"#{self.id}-text", Static) + def text_widget(self) -> textual.widgets.Static: + return self.query_one(f"#{self.id}-text", textual.widgets.Static) # -- status management ------------------------------------------------- @@ -90,7 +90,7 @@ def append_line(self, text: str, style: str = "dim") -> None: # --------------------------------------------------------------------------- -class MultiAgentApp(App): +class MultiAgentApp(textual.app.App): """Textual app for the multi-agent hooks demo.""" CSS = """ @@ -112,11 +112,11 @@ def __init__(self) -> None: self._current_hook: ai.HookPart | None = None self._ws: websockets.ClientConnection | None = None - def compose(self) -> ComposeResult: + def compose(self) -> textual.app.ComposeResult: yield AgentPanel("mothership", "mothership") yield AgentPanel("data_centers", "data-centers") yield AgentPanel("summary", "summary") - yield Input( + yield textual.widgets.Input( placeholder="waiting for agents...", disabled=True, id="input-bar", @@ -129,9 +129,9 @@ def on_mount(self) -> None: # WebSocket reader (background worker) # ------------------------------------------------------------------ - @work(exclusive=True) + @textual.work(exclusive=True) async def run_websocket(self) -> None: - worker = get_current_worker() + worker = textual.worker.get_current_worker() try: async with websockets.connect(WS_URL) as ws: @@ -228,7 +228,7 @@ def _on_run_complete(self) -> None: if panel: panel.status = "complete" - inp = self.query_one("#input-bar", Input) + inp = self.query_one("#input-bar", textual.widgets.Input) inp.disabled = True inp.placeholder = "done — press q to quit" @@ -249,12 +249,12 @@ def _maybe_activate_next_hook(self) -> None: branch = hook.metadata.get("branch", "unknown") tool = hook.metadata.get("tool", "?") - inp = self.query_one("#input-bar", Input) + inp = self.query_one("#input-bar", textual.widgets.Input) inp.disabled = False inp.placeholder = f"approve {branch}/{tool}? [y/n]" inp.focus() - async def on_input_submitted(self, event: Input.Submitted) -> None: + async def on_input_submitted(self, event: textual.widgets.Input.Submitted) -> None: if self._current_hook is None: event.input.clear() return @@ -299,7 +299,7 @@ def _get_panel(self, label: str) -> AgentPanel | None: return None def _set_input_placeholder(self, text: str) -> None: - inp = self.query_one("#input-bar", Input) + inp = self.query_one("#input-bar", textual.widgets.Input) inp.placeholder = text diff --git a/examples/multiagent-textual/server.py b/examples/multiagent-textual/server.py index 0e253b96..5e0f071d 100644 --- a/examples/multiagent-textual/server.py +++ b/examples/multiagent-textual/server.py @@ -15,15 +15,15 @@ import os import warnings +import fastapi import pydantic -from fastapi import FastAPI, WebSocket, WebSocketDisconnect import vercel_ai_sdk as ai # ToolPart.result is typed as dict but tools can return plain strings. warnings.filterwarnings("ignore", category=UserWarning, module="pydantic") -app = FastAPI(title="multiagent-textual") +app = fastapi.FastAPI(title="multiagent-textual") # --------------------------------------------------------------------------- # Tools @@ -168,7 +168,7 @@ def _normalise_message(data: dict) -> dict: @app.websocket("/ws") -async def ws_endpoint(websocket: WebSocket): +async def ws_endpoint(websocket: fastapi.WebSocket): await websocket.accept() print("Client connected") @@ -191,7 +191,7 @@ async def read_resolutions(): data["hook_id"], {"granted": data["granted"], "reason": data["reason"]}, ) - except WebSocketDisconnect: + except fastapi.WebSocketDisconnect: pass reader = asyncio.create_task(read_resolutions()) diff --git a/examples/temporal-durable/activities.py b/examples/temporal-durable/activities.py index 29191669..6993750d 100644 --- a/examples/temporal-durable/activities.py +++ b/examples/temporal-durable/activities.py @@ -7,25 +7,25 @@ from __future__ import annotations +import dataclasses import os -from dataclasses import dataclass from typing import Any -from temporalio import activity +import temporalio.activity import vercel_ai_sdk as ai -from vercel_ai_sdk.anthropic import AnthropicModel +import vercel_ai_sdk.anthropic # ── Tool activities (one per tool, plain functions) ─────────────── -@activity.defn(name="get_weather") +@temporalio.activity.defn(name="get_weather") async def get_weather_activity(city: str) -> str: return f"Sunny, 72F in {city}" -@activity.defn(name="get_population") +@temporalio.activity.defn(name="get_population") async def get_population_activity(city: str) -> int: return {"new york": 8_336_817, "los angeles": 3_979_576}.get( city.lower(), 1_000_000 @@ -35,21 +35,21 @@ async def get_population_activity(city: str) -> int: # ── LLM activity ───────────────────────────────────────────────── -@dataclass +@dataclasses.dataclass class LLMCallParams: messages: list[dict[str, Any]] tool_schemas: list[dict[str, Any]] -@dataclass +@dataclasses.dataclass class LLMCallResult: message: dict[str, Any] # serialized ai.Message -@activity.defn(name="llm_call") +@temporalio.activity.defn(name="llm_call") async def llm_call_activity(params: LLMCallParams) -> LLMCallResult: """Call the LLM, drain the stream, return the final message.""" - llm = AnthropicModel( + llm = ai.anthropic.AnthropicModel( model="anthropic/claude-sonnet-4", base_url="https://ai-gateway.vercel.sh", api_key=os.environ.get("AI_GATEWAY_API_KEY"), diff --git a/examples/temporal-durable/main.py b/examples/temporal-durable/main.py index 1beb6ffa..37363293 100644 --- a/examples/temporal-durable/main.py +++ b/examples/temporal-durable/main.py @@ -15,30 +15,34 @@ import sys import uuid -from temporalio.client import Client -from temporalio.worker import Worker +import temporalio.client +import temporalio.worker -from activities import get_weather_activity, get_population_activity, llm_call_activity -from workflow import AgentWorkflow +import activities +import workflow TASK_QUEUE = "agent-durable" async def main(user_query: str) -> None: - client = await Client.connect("localhost:7233") + temporal = await temporalio.client.Client.connect("localhost:7233") - async with Worker( - client, + async with temporalio.worker.Worker( + temporal, task_queue=TASK_QUEUE, - workflows=[AgentWorkflow], - activities=[llm_call_activity, get_weather_activity, get_population_activity], + workflows=[workflow.AgentWorkflow], + activities=[ + activities.llm_call_activity, + activities.get_weather_activity, + activities.get_population_activity, + ], ): workflow_id = f"agent-durable-{uuid.uuid4().hex[:8]}" print(f"Workflow {workflow_id}") print(f"Query: {user_query}\n") - result = await client.execute_workflow( - AgentWorkflow.run, + result = await temporal.execute_workflow( + workflow.AgentWorkflow.run, user_query, id=workflow_id, task_queue=TASK_QUEUE, diff --git a/examples/temporal-durable/workflow.py b/examples/temporal-durable/workflow.py index e7d42275..1f747286 100644 --- a/examples/temporal-durable/workflow.py +++ b/examples/temporal-durable/workflow.py @@ -2,28 +2,25 @@ from __future__ import annotations +import datetime from collections.abc import AsyncGenerator, Awaitable, Callable, Sequence -from datetime import timedelta from typing import override -from temporalio import workflow -from temporalio.common import RetryPolicy +import temporalio.common +import temporalio.workflow -with workflow.unsafe.imports_passed_through(): +with temporalio.workflow.unsafe.imports_passed_through(): import vercel_ai_sdk as ai - from activities import ( - LLMCallParams, - LLMCallResult, - get_population_activity, - get_weather_activity, - llm_call_activity, - ) + import activities class DurableModel(ai.LanguageModel): def __init__( - self, call_fn: Callable[[LLMCallParams], Awaitable[LLMCallResult]] + self, + call_fn: Callable[ + [activities.LLMCallParams], Awaitable[activities.LLMCallResult] + ], ) -> None: self.call_fn = call_fn @@ -34,7 +31,7 @@ async def stream( tools: Sequence[ai.ToolSchema] | None = None, ) -> AsyncGenerator[ai.Message, None]: result = await self.call_fn( - LLMCallParams( + activities.LLMCallParams( messages=[m.model_dump() for m in messages], tool_schemas=[t.model_dump() for t in (tools or [])], ) @@ -52,20 +49,20 @@ async def stream( @ai.tool async def get_weather(city: str) -> str: """Get current weather for a city.""" - return await workflow.execute_activity( - get_weather_activity, + return await temporalio.workflow.execute_activity( + activities.get_weather_activity, args=[city], - start_to_close_timeout=timedelta(minutes=2), + start_to_close_timeout=datetime.timedelta(minutes=2), ) @ai.tool async def get_population(city: str) -> int: """Get population of a city.""" - return await workflow.execute_activity( - get_population_activity, + return await temporalio.workflow.execute_activity( + activities.get_population_activity, args=[city], - start_to_close_timeout=timedelta(minutes=2), + start_to_close_timeout=datetime.timedelta(minutes=2), ) @@ -84,16 +81,16 @@ async def agent(llm: ai.LanguageModel, user_query: str) -> ai.StreamResult: # ── Workflow ───────────────────────────────────────────────────── -@workflow.defn +@temporalio.workflow.defn class AgentWorkflow: - @workflow.run + @temporalio.workflow.run async def run(self, user_query: str) -> str: llm = DurableModel( - lambda params: workflow.execute_activity( - llm_call_activity, + lambda params: temporalio.workflow.execute_activity( + activities.llm_call_activity, params, - start_to_close_timeout=timedelta(minutes=5), - retry_policy=RetryPolicy(maximum_attempts=3), + start_to_close_timeout=datetime.timedelta(minutes=5), + retry_policy=temporalio.common.RetryPolicy(maximum_attempts=3), ) )