haiku.rag/docs/capabilities/index.md
Yiorgis Gozadinos eb9934a6e5
Notice the endings a structured answer arrives in
An output tool call is a `ToolCallPart` like any other, and treating every tool call as
intermediate meant a model could search, skip citing, emit its structured answer and
finish with neither a redirect nor a record. A response ends the question when it
carries no tool calls, or when one of its calls names an output tool.

Some endings are not visible from a single response — a host running
`end_strategy="early"` can finish on text beside a function call — so `after_run` is
the backstop: it cannot ask the model for anything by then, but it records a question
that reached the end of its run undeclared. That also covers a question that was asked
once, ignored, and finished anyway, which previously returned early on the redirect
marker and went unrecorded.

The capability documentation and the changelog said a question that gathered no
evidence is left alone. That describes neither the code nor the intent: enforcement
applies wherever there is something to declare, which includes a follow-up that reuses
evidence cited earlier without searching again.
2026-08-13 13:39:43 +03:00

5.3 KiB

Capabilities

haiku.rag provides native Pydantic AI capabilities:

Capability Use it for
RAGCapability Grounded document search and citations.
AnalysisCapability 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.

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

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:

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:

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:

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()],
)

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.

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.

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 that has never cited anything is not enforced at all.

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.

State

Capabilities use a plain state: dict[str, Any] attribute on agent dependencies when one is available. RAG state lives under "rag"; analysis state lives under "analysis". This keeps state independent of any transport or UI protocol.

Applications serving AG-UI should adapt the agent with Pydantic AI's AGUIAdapter. Native model and tool events require no haiku.rag-specific bridge.

Database path

Both factories resolve their database in this order:

  1. The db_path argument.
  2. HAIKU_RAG_DB.
  3. config.storage.data_dir / "haiku.rag.lancedb".