
Vercel Ai Sdk
Implement Python LLM features with vercel-ai-sdk using ai.run, streaming stream_loop, @ai.tool functions, and gateway models without breaking Runtime context rules.
Install
npx skills add https://github.com/vercel-labs/py-ai --skill vercel-ai-sdkWhat is this skill?
- `ai.run(root, *args)` creates Runtime context; all stream_step, execute_tool, and hooks must run inside it.
- `@ai.tool` builds LLM-callable tools from async functions with schema from type hints and docstrings.
- `stream_loop` with `make_messages` drives multi-step tool-using agent conversations.
- `GatewayModel` via ai.ai_gateway for routed model access such as anthropic/claude-opus-4.6.
Adoption & trust: 25 installs on skills.sh; 70 GitHub stars; 1/3 security scanners passed (skills.sh audits).
Recommended Skills
Journey fit
Build is canonical because the skill teaches how to assemble agentic Python apps, tools, and streaming loops—not distribution or production ops. Agent-tooling matches SDK entry points, tool registration, Runtime injection, and stream_step patterns for coding agents.
Common Questions / FAQ
Is Vercel Ai Sdk safe to install?
skills.sh reports 1 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.
SKILL.md
READMESKILL.md - Vercel Ai Sdk
# Vercel AI SDK (Python) ```bash uv add vercel-ai-sdk ``` ```python import vercel_ai_sdk as ai ``` ## Core workflow `ai.run(root, *args, checkpoint=None)` is the entry point. It creates a `Runtime` (stored in a context var), starts `root` as a background task, processes an internal step queue, and yields `Message` objects. All SDK functions (`stream_step`, `execute_tool`, hooks) require this Runtime context -- they must be called within `ai.run()`. The root function is any async function. If it declares a param typed `ai.Runtime`, it's auto-injected. ```python @ai.tool async def talk_to_mothership(question: str) -> str: """Contact the mothership for important decisions.""" return "Soon." async def agent(llm: ai.LanguageModel, query: str) -> ai.StreamResult: return await ai.stream_loop( llm, messages=ai.make_messages(system="You are a robot assistant.", user=query), tools=[talk_to_mothership], ) llm = ai.ai_gateway.GatewayModel(model="anthropic/claude-opus-4.6") async for msg in ai.run(agent, llm, "When will the robots take over?"): print(msg.text_delta, end="") ``` **`@ai.tool`** turns an async function into a `Tool`. Schema is extracted from type hints + docstring. If a tool declares `runtime: ai.Runtime`, it's auto-injected (excluded from LLM schema). Tools are registered globally by name. **`ai.stream_step(llm, messages, tools=None, label=None, output_type=None)`** -- single LLM call. Returns `StreamResult` with `.text`, `.tool_calls`, `.output`, `.usage`, `.last_message`. **`ai.stream_loop(llm, messages, tools, label=None, output_type=None)`** -- agent loop: calls LLM → executes tools → repeats until no tool calls. Returns final `StreamResult`. Both are thin convenience wrappers (not magical -- they could be reimplemented by the user). `stream_step` is a `@ai.stream`-decorated function that calls `llm.stream()`. `stream_loop` calls `stream_step` in a while loop with `ai.execute_tool()` between iterations. **`ai.execute_tool(tool_call, message=None)`** runs a tool call by name from the global registry. Handles malformed JSON / invalid args gracefully -- reports as a tool error so the LLM can retry rather than crashing. ### Multi-agent Use `asyncio.gather` with labels to run agents in parallel: ```python async def multi(llm: ai.LanguageModel, query: str) -> ai.StreamResult: r1, r2 = await asyncio.gather( ai.stream_loop(llm, msgs1, tools=[t1], label="researcher"), ai.stream_loop(llm, msgs2, tools=[t2], label="analyst"), ) return await ai.stream_loop( llm, ai.make_messages(user=f"{r1.text}\n{r2.text}"), tools=[], label="summary", ) ``` The `label` field on messages lets the consumer distinguish which agent produced output (e.g. `msg.label == "researcher"`). ### Messages `ai.make_messages(system=None, user=str)` builds a message list. `Message` is a Pydantic model with `role`, `parts` (list of `TextPart | ToolPart | ReasoningPart | HookPart | StructuredOutputPart`), `label`, and `usage`. Serialize with `msg.model_dump()`, restore with `ai.Message.model_validate(data)`. Key properties for consuming streamed output: - `msg.text_delta` -- current text chunk (use for live streaming display) - `msg.text` -- full accumulated text - `msg.tool_calls` -- list of `ToolPart` objects - `msg.output` -- validated Pydantic instance (when using `output_type`) - `msg.is_done` -- true when all parts finished streaming - `msg.get_hook_part()` -- find a hook suspension part (for human-in-the-loop) ## Customization ### Custom loop When `stream_loop` doesn't fit (conditional tool execution, approval gates, custom routing), use `stream_step` in a manual loop: ```python async def agent(llm: ai.LanguageModel, query: str) -> ai.StreamResult: messages = ai.make_me