Skip to content

Commit

Permalink
Documentation (#37)
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelcolvin authored Nov 11, 2024
1 parent eff9d95 commit 234cde8
Show file tree
Hide file tree
Showing 39 changed files with 1,226 additions and 187 deletions.
14 changes: 13 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,18 @@ jobs:

- run: make typecheck-mypy

docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: astral-sh/setup-uv@v3
with:
enable-cache: true

- run: uv sync --python 3.12 --frozen --group docs
- run: uv run --frozen mkdocs build

test:
name: test on ${{ matrix.python-version }}
runs-on: ubuntu-latest
Expand Down Expand Up @@ -97,7 +109,7 @@ jobs:
# https://github.com/marketplace/actions/alls-green#why used for branch protection checks
check:
if: always()
needs: [lint, test, coverage]
needs: [lint, docs, test, coverage]
runs-on: ubuntu-latest

steps:
Expand Down
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ repos:
hooks:
- id: no-commit-to-branch # prevent direct commits to the `main` branch
- id: check-yaml
args: ["--unsafe"]
- id: check-toml
- id: end-of-file-fixer
- id: trailing-whitespace
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2024 - present Pydantic Services inc.
Copyright (c) Pydantic Services Inc. 2024 to present

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
19 changes: 18 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

.PHONY: install # Install the package, dependencies, and pre-commit for local development
install: .uv .pre-commit
uv sync --frozen --all-extras
uv sync --frozen --all-extras --group docs
pre-commit install --install-hooks

.PHONY: format # Format the code
Expand Down Expand Up @@ -54,5 +54,22 @@ testcov: test
@echo "building coverage html"
@uv run coverage html

# `--no-strict` so you can build the docs without insiders packages
.PHONY: docs # Build the documentation
docs:
uv run mkdocs build --no-strict

# `--no-strict` so you can build the docs without insiders packages
.PHONY: docs-serve # Build and serve the documentation
docs-serve:
uv run mkdocs serve --no-strict

.PHONY: cf-pages-build # Install uv, install dependencies and build the docs, used on CloudFlare Pages
cf-pages-build:
curl -LsSf https://astral.sh/uv/install.sh | sh
${HOME}/.local/bin/uv python install 3.12
${HOME}/.local/bin/uv sync --python 3.12 --frozen --group docs
${HOME}/.local/bin/uv run --no-sync mkdocs build

.PHONY: all
all: format lint typecheck testcov
22 changes: 22 additions & 0 deletions docs/api/agent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# `pydantic_ai.Agent`

::: pydantic_ai.Agent
options:
members:
- __init__
- run
- run_sync
- run_stream
- model
- last_run_messages
- system_prompt
- retriever_plain
- retriever_context
- result_validator

::: pydantic_ai.agent
options:
members:
- KnownModelName
- ResultValidatorFunc
- SystemPromptFunc
3 changes: 3 additions & 0 deletions docs/api/dependencies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# `pydantic_ai.dependencies`

::: pydantic_ai.dependencies
3 changes: 3 additions & 0 deletions docs/api/exceptions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# `pydantic_ai.exceptions`

::: pydantic_ai.exceptions
3 changes: 3 additions & 0 deletions docs/api/messages.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# `pydantic_ai.messages`

::: pydantic_ai.messages
3 changes: 3 additions & 0 deletions docs/api/models/function.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# `pydantic_ai.models.function`

::: pydantic_ai.models.function
3 changes: 3 additions & 0 deletions docs/api/models/gemini.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# `pydantic_ai.models.gemini`

::: pydantic_ai.models.gemini
3 changes: 3 additions & 0 deletions docs/api/models/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# `pydantic_ai.models`

::: pydantic_ai.models
3 changes: 3 additions & 0 deletions docs/api/models/openai.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# `pydantic_ai.models.openai`

::: pydantic_ai.models.openai
3 changes: 3 additions & 0 deletions docs/api/models/test.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# `pydantic_ai.models.test`

::: pydantic_ai.models.test
10 changes: 10 additions & 0 deletions docs/api/result.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# `pydantic_ai.result`

::: pydantic_ai.result
options:
members:
- ResultData
- RunResult
- StreamedRunResult
- _BaseRunResult
- Cost
53 changes: 53 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# PydanticAI

You can think of PydanticAI as an Agent Framework or a shim to use Pydantic with LLMs — they're the same thing.

PydanticAI ties to make working with LLMs feel similar to building a web application.

## Example — Retrievers

Partial example of using retrievers to help an LLM respond to a user's query about the weather:

```py title="weather_agent.py"
import httpx
from pydantic_ai import Agent, CallContext

weather_agent = Agent( # (1)!
'openai:gpt-4o', # (2)!
deps=httpx.AsyncClient, # (3)!
system_prompt='Be concise, reply with one sentence.', # (4)!
)


@weather_agent.retriever_context # (5)!
async def get_location(ctx: CallContext[httpx.AsyncClient], location_description: str) -> dict[str, float]:
"""Get the latitude and longitude of a location by its description.""" # (6)!
...


@weather_agent.retriever_context # (7)!
async def get_weather(ctx: CallContext[httpx.AsyncClient], lat: float, lng: float) -> dict[str, str]:
"""Get the weather at a location by its latitude and longitude."""
...


result = weather_agent.run_sync('What is the weather like in West London and in Wiltshire?') # (8)!
print(result.data) # (9)!
# > 'The weather in West London is raining, while in Wiltshire it is sunny.'

print(result.all_messages()) # (10)!
```

1. An agent that can tell users about the weather in a particular location. Agents combine a system prompt, a response type (here `str`) and "retrievers" (aka tools).
2. Here we configure the agent to use OpenAI's GPT-4o model, you can also customise the model when running the agent.
3. We specify a dependency for the agent, in this case an HTTP client, which retrievers will use to make requests to external services. PydanticAI's system of dependency injection provides a powerful, type safe way to customise the behaviour of your agents, including for unit tests and evals.
4. Static system prompts can be registered as key word arguments to the agent, dynamic system prompts can be registered with the `@agent.system_prompot` decorator and benefit from dependency injection.
5. Retrievers let you register "tools" which the LLM can call while trying to respond to a user. You inject dependencies into the retriever with `CallContext`, any other arguments become the tool schema passed to the LLM, Pydantic is used to validate these arguments, errors are passed back to the LLM so it can retry.
6. This docstring is also passed to the LLM as a description of the tool.
7. Multiple retrievers can be registered with the same agent, the LLM can choose which (if any) retrievers to call in order to respond to a user.
8. Run the agent synchronously, conducting a conversation with the LLM until a final response is reached. (internally agents are all async, `run_sync` is a helper using `asyncio.run` to call `run()`)
9. The response from the LLM, in this case a `str`, Agents are generic in both the type of `deps` and `result_type`, so calls are typed end-to-end.
10. `result.all_messages()` includes details of messages exchanged, this is useful both to understand the conversation that took place and useful if you want to continue the conversation later — messages can be passed back to later `run/sync_run` calls.

!!! tip "Complete `weather_agent.py` example"
The above `weather_agent.py` example is complete for the sake of brevity, but you can find a complete example [here](#TODO).
3 changes: 3 additions & 0 deletions docs/install.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Installation

TODO
138 changes: 138 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
site_name: PydanticAI
site_description: PydanticAI
strict: true
site_url: https://ai.pydantic.dev

repo_name: pydantic/pydantic-ai
repo_url: https://github.com/pydantic/pydantic-ai
edit_uri: edit/main/docs/

copyright: © Pydantic Services Inc. 2024 to present

nav:
- Getting Started:
- index.md
- install.md
- API Reference:
- api/agent.md
- api/result.md
- api/messages.md
- api/dependencies.md
- api/exceptions.md
- "pydantic_ai.models":
- api/models/index.md
- api/models/openai.md
- api/models/gemini.md
- api/models/test.md
- api/models/function.md

extra:
# hide the "Made with Material for MkDocs" message
generator: false

theme:
name: "material"
palette:
- media: "(prefers-color-scheme)"
scheme: default
primary: pink
accent: pink
toggle:
icon: material/lightbulb
name: "Switch to light mode"
- media: "(prefers-color-scheme: light)"
scheme: default
primary: pink
accent: pink
toggle:
icon: material/lightbulb-outline
name: "Switch to dark mode"
- media: "(prefers-color-scheme: dark)"
scheme: slate
primary: pink
accent: pink
toggle:
icon: material/lightbulb-auto-outline
name: "Switch to system preference"
features:
- search.suggest
- search.highlight
- content.tabs.link
- content.code.annotate
- content.code.copy
- content.code.select
- navigation.expand
- navigation.indexes
- navigation.path
- navigation.tabs
- navigation.sections
- navigation.tracking
- navigation.top # alternatively, we could do navigation.tabs.sticky
- toc.follow
# logo: "logo-white.svg"
# favicon: "favicon.png"

# https://www.mkdocs.org/user-guide/configuration/#validation
validation:
omitted_files: warn
absolute_links: warn
unrecognized_links: warn

# used for analytics
extra_javascript:
- "/flarelytics/client.js"

markdown_extensions:
- tables
- admonition
- attr_list
- md_in_html
- pymdownx.details
- pymdownx.caret
- pymdownx.critic
- pymdownx.mark
- pymdownx.superfences
- pymdownx.snippets
- pymdownx.tilde
- pymdownx.highlight:
pygments_lang_class: true
- pymdownx.extra:
pymdownx.superfences:
custom_fences:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format
- pymdownx.emoji:
emoji_index: !!python/name:material.extensions.emoji.twemoji
emoji_generator: !!python/name:material.extensions.emoji.to_svg
- pymdownx.tabbed:
alternate_style: true
- pymdownx.tasklist:
custom_checkbox: true
- sane_lists # this means you can start a list from any number

watch:
- pydantic_ai
- pydantic_ai_examples

plugins:
- search
- mkdocstrings:
handlers:
python:
paths: [src/packages/pydantic_ai/pydantic_ai]
options:
members_order: source
separate_signature: true
show_signature_annotations: true
signature_crossrefs: true
group_by_category: false
show_source: false
heading_level: 2
import:
- url: https://docs.python.org/3/objects.inv
- url: https://docs.pydantic.dev/latest/objects.inv
- url: https://fastapi.tiangolo.com/objects.inv
- url: https://typing-extensions.readthedocs.io/en/latest/objects.inv
- url: https://rich.readthedocs.io/en/stable/objects.inv
# waiting for https://github.com/encode/httpx/discussions/3091#discussioncomment-11205594
2 changes: 1 addition & 1 deletion pydantic_ai/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from importlib.metadata import version

from .agent import Agent
from .call_typing import CallContext
from .dependencies import CallContext
from .exceptions import ModelRetry, UnexpectedModelBehaviour, UserError

__all__ = 'Agent', 'CallContext', 'ModelRetry', 'UnexpectedModelBehaviour', 'UserError', '__version__'
Expand Down
6 changes: 3 additions & 3 deletions pydantic_ai/_pydantic.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

if TYPE_CHECKING:
from . import _retriever
from .call_typing import AgentDeps
from .dependencies import AgentDeps, RetrieverParams


__all__ = 'function_schema', 'LazyTypeAdapter'
Expand All @@ -41,7 +41,7 @@ class FunctionSchema(TypedDict):
var_positional_field: str | None


def function_schema(either_function: _retriever.RetrieverEitherFunc[AgentDeps, _retriever.P]) -> FunctionSchema: # noqa: C901
def function_schema(either_function: _retriever.RetrieverEitherFunc[AgentDeps, RetrieverParams]) -> FunctionSchema: # noqa: C901
"""Build a Pydantic validator and JSON schema from a retriever function.
Args:
Expand Down Expand Up @@ -314,7 +314,7 @@ def _infer_docstring_style(doc: str) -> DocstringStyle:


def _is_call_ctx(annotation: Any) -> bool:
from .call_typing import CallContext
from .dependencies import CallContext

return annotation is CallContext or (
_typing_extra.is_generic_alias(annotation) and get_origin(annotation) is CallContext
Expand Down
12 changes: 1 addition & 11 deletions pydantic_ai/_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,11 @@
from typing_extensions import Self, TypeAliasType, TypedDict

from . import _utils, messages
from .call_typing import AgentDeps, CallContext
from .dependencies import AgentDeps, CallContext, ResultValidatorFunc
from .exceptions import ModelRetry
from .messages import ModelStructuredResponse, ToolCall
from .result import ResultData

# A function that always takes `ResultData` and returns `ResultData`,
# but may or maybe not take `CallInfo` as a first argument, and may or may not be async.
# Usage `ResultValidator[AgentDeps, ResultData]`
ResultValidatorFunc = Union[
Callable[[CallContext[AgentDeps], ResultData], ResultData],
Callable[[CallContext[AgentDeps], ResultData], Awaitable[ResultData]],
Callable[[ResultData], ResultData],
Callable[[ResultData], Awaitable[ResultData]],
]


@dataclass
class ResultValidator(Generic[AgentDeps, ResultData]):
Expand Down
Loading

0 comments on commit 234cde8

Please sign in to comment.