Skip to content

Blank

The smallest agent the template supports: one output type, one prompt file, no tools.

Blank is a starting point, not a pattern. It is one agent module with a typed output model, a dependencies dataclass, a prompt in agent/prompts/<name>.txt, and a run_* function that returns a RunResult, wrapped in the template's guardrails: limits on requests and tokens, and an error when a provider blocks a response. Unlike the other examples, every symbol in it is renamed to the name you choose: --name newsletter gives you newsletter_agent, NewsletterOutput, NewsletterDeps and run_newsletter_agent. You also get a smoke test and an eval starter for it, as with any agent you add.

Use it when

  • You know what you want to build and none of the patterns matches its shape yet.
  • You would rather grow an agent from nothing than delete what you don't need from a worked example.
  • You want the scaffolding (tests, evals, limits, tracing) around an agent you write yourself.

Look elsewhere when

  • One of the patterns fits. Start from it. single is the same agent with a worked example and recipes for tools, dynamic instructions and multi-turn history, and Which pattern should I use? helps you choose.

What it shows

  • A structured output type (BlankOutput) and an injectable dependencies dataclass (BlankDeps)
  • Instructions loaded from a prompt file (prompts/blank.txt)
  • USAGE_LIMITS on every run, and the RaiseContentFilterError capability
  • A commented recipe for dynamic instructions, for when the prompt depends on runtime state

See it run: sample_run.md is a recorded run against a real model: what each agent was asked, which tools it called, and what it returned.

uv run python scripts/add_agent.py blank --name newsletter

Source

All of it is in examples/blank/.

"""Blank agent — an empty starting point with one agent, one output type, one prompt.

To use:
    1. Define your output type (or use str for unstructured output)
    2. Set your instructions in agent/prompts/blank.txt
    3. Add tools if needed
    4. Call run_blank_agent() and read `.output` from the RunResult it returns
"""

from __future__ import annotations

from dataclasses import dataclass

from pydantic import BaseModel
from pydantic_ai import (  # noqa: F401 — RunContext used in commented tool example below
    Agent,
    RunContext,
)
from pydantic_ai.capabilities import RaiseContentFilterError
from pydantic_ai.usage import UsageLimits

from agent.config import settings
from agent.logging import agent_label, configure_logging, get_logger
from agent.prompts.templates import load_prompt
from agent.runs import Flow, RunResult

logger = get_logger(__name__)
LABEL = agent_label(__name__)  # names this agent's run spans in Logfire traces

# Guardrail against runaway agentic loops. A run that exceeds any limit
# raises UsageLimitExceeded instead of silently burning tokens. Tune per task:
# request_limit caps model round-trips (each tool-call iteration is one
# request), total_tokens_limit caps overall tokens. Set AGENT_COST_LIMIT (USD) to
# add a spend cap — optional, off by default, and only useful for models with
# known pricing (see Settings.cost_limit).
USAGE_LIMITS = UsageLimits(
    request_limit=10, total_tokens_limit=100_000, cost_limit=settings.cost_limit
)


# --- Output type ---
# Replace with your actual output schema, or use str for unstructured output.
class BlankOutput(BaseModel):
    """Replace with your actual output schema."""

    result: str


# --- Dependencies ---
# Use a dataclass to inject runtime dependencies (DB connections, API clients, etc.)
# Remove if this agent needs no external dependencies.
@dataclass
class BlankDeps:
    """Runtime dependencies injected into the blank agent."""

    # example_client: SomeAPIClient  # Add your dependencies here
    pass


# --- Agent definition ---
blank_agent: Agent[BlankDeps, BlankOutput] = Agent(
    settings.model,
    name=LABEL,
    output_type=BlankOutput,
    deps_type=BlankDeps,
    # Fail fast when the provider filters a response, instead of retrying a
    # refused request or returning partial text.
    capabilities=[RaiseContentFilterError()],
    instructions=load_prompt("blank"),  # loads agent/prompts/blank.txt
)


# --- Tools ---
# Add tools here. See agent/tools/example.py for the full pattern.
# @blank_agent.tool
# async def my_tool(ctx: RunContext[BlankDeps], query: str) -> str:
#     """Tool description — this docstring is sent to the LLM."""
#     return "result"


# --- Dynamic instructions (optional) ---
# Use @blank_agent.instructions for instructions that depend on runtime state.
# @blank_agent.instructions
# async def dynamic_instructions(ctx: RunContext[BlankDeps]) -> str:
#     return f"Today is {date.today()}."


async def run_blank_agent(user_input: str, deps: BlankDeps | None = None) -> RunResult[BlankOutput]:
    """Run the blank agent with the given user input.

    Args:
        user_input: The user's message or task description.
        deps: Runtime dependencies. Created with defaults if not provided.

    Returns:
        A RunResult: `.output` is the validated BlankOutput, `.usage` the total usage, and
        `.steps[0].result` the native Pydantic AI result (messages, run id, ...).
    """
    if deps is None:
        deps = BlankDeps()

    logger.info("Running blank agent", extra={"user_input": user_input})

    flow = Flow(USAGE_LIMITS)
    result = await flow.run(blank_agent, user_input, deps=deps)

    logger.info("Blank agent run complete", extra={"output": result.output})
    return flow.finish(result.output)


if __name__ == "__main__":
    import asyncio

    configure_logging()
    result = asyncio.run(run_blank_agent("Hello, what can you do?"))
    print(result.output)
You are the blank agent.

TODO: replace this with real instructions for what this agent should do.
title = "Blank"
pattern = "blank"
summary = "An empty agent: one output type, one prompt, no tools. Start here and build from scratch."
smoke_input = "Hello, what can you do?"
templated = true  # `blank` / `Blank` in the source are replaced by the name you choose

[entrypoint]
deps = "BlankDeps"
run = "run_blank_agent"
"""Live check: the blank agent runs against the real model. Run with `pytest -m eval`."""

import pytest

from evals.trace import traced_run
from examples.blank import agent as module
from examples.live_support import assert_every_agent_ran, run_as_script

pytestmark = pytest.mark.eval


async def test_the_agent_answers():
    traced = await traced_run(module.run_blank_agent, "Hello, what can you do?")

    assert isinstance(traced.result.output, module.BlankOutput)
    assert traced.result.output.result.strip()
    assert [step.agent for step in traced.result.steps] == ["blank"]
    assert traced.result.usage.requests >= 1  # more if the model retried its output
    assert_every_agent_ran(module, traced.agents_ran)


async def test_the_demo_script_runs():
    assert "result=" in await run_as_script("examples.blank.agent")

Recorded run · gemini-3.1-flash-lite · 1 step · $0.0003

Recorded 2026-10-07 with google:gemini-3.1-flash-lite · 1 step · 261 tokens · $0.0003 · 1.4 s. Model output varies between runs. Regenerate with uv run python scripts/record_example.py blank.

Input

Hello, what can you do?

Steps

1. blank 261 tokens · $0.0003

Prompt

Hello, what can you do?

Output

{
  "result": "I am a versatile AI assistant. While I don't have a specific set task yet (I am currently a \"blank\" agent), I am capable of:\n\n*   **Writing and Editing:** Drafting emails, essays, creative stories, or refining your existing text.\n*   **Information and Analysis:** Answering questions, summarizing documents, or explaining complex topics.\n*   **Problem Solving:** Helping with logic puzzles, brainstorming ideas, or providing step-by-step guidance.\n*   **Coding/Technical Help:** Writing code, debugging, or explaining technical concepts.\n*   **Organization:** Creating schedules, lists, or helping to structure your thoughts.\n\n**How can I help you today?** If you have a specific goal or task in mind, let me know, and I can adapt my approach to help you best."
}

Result

run_blank_agent(...).output

{
  "result": "I am a versatile AI assistant. While I don't have a specific set task yet (I am currently a \"blank\" agent), I am capable of:\n\n*   **Writing and Editing:** Drafting emails, essays, creative stories, or refining your existing text.\n*   **Information and Analysis:** Answering questions, summarizing documents, or explaining complex topics.\n*   **Problem Solving:** Helping with logic puzzles, brainstorming ideas, or providing step-by-step guidance.\n*   **Coding/Technical Help:** Writing code, debugging, or explaining technical concepts.\n*   **Organization:** Creating schedules, lists, or helping to structure your thoughts.\n\n**How can I help you today?** If you have a specific goal or task in mind, let me know, and I can adapt my approach to help you best."
}