OpenUI Forge — Python

SkillAI & models

OpenUI generative UI with Python FastAPI backend. OpenAI and Anthropic SDK variants.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the OpenUI Forge — Python skill

What this skill tells your AI

The instructions your AI receives, as published by othmanadi/openui-forge in skills/openui-forge-python/SKILL.md and read by ahel’s review.

Build generative UI apps with a React frontend + Python FastAPI backend. Streams OpenAI-compatible NDJSON.

Activation Triggers

  • "openui python", "openui fastapi", "openui flask"
  • "generative ui python", "python streaming ui backend"

Prerequisites

  • Node.js >= 22 (24 LTS recommended) + React >= 18.3.1 (19+ recommended) (frontend)
  • Python >= 3.10 (backend)
  • OPENAI_API_KEY or ANTHROPIC_API_KEY set

Quick Start

  1. Create the React frontend and install OpenUI deps:
npm install @openuidev/react-ui @openuidev/react-headless @openuidev/react-lang lucide-react zod
  1. Generate the system prompt from your component library:
npx @openuidev/cli generate ./src/lib/library.ts --out backend/system-prompt.txt
  1. Set up the Python backend (see Full Code below)
  2. Run both: frontend on :3000, backend on :8000

Full Code

Backend: backend/requirements.txt

fastapi>=0.115.0
uvicorn>=0.24.0
openai>=2.0
anthropic>=0.111.0
python-dotenv>=1.0.0

The Python >= 3.10 floor comes from fastapi/uvicorn/python-dotenv; openai and anthropic themselves need only Python 3.9.

Backend (OpenAI): backend/main.py

import os
from pathlib import Path
from dotenv import load_dotenv
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse
from openai import AsyncOpenAI

load_dotenv()
app = FastAPI()
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000"],
    allow_methods=["POST"],
    allow_headers=["*"],
)

# AsyncOpenAI keeps the request from blocking the event loop during streaming.
client = AsyncOpenAI()
SYSTEM_PROMPT = Path("system-prompt.txt").read_text()

@app.post("/api/chat")
async def chat(request: Request):
    body = await request.json()
    messages = [{"role": "system", "content": SYSTEM_PROMPT}] + body["messages"]

    async def generate():
        response = await client.chat.completions.create(
            model=os.getenv("OPENAI_MODEL", "gpt-5.5"),
            stream=True,
            messages=messages,
        )
        async for chunk in response:
            data = chunk.model_dump_json()
            yield f"data: {data}\n\n"
        yield "data: [DONE]\n\n"

    return StreamingResponse(generate(), media_type="text/event-stream")

Backend (Anthropic variant): backend/main_anthropic.py

import os, json, time
from pathlib import Path
from dotenv import load_dotenv
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse
from anthropic import AsyncAnthropic

load_dotenv()
app = FastAPI()
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000"],
    allow_methods=["POST"],
    allow_headers=["*"],
)

# AsyncAnthropic mirrors AsyncOpenAI so the stream does not block the loop.
client = AsyncAnthropic()
SYSTEM_PROMPT = Path("system-prompt.txt").read_text()

@app.post("/api/chat")
async def chat(request: Request):
    body = await request.json()
    stream_id = f"chatcmpl-{int(time.time())}"

    async def generate():
        async with client.messages.stream(
            model=os.getenv("ANTHROPIC_MODEL", "claude-sonnet-4-6"),
            max_tokens=4096,
            system=SYSTEM_PROMPT,
            messages=body["messages"],
        ) as stream:
            async for text in stream.text_stream:
                chunk = {"id": stream_id, "object": "chat.completion.chunk",
                         "choices": [{"index": 0, "delta": {"content": text}, "finish_reason": None}]}
                yield f"data: {json.dumps(chunk)}\n\n"
        done = {"id": stream_id, "object": "chat.completion.chunk",
                "choices": [{"index": 0, "delta": {}, "finish_reason": "stop"}]}
        yield f"data: {json.dumps(done)}\n\n"
        yield "data: [DONE]\n\n"

    return StreamingResponse(generate(), media_type="text/event-stream")

Frontend: app/chat/page.tsx (or src/Chat.tsx for Vite)

"use client";
import { FullScreen } from "@openuidev/react-ui";
import { openuiChatLibrary } from "@openuidev/react-ui/genui-lib";
import {
  openAIAdapter,
  openAIMessageFormat,
} from "@openuidev/react-headless";

export default function ChatPage() {
  return (
    <FullScreen
      componentLibrary={openuiChatLibrary}
      streamProtocol={openAIAdapter()}
      messageFormat={openAIMessageFormat}
      apiUrl="http://localhost:8000/api/chat"
    />
  );
}

The Python backend emits SSE (data: {json}\n\n). Pair it with openAIAdapter() on the frontend. openAIReadableStreamAdapter() is for NDJSON (no data: prefix) and will silently produce no output here.

System Prompt Generation

Generate once, copy to backend directory:

npx @openuidev/cli generate ./src/lib/library.ts --out backend/system-prompt.txt

Regenerate after every component change.

Validation Checklist

  • system-prompt.txt exists in the backend directory
  • CORS allows the frontend origin
  • Backend streams data: {json}\n\n lines with OpenAI chunk format
  • Final chunk has finish_reason: "stop" followed by data: [DONE]
  • Frontend apiUrl points to the correct backend URL
  • Frontend uses streamProtocol={openAIAdapter()} and openAIMessageFormat
  • componentLibrary={openuiChatLibrary} prop passed to FullScreen
  • CSS import in root layout (@openuidev/react-ui/components.css)
  • Run backend: uvicorn main:app --reload --port 8000

Error Patterns

ErrorCauseFix
CORS blockedFrontend origin not allowedAdd origin to allow_origins list
Connection refusedBackend not runningStart with uvicorn main:app --port 8000
FileNotFoundErrorsystem-prompt.txt missingRun the CLI generate command
Stream not renderingBackend not sending SSE formatEnsure data: prefix and \n\n after each chunk
422 Unprocessable EntityRequest body missing messagesCheck frontend sends { messages: [...] }

Signals

GitHub stars
22
Last commit
Aug 2026
Advanced
Catalog kind
skill
Gateway key
openui-forge-python
Source
github.com/othmanadi/openui-forge