Update caps documentation

This commit is contained in:
Yiorgis Gozadinos 2026-08-13 14:08:09 +03:00
parent 0a0ddbd3b6
commit 7bdd11db39
No known key found for this signature in database
7 changed files with 151 additions and 92 deletions

View file

@ -18,18 +18,24 @@ When `qa.max_searches` or `analysis.max_executions` runs out, the exhausted tool
The sandbox exposes documents under `/documents/{document_id}/` with `metadata.json`, `content.txt`, `items.jsonl`, and `toc.json`.
## Compose with RAG
## Compose an agent
Register it on its own, not alongside `RAGCapability`: it already searches and cites,
and the two together give the model duplicate tools and separate budgets. See
[Capabilities](index.md#compose-an-agent).
```python
from pydantic_ai import Agent
from haiku.rag.capabilities.analysis import create_capability as analysis
from haiku.rag.capabilities.rag import create_capability as rag
from haiku.rag.capabilities.compaction import create_capability as compaction
from haiku.rag.capabilities.policy import create_capability as citation_policy
agent = Agent(
"openai:gpt-5",
capabilities=[
rag(db_path="my.lancedb"),
analysis(db_path="my.lancedb"),
compaction(),
citation_policy(),
],
)
```
@ -48,6 +54,6 @@ async with HaikuRAG("my.lancedb") as client:
When dependencies expose a state dictionary, `AnalysisState` is stored under `"analysis"`. It contains the document filter, code execution log, searches, citations, and the `evidence` record of what was retrieved and cited per question. Searches and executions are cleared when a new question starts, and a resumed question keeps them; the filter, citation index and evidence record persist.
This capability does not alter the message history either. Register the [compaction capability](index.md#multi-turn-conversations) to compact earlier questions.
This capability does not alter the message history either. Register the [compaction capability](compaction.md) to compact earlier questions.
The capability lazily opens both LanceDB and the sandbox only after it is loaded and a tool requires them. Resources close at the end of the agent run.

View file

@ -0,0 +1,43 @@
# Evidence compaction capability
`EvidenceCompactionCapability` keeps a multi-turn conversation from carrying every
search result it ever produced. Every question adds its evidence to the history, so
requests grow turn after turn, which degrades answers and can exceed a provider's
limits.
Register it alongside an evidence capability:
```python
from pydantic_ai import Agent
from haiku.rag.capabilities.compaction import create_capability as compaction
from haiku.rag.capabilities.rag import create_capability as rag
agent = Agent(
"openai:gpt-5",
capabilities=[rag(db_path="my.lancedb"), compaction()],
)
```
It exposes no tools and takes no configuration. Registering it is the only switch:
leave it out and the transcript reaches the model untouched.
## What it does
On each request, evidence from earlier questions is replaced by the evidence those
questions actually cited. Cited text and cited page images are kept in full, grouped by
the question that cited them, and stay citable by the same chunk ids. Every other
earlier evidence return becomes a short receipt. The current question is untouched.
Compaction rewrites the request, never the stored history, so `all_messages()` still
holds everything the run gathered.
This reduces what a request carries. It does not bound it: retained evidence still
grows with the conversation. A host that needs more aggressive pruning can compact its
own requests further, on the wire only.
## Resuming a question
Resuming a question (deferred tool results, an interruption, a suspension) requires the
host to carry the capability state from the run being resumed, alongside the message
history. Without it the identity of the question in progress is unknowable, and the run
fails rather than silently treating it as a new question.

View file

@ -6,105 +6,50 @@ haiku.rag provides native [Pydantic AI capabilities](https://ai.pydantic.dev/cap
|---|---|
| [`RAGCapability`](rag.md) | Grounded document search and citations. |
| [`AnalysisCapability`](analysis.md) | Corpus computation and structural analysis with sandboxed Python. |
| `EvidenceCompactionCapability` | Optional. Shrinking a conversation's history to the evidence that was cited. |
| `CitationPolicyCapability` | Optional. Requiring every answer to declare what grounds it. |
| [`EvidenceCompactionCapability`](compaction.md) | Optional. Shrinking a conversation's history to the evidence that was cited. |
| [`CitationPolicyCapability`](policy.md) | Optional. Requiring every answer to declare what grounds it. |
The two evidence capabilities are deferred by default. An agent initially sees only their descriptions and the standard `load_capability` tool. Instructions and tools enter the model context only when the model loads a capability.
## Compose an agent
Pick one evidence capability, and add both optional capabilities to it:
```python
from pydantic_ai import Agent
from haiku.rag.capabilities.rag import create_capability
rag = create_capability(db_path="my.lancedb")
agent = Agent("openai:gpt-5", capabilities=[rag])
result = await agent.run("What does the knowledge base say about X?")
print(result.output)
```
Attach both capabilities when an agent should choose between retrieval and computation:
```python
from haiku.rag.capabilities.analysis import create_capability as analysis
from haiku.rag.capabilities.rag import create_capability as rag
agent = Agent(
"openai:gpt-5",
capabilities=[rag(db_path="my.lancedb"), analysis(db_path="my.lancedb")],
)
```
## Multi-turn conversations
Every question adds its search results to the history, so requests grow turn after
turn, and can degrade answers or exceed a provider's limits as they do. Register the
compaction capability to replace earlier questions' evidence with the evidence that
was actually cited:
```python
from haiku.rag.capabilities.compaction import create_capability as compaction
from haiku.rag.capabilities.rag import create_capability as rag
agent = Agent(
"openai:gpt-5",
capabilities=[rag(db_path="my.lancedb"), compaction()],
)
```
Cited text and cited page images are kept in full, grouped by the question that
cited them, and stay citable by the same chunk ids. Everything else earlier becomes a
short receipt. Registering the capability is the only switch: leave it out and the
transcript reaches the model untouched. There is nothing to configure.
Compaction rewrites the request, never the stored history, so `all_messages()` still
holds everything the run gathered. Retained evidence still grows with the
conversation — this reduces what a request carries, it does not bound it. A host that
needs more aggressive pruning can compact its own requests further, on the wire only.
Resuming a question (deferred tool results, an interruption, a suspension) requires
the host to carry the capability state from the run being resumed, alongside the
message history. Without it the identity of the question in progress is unknowable
and the run fails rather than silently treating it as a new question.
## Requiring citations
Citing is always available and always recorded, but nothing requires it. Register the
citation policy capability to make every answer declare its grounding:
```python
from haiku.rag.capabilities.policy import create_capability as citation_policy
from haiku.rag.capabilities.rag import create_capability as rag
agent = Agent(
"openai:gpt-5",
capabilities=[rag(db_path="my.lancedb"), citation_policy()],
capabilities=[
rag(db_path="my.lancedb"),
compaction(),
citation_policy(),
],
)
result = await agent.run("What does the knowledge base say about X?")
print(result.output)
```
An empty citation is a valid declaration: a model that finds nothing relevant calls
the cite tool with an empty list, which records the answer as *ungrounded* — distinct
from an answer that declared nothing at all. That distinction is what makes requiring
a declaration possible without forcing the model to invent grounding.
Swap `rag` for `analysis` for an analysis agent. Both optional capabilities work the
same way with either one, and neither exposes tools or takes configuration.
When a question ends undeclared, the model is asked once to record what grounded the
answer it already gave. It is not asked to change the answer. If the cite tool is no
longer available by then, or the question finishes undeclared anyway, it is recorded as
a violation in `CitationPolicyState` under `"citation_policy"`, since pointing a model
at a tool that is gone costs it retries.
!!! note "Register one evidence capability, not both"
What gets enforced is every answer in a conversation that has something to declare:
either this question retrieved evidence, or the conversation has already cited
something, which stays available to later answers. So a follow-up about evidence cited
earlier is enforced even though it searched nothing — that case is the reason the
capability exists. It also means that once anything has been cited, later turns are
enforced too, a greeting included; the model satisfies the policy by citing an empty
list, at the cost of one extra request. A conversation with neither a current-question
evidence outcome nor any earlier citation is not enforced.
`RAGCapability` and `AnalysisCapability` overlap. Both search the same corpus and
both register citations, so an agent holding both must choose between two
near-identical search tools, and its citations land in whichever capability it
happened to call. Each also carries its own request limit and its own search
budget, so registering both doubles what a question may spend.
Exactly one policy capability makes the decision, however many evidence capabilities
are registered, so two of them cannot each demand a citation for one answer.
Choose by what the questions need. `RAGCapability` answers questions from retrieved
passages. `AnalysisCapability` adds a Python sandbox and a document filesystem, for
questions that compute over many documents or read their structure, and it can
search too. If you need computation, register the analysis capability alone rather
than adding it to the RAG one.
## State

View file

@ -0,0 +1,49 @@
# Citation policy capability
`CitationPolicyCapability` requires every answer to declare what grounds it. Citing is
always available and always recorded without it, but nothing makes the model do it.
Register it alongside an evidence capability:
```python
from pydantic_ai import Agent
from haiku.rag.capabilities.policy import create_capability as citation_policy
from haiku.rag.capabilities.rag import create_capability as rag
agent = Agent(
"openai:gpt-5",
capabilities=[rag(db_path="my.lancedb"), citation_policy()],
)
```
It exposes no tools and takes no configuration. Exactly one policy capability makes the
decision, however many evidence capabilities are registered, so two of them cannot each
demand a citation for one answer.
## Declaring nothing is a valid answer
A model that finds nothing relevant calls the cite tool with an empty list. That records
the answer as *ungrounded*, which is distinct from an answer that declared nothing at
all (*missing*). The distinction is what makes a declaration requirable without forcing
the model to invent grounding.
## What happens when a question ends undeclared
The model is asked once to record what grounded the answer it already gave. It is not
asked to change the answer. If the cite tool is no longer available by then, or the
question finishes undeclared anyway, the question is recorded in
`CitationPolicyState.violations` under the `"citation_policy"` state key. Pointing a
model at a tool that is gone costs it retries, so the capability records the failure
instead.
## Which answers are enforced
Every answer in a conversation that has something to declare: either this question
retrieved evidence, or the conversation has already cited something, which stays
available to later answers. A follow-up about evidence cited earlier is enforced even
though it searched nothing, which is the case the capability exists for.
Once anything has been cited, later turns are enforced too, a greeting included. The
model satisfies the policy by citing an empty list, at the cost of one extra request. A
conversation with neither a current-question evidence outcome nor any earlier citation
is not enforced.

View file

@ -13,12 +13,23 @@ The distinct `rag_` prefix lets this capability coexist with analysis and other
## Create and compose
Register it on its own rather than alongside `AnalysisCapability`, which searches and
cites as well. See [Capabilities](index.md#compose-an-agent).
```python
from pydantic_ai import Agent
from haiku.rag.capabilities.rag import create_capability
from haiku.rag.capabilities.compaction import create_capability as compaction
from haiku.rag.capabilities.policy import create_capability as citation_policy
from haiku.rag.capabilities.rag import create_capability as rag
rag = create_capability(db_path="my.lancedb")
agent = Agent("openai:gpt-5", capabilities=[rag])
agent = Agent(
"openai:gpt-5",
capabilities=[
rag(db_path="my.lancedb"),
compaction(),
citation_policy(),
],
)
result = await agent.run("What safety equipment does the manual require?")
print(result.output)
@ -49,7 +60,7 @@ State is ordinary application state; the capability does not depend on AG-UI. An
## Context management
This capability does not alter the message history. To stop long conversations resending old retrieved content, register the [compaction capability](index.md#multi-turn-conversations) alongside it.
This capability does not alter the message history. To stop long conversations resending old retrieved content, register the [compaction capability](compaction.md) alongside it.
## Domain context and vision

View file

@ -61,13 +61,16 @@ Retrieval stays text-based; the images are sent to the model alongside your mess
The default capability is `rag`. Enable `analysis` when the question needs computation, aggregation, comparison across documents, or section-scoped reading that a single search can't deliver:
```bash
# both capabilities (the agent routes between them)
haiku-rag chat -c rag -c analysis
# analysis only
# analysis instead of rag
haiku-rag chat -c analysis
# both, which gives the model duplicate search and cite tools
haiku-rag chat -c rag -c analysis
```
Prefer one. `analysis` searches and cites as well as computing, so pairing it with `rag`
duplicates tools and budgets. See [Capabilities](capabilities/index.md#compose-an-agent).
The `analysis` capability mounts every document as a virtual filesystem at `/documents/{id}/` (with `metadata.json`, `content.txt`, `items.jsonl`, and `toc.json`) and runs Python in a sandboxed interpreter with `search` and `list_documents` as awaitable functions. It's the right choice for questions like:
- "How many of these documents mention X?"

View file

@ -23,6 +23,8 @@ nav = [
"capabilities/index.md",
{ "RAG capability" = "capabilities/rag.md" },
{ "Analysis capability" = "capabilities/analysis.md" },
{ "Evidence compaction" = "capabilities/compaction.md" },
{ "Citation policy" = "capabilities/policy.md" },
] },
{ Configure = [
"configuration/index.md",