Skip to main content

Prompt-first declarative HTTP framework on top of FastAPI and PydanticAI

Project description

yapi

PyPI Python License: MIT

中文文档请见 README.zh-CN.md

Prompt-first declarative HTTP framework — write a normal Python function with a docstring, get an LLM-powered HTTP endpoint with structured JSON responses.

yapi is a thin layer on top of FastAPI and PydanticAI. PromptRouter is a true superset of fastapi.APIRouter: native routes work as-is, and prompt routes live in the router.prompt.* namespace.

Package name on PyPI is pyyapi (the unhyphenated yapi was taken by a 2018 project). Import path is still yapi.

Install

pip install pyyapi

Python 3.12+ required.

Quick start

from fastapi import FastAPI
from pydantic import BaseModel

from yapi import PromptRouter


class WishIn(BaseModel):
    user_id: str
    wish: str


class WishOut(BaseModel):
    """You are a wish-granting entity. Decide whether to grant the wish."""

    granted: bool
    message: str


app = FastAPI(title="yapi showcase")
router = PromptRouter()


@router.prompt.post("/wish")
def make_a_wish(req: WishIn) -> WishOut:
    """Decide whether to grant the user's wish."""


app.include_router(router)

Run it:

YAPI_MODEL=test uvicorn examples.wish_api:app --reload

YAPI_MODEL=test activates PydanticAI's built-in TestModel — no API key, no network, perfect for offline smoke tests. For real models, set e.g. YAPI_MODEL=openai:gpt-4o or YAPI_MODEL=anthropic:claude-3-5-sonnet.

Open http://localhost:8000/docs for the auto-generated OpenAPI UI.

Mixing native FastAPI routes with prompt routes

PromptRouter is now a real APIRouter superset. .get/.post/... keep their FastAPI semantics; only router.prompt.* enters the LLM pipeline.

router = PromptRouter(prefix="/v1", tags=["wishes"])


@router.get("/health")
def health() -> dict:
    return {"status": "ok"}


@router.prompt.post("/wish")
def make_a_wish(req: WishIn) -> WishOut:
    """Decide whether to grant the user's wish."""

Configuration

yapi is configured entirely through environment variables — the package never reads .env files itself. Use a launcher that injects them (recommended: uvicorn --env-file .env; alternatives: set -a; source .env; set +a in your shell, Docker --env-file, Kubernetes secrets, etc.).

YAPI_MODEL (required for the default runner)

PydanticAI model string in provider:model form. Read once when PromptRouter() is constructed without an explicit agent_runner.

YAPI_MODEL=openai:gpt-4o              # OpenAI
YAPI_MODEL=anthropic:claude-3-5-sonnet # Anthropic
YAPI_MODEL=openai:deepseek-chat        # DeepSeek (OpenAI-compatible)
YAPI_MODEL=test                        # PydanticAI TestModel, no key, no network

Unset → constructor emits a YapiUsageWarning, first request returns HTTP 500.

Provider credentials (read directly by PydanticAI)

yapi does not validate or even look at these — they are consumed by the underlying PydanticAI provider via os.environ:

Provider Env vars
OpenAI OPENAI_API_KEY
OpenAI-compatible endpoints (DeepSeek, Azure OpenAI, OneAPI, local servers, …) OPENAI_API_KEY + OPENAI_BASE_URL (e.g. https://api.deepseek.com/v1)
Anthropic ANTHROPIC_API_KEY
Others (Google, Groq, Mistral, …) See PydanticAI providers docs

Example .env (DeepSeek)

YAPI_MODEL=openai:deepseek-chat
OPENAI_API_KEY=sk-...
OPENAI_BASE_URL=https://api.deepseek.com/v1
uv run uvicorn examples.wish_api:app --reload --env-file .env

DeepSeek's "thinking" models (deepseek-reasoner, deepseek-v4-flash) currently reject OpenAI Function Calling's tool_choice parameter, which PydanticAI uses by default for structured output. Use deepseek-chat for now.

How a prompt route runs

For each request to a router.prompt.* route, yapi:

  1. parses path/query/header/cookie/body parameters via the function signature (FastAPI semantics, plus a single BaseModel request body),
  2. calls your function (sync or async def) to optionally produce a dynamic prompt (the function's return value, must be None or str),
  3. composes the final system prompt from: response-model docstring + function docstring + dynamic prompt,
  4. invokes the configured agent_runner (defaulting to a PydanticAI Agent) with a RunnerContext containing the prompt, request payload, injected fields, response model, path and method,
  5. validates the agent's output against your return annotation and serializes via FastAPI.

Contract (hard rules)

Applies inside router.prompt.*:

  • Return annotation must be a BaseModel subclass.
  • At most one parameter may be a BaseModel (the request body). Supports both req: WishIn and req: Annotated[WishIn, Body()].
  • Other parameters must be one of:
    • Depends(...) default or Annotated[T, Depends(...)]
    • Annotated[T, Query()/Header()/Cookie()/Path()/Form()/File()] or the equivalent = Query(...) default
  • *args / **kwargs are rejected at decoration time.
  • Function body must return None or a str (the dynamic prompt). Anything else raises at request time.
  • async def is supported.

Decoration kwargs:

  • Passed through to FastAPI: tags, summary, description, status_code, deprecated, operation_id, name, include_in_schema, responses, openapi_extra.
  • Rejected at decoration time with YapiDeclarationError: response_model, response_class, dependencies.
  • Any other unknown kwarg emits a YapiUsageWarning.

Violations are raised as YapiDeclarationError at decoration time — broken routes fail at import, not at request time.

Dependency injection

from fastapi import Depends
from typing import Annotated

def get_db():
    ...

@router.prompt.post("/wish")
def make_a_wish(
    req: WishIn,
    db: Annotated[Database, Depends(get_db)],
) -> WishOut:
    """..."""
    return f"user has {db.balance(req.user_id)} wishes left"

Custom agent runner

Implement the AgentRunner Protocol — any object with a .run(ctx: RunnerContext) -> dict | BaseModel method is accepted:

from yapi import AgentRunner, PromptRouter, RunnerContext

class MockRunner:
    def run(self, ctx: RunnerContext) -> dict:
        return {
            "granted": "moon" not in ctx.request["wish"].lower(),
            "message": f"path={ctx.path}",
        }

router = PromptRouter(agent_runner=MockRunner())

The legacy v2-style (*, prompt, request, injected, response_model) -> dict callable is still accepted (auto-adapted).

You can also inject a custom prompt_composer= to customize how the system prompt is assembled.

Development

uv sync --extra dev
uv run pytest
uv run uvicorn examples.wish_api:app --reload

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyyapi-0.2.0.tar.gz (15.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyyapi-0.2.0-py3-none-any.whl (12.3 kB view details)

Uploaded Python 3

File details

Details for the file pyyapi-0.2.0.tar.gz.

File metadata

  • Download URL: pyyapi-0.2.0.tar.gz
  • Upload date:
  • Size: 15.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pyyapi-0.2.0.tar.gz
Algorithm Hash digest
SHA256 3d83a78defef3445ef26f1e049bce91460def18d8ef0835f2bdc1e3c274bc64f
MD5 fd0c705542328c92fcae46b1973a0320
BLAKE2b-256 ddb0b7dd44412bc2b59d27b6bb5773f5d63e8dabaff462e9235494db7094ee50

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyyapi-0.2.0.tar.gz:

Publisher: release.yml on TokenRollAI/yapi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pyyapi-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: pyyapi-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 12.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pyyapi-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9c29d7e8b2f63b5d338f98c58f0fd4c8bb76fd583da1884c89cdb134de38c288
MD5 64c8fd6a4778e2b1fe2e64ea8f8021e6
BLAKE2b-256 860971009df46f9ef17a4a17403e8b4dd5c1d9fce22a2057f31d6b39520d4614

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyyapi-0.2.0-py3-none-any.whl:

Publisher: release.yml on TokenRollAI/yapi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page