pydantic-ai-chat-ui
A lightweight adapter that allows Pydantic AI agents to work with Vercel's Data Stream format via useChat, and more specifically the LlamaIndex Chat UI flavour. The adpater supports:
- streaming (SSE) real-time messages from Pydantic AI agents to your frontend, including intermediate tool calls
- conversion of historical Pydantic AI messages, should you store them and want to return a full message history
Check out:
- Vercel AI SDK (TypeScript): https://github.com/vercel/ai (>=5)
- LlamaIndex Chat UI (React): https://github.com/run-llama/chat-ui
Installation
uv add pydantic-ai-chat-ui
poetry add pydantic-ai-chat-ui
pip install pydantic-ai-chat-ui
Quickstart Example
Not all logic is implemented, but hopefully it's enough of a guide to point you in the right direction.
Route (FastAPI)
import logging
import uuid
import logfire
from fastapi import APIRouter, HTTPException, status
from fastapi.responses import StreamingResponse
from pydantic_ai_chat_ui import ChatRequest, stream_results
from pydantic_ai_chat_ui.messages import from_pydantic_ai_message
from pydantic_ai_chat_ui.tools import DataPartState
from voyageai.client_async import AsyncClient as AsyncVoyageClient
from your_app.agents import your_agent
from your_app.agents.operative_report_state import (
OperativeReportDeps,
OperativeReportState,
)
from your_app.app.security import UserIdDep
from your_app.config import settings
from your_app.database import SessionDep
from your_app.threads import (
create_or_get_thread,
get_thread,
store_message,
)
logger = logging.getLogger(__name__)
router = APIRouter()
@router.post("/chat")
async def agent_chat(
chat_request: ChatRequest, user_id: UserIdDep, db_session: SessionDep
) -> StreamingResponse:
thread = create_or_get_thread(chat_request.id, user_id, db_session=db_session)
deps = Deps(
thread_id=thread.id,
voyage_client=AsyncVoyageClient(settings.VOYAGE_API_KEY),
db_session=db_session,
)
return StreamingResponse(
stream_results(
chat_request.messages[0],
your_agent.agent,
deps,
message_history=thread.messages,
tool_messages={
"validate_data": {
DataPartState.PENDING: "Validating data...",
DataPartState.SUCCESS: "Data validated.",
DataPartState.ERROR: "Encountered an error during validation.",
},
"identify_stuff": {
DataPartState.PENDING: "Identifying and analysing stuff...",
DataPartState.SUCCESS: "Stuff identified.",
DataPartState.ERROR: "Encountered an error during identification.",
},
},
store_message_history=lambda message: store_message(
thread.id, message, db_session=db_session
),
),
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"Connection": "keep-alive",
# this is an important header for having types picked up
"X-Vercel-AI-UI-Message-Stream": "v1",
},
)
Frontend
import { useChat } from "@ai-sdk/react";
import {
ChatCanvas,
ChatInput,
ChatMessage,
ChatMessages,
ChatSection,
useChatUI,
} from "@llamaindex/chat-ui";
import { createFileRoute } from "@tanstack/react-router";
import { DefaultChatTransport, type UIMessage } from "ai";
import { toast } from "sonner";
import { v4 as uuid4 } from "uuid";
import "@llamaindex/chat-ui/styles/editor.css";
import "@llamaindex/chat-ui/styles/markdown.css";
import "@llamaindex/chat-ui/styles/pdf.css";
import "@mdxeditor/editor/style.css";
export const Route = createFileRoute("/(app)/chat")({
component: ChatRoute,
});
function ChatRoute() {
const initialMessages: UIMessage[] = [];
const { messages, status, sendMessage, stop, regenerate, setMessages } =
useChat({
transport: new DefaultChatTransport({
api: `${import.meta.env.VITE_BASE_API_URL}/chat`,
}),
generateId: () => uuid4(),
messages: initialMessages,
onError: (err) => toast.error(err.message),
});
const CustomChatMessages = () => {
const { messages } = useChatUI();
return (
<>
{messages.map((message, idx) => (
<ChatMessage
key={`message-${message.id}`}
message={message}
isLast={idx === messages.length - 1}
>
<ChatMessage.Avatar />
<ChatMessage.Content>
<ChatMessage.Part.Markdown />
<ChatMessage.Part.Artifact />
<ChatMessage.Part.Event />
<ChatMessage.Part.Suggestion />
</ChatMessage.Content>
<ChatMessage.Actions />
</ChatMessage>
))}
</>
);
};
return (
<ChatSection
handler={{
messages,
status,
sendMessage,
stop,
regenerate,
setMessages,
}}
className="h-full flex-row gap-4 p-0 flex md:p-5"
>
<div className="mx-auto flex h-full min-w-0 max-w-full flex-1 flex-col gap-4">
<ChatMessages>
<ChatMessages.List>
<CustomChatMessages />
</ChatMessages.List>
<ChatMessages.Empty />
<ChatMessages.Loading />
</ChatMessages>
<ChatInput>
<ChatInput.Form>
<ChatInput.Field className="max-h-32" />
<ChatInput.Submit />
</ChatInput.Form>
</ChatInput>
</div>
<ChatCanvas className="w-full md:w-2/3" />
</ChatSection>
);
}
Metadata
Release files for pydantic-ai-chat-ui 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pydantic_ai_chat_ui-0.1.0.tar.gz | 7.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pydantic_ai_chat_ui-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 17.6 kB
Release files / pydantic_ai_chat_ui-0.1.0.tar.gz
| Download URL | pydantic_ai_chat_ui-0.1.0.tar.gz |
|---|---|
| Size | 7.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2d53e4171624ab957e07a2ca74ea7779da53d445aab98cb7f8f49d37e61c1dab
|
|
BLAKE2b-256 checksum How to use checksums |
cfb5e6a80ff63e51ca5c351e6e027f4ac876a8a9e9be74ff81cec081d2bf064d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 21, 2025.
Transparency logRelease files / pydantic_ai_chat_ui-0.1.0-py3-none-any.whl
| Download URL | pydantic_ai_chat_ui-0.1.0-py3-none-any.whl |
|---|---|
| Size | 10.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8a39c56e7b75e7d0fae544bfe488aedce323f30b5fd032aa27ed38b19cccc801
|
|
BLAKE2b-256 checksum How to use checksums |
ca1e20148f844820d4db2751cd361b231aab9fcf650b01174c921cf2a3d49ddd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 21, 2025.
Transparency log