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
5 changes: 4 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@

1. use `uv` to manage the project; `uv add` and `uv remove` to manage dependencies, `uv run` to run
2. after making changes run lint and typecheck: `uv run ruff check --fix src tests` and `uv run mypy src tests`
3. import by module (except `typing`) to improve readability via namespacing
3. imports:
- import by module, using the shortest unambiguous relative path. `from ..core import helpers`, `from . import streaming`
- UNLESS it's `typing` — then `from typing import Foo` (there are too many of them).
- if the module name shadows a local variable in the same file, add a trailing underscore to the import: `from ..types import messages as messages_`. do not add trailing underscores preemptively — only when there is an actual collision.
4. treat `stream_step` and `stream_loop` as user code. they are convenience functions that could be reimplemented by the user, they *must* stay clean.

## design principles
Expand Down
15 changes: 9 additions & 6 deletions examples/fastapi-vite/backend/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,21 @@ async def talk_to_mothership(question: str) -> str:

async def _execute_with_approval(
tc: ai.ToolPart, message: ai.Message | None = None
) -> None:
) -> ai.ToolPart:
"""Execute a tool call only after the user grants approval.

Creates a ToolApproval hook that suspends execution until the
frontend responds with an approve/reject decision.
Returns the updated (immutable) ToolPart with the result.
"""
approval = await ai.ToolApproval.create( # type: ignore[attr-defined]
f"approve_{tc.tool_call_id}",
metadata={"tool_name": tc.tool_name, "tool_args": tc.tool_args},
)

if approval.granted:
await ai.execute_tool(tc, message=message)
else:
tc.set_error("Tool call was denied by the user.")
return await ai.execute_tool(tc, message=message)
return tc.with_error("Tool call was denied by the user.")


chat_agent = ai.agent(
Expand Down Expand Up @@ -73,8 +73,11 @@ async def graph(

last_msg = result.last_message
assert last_msg is not None
local_messages.append(last_msg)

await asyncio.gather(
updated_parts = await asyncio.gather(
*(_execute_with_approval(tc, message=last_msg) for tc in result.tool_calls)
)
updated_msg = last_msg
for updated_tc in updated_parts:
updated_msg = updated_msg.replace(updated_tc)
local_messages.append(updated_msg)
9 changes: 2 additions & 7 deletions examples/models/buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,16 @@

import asyncio

import vercel_ai_sdk as ai
from vercel_ai_sdk import models as m
from vercel_ai_sdk.types import messages as messages_

model = m.Model(
id="anthropic/claude-sonnet-4",
adapter="ai-gateway-v3",
provider="ai-gateway",
)

messages = [
messages_.Message(
role="user",
parts=[messages_.TextPart(text="What is 2 + 2?")],
),
]
messages = [ai.user_message("What is 2 + 2?")]


async def main() -> None:
Expand Down
9 changes: 2 additions & 7 deletions examples/models/direct_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
import asyncio
import os

import vercel_ai_sdk as ai
from vercel_ai_sdk import models as m
from vercel_ai_sdk.models import ai_gateway as ai_gateway_v3
from vercel_ai_sdk.types import messages as messages_

model = m.Model(
id="anthropic/claude-sonnet-4",
Expand All @@ -18,12 +18,7 @@
api_key=os.environ["AI_GATEWAY_API_KEY"],
)

messages = [
messages_.Message(
role="user",
parts=[messages_.TextPart(text="Say hello in three languages.")],
),
]
messages = [ai.user_message("Say hello in three languages.")]


async def main() -> None:
Expand Down
9 changes: 2 additions & 7 deletions examples/models/explicit_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import asyncio
import os

import vercel_ai_sdk as ai
from vercel_ai_sdk import models as m
from vercel_ai_sdk.types import messages as messages_

model = m.Model(
id="anthropic/claude-sonnet-4",
Expand All @@ -19,12 +19,7 @@
headers={"X-Custom-Header": "example"},
)

messages = [
messages_.Message(
role="user",
parts=[messages_.TextPart(text="Hello!")],
),
]
messages = [ai.user_message("Hello!")]


async def main() -> None:
Expand Down
19 changes: 6 additions & 13 deletions examples/models/image_generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
import base64
import pathlib

import vercel_ai_sdk as ai
from vercel_ai_sdk import models as m
from vercel_ai_sdk.types import messages as messages_

model = m.Model(
id="google/imagen-4.0-generate-001",
Expand All @@ -15,18 +15,11 @@
)

messages = [
messages_.Message(
role="user",
parts=[
messages_.TextPart(
text=(
"Anime girl with twin tails and cat ears, wearing a "
"sailor school uniform, striking a victory pose in front "
"of a futuristic Tokyo skyline at night, neon lights "
"reflecting in her eyes, digital art style"
)
),
],
ai.user_message(
"Anime girl with twin tails and cat ears, wearing a "
"sailor school uniform, striking a victory pose in front "
"of a futuristic Tokyo skyline at night, neon lights "
"reflecting in her eyes, digital art style"
),
]

Expand Down
30 changes: 8 additions & 22 deletions examples/models/inline_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
import base64
import pathlib

import vercel_ai_sdk as ai
from vercel_ai_sdk import models as m
from vercel_ai_sdk.types import messages as messages_

# This is a language model that can also output images inline.
model = m.Model(
Expand All @@ -21,33 +21,19 @@
)

messages = [
messages_.Message(
role="system",
parts=[
messages_.TextPart(
text=(
"You are an anime art assistant. When asked to draw or create "
"an image, generate it in a soft pastel anime style."
)
),
],
ai.system_message(
"You are an anime art assistant. When asked to draw or create "
"an image, generate it in a soft pastel anime style."
),
messages_.Message(
role="user",
parts=[
messages_.TextPart(
text=(
"Draw an anime girl with long silver hair and violet eyes, "
"sitting in a field of cherry blossoms at sunset."
)
),
],
ai.user_message(
"Draw an anime girl with long silver hair and violet eyes, "
"sitting in a field of cherry blossoms at sunset."
),
]


async def main() -> None:
last_msg: messages_.Message | None = None
last_msg: ai.Message | None = None

# Stream — text deltas arrive as usual, images arrive as FileParts
async for msg in m.stream(model, messages):
Expand Down
11 changes: 4 additions & 7 deletions examples/models/multimodal_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import asyncio
import pathlib

import vercel_ai_sdk as ai
from vercel_ai_sdk import models as m
from vercel_ai_sdk.types import messages as messages_

model = m.Model(
id="anthropic/claude-sonnet-4",
Expand All @@ -17,12 +17,9 @@
image_data = image_path.read_bytes()

messages = [
messages_.Message(
role="user",
parts=[
messages_.TextPart(text="Describe this image in detail."),
messages_.FilePart(data=image_data, media_type="image/jpeg"),
],
ai.user_message(
"Describe this image in detail.",
ai.file_part(image_data, media_type="image/jpeg"),
),
]

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

import asyncio

import vercel_ai_sdk as ai
from vercel_ai_sdk import models as m
from vercel_ai_sdk.types import messages as messages_

model = m.Model(
id="anthropic/claude-sonnet-4",
Expand All @@ -12,13 +12,8 @@
)

messages = [
messages_.Message(role="system", parts=[messages_.TextPart(text="Be concise.")]),
messages_.Message(
role="user",
parts=[
messages_.TextPart(text="Explain why the sky is blue in two sentences.")
],
),
ai.system_message("Be concise."),
ai.user_message("Explain why the sky is blue in two sentences."),
]


Expand Down
9 changes: 2 additions & 7 deletions examples/models/structured_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

import pydantic

import vercel_ai_sdk as ai
from vercel_ai_sdk import models as m
from vercel_ai_sdk.types import messages as messages_

model = m.Model(
id="anthropic/claude-sonnet-4",
Expand All @@ -21,12 +21,7 @@ class Recipe(pydantic.BaseModel):
prep_time_minutes: int


messages = [
messages_.Message(
role="user",
parts=[messages_.TextPart(text="Give me a simple pancake recipe.")],
),
]
messages = [ai.user_message("Give me a simple pancake recipe.")]


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

import asyncio

import vercel_ai_sdk as ai
from vercel_ai_sdk import models as m
from vercel_ai_sdk.types import messages as messages_
from vercel_ai_sdk.types import tools as tools_

model = m.Model(
Expand All @@ -26,12 +26,7 @@
return_type=str,
)

messages = [
messages_.Message(
role="user",
parts=[messages_.TextPart(text="What's the weather in Tokyo?")],
),
]
messages = [ai.user_message("What's the weather in Tokyo?")]


async def main() -> None:
Expand Down
19 changes: 6 additions & 13 deletions examples/models/video_generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
import base64
import pathlib

import vercel_ai_sdk as ai
from vercel_ai_sdk import models as m
from vercel_ai_sdk.types import messages as messages_

model = m.Model(
id="google/veo-3.0-generate-001",
Expand All @@ -15,18 +15,11 @@
)

messages = [
messages_.Message(
role="user",
parts=[
messages_.TextPart(
text=(
"An anime girl with long pink hair and a flowing white "
"dress stands on a hilltop at golden hour. A warm breeze "
"lifts her hair as she releases a paper lantern into the "
"sunset sky. Soft cel-shaded anime art style, warm palette."
)
),
],
ai.user_message(
"An anime girl with long pink hair and a flowing white "
"dress stands on a hilltop at golden hour. A warm breeze "
"lifts her hair as she releases a paper lantern into the "
"sunset sky. Soft cel-shaded anime art style, warm palette."
),
]

Expand Down
Loading
Loading