Skip to content

Commit 5f487d6

Browse files
authored
upgrade package for ai-chat-sdk to v6 (#8864)
1 parent ec56fa0 commit 5f487d6

11 files changed

Lines changed: 90 additions & 238 deletions

File tree

examples/ai/chat/pydantic-ai-chat.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
1+
# /// script
2+
# requires-python = ">=3.13"
3+
# dependencies = [
4+
# "httpx==0.28.1",
5+
# "marimo>=0.21.1",
6+
# "pydantic==2.12.5",
7+
# ]
8+
# ///
19
import marimo
210

3-
__generated_with = "0.19.2"
11+
__generated_with = "0.21.1"
412
app = marimo.App(width="medium")
513

614
with app.setup(hide_code=True):

frontend/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"./unstable_internal/*": "./src/*"
2222
},
2323
"dependencies": {
24-
"@ai-sdk/react": "^2.0.125",
24+
"@ai-sdk/react": "^3.0.131",
2525
"@anywidget/types": "^0.2.0",
2626
"@codemirror/autocomplete": "^6.20.1",
2727
"@codemirror/commands": "^6.10.2",
@@ -115,7 +115,7 @@
115115
"@xterm/addon-web-links": "^0.12.0",
116116
"@xterm/xterm": "^5.5.0",
117117
"@zed-industries/agent-client-protocol": "^0.4.5",
118-
"ai": "^5.0.123",
118+
"ai": "^6.0.129",
119119
"ansi_up": "^6.0.6",
120120
"class-variance-authority": "^0.7.1",
121121
"clsx": "^2.1.1",

frontend/src/components/chat/chat-utils.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
/* Copyright 2026 Marimo. All rights reserved. */
22

33
import type { components } from "@marimo-team/marimo-api";
4-
import type { FileUIPart, ToolUIPart, UIMessage } from "ai";
4+
import type {
5+
ChatAddToolOutputFunction,
6+
FileUIPart,
7+
ToolUIPart,
8+
UIMessage,
9+
} from "ai";
510
import { useState } from "react";
611
import useEvent from "react-use-event-hook";
712
import type { ProviderId } from "@/core/ai/ids/ids";
@@ -111,20 +116,14 @@ export async function buildCompletionRequestBody(
111116
};
112117
}
113118

114-
interface AddToolOutput {
115-
tool: string;
116-
toolCallId: string;
117-
output: unknown;
118-
}
119-
120119
export async function handleToolCall({
121120
invokeAiTool,
122121
addToolOutput, // Important that we don't await addToolOutput to prevent potential deadlocks
123122
toolCall,
124123
toolContext,
125124
}: {
126125
invokeAiTool: (request: InvokeAiToolRequest) => Promise<InvokeAiToolResponse>;
127-
addToolOutput: (output: AddToolOutput) => Promise<void>;
126+
addToolOutput: ChatAddToolOutputFunction<UIMessage>;
128127
toolCall: {
129128
toolName: string;
130129
toolCallId: string;

frontend/src/core/ai/staged-cells.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,12 @@ export function useStagedCells(store: JotaiStore) {
161161
case "tool-output-error":
162162
Logger.error("Error", chunk.type, { chunk });
163163
break;
164+
case "tool-approval-request":
165+
Logger.log("Tool approval request", { chunk });
166+
break;
167+
case "tool-output-denied":
168+
Logger.error("Tool output denied", { chunk });
169+
break;
164170
// These logs are not useful for debugging
165171
case "start":
166172
case "start-step":

marimo/_plugins/ui/_impl/chat/chat.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,17 @@
3636
)
3737

3838
# The version of the Vercel AI SDK we use
39-
AI_SDK_VERSION: Final[Literal[5, 6]] = 5
39+
AI_SDK_VERSION: Final[Literal[5, 6]] = 6
4040
DONE_CHUNK: Final[str] = "[DONE]"
4141

4242

43+
def require_vercel_ai_sdk_support() -> None:
44+
"""Only Pydantic AI >=1.52.0 supports AI SDK v6. So, we require it."""
45+
DependencyManager.pydantic_ai.require_at_version(
46+
why="for Vercel AI SDK support", min_version="1.52.0"
47+
)
48+
49+
4350
@dataclass
4451
class SendMessageRequest:
4552
messages: list[ChatMessage]
@@ -413,9 +420,8 @@ def _convert_value(self, value: dict[str, Any]) -> list[ChatMessage]:
413420

414421
part_validator_class = None
415422
if DependencyManager.pydantic_ai.imported():
416-
from pydantic_ai.ui.vercel_ai.request_types import (
417-
UIMessagePart,
418-
)
423+
require_vercel_ai_sdk_support()
424+
from pydantic_ai.ui.vercel_ai.request_types import UIMessagePart
419425

420426
# The frontend sends messages as ChatMessage parts so we use pydantic-ai to cast them
421427
# as Vercel UIMessagePart
@@ -466,20 +472,15 @@ def handle_chunk(self, chunk: Any) -> None:
466472

467473
# Handle Pydantic AI's Vercel AI SDK chunks
468474
if DependencyManager.pydantic_ai.imported():
475+
require_vercel_ai_sdk_support()
469476
from pydantic_ai.ui.vercel_ai.response_types import (
470477
BaseChunk,
471478
)
472479

473480
if isinstance(chunk, BaseChunk):
474-
try:
475-
serialized = json.loads(
476-
chunk.encode(sdk_version=AI_SDK_VERSION)
477-
)
478-
except TypeError:
479-
# Fallback for pydantic-ai < 1.52.0 which doesn't have sdk_version param
480-
serialized = chunk.model_dump(
481-
mode="json", by_alias=True, exclude_none=True
482-
)
481+
serialized = json.loads(
482+
chunk.encode(sdk_version=AI_SDK_VERSION)
483+
)
483484
self.on_send_chunk(serialized)
484485
return
485486

marimo/_server/ai/providers.py

Lines changed: 14 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@
2525
generate_id,
2626
)
2727
from marimo._dependencies.dependencies import Dependency, DependencyManager
28-
from marimo._plugins.ui._impl.chat.chat import AI_SDK_VERSION
28+
from marimo._plugins.ui._impl.chat.chat import (
29+
AI_SDK_VERSION,
30+
require_vercel_ai_sdk_support,
31+
)
2932
from marimo._server.ai.config import AnyProviderConfig
3033
from marimo._server.ai.ids import AiModelId
3134
from marimo._server.ai.tools.tool_manager import get_tool_manager
@@ -40,7 +43,6 @@
4043
from openai import AsyncOpenAI
4144
from openai.types.shared.reasoning_effort import ReasoningEffort
4245
from pydantic_ai import Agent, DeferredToolRequests, FunctionToolset
43-
from pydantic_ai.messages import ThinkingPart
4446
from pydantic_ai.models import Model
4547
from pydantic_ai.models.bedrock import BedrockConverseModel
4648
from pydantic_ai.models.google import GoogleModel
@@ -54,9 +56,7 @@
5456
)
5557
from pydantic_ai.providers.google import GoogleProvider as PydanticGoogle
5658
from pydantic_ai.providers.openai import OpenAIProvider as PydanticOpenAI
57-
from pydantic_ai.ui.vercel_ai import VercelAIAdapter
5859
from pydantic_ai.ui.vercel_ai.request_types import UIMessage, UIMessagePart
59-
from pydantic_ai.ui.vercel_ai.response_types import BaseChunk
6060
from starlette.responses import StreamingResponse
6161

6262

@@ -101,6 +101,7 @@ def __init__(
101101
*(deps or []),
102102
source="server",
103103
)
104+
require_vercel_ai_sdk_support()
104105

105106
self.model = model
106107
self.config = config
@@ -132,12 +133,6 @@ def create_agent(
132133
output_type=output_type,
133134
)
134135

135-
def get_vercel_adapter(self) -> type[VercelAIAdapter[Any, Any]]:
136-
"""Return the Vercel AI adapter for the given provider."""
137-
from pydantic_ai.ui.vercel_ai import VercelAIAdapter
138-
139-
return VercelAIAdapter
140-
141136
def convert_messages(
142137
self, messages: list[ServerUIMessage]
143138
) -> list[UIMessage]:
@@ -153,6 +148,7 @@ async def stream_completion(
153148
stream_options: Optional[StreamOptions] = None,
154149
) -> StreamingResponse:
155150
"""Return a streaming response from the given messages. The response are AI SDK events."""
151+
from pydantic_ai.ui.vercel_ai import VercelAIAdapter
156152
from pydantic_ai.ui.vercel_ai.request_types import SubmitMessage
157153

158154
tools = (self.config.tools or []) + additional_tools
@@ -169,18 +165,12 @@ async def stream_completion(
169165
# TODO: Text only and format stream are not supported yet
170166
stream_options = stream_options or StreamOptions()
171167

172-
vercel_adapter = self.get_vercel_adapter()
173-
if DependencyManager.pydantic_ai.has_at_version(min_version="1.52.0"):
174-
adapter = vercel_adapter(
175-
agent=agent,
176-
run_input=run_input,
177-
accept=stream_options.accept,
178-
sdk_version=AI_SDK_VERSION,
179-
)
180-
else:
181-
adapter = vercel_adapter(
182-
agent=agent, run_input=run_input, accept=stream_options.accept
183-
)
168+
adapter = VercelAIAdapter(
169+
agent=agent,
170+
run_input=run_input,
171+
accept=stream_options.accept,
172+
sdk_version=AI_SDK_VERSION,
173+
)
184174
event_stream = adapter.run_stream()
185175
return adapter.streaming_response(event_stream)
186176

@@ -193,16 +183,16 @@ async def stream_text(
193183
additional_tools: list[ToolDefinition],
194184
) -> AsyncGenerator[str]:
195185
"""Return a stream of text from the given messages."""
186+
from pydantic_ai.ui.vercel_ai import VercelAIAdapter
196187

197188
tools = (self.config.tools or []) + additional_tools
198189
agent = self.create_agent(
199190
max_tokens=max_tokens, tools=tools, system_prompt=system_prompt
200191
)
201-
vercel_adapter = self.get_vercel_adapter()
202192

203193
async with agent.run_stream(
204194
user_prompt=user_prompt,
205-
message_history=vercel_adapter.load_messages(
195+
message_history=VercelAIAdapter.load_messages(
206196
self.convert_messages(messages)
207197
),
208198
) as result:
@@ -800,73 +790,6 @@ def process_part(self, part: UIMessagePart) -> UIMessagePart:
800790
)
801791
return part
802792

803-
def get_vercel_adapter(
804-
self,
805-
) -> type[VercelAIAdapter[None, DeferredToolRequests | str]]:
806-
"""
807-
Return a custom adapter that includes thinking signatures in ReasoningEndChunk.
808-
809-
pydantic_ai's VercelAIEventStream.handle_thinking_end doesn't pass the signature
810-
from ThinkingPart to ReasoningEndChunk, which breaks Anthropic's extended thinking
811-
on follow-up messages (Anthropic requires signatures on thinking blocks).
812-
813-
This is a patch for pydantic-ai <1.47.0, which doesn't include the signature in the ReasoningEndChunk.
814-
"""
815-
if DependencyManager.pydantic_ai.has_at_version(min_version="1.47.0"):
816-
return super().get_vercel_adapter()
817-
818-
from pydantic_ai import DeferredToolRequests
819-
from pydantic_ai.ui.vercel_ai import VercelAIAdapter
820-
from pydantic_ai.ui.vercel_ai._event_stream import VercelAIEventStream
821-
from pydantic_ai.ui.vercel_ai.response_types import ReasoningEndChunk
822-
823-
AnthropicOutputType = DeferredToolRequests | str
824-
825-
# Custom event stream that includes signature in ReasoningEndChunk
826-
class AnthropicVercelAIEventStream(
827-
VercelAIEventStream[None, AnthropicOutputType]
828-
):
829-
async def handle_thinking_end(
830-
self, part: ThinkingPart, followed_by_thinking: bool = False
831-
) -> AsyncIterator[BaseChunk]:
832-
"""Override to include signature in provider_metadata."""
833-
try:
834-
provider_metadata = None
835-
if part.signature:
836-
pydantic_ai_meta: dict[str, Any] = {
837-
"signature": part.signature
838-
}
839-
if part.provider_name:
840-
pydantic_ai_meta["provider_name"] = (
841-
part.provider_name
842-
)
843-
if part.id:
844-
pydantic_ai_meta["id"] = part.id
845-
provider_metadata = {"pydantic_ai": pydantic_ai_meta}
846-
847-
yield ReasoningEndChunk(
848-
id=self.message_id, provider_metadata=provider_metadata
849-
)
850-
except Exception as e:
851-
LOGGER.warning(
852-
f"Error in AnthropicVercelAIEventStream.handle_thinking_end: {e}"
853-
)
854-
async for chunk in super().handle_thinking_end(
855-
part, followed_by_thinking
856-
):
857-
yield chunk
858-
859-
# Custom adapter that uses the custom event stream
860-
class AnthropicVercelAIAdapter(
861-
VercelAIAdapter[None, AnthropicOutputType]
862-
):
863-
def build_event_stream(self) -> AnthropicVercelAIEventStream:
864-
return AnthropicVercelAIEventStream(
865-
self.run_input, accept=self.accept
866-
)
867-
868-
return AnthropicVercelAIAdapter
869-
870793

871794
class BedrockProvider(PydanticProvider["PydanticBedrock"]):
872795
def setup_credentials(self, config: AnyProviderConfig) -> None:

0 commit comments

Comments
 (0)