Skip to content

Calls to a language model

A call to a language model takes its prompt from a file of the package and returns a validated pydantic model, through a pydantic_ai.Agent.

What to read

  • The prompt is app/prompts/extract_parties.md, a Markdown file next to the code. A change to it is reviewed and versioned like a change to the code.
  • load_prompt reads it with importlib.resources.files, which finds the file in an installed wheel as well as in a checkout, and caches it for the process.
  • parties_agent builds the agent with the prompt as instructions and Parties as output_type. pydantic-ai validates the answer of the model against Parties and, when the validation fails, sends the error back to the model, up to retries times.
  • extract_parties runs the agent and returns result.output, already a Parties.
  • The tests drive the agent with a FunctionModel, which stands for the provider: no key and no network.

What to adapt

  • The prompt, Party and Parties are one example: one prompt file and one output model per task.
  • The model is a setting (APP_LLM_MODEL, read by the settings module), passed to the agent as a name such as "openai:gpt-5.5" or as a Model.
  • Do not name a prompt file *.prompt.yml: that is the format of GitHub Models, and gh models reads such files with its own schema.
  • Check that the build of the package includes the prompt files.

References

code/app/prompts/extract_parties.md

a prompt: a Markdown file of the package, reviewed and versioned like code

You read a contract and list its parties.

A party is a person or an organization that signs the contract or is bound by it. For each party,
give its name as the contract writes it and its role in the contract, such as "buyer", "seller",
"lessor" or "lessee".

Leave out witnesses, notaries and people who are only mentioned. If the contract names no party,
return an empty list.

code/app/core/prompts.py

the prompt is read with importlib.resources, so it is found in an installed wheel too

"""The prompts of the package, read from the Markdown files of `app/prompts/`."""

from __future__ import annotations

from functools import cache
from importlib.resources import files


@cache
def load_prompt(name: str) -> str:
    """Return the text of the prompt `app/prompts/<name>.md`, read once per process."""
    return files("app.prompts").joinpath(f"{name}.md").read_text(encoding="utf-8")

code/app/core/parties.py

the agent: the prompt as instructions, a pydantic model as output_type, and the model passed in

"""The parties of a contract, extracted by a model into a validated structure."""

from __future__ import annotations

from typing import TYPE_CHECKING

from pydantic import BaseModel, Field
from pydantic_ai import Agent

from app.core.prompts import load_prompt

if TYPE_CHECKING:
    from pydantic_ai.models import Model


class Party(BaseModel):
    """A person or an organization bound by the contract."""

    name: str = Field(min_length=1)
    role: str = Field(min_length=1)


class Parties(BaseModel):
    """The parties of one contract."""

    parties: list[Party]


def parties_agent(model: Model | str) -> Agent[None, Parties]:
    """Return the agent that extracts the parties, with the model the settings name."""
    return Agent(
        model,
        instructions=load_prompt("extract_parties"),
        output_type=Parties,
        retries=2,
    )


async def extract_parties(agent: Agent[None, Parties], contract: str) -> Parties:
    """Return the parties of the text of a contract."""
    result = await agent.run(contract)
    return result.output

https://pydantic.dev/docs/ai/core-concepts/output/#structured-output

how pydantic-ai turns the output of the model into the output_type, and sends a validation error back to it

https://pydantic.dev/docs/ai/core-concepts/agent/#instructions

instructions and not system_prompt

In general, we recommend using instructions instead of system_prompt unless you have a specific reason to use system_prompt.

https://docs.python.org/3/library/importlib.resources.html#importlib.resources.files

reading a file of a package, wherever the package is installed