docs: rebuild Skills section, drop pitch prose
This commit is contained in:
parent
19e2be003e
commit
910d178b00
20 changed files with 537 additions and 578 deletions
|
|
@ -1,117 +0,0 @@
|
|||
# Analysis
|
||||
|
||||
The analysis skill enables complex analytical tasks by writing and executing Python code in a sandboxed environment. It solves problems that traditional RAG struggles with:
|
||||
|
||||
- **Aggregation**: "How many documents mention security vulnerabilities?"
|
||||
- **Computation**: "What's the average revenue across all quarterly reports?"
|
||||
- **Multi-document analysis**: "Compare the key findings between Report A and Report B"
|
||||
- **Structured data extraction**: "Extract all dollar amounts and compute totals"
|
||||
|
||||
## How It Works
|
||||
|
||||
1. The skill receives a question
|
||||
2. It writes Python code to explore the knowledge base
|
||||
3. Code executes in a sandboxed Python interpreter with access to search and a virtual filesystem of documents
|
||||
4. The skill iterates: run code, examine results, refine approach
|
||||
5. Final answer is synthesized from the gathered data
|
||||
|
||||
## CLI Usage
|
||||
|
||||
```bash
|
||||
# Basic usage
|
||||
haiku-rag analyze "How many documents are in the database?"
|
||||
|
||||
# With document filter (restricts what the skill can access)
|
||||
haiku-rag analyze "Summarize the key points" --filter "uri LIKE '%report%'"
|
||||
```
|
||||
|
||||
## Python Usage
|
||||
|
||||
```python
|
||||
from haiku.rag.client import HaikuRAG
|
||||
|
||||
async with HaikuRAG(path_to_db) as client:
|
||||
result = await client.analyze("How many documents mention 'security'?")
|
||||
print(result.answer)
|
||||
for citation in result.citations:
|
||||
print(citation.uri, citation.title)
|
||||
|
||||
# With filter (skill can only see filtered documents)
|
||||
result = await client.analyze(
|
||||
"What is the total revenue?",
|
||||
filter="title LIKE '%Financial%'"
|
||||
)
|
||||
```
|
||||
|
||||
The executed Python program(s) for each turn are not on `AnalysisResult` itself; they live on `AnalysisState.executions` while the skill runs.
|
||||
|
||||
## Sandbox Capabilities
|
||||
|
||||
The agent's code runs in a sandboxed Python interpreter ([pydantic-monty](https://github.com/pydantic/monty)) with:
|
||||
|
||||
### Functions
|
||||
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `search(query, limit)` | Hybrid search (vector + full-text) with automatic context expansion. Returns `doc_item_refs` and `picture_refs` for cross-referencing with `items.jsonl` |
|
||||
| `list_documents()` | List all documents in the knowledge base |
|
||||
|
||||
### Document Filesystem
|
||||
|
||||
All documents are mounted as a virtual filesystem at `/documents/`. The agent uses standard Python `pathlib.Path` to browse and read files:
|
||||
|
||||
```
|
||||
/documents/{document_id}/
|
||||
metadata.json # {id, title, uri, created_at}
|
||||
content.txt # Full document text
|
||||
items.jsonl # Structured items: position, self_ref, label, text, page_numbers
|
||||
```
|
||||
|
||||
- **`metadata.json`** — Loaded eagerly (small). Use `Path('/documents').iterdir()` to discover documents.
|
||||
- **`content.txt`** — Lazy-loaded on first read. Full document text for regex or keyword search.
|
||||
- **`items.jsonl`** — Lazy-loaded on first read. One JSON object per line with structured document elements. Tables are pre-rendered as markdown. Labels include `section_header`, `text`, `table`, `list_item`, `caption`, `formula`, `picture`, `code`, `footnote`, etc.
|
||||
|
||||
Search results include `doc_item_refs` (e.g. `["#/texts/5", "#/tables/0"]`) that match `self_ref` values in `items.jsonl`, enabling navigation from search hits to document structure.
|
||||
|
||||
### Python Features
|
||||
|
||||
The interpreter supports a subset of Python: variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, async/await, `filter()`, `getattr()`, try/except, file I/O via `pathlib.Path`, and the `json`, `re`, `math` modules.
|
||||
|
||||
Not supported: most imports (only `json`, `re`, `math`, `pathlib` are available), class definitions, generators/yield, match statements, decorators, `with` statements. For pattern matching, use `import re` or string methods.
|
||||
|
||||
### Security
|
||||
|
||||
Code executes in an isolated interpreter with:
|
||||
|
||||
- **Virtual filesystem only**: The `/documents/` filesystem is sandboxed — no access to the real filesystem
|
||||
- **No network access**: Code cannot make HTTP requests or open sockets
|
||||
- **No imports**: Only `json`, `re`, `math`, and `pathlib` modules are available
|
||||
- **Execution timeout**: Configurable limit (default 60s)
|
||||
- **Output truncation**: Large outputs are truncated to prevent memory issues
|
||||
|
||||
## Context Filter
|
||||
|
||||
The `filter` parameter restricts what documents the skill can access. Unlike tool parameters, the filter is applied automatically and cannot be bypassed by the LLM — both the VFS and search results are scoped to the filter:
|
||||
|
||||
```python
|
||||
# Skill can only see documents with "confidential" in the URI
|
||||
result = await client.analyze(
|
||||
"Summarize all findings",
|
||||
filter="uri LIKE '%confidential%'"
|
||||
)
|
||||
```
|
||||
|
||||
This is useful for scoping to specific document sets, enforcing access control, or limiting context for focused analysis.
|
||||
|
||||
## Configuration
|
||||
|
||||
Analysis settings can be configured in `haiku.rag.yaml`:
|
||||
|
||||
```yaml
|
||||
analysis:
|
||||
model:
|
||||
provider: anthropic
|
||||
name: claude-sonnet-4-20250514
|
||||
code_timeout: 60.0 # Max seconds for code execution
|
||||
max_output_chars: 50000 # Truncate output after this many chars
|
||||
```
|
||||
|
|
@ -61,12 +61,21 @@ evaluations run repliqa --config /path/to/haiku.rag.yaml --db /path/to/custom.la
|
|||
- `--skip-qa` - Skip QA benchmark
|
||||
- `--limit N` - Limit number of test cases
|
||||
- `--name NAME` - Override the evaluation name
|
||||
- `--judge-model PROVIDER:NAME` - Override the LLM judge model. Defaults to `ollama:qwen3.6` so the judge stays stable when the answering model changes.
|
||||
- `--target {rag-skill,analysis-skill}` - Choose which [skill](skills/index.md) to benchmark end-to-end against the same datasets and judge (default: `rag-skill`).
|
||||
- `--skill-model PROVIDER:NAME` - Override the skill model independently from the judge (default: `config.qa.model`, or `config.analysis.model` when set for `--target analysis-skill`).
|
||||
|
||||
If no config file is specified, the script searches standard locations: `./haiku.rag.yaml`, user config directory, then falls back to defaults.
|
||||
|
||||
To pin the LLM judge in YAML (rather than the default `ollama:qwen3.6`):
|
||||
|
||||
```yaml
|
||||
evaluations:
|
||||
judge:
|
||||
provider: openai
|
||||
name: gpt-4o-mini
|
||||
base_url: http://localhost:8000/v1 # optional, for OpenAI-compatible servers (vLLM, LM Studio, etc.)
|
||||
```
|
||||
|
||||
## Methodology
|
||||
|
||||
### Retrieval Metrics
|
||||
|
|
@ -88,7 +97,7 @@ If no config file is specified, the script searches standard locations: `./haiku
|
|||
|
||||
### QA Accuracy
|
||||
|
||||
For question-answering evaluation, `pydantic-evals` coordinates an LLM judge to determine whether answers are correct. The default judge is `ollama:qwen3.6` — pinned so changes to the skill model don't change the judge underneath. Override per run with `--judge-model provider:name`. Accuracy is the fraction of correctly answered questions.
|
||||
For question-answering evaluation, `pydantic-evals` coordinates an LLM judge to determine whether answers are correct. The default judge is `ollama:qwen3.6` — pinned so changes to the skill model don't change the judge underneath. Set `evaluations.judge` in `haiku.rag.yaml` to override (including a custom `base_url` for any OpenAI-compatible endpoint). Accuracy is the fraction of correctly answered questions.
|
||||
|
||||
We picked `qwen3.6` over the previously-pinned `gpt-oss` after a 4-cell calibration (gpt-oss / qwen3.6 as both answerer and judge, with Claude Opus 4.7 as a reference). `qwen3.6` had κ ≥ 0.66 vs the reference on both same-family and cross-family answerers (vs ~0.39–0.55 for `gpt-oss`) and showed no measurable self-preference bias, while `gpt-oss` was ~10 pp more lenient on its own outputs.
|
||||
|
||||
|
|
@ -131,7 +140,7 @@ Numbers measured under the current pinned judge (`ollama:qwen3.6`) on a recent `
|
|||
Two approaches are benchmarked separately:
|
||||
|
||||
- **Multimodal embedder** (`Qwen/Qwen3-VL-Embedding-8B`, served via vLLM): picture bytes and text live in a shared vector space, no VLM is run at ingest.
|
||||
- **Text embedder + VLM picture descriptions** (`qwen3-embedding:4b` + `ollama/ministral-3`): pictures are described at ingest and the descriptions are woven into chunk text; retrieval runs over text only. See [Picture Description configuration](configuration/processing.md#picture-description-vlm).
|
||||
- **Text embedder + VLM picture descriptions** (`qwen3-embedding:4b` + `ollama/ministral-3`): pictures are described at ingest and the descriptions are woven into chunk text; retrieval runs over text only. See [Picture handling configuration](configuration/processing.md#picture-handling).
|
||||
|
||||
#### Multimodal embedder
|
||||
|
||||
|
|
|
|||
27
docs/chat.md
27
docs/chat.md
|
|
@ -13,23 +13,21 @@ haiku-rag chat --db /path/to/database.lancedb
|
|||
haiku-rag chat --model openai:gpt-4o
|
||||
```
|
||||
|
||||

|
||||
|
||||
<div style="padding:56.25% 0 0 0;position:relative;"><iframe src="https://player.vimeo.com/video/1159658167?badge=0&autopause=0&player_id=0&app_id=58479" frameborder="0" allow="autoplay; fullscreen; picture-in-picture; clipboard-write; encrypted-media" style="position:absolute;top:0;left:0;width:100%;height:100%;" title="haiku.rag Chat TUI demo"></iframe></div><script src="https://player.vimeo.com/api/player.js"></script>
|
||||
|
||||
*Demo: chatting with an agent over 1000 arXiv papers. Shows context building (3:00), citations with visual grounding (3:20), and document listing.*
|
||||

|
||||
|
||||
## How it works
|
||||
|
||||
The chat is a Pydantic AI agent with the `rag` [skill](skills/rag.md) attached. Each turn the agent decides which tool to call next, runs hybrid search against your documents, expands context around the hits, may issue further searches, and answers with citations. You see streaming text and a live indicator of which tool is running.
|
||||
The chat is a Pydantic AI agent with the `rag` [skill](skills/rag.md) attached by default. Each turn the agent decides which tool to call next, runs hybrid search against your documents, expands context around the hits, may issue further searches, and answers with citations. You see streaming text and a live indicator of which tool is running.
|
||||
|
||||
The session is in-memory for the lifetime of the TUI. Conversation history is kept across turns so follow-up questions reuse prior context. Citations are tracked per turn and inspectable via the command palette. Clearing the chat resets the session and the agent's memory.
|
||||
|
||||
## Citations and visual grounding
|
||||
|
||||
Each answer cites the chunks the agent used, with source document, page numbers, and section headings. Citations are expandable inline.
|
||||
Each answer cites the chunks the agent used, with source document, page numbers, and section headings. Citations are expandable inline. Picture citations render the figure directly underneath the text snippet.
|
||||
|
||||
For visual grounding (the chunk highlighted on its page image), open the command palette and pick "Show visual grounding". This requires:
|
||||

|
||||
|
||||
For visual grounding of a text chunk (the chunk highlighted on its source page image), open the command palette and pick "Show visual grounding". This requires:
|
||||
|
||||
- Documents processed via Docling with page images (default for PDFs).
|
||||
- A terminal that supports inline images (iTerm2, WezTerm, Kitty).
|
||||
|
|
@ -55,17 +53,24 @@ haiku-rag visualize <chunk_id>
|
|||
|
||||
## Skills
|
||||
|
||||
The default skill is `rag`. Add `analysis` for sandboxed Python execution over your documents:
|
||||
The default skill 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 skills
|
||||
# both skills (the agent routes between them)
|
||||
haiku-rag chat -s rag -s analysis
|
||||
|
||||
# analysis only
|
||||
haiku-rag chat -s analysis
|
||||
```
|
||||
|
||||
The `analysis` skill mounts a virtual filesystem under `/documents/{id}/` and runs Python code against it inside a sandbox. Useful for aggregation, computation, and multi-document analysis. See [Analysis skill](skills/analysis.md).
|
||||
The `analysis` skill 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?"
|
||||
- "Summarize Section 5 of paper Y."
|
||||
- "Compare the experimental sections across these three reports."
|
||||
- "Which section discusses the proof of Theorem 4.10?"
|
||||
|
||||
For everyday Q&A, the rag skill alone is faster and cheaper. Attaching both lets the agent pick. See [Analysis skill](skills/analysis.md) for the full sandbox capabilities and worked code patterns.
|
||||
|
||||
## Document filter
|
||||
|
||||
|
|
|
|||
|
|
@ -253,7 +253,7 @@ processing:
|
|||
chunk_size: 256 # Maximum tokens per chunk
|
||||
```
|
||||
|
||||
Context expansion settings (for enriching search results with surrounding content) are configured in the `search` section. See [Search Settings](qa-research.md#search-settings).
|
||||
Context expansion settings (for enriching search results with surrounding content) are configured in the `search` section. See [Search Settings](qa.md#search-settings).
|
||||
|
||||
### Table Serialization
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ qa:
|
|||
```
|
||||
|
||||
- **model**: LLM configuration (see [Providers](providers.md#model-settings))
|
||||
- **model.vision**: Set to `true` for vision-capable models (`qwen2.5vl`, `qwen3.6`, `gpt-4o`, `claude-sonnet`, …). The skill's `search` tool only attaches picture bytes (`BinaryContent`) to its `ToolReturn` when this is `true`; otherwise picture bytes are withheld. See [Pictures × embedder × QA model](processing.md#pictures--embedder--qa-model-how-the-pieces-compose) for the full matrix.
|
||||
- **model.vision**: Set to `true` for vision-capable models (`qwen2.5vl`, `qwen3.6`, `gpt-4o`, `claude-sonnet`, …). The skill's `search` tool only attaches picture bytes (`BinaryContent`) to its `ToolReturn` when this is `true`; otherwise picture bytes are withheld. See [Pictures × embedder × QA model](processing.md#pictures-embedder-qa-model-how-the-pieces-compose) for the full matrix.
|
||||
- **max_searches**: Maximum number of search tool calls the rag skill can make per question (default: 3)
|
||||
|
||||
## Analysis Configuration
|
||||
|
|
@ -55,4 +55,4 @@ analysis:
|
|||
- **code_timeout**: Maximum seconds for each code execution (default: 60)
|
||||
- **max_output_chars**: Truncate code output after this many characters (default: 50000)
|
||||
|
||||
See [Analysis](../agents/analysis.md) for usage details.
|
||||
See [Analysis skill](../skills/analysis.md) for usage details.
|
||||
|
|
|
|||
BIN
docs/img/chat-citation-figure.png
Normal file
BIN
docs/img/chat-citation-figure.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 473 KiB |
BIN
docs/img/chat-qa.png
Normal file
BIN
docs/img/chat-qa.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 470 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 64 KiB |
|
|
@ -19,7 +19,7 @@ haiku-rag add-src ~/Documents/some-paper.pdf
|
|||
haiku-rag chat
|
||||
```
|
||||
|
||||
The chat TUI is the fastest way to test retrieval and answer quality. `haiku-rag ask` and `haiku-rag search` cover one-shot CLI usage. Beyond that, the same database backs Python integrations, agents, skills, and the MCP server.
|
||||
The chat TUI is one way to interact with the database. `haiku-rag ask` and `haiku-rag search` cover one-shot CLI usage. Python integrations, skills, and the MCP server work against the same database.
|
||||
|
||||
## What it does
|
||||
|
||||
|
|
@ -35,7 +35,7 @@ The chat TUI is the fastest way to test retrieval and answer quality. `haiku-rag
|
|||
|
||||
## Where to go next
|
||||
|
||||
- [Quickstart](tutorial.md): install through first chat in five minutes.
|
||||
- [Quickstart](tutorial.md): install, index, chat.
|
||||
- [Skills](skills/index.md): the rag and rag-analysis skills you compose into Pydantic AI agents.
|
||||
- [Python API](python.md): use haiku.rag from code.
|
||||
- [MCP server](mcp.md): expose haiku.rag to Claude Desktop or other AI assistants.
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@ The full package includes **all features and extras**:
|
|||
- **All embedding providers** - VoyageAI
|
||||
- **All rerankers** - MixedBread AI, Cohere, Zero Entropy
|
||||
|
||||
This is the easiest way to get started with all features enabled.
|
||||
|
||||
### Slim Package (Minimal Dependencies)
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -319,7 +319,7 @@ answer, citations = await client.ask(
|
|||
)
|
||||
```
|
||||
|
||||
`client.ask` runs the [rag skill](skills/index.md) under the hood and returns `(answer_text, list[Citation])`. Citations include page numbers, section headings, and document references.
|
||||
`client.ask` runs the [rag skill](skills/index.md) and returns `(answer_text, list[Citation])`. Citations include page numbers, section headings, and document references.
|
||||
|
||||
The QA provider and model are configured in `haiku.rag.yaml` or can be passed directly to the client (see [Configuration](configuration/index.md)).
|
||||
|
||||
|
|
@ -345,31 +345,13 @@ result = await client.analyze(
|
|||
|
||||
`client.analyze` runs the [analysis skill](skills/index.md), which writes and executes Python code in a sandboxed environment to solve problems that traditional RAG struggles with: aggregation, computation, and multi-document analysis.
|
||||
|
||||
See [Analysis](agents/analysis.md) for details on capabilities and configuration.
|
||||
See [Analysis skill](skills/analysis.md) for details on capabilities and configuration.
|
||||
|
||||
## Building Custom Agents
|
||||
## Building custom agents
|
||||
|
||||
haiku.rag provides a RAG skill built on [haiku.skills](https://github.com/ggozad/haiku.skills) that bundles all capabilities into a composable agent:
|
||||
`client.ask` and `client.analyze` are the convenience wrappers. To build your own Pydantic AI agent against the same database, attach the rag and rag-analysis skills directly with `SkillToolset`. See [Skills](skills/index.md) for the full story and worked examples.
|
||||
|
||||
```python
|
||||
from pydantic_ai import Agent
|
||||
from haiku.rag.skills.rag import create_skill
|
||||
from haiku.skills.agent import SkillToolset
|
||||
from haiku.skills.prompts import build_system_prompt
|
||||
|
||||
skill = create_skill(db_path=db_path, config=config)
|
||||
toolset = SkillToolset(skills=[skill])
|
||||
|
||||
agent = Agent(
|
||||
"openai-chat:gpt-4o",
|
||||
instructions=build_system_prompt(toolset.skill_catalog),
|
||||
toolsets=[toolset],
|
||||
)
|
||||
|
||||
result = await agent.run("What are the main findings?")
|
||||
```
|
||||
|
||||
See [Toolsets](tools.md) for the full API reference.
|
||||
For the low-level toolset factories under `haiku.rag.tools` (one rung below the skill abstraction), see [Toolsets](tools.md).
|
||||
|
||||
## Importing Pre-Processed Documents
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ docling-serve is a REST API service that provides:
|
|||
|
||||
### Docker Compose (Recommended)
|
||||
|
||||
The easiest way to use haiku.rag with docling-serve is using the slim Docker image with docker-compose. See `examples/docker/docker-compose.yml` for a complete setup that includes both services.
|
||||
The slim Docker image with docker-compose is the recommended setup. See `examples/docker/docker-compose.yml` for a complete configuration that includes both services.
|
||||
|
||||
### Running docling-serve Manually
|
||||
|
||||
|
|
|
|||
|
|
@ -1,32 +1,58 @@
|
|||
# Analysis Skill
|
||||
|
||||
The analysis skill provides computational analysis via code execution. It writes and runs Python code in a sandboxed interpreter to answer questions that require computation, aggregation, or data traversal.
|
||||
Plain RAG (search → cite → answer) works for questions whose answer sits in a chunk or two: "Who wrote this?", "What does X say about Y?". It struggles when the answer requires touching the whole corpus, reading a specific section in full, or doing arithmetic on the data.
|
||||
|
||||
## `create_skill(db_path?, config?)`
|
||||
The analysis skill (`rag-analysis`) gives the agent a second tool — `execute_code` — that runs Python in a sandboxed interpreter against a structured view of your documents. The agent can search, read, count, slice, and compare without leaving the tool call. Citations work the same way as the rag skill.
|
||||
|
||||
```python
|
||||
from haiku.rag.skills.analysis import create_skill
|
||||
`client.analyze`, `haiku-rag analyze`, the MCP `analyze` tool, and the chat TUI (when `-s analysis` is enabled) all run through this skill.
|
||||
|
||||
skill = create_skill(db_path=db_path, config=config)
|
||||
```
|
||||
## When to use it
|
||||
|
||||
| Parameter | Default | Description |
|
||||
|-----------|---------|-------------|
|
||||
| `db_path` | `None` | Path to LanceDB database. Falls back to `HAIKU_RAG_DB` env var, then config default. |
|
||||
| `config` | `None` | `AppConfig` instance. If None, uses `get_config()`. |
|
||||
Reach for the analysis skill when the question needs more than a search:
|
||||
|
||||
- **Aggregation across the corpus.** "How many documents mention security vulnerabilities?"
|
||||
- **Section-scoped reading.** "Summarize Section 5 of paper Y."
|
||||
- **Structural comparison.** "Do both papers have an Experimental Results section?"
|
||||
- **Computation on retrieved data.** "What's the average revenue across these quarterly reports?"
|
||||
- **Multi-step chains.** Search, filter the results in Python, search again, aggregate — all in one tool call.
|
||||
|
||||
For everyday Q&A, the [RAG skill](rag.md) is faster and cheaper. Attach both and the agent routes.
|
||||
|
||||
## How it works
|
||||
|
||||
Two things make the agent's programs short and the resulting analyses tractable:
|
||||
|
||||
1. **Search and document listing are awaitable inside the code.** `await search(query)` returns the same hits the rag skill sees — chunk IDs, text, source metadata, picture refs. The agent can immediately filter, sort, count, or follow up with another search without exiting the tool call.
|
||||
|
||||
2. **Every document is mounted as a virtual filesystem at `/documents/{id}/`.** The agent reads four files per document: identifiers and metadata, full text, a list of structured items (paragraphs, tables, figures, headings), and a section tree built from the document's headings. The structure exposes what search alone hides — the agent can navigate from a search hit to the section it lives in, slice a single section instead of pulling the whole document, or scan a document's text directly when keyword precision matters.
|
||||
|
||||
A search hit is always a starting point. The agent reads structure around it, drills into the right section, and cites the chunks it actually used. Chunk IDs from search results and chunk IDs surfaced through the VFS are both accepted by `cite`.
|
||||
|
||||
### Sandbox guarantees
|
||||
|
||||
The interpreter is [pydantic-monty](https://github.com/pydantic/monty), isolated from the host:
|
||||
|
||||
- **Virtual filesystem only.** `/documents/` is the entire FS.
|
||||
- **No network.** HTTP, sockets, and the `requests` family are unavailable.
|
||||
- **Limited imports.** Only `json`, `re`, `math`, `pathlib`.
|
||||
- **Execution timeout** (default 60s, configurable via `analysis.code_timeout`).
|
||||
- **Output truncation** (default 50000 chars, configurable via `analysis.max_output_chars`).
|
||||
|
||||
Variables persist between `execute_code` calls within one invocation, so the agent can build state step by step. A fresh sandbox is built per `client.analyze` call.
|
||||
|
||||
## Tools
|
||||
|
||||
| Tool | Purpose |
|
||||
|------|---------|
|
||||
| `search(query, limit?)` | Hybrid search (vector + full-text) with context expansion |
|
||||
| `list_documents()` | List all documents in the knowledge base |
|
||||
| `execute_code(code)` | Execute Python code in a sandboxed interpreter with VFS access |
|
||||
| `cite(chunk_ids)` | Register chunk IDs as citations for the current answer |
|
||||
| `search(query, limit?)` | Hybrid search with context expansion. Same as the RAG skill's `search`. |
|
||||
| `execute_code(code)` | Run Python in a sandboxed interpreter with VFS access. |
|
||||
| `cite(chunk_ids)` | Register chunk IDs as citations. Call before producing the final answer. |
|
||||
|
||||
`list_documents` isn't exposed as a top-level tool but is available inside `execute_code` as `await list_documents()`.
|
||||
|
||||
## State
|
||||
|
||||
The skill manages an `AnalysisState` under the `"analysis"` namespace:
|
||||
`AnalysisState` lives under the `"analysis"` namespace:
|
||||
|
||||
```python
|
||||
class AnalysisState(BaseModel):
|
||||
|
|
@ -37,32 +63,135 @@ class AnalysisState(BaseModel):
|
|||
searches: dict[str, list[SearchResult]] = {}
|
||||
```
|
||||
|
||||
- **document_filter** — SQL WHERE clause applied to `search` and `list_documents` calls. Persists across invocations as session-level configuration.
|
||||
- **executions** — Each `execute_code` call appends a `CodeExecutionEntry` with code, stdout, stderr, and success status. Cleared at the start of each invocation; mirrors the sandbox lifecycle (variables persist across calls within one invocation, a fresh sandbox is built per invocation).
|
||||
- **citation_index** — Citations indexed by chunk ID. Accumulates across invocations (same semantics as the RAG skill).
|
||||
- **citations** — Chunk IDs cited during the current invocation. Deduplicated; cleared at the start of each invocation.
|
||||
- **searches** — Search results from both the `search` tool and sandbox-internal searches. Cleared at the start of each invocation.
|
||||
- **document_filter** — SQL WHERE clause applied to `search` and the VFS. The LLM can't bypass it: both views are scoped.
|
||||
- **executions** — Each `execute_code` call appends an entry with code, stdout, stderr, success. Cleared at the start of each invocation.
|
||||
- **citation_index** — All citations indexed by chunk ID. Accumulates across invocations.
|
||||
- **citations** — Chunk IDs registered via `cite` during the current invocation. Deduplicated, cleared per-invocation.
|
||||
- **searches** — Search results from both the `search` tool and sandbox-internal searches. Cleared per-invocation.
|
||||
|
||||
## Usage with RAG Skill
|
||||
## `create_skill(db_path?, config?)`
|
||||
|
||||
Combine both skills to give the agent full RAG + analysis capabilities:
|
||||
```python
|
||||
from haiku.rag.skills.analysis import create_skill
|
||||
|
||||
skill = create_skill(db_path="my.lancedb")
|
||||
```
|
||||
|
||||
| Parameter | Default | Description |
|
||||
|-----------|---------|-------------|
|
||||
| `db_path` | `None` | Path to LanceDB database. Falls back to `HAIKU_RAG_DB` env var, then config default. |
|
||||
| `config` | `None` | `AppConfig` instance. Falls back to `get_config()`. |
|
||||
|
||||
## Use it
|
||||
|
||||
### From `client.analyze`
|
||||
|
||||
```python
|
||||
from haiku.rag.client import HaikuRAG
|
||||
|
||||
async with HaikuRAG("my.lancedb") as client:
|
||||
result = await client.analyze("How many documents mention 'security'?")
|
||||
print(result.answer)
|
||||
for citation in result.citations:
|
||||
print(citation.uri, citation.title)
|
||||
```
|
||||
|
||||
`client.analyze` runs the skill end-to-end and returns an `AnalysisResult` with `answer` and `citations`. The executed Python programs live on `AnalysisState.executions` during the run, not on the returned result.
|
||||
|
||||
### Combine with the RAG skill
|
||||
|
||||
```python
|
||||
from haiku.rag.skills.rag import create_skill as create_rag_skill
|
||||
from haiku.rag.skills.analysis import create_skill as create_analysis_skill
|
||||
from haiku.skills.agent import SkillToolset
|
||||
from haiku.skills.prompts import build_system_prompt
|
||||
from pydantic_ai import Agent
|
||||
|
||||
rag = create_rag_skill(db_path=db_path)
|
||||
analysis = create_analysis_skill(db_path=db_path)
|
||||
rag = create_rag_skill(db_path="my.lancedb")
|
||||
analysis = create_analysis_skill(db_path="my.lancedb")
|
||||
toolset = SkillToolset(skills=[rag, analysis])
|
||||
```
|
||||
|
||||
agent = Agent(
|
||||
"openai-chat:gpt-4o",
|
||||
instructions=build_system_prompt(toolset.skill_catalog),
|
||||
toolsets=[toolset],
|
||||
The agent routes Q&A to the rag skill and computational questions to rag-analysis.
|
||||
|
||||
## What the agent actually writes
|
||||
|
||||
You don't write these programs yourself — the agent does, inside `execute_code`. Seeing the shape helps when you tune prompts, debug a run via `AnalysisState.executions`, or design a custom skill.
|
||||
|
||||
**Aggregate across the corpus.** *"How many documents mention security vulnerabilities?"*
|
||||
|
||||
```python
|
||||
hits = await search("security vulnerability", limit=50)
|
||||
|
||||
doc_ids = {h['document_id'] for h in hits}
|
||||
print(f"{len(doc_ids)} documents mention security vulnerabilities")
|
||||
|
||||
# Cite the top hit per document
|
||||
seen = set()
|
||||
for hit in hits:
|
||||
if hit['document_id'] not in seen:
|
||||
seen.add(hit['document_id'])
|
||||
await cite(hit['chunk_id'])
|
||||
```
|
||||
|
||||
**Read one section in depth.** *"Summarize Section 5."*
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
import json
|
||||
|
||||
doc_id = "..." # from a prior search or list_documents
|
||||
toc = json.loads(Path(f'/documents/{doc_id}/toc.json').read_text())
|
||||
|
||||
section = next(n for n in toc['tree'] if n['title'].startswith('5'))
|
||||
start, end = section['item_range']
|
||||
lines = Path(f'/documents/{doc_id}/items.jsonl').read_text().splitlines()[start:end]
|
||||
|
||||
for line in lines:
|
||||
print(json.loads(line)['text'])
|
||||
|
||||
await cite(section['chunk_ids'])
|
||||
```
|
||||
|
||||
The section node already aggregates the chunks underneath it, so the agent cites the whole section without a separate search.
|
||||
|
||||
**Compare structure across documents.** *"Do both papers have an Experimental Results section?"*
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
import json
|
||||
|
||||
for doc_id in ["doc-a-id", "doc-b-id"]:
|
||||
toc = json.loads(Path(f'/documents/{doc_id}/toc.json').read_text())
|
||||
print(f"\n=== {toc['title']} ===")
|
||||
for node in toc['tree']:
|
||||
if 'experiment' in node['title'].lower():
|
||||
print(f" {node['title']} (pages {node['page_numbers']})")
|
||||
await cite(node['chunk_ids'])
|
||||
```
|
||||
|
||||
## Context filter
|
||||
|
||||
The `filter` parameter is enforced at the deps layer. The LLM can't bypass it: both the VFS and search results are scoped to the filter.
|
||||
|
||||
```python
|
||||
result = await client.analyze(
|
||||
"Summarize all findings",
|
||||
filter="uri LIKE '%confidential%'"
|
||||
)
|
||||
```
|
||||
|
||||
See the [Analysis Agent](../agents/analysis.md) documentation for details on how the underlying sandbox works.
|
||||
Useful for scoping to a corpus subset, enforcing access control, or restricting context.
|
||||
|
||||
## Configuration
|
||||
|
||||
```yaml
|
||||
analysis:
|
||||
model:
|
||||
provider: anthropic
|
||||
name: claude-sonnet-4-20250514
|
||||
code_timeout: 60.0 # Max seconds per code execution
|
||||
max_output_chars: 50000 # Truncate output after this many chars
|
||||
```
|
||||
|
||||
When `analysis.model` is unset, the skill falls back to `qa.model`.
|
||||
|
||||
See [Search and question answering](../configuration/qa.md#analysis-configuration) for the full set.
|
||||
|
|
|
|||
124
docs/skills/custom.md
Normal file
124
docs/skills/custom.md
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
# Custom Skills
|
||||
|
||||
The two skills haiku.rag ships work against any LanceDB database. When you want a *domain-specific* skill that bundles its own data, prompt, and tool surface — for example, a "recipes" skill that knows about cooking and ships with a recipes database — generate one with `haiku-rag create-skill`.
|
||||
|
||||
The generated package is a regular pip-installable Python package that registers as a `haiku.skills` entry point. Any haiku.skills-aware host (haiku.skills CLI, your own agent, the AG-UI adapter) discovers it automatically.
|
||||
|
||||
## When to use a custom skill
|
||||
|
||||
- The model should consult a specific knowledge base for a specific kind of question, alongside other skills.
|
||||
- You want a different instruction prompt than the generic `rag` skill (different tone, refusal style, domain rules).
|
||||
- You want to ship a knowledge base plus its prompt as one distributable unit.
|
||||
- You're running multiple skills against different databases in the same agent.
|
||||
|
||||
If you just want to point a haiku.rag database at your own model and prompt, configure `haiku.rag.yaml` and use the built-in `rag` skill — no custom package needed.
|
||||
|
||||
## Generate
|
||||
|
||||
```bash
|
||||
haiku-rag create-skill \
|
||||
--name recipes \
|
||||
--db /path/to/recipes.lancedb \
|
||||
--tools search,cite \
|
||||
--description "Recipe and cooking knowledge base" \
|
||||
--preamble "You are a culinary expert helping with recipes and cooking techniques."
|
||||
```
|
||||
|
||||
Then install and use:
|
||||
|
||||
```bash
|
||||
uv pip install -e ./recipes-skill
|
||||
|
||||
haiku-skills list --use-entrypoints
|
||||
# recipes — Recipe and cooking knowledge base
|
||||
|
||||
haiku-skills chat --use-entrypoints --skill recipes
|
||||
```
|
||||
|
||||
### Flags
|
||||
|
||||
| Flag | Description | Default |
|
||||
|------|-------------|---------|
|
||||
| `--name` | Skill name (lowercase alphanumeric and hyphens). Required. | — |
|
||||
| `--db` | Path to the LanceDB database to embed. Required. | — |
|
||||
| `--description` | One-line skill description. The agent reads this to decide when to invoke. | Standard RAG description |
|
||||
| `--tools` | Comma-separated tool subset, or `all`. | `all` |
|
||||
| `--preamble` | Custom preamble for the skill's instructions. | Standard RAG preamble |
|
||||
| `--config-file` | Path to a `haiku.rag.yaml` to embed alongside the database. | None |
|
||||
| `--output` / `-o` | Output directory. | Current directory |
|
||||
|
||||
### Available tools
|
||||
|
||||
`cite`, `execute_code`, `get_document`, `list_documents`, `search`.
|
||||
|
||||
Drop `execute_code` from `--tools` if the skill shouldn't run sandboxed Python — that gives you a search-and-cite-only skill with no analysis capabilities.
|
||||
|
||||
## Anatomy of a generated skill
|
||||
|
||||
```
|
||||
{name}-skill/
|
||||
├── pyproject.toml
|
||||
└── {name}_skill/
|
||||
├── __init__.py # create_skill() entry point
|
||||
├── SKILL.md # Skill metadata and instructions
|
||||
└── assets/
|
||||
├── {name}.lancedb/ # The embedded database
|
||||
└── haiku.rag.yaml # Optional config (only if --config-file passed)
|
||||
```
|
||||
|
||||
- **`SKILL.md`** carries the instruction prompt the agent will follow. The frontmatter includes the skill name and description; everything below is the prompt body. Edit this to change behavior.
|
||||
- **`__init__.py`** exposes `create_skill()` (the entry point) and `visualize_chunk()` for rendering visual grounding.
|
||||
- **`assets/{name}.lancedb/`** is the database, shipped inside the package.
|
||||
- **`assets/haiku.rag.yaml`** (optional) pins provider settings the skill needs.
|
||||
|
||||
The package can be installed locally with `uv pip install -e .` or published to PyPI.
|
||||
|
||||
## Generating visual grounding from a custom skill
|
||||
|
||||
Each generated skill exposes a `visualize_chunk()` function that returns the chunk's bounding boxes rendered onto its source page:
|
||||
|
||||
```python
|
||||
from recipes_skill import visualize_chunk
|
||||
|
||||
images = await visualize_chunk(chunk_id)
|
||||
# images is a list of PIL.Image objects, one per page the chunk covers
|
||||
images[0].save("citation.png")
|
||||
```
|
||||
|
||||
Pass chunk IDs from skill citations or search results. Same prerequisites as elsewhere in haiku.rag: documents need stored page images, and the chunk must come from a PDF or other docling-converted source.
|
||||
|
||||
## Multi-skill agents
|
||||
|
||||
Each generated skill is self-contained with its own database and instructions. Compose multiple skills in one agent and the model routes between them via their descriptions:
|
||||
|
||||
```python
|
||||
from recipes_skill import create_skill as create_recipes_skill
|
||||
from medic_skill import create_skill as create_medic_skill
|
||||
from haiku.skills.agent import SkillToolset
|
||||
from haiku.skills.prompts import build_system_prompt
|
||||
from pydantic_ai import Agent
|
||||
|
||||
recipes = create_recipes_skill()
|
||||
medic = create_medic_skill()
|
||||
toolset = SkillToolset(skills=[recipes, medic])
|
||||
|
||||
agent = Agent(
|
||||
"openai-chat:gpt-4o",
|
||||
instructions=build_system_prompt(toolset.skill_catalog),
|
||||
toolsets=[toolset],
|
||||
)
|
||||
|
||||
await agent.run("What's the optimal temperature for braising short ribs?")
|
||||
# Routes to recipes
|
||||
|
||||
await agent.run("What's the field treatment for tension pneumothorax?")
|
||||
# Routes to medic
|
||||
```
|
||||
|
||||
Each skill maintains state under its own namespace (`recipes`, `medic`, …), so citations and searches don't collide.
|
||||
|
||||
## Writing a skill from scratch
|
||||
|
||||
`create-skill` is the convenience path. If you need full control over the tools, state model, or instruction loading, write the skill against [haiku.skills](https://github.com/ggozad/haiku.skills) directly. The generated package in `{name}_skill/__init__.py` is a good reference — it composes haiku.rag's `_tools` factory with a `haiku.skills.Skill` and registers under the `haiku.skills` entry point group in `pyproject.toml`.
|
||||
|
||||
See the haiku.skills repository for the full Skill contract.
|
||||
|
|
@ -1,15 +1,19 @@
|
|||
# Skills
|
||||
|
||||
haiku.rag exposes its RAG capabilities as [haiku.skills](https://github.com/ggozad/haiku.skills) skills. Skills are self-contained units that bundle tools, instructions, and state — they can be composed into any pydantic-ai agent via `SkillToolset`.
|
||||
Skills put haiku.rag in front of a model. A skill bundles tools, an instruction prompt, and managed state into a unit that drops into any Pydantic AI agent via `SkillToolset`. haiku.rag ships two skills and supports custom skills.
|
||||
|
||||
## Available Skills
|
||||
Built on [haiku.skills](https://github.com/ggozad/haiku.skills).
|
||||
|
||||
| Skill | Description |
|
||||
|-------|-------------|
|
||||
| [`rag`](rag.md) | Search, retrieve, and answer questions from the knowledge base |
|
||||
| [`rag-analysis`](analysis.md) | Computational analysis via code execution |
|
||||
## Available skills
|
||||
|
||||
## Usage
|
||||
| Skill | What it does | Reach for it when |
|
||||
|-------|--------------|-------------------|
|
||||
| [`rag`](rag.md) | Search, retrieve, and cite content from a knowledge base. | The model needs to find and quote evidence from documents. |
|
||||
| [`rag-analysis`](analysis.md) | Same as `rag`, plus a sandboxed Python interpreter mounting every document as a virtual filesystem. | The question requires computation, aggregation, structural traversal, or section-scoped reading. |
|
||||
|
||||
To ship your own skill (bundled with its own database), see [Custom skills](custom.md).
|
||||
|
||||
## Your first agent
|
||||
|
||||
```python
|
||||
from haiku.rag.skills.rag import create_skill
|
||||
|
|
@ -17,8 +21,8 @@ from haiku.skills.agent import SkillToolset
|
|||
from haiku.skills.prompts import build_system_prompt
|
||||
from pydantic_ai import Agent
|
||||
|
||||
skill = create_skill(db_path=db_path, config=config)
|
||||
toolset = SkillToolset(skills=[skill])
|
||||
rag = create_skill(db_path="my.lancedb")
|
||||
toolset = SkillToolset(skills=[rag])
|
||||
|
||||
agent = Agent(
|
||||
"openai-chat:gpt-4o",
|
||||
|
|
@ -26,21 +30,37 @@ agent = Agent(
|
|||
toolsets=[toolset],
|
||||
)
|
||||
|
||||
result = await agent.run("What documents do we have?")
|
||||
result = await agent.run("What does the knowledge base say about X?")
|
||||
print(result.output)
|
||||
```
|
||||
|
||||
## State Management
|
||||
The skill searches, cites, and answers. You supply the model and the question.
|
||||
|
||||
Each skill manages its own state under a dedicated namespace. State is automatically synced via the AG-UI protocol when using `AGUIAdapter`.
|
||||
To run analysis against the same database, swap in the `rag-analysis` skill or attach both:
|
||||
|
||||
```python
|
||||
from haiku.rag.skills.rag import create_skill as create_rag_skill
|
||||
from haiku.rag.skills.analysis import create_skill as create_analysis_skill
|
||||
|
||||
rag = create_rag_skill(db_path="my.lancedb")
|
||||
analysis = create_analysis_skill(db_path="my.lancedb")
|
||||
toolset = SkillToolset(skills=[rag, analysis])
|
||||
```
|
||||
|
||||
The agent reads each skill's description and routes questions itself. See the individual skill pages for the tool surface, state model, and worked examples.
|
||||
|
||||
## State
|
||||
|
||||
Each skill manages its own state under a dedicated namespace. State is synced via the AG-UI protocol when using `AGUIAdapter`.
|
||||
|
||||
```python
|
||||
rag_state = toolset.get_namespace("rag")
|
||||
analysis_state = toolset.get_namespace("analysis")
|
||||
```
|
||||
|
||||
See the individual skill pages for state model details.
|
||||
Both state models track citations, the current document filter, and per-turn searches. Analysis state also carries the sandbox execution log. See [RAG skill: state](rag.md#state) and [Analysis skill: state](analysis.md#state).
|
||||
|
||||
## Database Path Resolution
|
||||
## Database path resolution
|
||||
|
||||
Both skills resolve the database path in the same order:
|
||||
|
||||
|
|
@ -48,9 +68,9 @@ Both skills resolve the database path in the same order:
|
|||
2. `HAIKU_RAG_DB` environment variable
|
||||
3. Config default (`config.storage.data_dir / "haiku.rag.lancedb"`)
|
||||
|
||||
## AG-UI Streaming
|
||||
## AG-UI streaming for web apps
|
||||
|
||||
For web applications, use pydantic-ai's `AGUIAdapter` to stream tool calls, text, and state deltas:
|
||||
For browser apps, use pydantic-ai's `AGUIAdapter` to stream tool calls, text, and state deltas:
|
||||
|
||||
```python
|
||||
from pydantic_ai.ui.ag_ui import AGUIAdapter
|
||||
|
|
@ -60,11 +80,21 @@ event_stream = adapter.run_stream()
|
|||
sse_event_stream = adapter.encode_stream(event_stream)
|
||||
```
|
||||
|
||||
See the [Web application](../apps.md) reference implementation for an end-to-end example.
|
||||
See the [Web application](../apps.md) reference implementation.
|
||||
|
||||
## Exposing via MCP
|
||||
|
||||
To use a skill from Claude Desktop or another MCP-aware client, run the MCP server:
|
||||
|
||||
```bash
|
||||
haiku-rag serve --mcp --stdio
|
||||
```
|
||||
|
||||
The server exposes the skill tools (search, ask, analyze) over MCP. See [MCP](../mcp.md).
|
||||
|
||||
## Discovery
|
||||
|
||||
Skills are registered as Python entrypoints under `haiku.skills`. They are discovered automatically by `haiku.skills`:
|
||||
Skills are registered as Python entry points under `haiku.skills`. They are discovered automatically:
|
||||
|
||||
```bash
|
||||
haiku-skills list --use-entrypoints
|
||||
|
|
@ -72,37 +102,4 @@ haiku-skills list --use-entrypoints
|
|||
# rag-analysis — Analyze documents using code execution in a sandboxed interpreter.
|
||||
```
|
||||
|
||||
## Generating Custom Skills
|
||||
|
||||
Use `create-skill` to generate a standalone skill package with an embedded database:
|
||||
|
||||
```bash
|
||||
haiku-rag create-skill \
|
||||
--name recipes \
|
||||
--db /path/to/recipes.lancedb \
|
||||
--tools search,cite \
|
||||
--description "Recipe knowledge base" \
|
||||
--preamble "You are a recipe expert."
|
||||
```
|
||||
|
||||
This generates a pip-installable package (`recipes-skill/`) that bundles the database and registers as a `haiku.skills` entry point. After installing (`uv pip install -e ./recipes-skill`), the skill is automatically discovered:
|
||||
|
||||
```bash
|
||||
haiku-skills list --use-entrypoints
|
||||
# recipes — Recipe knowledge base
|
||||
|
||||
haiku-skills chat --use-entrypoints --skill recipes
|
||||
```
|
||||
|
||||
Since each generated skill is self-contained with its own database and instructions, you can generate multiple skills for different domains and run them together. The agent sees each skill's description and routes questions to the appropriate knowledge base automatically.
|
||||
|
||||
Generated skills also expose `visualize_chunk()` for rendering visual grounding. Use chunk IDs from citations or search results in state:
|
||||
|
||||
```python
|
||||
from my_skill import visualize_chunk
|
||||
|
||||
images = await visualize_chunk(chunk_id)
|
||||
# Returns list of PIL Images with highlighted bounding boxes
|
||||
```
|
||||
|
||||
See [CLI: Create Skill](../cli.md#create-skill) for all options.
|
||||
This is what makes custom skills installable as plain pip packages. See [Custom skills](custom.md).
|
||||
|
|
|
|||
|
|
@ -1,28 +1,23 @@
|
|||
# RAG Skill
|
||||
|
||||
The RAG skill is the primary way to use haiku.rag tools. It bundles search, document browsing, and citation management into a single skill with managed state.
|
||||
The `rag` skill answers questions over a knowledge base with hybrid search, structure-aware context expansion, and explicit citations. `client.ask`, `haiku-rag ask`, the MCP `ask_question` tool, and the chat TUI all run through this skill.
|
||||
|
||||
## `create_skill(db_path?, config?)`
|
||||
## When to use it
|
||||
|
||||
```python
|
||||
from haiku.rag.skills.rag import create_skill
|
||||
- The model needs to find and quote evidence from a document corpus.
|
||||
- You want citations under every answer.
|
||||
- You're building a Q&A agent, a documentation chatbot, or any RAG-style integration.
|
||||
|
||||
skill = create_skill(db_path=db_path, config=config)
|
||||
```
|
||||
|
||||
| Parameter | Default | Description |
|
||||
|-----------|---------|-------------|
|
||||
| `db_path` | `None` | Path to LanceDB database. Falls back to `HAIKU_RAG_DB` env var, then config default. |
|
||||
| `config` | `None` | `AppConfig` instance. If None, uses `get_config()`. |
|
||||
If the question requires *computation* over the corpus (counts, aggregates, comparisons, section-scoped reading), reach for the [Analysis skill](analysis.md) instead — or attach both.
|
||||
|
||||
## Tools
|
||||
|
||||
| Tool | Purpose |
|
||||
|------|---------|
|
||||
| `search(query, limit?)` | Hybrid search (vector + full-text) with context expansion |
|
||||
| `list_documents()` | List all documents in the knowledge base |
|
||||
| `get_document(query)` | Retrieve a document by ID, title, or URI |
|
||||
| `cite(chunk_ids)` | Register chunk IDs as citations for the current answer |
|
||||
| `search(query, limit?)` | Hybrid search (vector + full-text) with section-aware context expansion. Returns `chunk_id`, content, `doc_item_refs`, `picture_refs`, `picture_captions`, source metadata. |
|
||||
| `list_documents()` | List all documents in the knowledge base. |
|
||||
| `get_document(query)` | Fetch a document by ID, title, or URI. Partial matches work. |
|
||||
| `cite(chunk_ids)` | Register chunk IDs as citations for the current answer. The agent calls this before writing the final response. |
|
||||
|
||||
## State
|
||||
|
||||
|
|
@ -36,7 +31,149 @@ class RAGState(BaseModel):
|
|||
searches: dict[str, list[SearchResult]] = {}
|
||||
```
|
||||
|
||||
- **citation_index** — All citations indexed by chunk ID. Accumulates across invocations so historical turns' chunk IDs remain resolvable in the UI scrollback.
|
||||
- **citations** — Chunk IDs registered via the `cite` tool during the current invocation. Deduplicated; cleared at the start of each invocation.
|
||||
- **document_filter** — SQL WHERE clause applied to `search` and `list_documents` calls. Persists across invocations as session-level configuration.
|
||||
- **citation_index** — All citations indexed by chunk ID. Accumulates across invocations so historical chunk IDs stay resolvable in UI scrollback.
|
||||
- **citations** — Chunk IDs registered via `cite` during the current invocation. Deduplicated; cleared at the start of each invocation.
|
||||
- **document_filter** — SQL WHERE clause applied to `search` and `list_documents`. Persists across invocations.
|
||||
- **searches** — Search results keyed by query string. Cleared at the start of each invocation.
|
||||
|
||||
## `create_skill(db_path?, config?)`
|
||||
|
||||
```python
|
||||
from haiku.rag.skills.rag import create_skill
|
||||
|
||||
skill = create_skill(db_path="my.lancedb")
|
||||
```
|
||||
|
||||
| Parameter | Default | Description |
|
||||
|-----------|---------|-------------|
|
||||
| `db_path` | `None` | Path to LanceDB database. Falls back to `HAIKU_RAG_DB` env var, then config default. |
|
||||
| `config` | `None` | `AppConfig` instance. Falls back to `get_config()`. |
|
||||
|
||||
## Examples
|
||||
|
||||
### Minimal QA agent
|
||||
|
||||
```python
|
||||
from haiku.rag.skills.rag import create_skill
|
||||
from haiku.skills.agent import SkillToolset
|
||||
from haiku.skills.prompts import build_system_prompt
|
||||
from pydantic_ai import Agent
|
||||
|
||||
rag = create_skill(db_path="my.lancedb")
|
||||
toolset = SkillToolset(skills=[rag])
|
||||
|
||||
agent = Agent(
|
||||
"openai-chat:gpt-4o",
|
||||
instructions=build_system_prompt(toolset.skill_catalog),
|
||||
toolsets=[toolset],
|
||||
)
|
||||
|
||||
result = await agent.run("What does the manual say about safety procedures?")
|
||||
print(result.output)
|
||||
|
||||
# Inspect what the model cited
|
||||
state = toolset.get_namespace("rag")
|
||||
for chunk_id in state.citations:
|
||||
citation = state.citation_index[chunk_id]
|
||||
print(f"- {citation.document_title}: {citation.content[:100]}…")
|
||||
```
|
||||
|
||||
### Domain customization
|
||||
|
||||
Set a domain preamble in `haiku.rag.yaml` and the skill picks it up:
|
||||
|
||||
```yaml
|
||||
prompts:
|
||||
domain_preamble: |
|
||||
The knowledge base contains the operations manual for the Helios solar array.
|
||||
"The array" or unqualified specs refer to Helios. Terminology like "string"
|
||||
refers to a series-connected panel chain, not text.
|
||||
```
|
||||
|
||||
To scope a session to a subset of documents, set the filter on the namespace state:
|
||||
|
||||
```python
|
||||
state = toolset.get_namespace("rag")
|
||||
state.document_filter = "uri LIKE '%helios/v4/%'"
|
||||
|
||||
result = await agent.run("What's the maintenance interval for the inverters?")
|
||||
```
|
||||
|
||||
The filter applies to every `search` and `list_documents` call for the rest of the session, including the model can't bypass it from inside.
|
||||
|
||||
### Combining with the analysis skill
|
||||
|
||||
Attach both skills and the agent routes between them:
|
||||
|
||||
```python
|
||||
from haiku.rag.skills.rag import create_skill as create_rag_skill
|
||||
from haiku.rag.skills.analysis import create_skill as create_analysis_skill
|
||||
|
||||
rag = create_rag_skill(db_path="my.lancedb")
|
||||
analysis = create_analysis_skill(db_path="my.lancedb")
|
||||
toolset = SkillToolset(skills=[rag, analysis])
|
||||
|
||||
agent = Agent(
|
||||
"openai-chat:gpt-4o",
|
||||
instructions=build_system_prompt(toolset.skill_catalog),
|
||||
toolsets=[toolset],
|
||||
)
|
||||
|
||||
# Q&A → uses rag
|
||||
await agent.run("What safety equipment is required on-site?")
|
||||
|
||||
# Computational question → uses rag-analysis
|
||||
await agent.run("How many checklists mention torque specifications?")
|
||||
```
|
||||
|
||||
### Streaming to a web frontend
|
||||
|
||||
Wrap the agent with `AGUIAdapter` to stream tool calls, text deltas, and state changes to a CopilotKit-style frontend:
|
||||
|
||||
```python
|
||||
from pydantic_ai.ui.ag_ui import AGUIAdapter
|
||||
|
||||
adapter = AGUIAdapter(agent=agent, run_input=run_input)
|
||||
sse_stream = adapter.encode_stream(adapter.run_stream())
|
||||
```
|
||||
|
||||
See the [Web application](../apps.md) reference implementation for the full Starlette + Next.js setup.
|
||||
|
||||
### Exposing via MCP
|
||||
|
||||
To call the skill from Claude Desktop (or any MCP client), run the MCP server:
|
||||
|
||||
```bash
|
||||
haiku-rag serve --mcp --stdio
|
||||
```
|
||||
|
||||
The exposed `ask_question` tool runs this skill. See [MCP](../mcp.md) for the configuration block.
|
||||
|
||||
## Configuration
|
||||
|
||||
The skill picks up its model and search behavior from the standard config sections:
|
||||
|
||||
```yaml
|
||||
qa:
|
||||
model:
|
||||
provider: ollama
|
||||
name: gpt-oss
|
||||
enable_thinking: true
|
||||
temperature: 0.3
|
||||
vision: false # set true for vision-capable QA models
|
||||
max_searches: 3
|
||||
|
||||
search:
|
||||
limit: 5
|
||||
max_context_chars: 10000
|
||||
```
|
||||
|
||||
See [Search and question answering](../configuration/qa.md) for every knob.
|
||||
|
||||
## Vision support
|
||||
|
||||
When `qa.model.vision: true` is set, the skill's `search` tool attaches picture bytes to its tool returns as `BinaryContent`. The model can then read figures, diagrams, and screenshots directly alongside the surrounding text. Requires `processing.pictures != none` so the bytes exist on disk. See the [pictures × embedder × QA model matrix](../configuration/processing.md#picture-handling) for the combinations that make sense.
|
||||
|
||||
## Customizing the skill prompt
|
||||
|
||||
The skill's instruction prompt lives in `SKILL.md` inside the package. For behavior changes (different phrasing, refusal style, additional rules), the supported path is to fork the skill with `haiku-rag create-skill` and edit the generated `SKILL.md`. The `domain_preamble` field above is for *what the corpus is about*, not for *how the agent should behave*. See [Custom skills](custom.md).
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
# Toolsets
|
||||
|
||||
haiku.rag exposes its RAG capabilities as [haiku.skills](https://github.com/ggozad/haiku.skills) skills. See the [Skills](skills/index.md) section for the primary way to use haiku.rag tools.
|
||||
haiku.rag exposes its RAG capabilities as [haiku.skills](https://github.com/ggozad/haiku.skills) skills. For most integrations, see [Skills](skills/index.md).
|
||||
|
||||
For lower-level access, `haiku.rag.tools` provides individual `FunctionToolset` factories used internally by agents.
|
||||
For lower-level access, `haiku.rag.tools` provides individual `FunctionToolset` factories that the skills themselves compose.
|
||||
|
||||
## Low-Level Toolsets
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ Model and temperature selection affect answer quality directly — see [Provider
|
|||
|
||||
## Inspector
|
||||
|
||||
The inspector is the fastest way to see what your model would actually receive for a given query. Run it against your database and step through the same hybrid search, context expansion, and chunk previews the rag skill uses at runtime. Press `c` on a chunk and you see the exact context the LLM would get back from a search hit.
|
||||
The inspector shows what your model would actually receive for a given query. Run it against your database and step through the same hybrid search, context expansion, and chunk previews the rag skill uses at runtime. Press `c` on a chunk and you see the exact context the LLM would get back from a search hit.
|
||||
|
||||
```bash
|
||||
haiku-rag inspect
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Quickstart
|
||||
|
||||
Goal: install haiku.rag, index a document, and chat with it. Five minutes if you already have Ollama.
|
||||
Install haiku.rag, index a document, and chat with it.
|
||||
|
||||
## Install
|
||||
|
||||
|
|
|
|||
11
mkdocs.yml
11
mkdocs.yml
|
|
@ -62,6 +62,11 @@ nav:
|
|||
- Use it:
|
||||
- CLI: cli.md
|
||||
- Chat: chat.md
|
||||
- Skills:
|
||||
- skills/index.md
|
||||
- RAG skill: skills/rag.md
|
||||
- Analysis skill: skills/analysis.md
|
||||
- Custom skills: skills/custom.md
|
||||
- Configure:
|
||||
- configuration/index.md
|
||||
- Providers: configuration/providers.md
|
||||
|
|
@ -74,13 +79,9 @@ nav:
|
|||
- Server: server.md
|
||||
- MCP: mcp.md
|
||||
- Remote processing: remote-processing.md
|
||||
- Build with it:
|
||||
- Develop:
|
||||
- Python: python.md
|
||||
- Custom pipelines: custom-pipelines.md
|
||||
- Skills:
|
||||
- skills/index.md
|
||||
- RAG: skills/rag.md
|
||||
- Analysis: skills/analysis.md
|
||||
- Toolsets: tools.md
|
||||
- Web app: apps.md
|
||||
- Reference:
|
||||
|
|
|
|||
Loading…
Reference in a new issue