Skip to main content

pydantic-ai-chat-ui

PyPI version Python versions License CI

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:

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)

Source distribution for pydantic-ai-chat-ui 0.1.0
File Size Uploaded
pydantic_ai_chat_ui-0.1.0.tar.gz 7.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pydantic-ai-chat-ui 0.1.0
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page