# Pydantic AI
`writerslogic_warm_integrations.pydantic_ai` provides a memory toolset and a persistent message-history store.
Tested against `pydantic-ai-slim==2.58.0`, Python 3.12.
## Install
```sh
pip install "writerslogic-warm-integrations[pydantic-ai]"
```
Needs a running `warm-server` whose `--input-dim` matches the embedder (default embedder: 384).
## Toolset
```python
from pydantic_ai import Agent
from writerslogic_warm_integrations.core import WarmConfig
from writerslogic_warm_integrations.pydantic_ai import memory_toolset
config = WarmConfig(url="http://127.0.0.1:8080", tenant_id="acme")
agent = Agent("openai:gpt-5", toolsets=[memory_toolset(config, user_id="alice")])
```
`memory_toolset` returns a `FunctionToolset` with `remember(text, tags?)`, `recall(query, k?)` and `forget(memory_id)`. Schemas are generated by Pydantic AI from the function signatures. `user_id` is stored as metadata, restricts `recall` (service-side equality filter) and makes `forget` refuse other users' ids. `sync=True` builds blocking tools on `WarmBackend` (Pydantic AI runs them in a worker thread); the default is async on `AsyncWarmBackend`. Extra keyword arguments (`max_retries`, `timeout`, `requires_approval`, ...) go to `FunctionToolset`; `toolset_id` defaults to `"writerslogic-warm"`.
## Message history
```python
from writerslogic_warm_integrations.pydantic_ai import MessageStore # AsyncMessageStore for async
store = MessageStore(config)
result = agent.run_sync("hi", message_history=store.load("chat-1"))
store.save("chat-1", result.all_messages())
```
Each `ModelMessage` is stored as one JSON item through `ModelMessagesTypeAdapter`, so every part type round-trips (tested with text, tool calls and returns, and a 30 000-character tool return). `load(id, limit=None)`, `append`, `save`, `replace`, `delete`.
- `save` appends only the tail beyond what is stored, assuming `messages` extends the stored history. If `messages` is shorter (a history processor compacted it) the conversation is rewritten. If a processor rewrote messages in place without changing the count, call `replace`.
- Storage is a `core.paged` log: unbounded message count, page size `min(50, max_fetch)`, an item up to `chunk_chars * max_parts` characters (default 8000 x 100), no concurrent writers to one conversation id across processes. See `openai-agents.md` for the layout.
- `Conversation` objects (usage, conversation id, deferred requests) are not stored; only messages, as with `message_history=`.
## Knobs
All on `WarmConfig`: `url`, `api_key`, `tenant_id`, `collection`, `embedder`, `text_queries`, `zero_trust_key`, `max_fetch`, `overfetch`. BM25 and title weight are native-engine options and not available over the HTTP service.
## Filters
No filter argument is exposed; `user_id` (tools) and conversation id (history) are top-level equality filters evaluated by the service.
## Conformance
Verified through `Agent.run` with `FunctionModel` (scripted tool calls, schema inspection through `AgentInfo.function_tools`) and `TestModel` (which calls every tool with generated arguments), against a real `warm-server`: `pytest tests/pydantic_ai`. Boundaries: 49/50/51/120 messages across pages.
## Limitations
Retrieval is by similarity over the configured embedder; the default `HashingEmbedder` is lexical. Supply an `embedder` for semantic recall.