Pydantic AI System Prompts: Instructions, Context, and Tests

Pydantic AI supports both static and dynamic system prompts. Static prompts set stable rules; dynamic prompts inject context at runtime. Here is how to define them, separate rules from request data, and test the behavior.

Updated 4 min read

Pydantic AI lets you set system prompts in two ways: as a static string in the Agent constructor, or as a decorated function that runs at the start of each request and can read from the current context. Knowing when to use each one, and how to separate stable rules from request data, makes the agent's behavior easier to test and easier to change.

Tools like fenbs store the project decisions that go behind those instructions, so when a system prompt encodes a business rule, the reason for that rule is recorded in one place any connected AI or team member can read.

How Pydantic AI handles system prompts

In Pydantic AI, system prompts accumulate: you can combine a static string with one or more dynamic functions, and all of them are joined before the model sees the message. This makes it straightforward to separate the stable part of the instruction (the role, the output format, the standing rules) from the dynamic part (the current user, the request context, the retrieved data).

The full documentation for Pydantic AI system prompts is at https://ai.pydantic.dev (opens in a new tab). Check that source before building on any behavior described here, as the API can change between releases.

Defining a static system prompt

Pass the system_prompt argument to the Agent constructor for instructions that never change between requests:

Static system prompt in Pydantic AI
from pydantic_ai import Agent

agent = Agent(
    'anthropic:claude-sonnet-4-6',
    system_prompt=(
        'You are a research assistant. '
        'Answer only from the documents provided. '
        'If the documents do not address the question, say so.'
    ),
)

Use the static prompt for rules that are the same for every call: the agent's role, its output format, and any constraints that apply regardless of the request. Keep this part short and specific: every word here is included in every call.

Adding dynamic context at runtime

The @agent.system_prompt decorator marks a function that runs before each request and returns a string that is appended to the static prompt. The function receives a RunContext object, which provides type-safe access to the dependencies you defined for the agent.

Dynamic system prompt with RunContext
from pydantic_ai import Agent, RunContext
from dataclasses import dataclass

@dataclass
class ResearchDeps:
    user_name: str
    available_documents: list[str]

agent = Agent(
    'anthropic:claude-sonnet-4-6',
    deps_type=ResearchDeps,
    system_prompt='You are a research assistant. Answer only from the documents provided.',
)

@agent.system_prompt
def add_user_context(ctx: RunContext[ResearchDeps]) -> str:
    docs = ', '.join(ctx.deps.available_documents)
    return f'User: {ctx.deps.user_name}. Documents available: {docs}.'

The dynamic function keeps request-specific facts out of the static prompt and makes them available only when they are relevant. The static prompt stays lean; the dynamic one adds only what this specific request needs.

Separating stable rules from request data

A common mistake is mixing standing rules and request data in the same prompt string. When the rules change, you have to find them inside a string that also contains runtime values. When the request data changes, you have to edit code that also holds the rules.

  • Static prompt: role, output format, constraints that apply to all requests.
  • Dynamic prompt: current user, session context, retrieved documents, request-specific scope.
  • Dependencies: values the dynamic prompt reads from, typed as a dataclass for safety.

If a rule is a business decision, store the reason behind it in fenbs Decisions and rules so it is not buried in the code. The code enforces the rule; fenbs records why it was chosen and who approved it. When the rule changes, the decision is updated in one place rather than hunted across a codebase.

One practical test: if you removed a dynamic prompt function and the agent still behaved correctly on a sample request, the function is not adding anything. A function that always returns the same string should be moved to the static prompt. A function that reads from context and produces different output per request belongs in the decorator.

Testing system prompt behavior

Pydantic AI supports testing agents without live model calls using TestModel or FunctionModel. Use these to verify that system prompt content is present and that the right values are injected from context.

Testing that context is injected
from pydantic_ai import models

def test_system_prompt_includes_user():
    captured = []

    def capture_prompt(messages, info):
        captured.append(str(messages))
        return models.ModelResponse.from_text('ok')

    with models.override(capture_fn=capture_prompt):
        agent.run_sync('summarize', deps=ResearchDeps(
            user_name='Alice',
            available_documents=['doc1', 'doc2'],
        ))

    assert 'Alice' in captured[0]
    assert 'doc1' in captured[0]

Test each constraint in the static prompt separately. A constraint that works in a manual run can fail under edge-case inputs that you only discover in a test. Write at least one test per standing rule.

When a constraint in the static prompt appears to be ignored, check the dynamic functions first. A dynamic function that injects a contradicting instruction can override a static rule. Reading the accumulated prompt as a whole, before debugging the static part in isolation, often reveals the source of the conflict faster.

How to do this with fenbs

  1. Create a free account and add a project for the agent you are building.
  2. Add the agent's instruction template to Skills and prompts in fenbs so the team can see the current approved version.
  3. Record business rules that drive the static prompt in Decisions and rules in fenbs, with the reason and the person who approved them.
  4. Connect your AI app over MCP from Integrations so team members working on the project can read the current decisions before changing the prompt.
How fenbs helps

Keep the rules behind the prompt separate from the prompt itself

In fenbs, record the decisions behind each standing rule in the system prompt: what the rule is, why it was chosen, and who approved it. When the rule changes, update the decision and the code. Any connected team member or AI app reads the current decision before working on the prompt, so changes are made with the full context available.

Create a free account

Free for one person · No credit card · Works with Claude, ChatGPT, Cursor and more

Where to find the Pydantic AI documentation

The Pydantic AI documentation at https://ai.pydantic.dev (opens in a new tab) covers the current API for system prompts, RunContext, dependencies, and the test models. The framework updates regularly; check the changelog before relying on any specific method signature.

For general system prompt patterns that apply across frameworks, system prompt examples covers the instruction structures that produce reliable results regardless of which library you use.

Questions people ask.

What is the difference between a static and a dynamic system prompt in Pydantic AI?

A static prompt is a string set in the Agent constructor that is the same for every request. A dynamic prompt is a decorated function that runs before each request and can read from the current context to inject request-specific information.

Can I combine a static and a dynamic system prompt?

Yes. Pydantic AI accumulates all system prompt sources into one message before calling the model. You can have one static string and multiple dynamic functions; all of them contribute to the final prompt.

How do I test that a system prompt is working correctly?

Use TestModel or FunctionModel to run the agent without a live model call. Capture the messages passed to the model and assert that the required values and constraints are present.

What should go in the static prompt versus the dynamic prompt?

Static: the role, output format, and standing rules that apply to all requests. Dynamic: the current user, session context, retrieved data, and anything that changes between requests.

Where should I record the reason a system prompt rule was added?

Not in the code comment alone. Record it in a shared place the team can find, such as Decisions and rules in fenbs, with the reason, the person who approved it, and the date. Code comments can be missed during review; a shared decision record cannot.

More in Prompts and skills for AI work

All Prompts and skills for AI work articles

Start with one thing.

Free for one person · No credit card · Works with Claude, ChatGPT, Cursor and more

There is nothing to set up first. Write one line and you’ve started.