웹 채팅 UI

웹 채팅 UI (Web Chat UI)

Pydantic AI에는 브라우저로 에이전트와 상호작용할 수 있는 내장 웹 채팅 인터페이스가 포함돼 있어요.

Web Chat UI

clai web로 CLI에서 쓰는 방법은 CLI - Web Chat UI 문서를 참고하세요.

출처: 문서

본문

참고

웹 UI는 로컬 개발과 디버깅을 위한 것이에요. 프로덕션에서는 UI Event Stream 통합 중 하나로 에이전트를 커스텀 프론트엔드에 연결할 수 있어요.

설치 (Installation)

web 엑스트라(Starlette과 Uvicorn 설치)를 설치하세요:

pip install 'pydantic-ai-slim[web]'
uv add 'pydantic-ai-slim[web]'

기본 사용법 (Basic Usage)

에이전트 인스턴스에서 Agent.to_web()로 웹 앱을 만드세요:

from pydantic_ai import Agent

agent = Agent('openai:gpt-5.2', instructions='You are a helpful assistant.')

@agent.tool_plain
def get_weather(city: str) -> str:
    return f'The weather in {city} is sunny'

app = agent.to_web()

아무 ASGI 서버나 사용해 앱을 실행하세요:

uvicorn my_module:app --host 127.0.0.1 --port 7932

모델 구성 (Configuring Models)

UI에서 사용할 수 있게 추가 모델을 지정할 수 있어요. 모델은 모델 이름/인스턴스의 리스트로, 또는 표시 라벨을 모델 이름/인스턴스에 매핑하는 딕셔너리로 제공할 수 있어요.

from pydantic_ai import Agent
from pydantic_ai.models.anthropic import AnthropicModel

# Model with custom configuration
anthropic_model = AnthropicModel('claude-sonnet-4-5')

agent = Agent('openai:gpt-5.2')

app = agent.to_web(
    models=['openai:gpt-5.2', anthropic_model],
)

# Or with custom display labels
app = agent.to_web(
    models={'GPT 5.2': 'openai:gpt-5.2', 'Claude': anthropic_model},
)

네이티브 도구 지원 (Native Tool Support)

에이전트에 capabilities=[NativeTool(...)]네이티브 도구를 구성해 UI에서 옵션으로 노출할 수 있어요(각 도구를 지원하는 모델에 대해서만 표시됨):

from pydantic_ai import Agent
from pydantic_ai.capabilities import NativeTool
from pydantic_ai.native_tools import CodeExecutionTool, WebSearchTool

agent = Agent(
    'openai:gpt-5.2',
    capabilities=[NativeTool(CodeExecutionTool()), NativeTool(WebSearchTool())],
)

app = agent.to_web(models=['anthropic:claude-sonnet-4-6'])

메모리 도구

memory 네이티브 도구는 to_web()clai web을 통해서는 지원되지 않아요. 에이전트에 메모리가 필요하다면 MemoryTool을 생성 시점에 에이전트에 직접 구성하세요.

추가 지시문 (Extra Instructions)

각 에이전트 실행에 포함될 추가 지시문을 전달할 수 있어요:

from pydantic_ai import Agent

agent = Agent('openai:gpt-5.2')

app = agent.to_web(instructions='Always respond in a friendly tone.')

도구 승인 (Tool Approval)

승인이 필요한 도구는 UI에서 승인/거부 프롬프트로 표시돼요. 에이전트가 그런 도구를 호출하면 UI가 대기 중인 호출을 렌더링하고, 실행이 계속되기 전에 승인하거나 거부하게 해요. 추가 구성 없이 바로 동작합니다.

주의

채팅 엔드포인트는 클라이언트가 릴레이한 도구 승인을 실행해요. requires_approval=True로 표시된 도구를 포함해서요. 서버는 받은 승인 결정을 신뢰하므로, 엔드포인트에 닿을 수 있는 어떤 클라이언트든 대기 중인 호출을 승인할 수 있어요.

localhost에 바인딩하는 것 자체가 여기서 보안 경계는 아니에요. 같은 브라우저에서 연 웹 페이지도 http://127.0.0.1:7932에 닿을 수 있거든요. 그래서 채팅 엔드포인트는 Content-Type: application/json만 받아요. 브라우저는 서버가 거부하는 preflight 없이는 크로스 오리진으로 보낼 수 없고, 앱은 로컬 Host 헤더에만 응답해요. 승인 프롬프트를 UI를 조종하는 개발자를 위한 편의 수단으로 취급하고, 인증을 앞에 두지 않은 채 to_web()을 신뢰할 수 없는 클라이언트에 노출하지 마세요.

호스트네임으로 UI에 접근하기 (Reaching the UI under a hostname)

앱은 Host 헤더가 IP 주소(127.0.0.1, [::1], 또는 192.168.1.5 같은 LAN 주소) 또는 localhost(그 아래 이름, 예: my-app.localhost 포함)인 요청에만 응답해요. 다른 모든 Host421 Misdirected Request를 받아요. 호스트네임은 ASCII 형태로 비교되므로, 국제화된 이름은 punycode(xn--bcher-kva.example)로 목록에 들어가며, 이게 브라우저가 보내는 형태예요.

이것이 웹사이트가 제어하는 호스트네임을 127.0.0.1에 가리켜서 당신 머신의 UI에 닿는 것을 막아요. 이를 DNS 리바인딩 공격이라고 하는데, 브라우저가 그 웹사이트와 UI를 같은 오리진으로 취급하게 만들어 위의 콘텐츠 타입 요구사항이 더 이상 적용되지 않게 하죠. IP 주소는 이름을 주소에 가리키는 방식으로 동작하는 리바인딩의 대상이 될 수 없어요.

리버스 프록시 뒤나 ngrok 같은 터널을 통해 실제 호스트네임으로 UI를 제공한다면, 그 호스트네임을 allowed_hosts에 지정하세요:

from pydantic_ai import Agent

agent = Agent('openai:gpt-5.2')

app = agent.to_web(allowed_hosts=['ui.example.com'])

# `*.example.com` matches subdomains only; list the apex separately if you serve it too
app = agent.to_web(allowed_hosts=['example.com', '*.example.com'])

또는 CLI로:

clai web -m openai:gpt-5.2 --allowed-host ui.example.com

clai web --host <name>은 그 이름을 자동으로 추가하므로, 출력하는 URL이 항상 동작해요.

모든 라우트가 검사되며 /api/health도 포함돼요. Host 헤더에 DNS 이름을 보내는 헬스 체크나 컨테이너 프로브도 같은 421을 받아요. 모니터링 시스템은 상태 코드만 기록하거나 자체 오류 페이지로 바꿔치기하는 경우가 많아서, 그 설명이 당신에게 도달하지 않을 수 있어요. 프로브를 바인딩된 IP 주소나 localhost에 가리키거나, 여기에 그 호스트네임을 추가하세요.

allowed_hosts=['*']를 넘기면 어떤 호스트에도 응답하지만, 앱 앞에 뭔가가 이미 요청을 인증할 때만 그렇게 하세요. 통제하는 서브도메인만 나열하세요. 누구나 서브도메인을 얻을 수 있는 도메인에 와일드카드를 쓰면 문제가 다시 열려요.

예약 라우트 (Reserved Routes)

모든 라우트는 허용된 Host 헤더에 대해서만 응답돼요. 웹 UI 앱은 다음 라우트를 사용하므로 덮어써서는 안 됩니다:

  • //{id} - 채팅 UI 제공
  • /api/chat - 채팅 엔드포인트 (POST, OPTIONS). Content-Type: application/json 필요; 다른 콘텐츠 타입은 415로 거부
  • /api/configure - 프론트엔드 구성 (GET)
  • /api/health - 헬스 체크 (GET)

앱은 현재 하위 경로(예: /chat)에 마운트할 수 없어요. UI가 이 라우트들을 루트에서 기대하기 때문이에요. 앱에 추가 라우트를 더할 수 있지만, 이 예약 경로들과 충돌하지 않도록 하세요.

커스텀 HTML 소스 (Custom HTML Source)

기본적으로 웹 UI는 CDN에서 가져와 로컬에 캐시돼요. 오프라인 사용이나 엔터프라이즈 환경을 위해 html_source를 제공해 덮어쓸 수 있어요.

오프라인 및 에어갭 배포 (Offline and air-gapped deployments)

기본 UI 빌드는 여러 파일로 나뉘어 있어요. index.html은 스타일시트를 참조하고, 런타임에는 문법 하이라이팅·다이어그램·수학을 위한 청크를 지연(lazily) 임포트해요. 이 참조들은 CDN을 가리키므로, index.html만 다운로드하면 페이지가 부팅된 뒤 코드 블록이나 수식이 나타나는 즉시 렌더링에 실패하게 돼요.

대신 오프라인 빌드를 쓰세요. 모든 청크·폰트·아이콘이 인라인된 단일 자립형 파일이라, 자신의 서버 외에는 네트워크 접근이 필요 없어요:

from pydantic_ai.ui import OFFLINE_HTML_URL

print(OFFLINE_HTML_URL)  # Use this URL to download the self-contained UI HTML file
#> https://cdn.jsdelivr.net/npm/@pydantic/[email protected]/offline/index.html

인터넷 접근이 있는 머신에서 한 번 다운로드한 뒤 에어갭 환경으로 옮기세요:

curl -o ~/pydantic-ai-ui.html <chat_ui_url>

그런 다음 html_source로 로컬 파일이나 커스텀 URL을 가리키게 하세요:

from pydantic_ai import Agent

agent = Agent('openai:gpt-5.2')

# Use a local file (e.g., for offline usage)
app = agent.to_web(html_source='~/pydantic-ai-ui.html')

# Or use a custom URL (e.g., for enterprise environments)
app = agent.to_web(html_source='https://cdn.example.com/ui/index.html')

오프라인 파일은 약 16MB예요. 이것은 무게가 더해진 게 아니라 옮겨진 무게예요. 기본 빌드는 같은 자산을 400여 개 파일로 나뉜 채 배포하고 브라우저가 CDN에서 요청에 따라 가져오는 반면, 오프라인 빌드는 그 모두를 첫 요청에 몰아 넣기 때문이에요. 기본 to_web() 경로는 변함없이 분할 빌드를 사용합니다:

from pydantic_ai.ui import DEFAULT_HTML_URL

print(DEFAULT_HTML_URL)
#> https://cdn.jsdelivr.net/npm/@pydantic/[email protected]/dist/index.html

더 알아보기 (Learn more)