diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index f82b6e9f..f5a51ba8 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -76,22 +76,3 @@ jobs:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage.xml
fail_ci_if_error: false
-
- test-docker-sandbox:
- needs: [lint]
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- - uses: astral-sh/setup-uv@v4
- with:
- enable-cache: true
- - name: Set up Python
- uses: actions/setup-python@v5
- with:
- python-version-file: "pyproject.toml"
- - name: Install dependencies
- run: uv sync --all-extras
- - name: Build Docker image
- run: docker build -t haiku-rag-slim:test -f docker/Dockerfile.slim .
- - name: Run Docker integration tests
- run: uv run pytest tests/agents/rlm/test_sandbox.py -v
diff --git a/docs/agents/rlm.md b/docs/agents/rlm.md
index f4aff8e0..fbf267ad 100644
--- a/docs/agents/rlm.md
+++ b/docs/agents/rlm.md
@@ -5,13 +5,13 @@ The RLM agent enables complex analytical tasks by writing and executing Python c
- **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 tables from the document and summarize them"
+- **Structured data extraction**: "Extract all dollar amounts and compute totals"
## How It Works
1. The agent receives a question
2. It writes Python code to explore the knowledge base
-3. Code executes in a sandboxed environment with access to haiku.rag functions
+3. Code executes in a sandboxed Python interpreter with access to haiku.rag functions
4. The agent iterates: run code, examine results, refine approach
5. Final answer is synthesized from the gathered data
@@ -93,22 +93,19 @@ if content:
Returns the document content as a string, or `None` if not found.
-### get_docling_document(id_or_title)
+### get_chunk(chunk_id)
-Get the structured DoclingDocument object for advanced analysis of tables, figures, and document structure.
+Get a specific chunk by its ID (from search results). Use this to retrieve full chunk details and metadata for citations.
```python
-doc = get_docling_document("Technical Manual")
-if doc:
- print(f"Tables: {len(doc.tables)}")
- print(f"Pictures: {len(doc.pictures)}")
-
- # Extract table data
- for table in doc.tables:
- for cell in table.data.table_cells:
- print(f"Row {cell.start_row_offset_idx}, Col {cell.start_col_offset_idx}: {cell.text}")
+results = search("safety requirements", limit=5)
+for r in results:
+ chunk = get_chunk(r['chunk_id'])
+ print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}")
```
+Returns dict with keys: `chunk_id`, `content`, `document_id`, `document_title`, `headings`, `page_numbers`, `labels`
+
### llm(prompt)
Call an LLM directly for classification, summarization, or extraction tasks.
@@ -133,36 +130,39 @@ for doc in documents:
Each document dict has keys: `id`, `title`, `uri`, `content`
-## Imports
+## Python Features
-The sandbox runs in a Docker container with full Python available. Any module installed in the container image can be imported:
+The sandbox uses [pydantic-monty](https://github.com/pydantic/monty), a minimal secure Python interpreter written in Rust. It supports a subset of Python:
+
+**Supported:** variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, try/except, and the `json` module.
+
+**Not supported:** imports (other than `json`), class definitions, generators/yield, match statements, decorators, `with` statements.
+
+For pattern matching or text extraction, use string methods (`str.split`, `str.find`, `str.startswith`, `in` operator) or the `llm()` function:
```python
-import re
-import json
-from collections import Counter
-
-# Extract and count patterns
-results = search("error", limit=50)
-error_types = []
+# Extract data with llm() instead of regex
+numbers = []
+results = search("financial data", limit=20)
for r in results:
- matches = re.findall(r'Error: (\w+)', r['content'])
- error_types.extend(matches)
-
-print(Counter(error_types).most_common(10))
+ extracted = llm(f"Extract all dollar amounts as a comma-separated list of numbers (no $ signs): {r['content']}")
+ for part in extracted.split(','):
+ part = part.strip().replace(',', '')
+ if part.isdigit():
+ numbers.append(int(part))
+if numbers:
+ print(f"Average: {sum(numbers) / len(numbers)}")
```
-The default image (`ghcr.io/ggozad/haiku.rag-slim`) includes the Python standard library. Custom images can add additional packages like `pandas` or `numpy`.
+## Sandboxed Execution
-## Docker Sandbox
+Code executes in an isolated interpreter with:
-Code executes in an isolated Docker container with:
-
-- **Read-only database**: The LanceDB database is mounted read-only
-- **Memory limits**: Configurable memory limit (default 512MB)
+- **No filesystem access**: Code cannot read or write files
+- **No network access**: Code cannot make HTTP requests or open sockets
+- **No imports**: Only the `json` module is available
- **Execution timeout**: Code times out after configurable limit (default 60s)
- **Output truncation**: Large outputs are truncated to prevent memory issues
-- **Container reuse**: Within a single `rlm()` call, the container stays warm for multiple code executions
## Context Filter
@@ -193,26 +193,4 @@ rlm:
name: claude-sonnet-4-20250514
code_timeout: 60.0 # Max seconds for code execution
max_output_chars: 50000 # Truncate output after this many chars
- docker_image: "ghcr.io/ggozad/haiku.rag-slim:latest" # Container image
- docker_memory_limit: "512m" # Container memory limit
-```
-
-### Custom Docker Image
-
-To add additional Python packages, create a custom Dockerfile:
-
-```dockerfile
-FROM ghcr.io/ggozad/haiku.rag-slim:latest
-RUN pip install pandas numpy
-```
-
-Build and configure:
-
-```bash
-docker build -t my-rlm-image .
-```
-
-```yaml
-rlm:
- docker_image: "my-rlm-image"
```
diff --git a/haiku_rag_slim/haiku/rag/agents/rlm/prompts.py b/haiku_rag_slim/haiku/rag/agents/rlm/prompts.py
index 10991517..bc137b48 100644
--- a/haiku_rag_slim/haiku/rag/agents/rlm/prompts.py
+++ b/haiku_rag_slim/haiku/rag/agents/rlm/prompts.py
@@ -6,7 +6,7 @@ CRITICAL: Inside execute_code, these functions are ALREADY available in the name
- search("query") ✓ CORRECT
- from haiku.rag import search ✗ WRONG - will fail
-You have access to a sandboxed Python environment with these haiku.rag functions (use them directly, no imports needed):
+You have access to a sandboxed Python interpreter with these haiku.rag functions (use them directly, no imports needed):
## Available Functions
@@ -22,10 +22,10 @@ Returns list of dicts with keys: id, title, uri, created_at
Get the full text content of a document by ID, title, or URI.
Returns the document content as a string, or None if not found.
-### get_docling_document(id_or_title) -> DoclingDocument | None
-Get the structured DoclingDocument object for advanced analysis.
-Returns a DoclingDocument object, or None if not found.
-See "DoclingDocument API" section below for how to use it.
+### get_chunk(chunk_id) -> dict | None
+Get a specific chunk by its ID (from search results).
+Returns dict with keys: chunk_id, content, document_id, document_title, headings, page_numbers, labels
+Use this to retrieve full chunk details and metadata for citation.
### llm(prompt) -> str
Call an LLM directly with the given prompt. Returns the response as a string.
@@ -42,8 +42,13 @@ for doc in documents:
```
Check if it exists with: `if 'documents' in dir(): ...`
-## Standard Library Modules
-You can import any Python standard library module.
+## Available Python Features
+
+The interpreter supports: variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, try/except, and the `json` module.
+
+Not supported: imports (other than `json`), class definitions, generators/yield, match statements, decorators, `with` statements.
+
+For pattern matching or text extraction, use string methods (`str.split`, `str.find`, `str.startswith`, `in` operator) or the `llm()` function.
## Strategy Guide
@@ -51,51 +56,9 @@ You can import any Python standard library module.
2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
-5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and use collections.
+5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and data structures.
6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
-7. **Cite Your Sources**: Track which documents/chunks informed your answer for citation.
-
-## DoclingDocument API
-
-When you call `get_docling_document(id_or_title)`, you get a DoclingDocument object for structured document analysis.
-
-### Properties
-- `doc.texts` - List of all text items (paragraphs, headings, etc.)
-- `doc.tables` - List of all tables
-- `doc.pictures` - List of all pictures/figures
-- `doc.name` - Document name
-
-### Methods
-- `doc.iterate_items(with_groups=False)` - Iterate all items with hierarchy level
- Returns tuples of (item, level) where level is nesting depth
-- `doc.export_to_markdown()` - Export entire document as markdown string
-
-### Text Item Properties
-- `item.text` - The text content
-- `item.label` - Type: title, paragraph, section_header, list_item, etc. (lowercase enum values)
-- `item.prov` - Provenance (page numbers, bounding boxes)
-
-### Table Access
-- `table.data.num_rows`, `table.data.num_cols` - Dimensions
-- `table.data.table_cells` - List of TableCell objects
-- `cell.text`, `cell.start_row_offset_idx`, `cell.start_col_offset_idx`
-
-### Example Usage
-```python
-doc = get_docling_document("My Document")
-
-# Get all headings
-headings = [t.text for t in doc.texts if "header" in str(t.label)]
-
-# Iterate with structure
-for item, level in doc.iterate_items():
- print(" " * level + item.text[:50])
-
-# Extract table data
-for table in doc.tables:
- for cell in table.data.table_cells:
- print(f"Row {cell.start_row_offset_idx}, Col {cell.start_col_offset_idx}: {cell.text}")
-```
+7. **Cite Your Sources**: Use get_chunk() to retrieve chunk metadata for citations. Track which documents/chunks informed your answer.
## Example Patterns
@@ -111,23 +74,31 @@ for doc in docs:
print(f"Total: {count}")
```
-### Aggregating data across documents
+### Extracting data with llm()
```python
-import re
numbers = []
results = search("financial data", limit=20)
for r in results:
- matches = re.findall(r'\\$([\\d,]+)', r['content'])
- for m in matches:
- numbers.append(int(m.replace(',', '')))
-print(f"Average: ${sum(numbers)/len(numbers):,.2f}")
+ extracted = llm(f"Extract all dollar amounts from this text as a comma-separated list of numbers (no $ signs): {r['content']}")
+ for part in extracted.split(','):
+ part = part.strip().replace(',', '')
+ if part.isdigit():
+ numbers.append(int(part))
+if numbers:
+ print(f"Average: {sum(numbers) / len(numbers)}")
+```
+
+### Using search results with get_chunk for citations
+```python
+results = search("safety requirements", limit=5)
+for r in results:
+ chunk = get_chunk(r['chunk_id'])
+ print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}")
```
### Using llm() for classification
```python
-# Get document content
content = get_document("Q1 Report")
-# Use llm() to classify sentiment
sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
print(sentiment)
```
diff --git a/tests/cassettes/test_rlm/TestClientRLMIntegration.test_rlm_aggregation.yaml b/tests/cassettes/test_rlm/TestClientRLMIntegration.test_rlm_aggregation.yaml
index faf2662d..48e578d9 100644
--- a/tests/cassettes/test_rlm/TestClientRLMIntegration.test_rlm_aggregation.yaml
+++ b/tests/cassettes/test_rlm/TestClientRLMIntegration.test_rlm_aggregation.yaml
@@ -128,7 +128,7 @@ interactions:
connection:
- keep-alive
content-length:
- - '7735'
+ - '7099'
content-type:
- application/json
host:
@@ -145,7 +145,7 @@ interactions:
- search("query") ✓ CORRECT
- from haiku.rag import search ✗ WRONG - will fail
- You have access to a sandboxed Python environment with these haiku.rag functions (use them directly, no imports needed):
+ You have access to a sandboxed Python interpreter with these haiku.rag functions (use them directly, no imports needed):
## Available Functions
@@ -161,10 +161,10 @@ interactions:
Get the full text content of a document by ID, title, or URI.
Returns the document content as a string, or None if not found.
- ### get_docling_document(id_or_title) -> DoclingDocument | None
- Get the structured DoclingDocument object for advanced analysis.
- Returns a DoclingDocument object, or None if not found.
- See "DoclingDocument API" section below for how to use it.
+ ### get_chunk(chunk_id) -> dict | None
+ Get a specific chunk by its ID (from search results).
+ Returns dict with keys: chunk_id, content, document_id, document_title, headings, page_numbers, labels
+ Use this to retrieve full chunk details and metadata for citation.
### llm(prompt) -> str
Call an LLM directly with the given prompt. Returns the response as a string.
@@ -181,8 +181,13 @@ interactions:
```
Check if it exists with: `if 'documents' in dir(): ...`
- ## Standard Library Modules
- You can import any Python standard library module.
+ ## Available Python Features
+
+ The interpreter supports: variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, try/except, and the `json` module.
+
+ Not supported: imports (other than `json`), class definitions, generators/yield, match statements, decorators, `with` statements.
+
+ For pattern matching or text extraction, use string methods (`str.split`, `str.find`, `str.startswith`, `in` operator) or the `llm()` function.
## Strategy Guide
@@ -190,51 +195,9 @@ interactions:
2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
- 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and use collections.
+ 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and data structures.
6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
- 7. **Cite Your Sources**: Track which documents/chunks informed your answer for citation.
-
- ## DoclingDocument API
-
- When you call `get_docling_document(id_or_title)`, you get a DoclingDocument object for structured document analysis.
-
- ### Properties
- - `doc.texts` - List of all text items (paragraphs, headings, etc.)
- - `doc.tables` - List of all tables
- - `doc.pictures` - List of all pictures/figures
- - `doc.name` - Document name
-
- ### Methods
- - `doc.iterate_items(with_groups=False)` - Iterate all items with hierarchy level
- Returns tuples of (item, level) where level is nesting depth
- - `doc.export_to_markdown()` - Export entire document as markdown string
-
- ### Text Item Properties
- - `item.text` - The text content
- - `item.label` - Type: title, paragraph, section_header, list_item, etc. (lowercase enum values)
- - `item.prov` - Provenance (page numbers, bounding boxes)
-
- ### Table Access
- - `table.data.num_rows`, `table.data.num_cols` - Dimensions
- - `table.data.table_cells` - List of TableCell objects
- - `cell.text`, `cell.start_row_offset_idx`, `cell.start_col_offset_idx`
-
- ### Example Usage
- ```python
- doc = get_docling_document("My Document")
-
- # Get all headings
- headings = [t.text for t in doc.texts if "header" in str(t.label)]
-
- # Iterate with structure
- for item, level in doc.iterate_items():
- print(" " * level + item.text[:50])
-
- # Extract table data
- for table in doc.tables:
- for cell in table.data.table_cells:
- print(f"Row {cell.start_row_offset_idx}, Col {cell.start_col_offset_idx}: {cell.text}")
- ```
+ 7. **Cite Your Sources**: Use get_chunk() to retrieve chunk metadata for citations. Track which documents/chunks informed your answer.
## Example Patterns
@@ -250,23 +213,31 @@ interactions:
print(f"Total: {count}")
```
- ### Aggregating data across documents
+ ### Extracting data with llm()
```python
- import re
numbers = []
results = search("financial data", limit=20)
for r in results:
- matches = re.findall(r'\$([\d,]+)', r['content'])
- for m in matches:
- numbers.append(int(m.replace(',', '')))
- print(f"Average: ${sum(numbers)/len(numbers):,.2f}")
+ extracted = llm(f"Extract all dollar amounts from this text as a comma-separated list of numbers (no $ signs): {r['content']}")
+ for part in extracted.split(','):
+ part = part.strip().replace(',', '')
+ if part.isdigit():
+ numbers.append(int(part))
+ if numbers:
+ print(f"Average: {sum(numbers) / len(numbers)}")
+ ```
+
+ ### Using search results with get_chunk for citations
+ ```python
+ results = search("safety requirements", limit=5)
+ for r in results:
+ chunk = get_chunk(r['chunk_id'])
+ print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}")
```
### Using llm() for classification
```python
- # Get document content
content = get_document("Q1 Report")
- # Use llm() to classify sentiment
sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
print(sentiment)
```
@@ -343,7 +314,7 @@ interactions:
response:
headers:
content-length:
- - '773'
+ - '744'
content-type:
- application/json
parsed_body:
@@ -352,26 +323,26 @@ interactions:
index: 0
message:
content: ''
- reasoning: We need to search for quarterly reports revenue sections. Let's search "quarterly report revenue"
+ reasoning: Need to search for quarterly reports. Likely files like "Q1 Report", "Quarterly Report". Search for "quarterly
+ report revenue".
role: assistant
tool_calls:
- function:
- arguments: '{"code":"# Search for revenue in quarterly reports\nresults = search(\"quarterly report revenue\",
- limit=50)\nprint(len(results))\nfor i, r in enumerate(results[:10]):\n print(i, r[''document_title''],
- r[''page_numbers''], r[''score''])\n"}'
+ arguments: '{"code":"results = search(\"quarterly report revenue\", limit=20)\nprint(len(results))\nfor r in
+ results:\n print(r[''document_title''], r[''chunk_id''], r[''content''][:200])\n"}'
name: execute_code
- id: call_n1jgyyay
+ id: call_djn3wl0z
index: 0
type: function
- created: 1771336276
- id: chatcmpl-179
+ created: 1771336707
+ id: chatcmpl-891
model: gpt-oss
object: chat.completion
system_fingerprint: fp_ollama
usage:
- completion_tokens: 101
- prompt_tokens: 1736
- total_tokens: 1837
+ completion_tokens: 99
+ prompt_tokens: 1564
+ total_tokens: 1663
status:
code: 200
message: OK
@@ -424,7 +395,7 @@ interactions:
connection:
- keep-alive
content-length:
- - '8716'
+ - '8142'
content-type:
- application/json
host:
@@ -441,7 +412,7 @@ interactions:
- search("query") ✓ CORRECT
- from haiku.rag import search ✗ WRONG - will fail
- You have access to a sandboxed Python environment with these haiku.rag functions (use them directly, no imports needed):
+ You have access to a sandboxed Python interpreter with these haiku.rag functions (use them directly, no imports needed):
## Available Functions
@@ -457,10 +428,10 @@ interactions:
Get the full text content of a document by ID, title, or URI.
Returns the document content as a string, or None if not found.
- ### get_docling_document(id_or_title) -> DoclingDocument | None
- Get the structured DoclingDocument object for advanced analysis.
- Returns a DoclingDocument object, or None if not found.
- See "DoclingDocument API" section below for how to use it.
+ ### get_chunk(chunk_id) -> dict | None
+ Get a specific chunk by its ID (from search results).
+ Returns dict with keys: chunk_id, content, document_id, document_title, headings, page_numbers, labels
+ Use this to retrieve full chunk details and metadata for citation.
### llm(prompt) -> str
Call an LLM directly with the given prompt. Returns the response as a string.
@@ -477,8 +448,13 @@ interactions:
```
Check if it exists with: `if 'documents' in dir(): ...`
- ## Standard Library Modules
- You can import any Python standard library module.
+ ## Available Python Features
+
+ The interpreter supports: variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, try/except, and the `json` module.
+
+ Not supported: imports (other than `json`), class definitions, generators/yield, match statements, decorators, `with` statements.
+
+ For pattern matching or text extraction, use string methods (`str.split`, `str.find`, `str.startswith`, `in` operator) or the `llm()` function.
## Strategy Guide
@@ -486,51 +462,9 @@ interactions:
2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
- 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and use collections.
+ 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and data structures.
6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
- 7. **Cite Your Sources**: Track which documents/chunks informed your answer for citation.
-
- ## DoclingDocument API
-
- When you call `get_docling_document(id_or_title)`, you get a DoclingDocument object for structured document analysis.
-
- ### Properties
- - `doc.texts` - List of all text items (paragraphs, headings, etc.)
- - `doc.tables` - List of all tables
- - `doc.pictures` - List of all pictures/figures
- - `doc.name` - Document name
-
- ### Methods
- - `doc.iterate_items(with_groups=False)` - Iterate all items with hierarchy level
- Returns tuples of (item, level) where level is nesting depth
- - `doc.export_to_markdown()` - Export entire document as markdown string
-
- ### Text Item Properties
- - `item.text` - The text content
- - `item.label` - Type: title, paragraph, section_header, list_item, etc. (lowercase enum values)
- - `item.prov` - Provenance (page numbers, bounding boxes)
-
- ### Table Access
- - `table.data.num_rows`, `table.data.num_cols` - Dimensions
- - `table.data.table_cells` - List of TableCell objects
- - `cell.text`, `cell.start_row_offset_idx`, `cell.start_col_offset_idx`
-
- ### Example Usage
- ```python
- doc = get_docling_document("My Document")
-
- # Get all headings
- headings = [t.text for t in doc.texts if "header" in str(t.label)]
-
- # Iterate with structure
- for item, level in doc.iterate_items():
- print(" " * level + item.text[:50])
-
- # Extract table data
- for table in doc.tables:
- for cell in table.data.table_cells:
- print(f"Row {cell.start_row_offset_idx}, Col {cell.start_col_offset_idx}: {cell.text}")
- ```
+ 7. **Cite Your Sources**: Use get_chunk() to retrieve chunk metadata for citations. Track which documents/chunks informed your answer.
## Example Patterns
@@ -546,23 +480,31 @@ interactions:
print(f"Total: {count}")
```
- ### Aggregating data across documents
+ ### Extracting data with llm()
```python
- import re
numbers = []
results = search("financial data", limit=20)
for r in results:
- matches = re.findall(r'\$([\d,]+)', r['content'])
- for m in matches:
- numbers.append(int(m.replace(',', '')))
- print(f"Average: ${sum(numbers)/len(numbers):,.2f}")
+ extracted = llm(f"Extract all dollar amounts from this text as a comma-separated list of numbers (no $ signs): {r['content']}")
+ for part in extracted.split(','):
+ part = part.strip().replace(',', '')
+ if part.isdigit():
+ numbers.append(int(part))
+ if numbers:
+ print(f"Average: {sum(numbers) / len(numbers)}")
+ ```
+
+ ### Using search results with get_chunk for citations
+ ```python
+ results = search("safety requirements", limit=5)
+ for r in results:
+ chunk = get_chunk(r['chunk_id'])
+ print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}")
```
### Using llm() for classification
```python
- # Get document content
content = get_document("Q1 Report")
- # Use llm() to classify sentiment
sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
print(sentiment)
```
@@ -590,21 +532,22 @@ interactions:
- content: What is the total revenue across all quarterly reports?
role: user
- content: null
- reasoning: We need to search for quarterly reports revenue sections. Let's search "quarterly report revenue"
+ reasoning: Need to search for quarterly reports. Likely files like "Q1 Report", "Quarterly Report". Search for "quarterly
+ report revenue".
role: assistant
tool_calls:
- function:
- arguments: '{"code":"# Search for revenue in quarterly reports\nresults = search(\"quarterly report revenue\",
- limit=50)\nprint(len(results))\nfor i, r in enumerate(results[:10]):\n print(i, r[''document_title''], r[''page_numbers''],
- r[''score''])\n"}'
+ arguments: '{"code":"results = search(\"quarterly report revenue\", limit=20)\nprint(len(results))\nfor r in results:\n print(r[''document_title''],
+ r[''chunk_id''], r[''content''][:200])\n"}'
name: execute_code
- id: call_n1jgyyay
+ id: call_djn3wl0z
type: function
- - content: '{"code":"# Search for revenue in quarterly reports\nresults = search(\"quarterly report revenue\", limit=50)\nprint(len(results))\nfor
- i, r in enumerate(results[:10]):\n print(i, r[''document_title''], r[''page_numbers''], r[''score''])\n","stdout":"3\n0
- Q1 Report [] 0.03226645663380623\n1 Q2 Report [] 0.03226645663380623\n2 Q3 Report [] 0.032258063554763794\n","stderr":"","success":true}'
+ - content: '{"code":"results = search(\"quarterly report revenue\", limit=20)\nprint(len(results))\nfor r in results:\n print(r[''document_title''],
+ r[''chunk_id''], r[''content''][:200])\n","stdout":"3\nQ1 Report 3b154808-f8fa-4322-ad0c-4d462b54f59a Sales report
+ Q1: Revenue was $100,000.\nQ2 Report debfe200-93d6-4c8d-beed-5e244b0474dd Sales report Q2: Revenue was $150,000.\nQ3
+ Report 9b7ef62a-b01e-48aa-92ca-927284307fcf Sales report Q3: Revenue was $200,000.\n","stderr":"","success":true}'
role: tool
- tool_call_id: call_n1jgyyay
+ tool_call_id: call_djn3wl0z
model: gpt-oss
reasoning_effort: low
stream: false
@@ -655,7 +598,7 @@ interactions:
response:
headers:
content-length:
- - '1037'
+ - '612'
content-type:
- application/json
parsed_body:
@@ -664,28 +607,25 @@ interactions:
index: 0
message:
content: ''
- reasoning: 3 docs. Need revenue numbers. Let's fetch each document content.
+ reasoning: We need total revenue across all quarterly reports. There are Q1, Q2, Q3. Probably Q4 as well. Search
+ for Q4.
role: assistant
tool_calls:
- function:
- arguments: '{"code":"docs = [\"Q1 Report\", \"Q2 Report\", \"Q3 Report\"]\nrevenues = {}\nfor d in docs:\n content
- = get_document(d)\n if content:\n import re\n # look for ''Revenue'' line\n m = re.search(r''Revenue\\s*[:=]\\s*\\$?([\\d,\\.]+)'',
- content, re.IGNORECASE)\n if m:\n amount = int(m.group(1).replace('','',''''))\n revenues[d]
- = amount\n else:\n revenues[d] = None\nprint(revenues)\nprint(''total'', sum(v for v in
- revenues.values() if v))\n"}'
+ arguments: '{"code":"results = search(\"Q4 Report\", limit=10)\nprint(results)\n"}'
name: execute_code
- id: call_o1d3emy8
+ id: call_ls6idggl
index: 0
type: function
- created: 1771336279
- id: chatcmpl-589
+ created: 1771336709
+ id: chatcmpl-119
model: gpt-oss
object: chat.completion
system_fingerprint: fp_ollama
usage:
- completion_tokens: 187
- prompt_tokens: 1968
- total_tokens: 2155
+ completion_tokens: 71
+ prompt_tokens: 1851
+ total_tokens: 1922
status:
code: 200
message: OK
@@ -698,288 +638,34 @@ interactions:
connection:
- keep-alive
content-length:
- - '10186'
+ - '79'
content-type:
- application/json
host:
- localhost:11434
method: POST
parsed_body:
- messages:
- - content: |-
- You are a Recursive Language Model (RLM) agent that solves complex research questions by writing and executing Python code.
-
- IMPORTANT: You MUST use the `execute_code` tool to run Python code. The functions described below are ONLY available inside the execute_code tool - you cannot access them any other way. Always execute code to answer questions; do not just describe what code would do.
-
- CRITICAL: Inside execute_code, these functions are ALREADY available in the namespace. Do NOT import them - just use them directly:
- - search("query") ✓ CORRECT
- - from haiku.rag import search ✗ WRONG - will fail
-
- You have access to a sandboxed Python environment with these haiku.rag functions (use them directly, no imports needed):
-
- ## Available Functions
-
- ### search(query, limit=10) -> list[dict]
- Search the knowledge base using hybrid search (vector + full-text).
- Returns list of dicts with keys: chunk_id, content, document_id, document_title, document_uri, score, page_numbers, headings
-
- ### list_documents(limit=10, offset=0) -> list[dict]
- List available documents in the knowledge base.
- Returns list of dicts with keys: id, title, uri, created_at
-
- ### get_document(id_or_title) -> str | None
- Get the full text content of a document by ID, title, or URI.
- Returns the document content as a string, or None if not found.
-
- ### get_docling_document(id_or_title) -> DoclingDocument | None
- Get the structured DoclingDocument object for advanced analysis.
- Returns a DoclingDocument object, or None if not found.
- See "DoclingDocument API" section below for how to use it.
-
- ### llm(prompt) -> str
- Call an LLM directly with the given prompt. Returns the response as a string.
- Use this for classification, summarization, extraction, or any task where you
- already have the content and just need LLM reasoning.
-
- ## Pre-loaded Documents Variable
-
- If documents were pre-loaded for this session, a `documents` variable is available:
- ```python
- # documents is a list of dicts with keys: id, title, uri, content
- for doc in documents:
- print(doc['title'], len(doc['content']))
- ```
- Check if it exists with: `if 'documents' in dir(): ...`
-
- ## Standard Library Modules
- You can import any Python standard library module.
-
- ## Strategy Guide
-
- 1. **Explore First**: Start by listing documents or searching to understand what's available. Document names may differ from filenames (e.g., "tbmed593.pdf" might be stored as "TB MED 593" or similar).
- 2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
- 3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
- 4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
- 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and use collections.
- 6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
- 7. **Cite Your Sources**: Track which documents/chunks informed your answer for citation.
-
- ## DoclingDocument API
-
- When you call `get_docling_document(id_or_title)`, you get a DoclingDocument object for structured document analysis.
-
- ### Properties
- - `doc.texts` - List of all text items (paragraphs, headings, etc.)
- - `doc.tables` - List of all tables
- - `doc.pictures` - List of all pictures/figures
- - `doc.name` - Document name
-
- ### Methods
- - `doc.iterate_items(with_groups=False)` - Iterate all items with hierarchy level
- Returns tuples of (item, level) where level is nesting depth
- - `doc.export_to_markdown()` - Export entire document as markdown string
-
- ### Text Item Properties
- - `item.text` - The text content
- - `item.label` - Type: title, paragraph, section_header, list_item, etc. (lowercase enum values)
- - `item.prov` - Provenance (page numbers, bounding boxes)
-
- ### Table Access
- - `table.data.num_rows`, `table.data.num_cols` - Dimensions
- - `table.data.table_cells` - List of TableCell objects
- - `cell.text`, `cell.start_row_offset_idx`, `cell.start_col_offset_idx`
-
- ### Example Usage
- ```python
- doc = get_docling_document("My Document")
-
- # Get all headings
- headings = [t.text for t in doc.texts if "header" in str(t.label)]
-
- # Iterate with structure
- for item, level in doc.iterate_items():
- print(" " * level + item.text[:50])
-
- # Extract table data
- for table in doc.tables:
- for cell in table.data.table_cells:
- print(f"Row {cell.start_row_offset_idx}, Col {cell.start_col_offset_idx}: {cell.text}")
- ```
-
- ## Example Patterns
-
- ### Counting documents matching a condition
- ```python
- docs = list_documents(limit=100)
- count = 0
- for doc in docs:
- content = get_document(doc['id'])
- if content and 'keyword' in content.lower():
- count += 1
- print(f"Found in: {doc['title']}")
- print(f"Total: {count}")
- ```
-
- ### Aggregating data across documents
- ```python
- import re
- numbers = []
- results = search("financial data", limit=20)
- for r in results:
- matches = re.findall(r'\$([\d,]+)', r['content'])
- for m in matches:
- numbers.append(int(m.replace(',', '')))
- print(f"Average: ${sum(numbers)/len(numbers):,.2f}")
- ```
-
- ### Using llm() for classification
- ```python
- # Get document content
- content = get_document("Q1 Report")
- # Use llm() to classify sentiment
- sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
- print(sentiment)
- ```
-
- ## Workflow
-
- 1. **ALWAYS start by using execute_code** to explore the knowledge base
- 2. Run multiple code blocks as needed to gather information
- 3. After collecting data, provide your final answer
-
- ## Output Format
-
- CRITICAL: Your final response MUST be valid JSON matching this exact schema:
- ```json
- {"answer": "Your complete answer here as a string", "program": "Your final consolidated program here as a string"}
- ```
-
- - `answer`: A clear answer to the user's question with key findings and references to specific documents/chunks.
- - `program`: A single, self-contained Python program that produces the answer. Consolidate your exploratory code executions into one clean script.
-
- Do NOT return arbitrary JSON structures. Always use the exact format: {"answer": "...", "program": "..."}
-
- CRITICAL: You MUST call execute_code at least once before providing your answer. Never give up without trying to execute code first.
- role: system
- - content: What is the total revenue across all quarterly reports?
- role: user
- - content: null
- reasoning: We need to search for quarterly reports revenue sections. Let's search "quarterly report revenue"
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"# Search for revenue in quarterly reports\nresults = search(\"quarterly report revenue\",
- limit=50)\nprint(len(results))\nfor i, r in enumerate(results[:10]):\n print(i, r[''document_title''], r[''page_numbers''],
- r[''score''])\n"}'
- name: execute_code
- id: call_n1jgyyay
- type: function
- - content: '{"code":"# Search for revenue in quarterly reports\nresults = search(\"quarterly report revenue\", limit=50)\nprint(len(results))\nfor
- i, r in enumerate(results[:10]):\n print(i, r[''document_title''], r[''page_numbers''], r[''score''])\n","stdout":"3\n0
- Q1 Report [] 0.03226645663380623\n1 Q2 Report [] 0.03226645663380623\n2 Q3 Report [] 0.032258063554763794\n","stderr":"","success":true}'
- role: tool
- tool_call_id: call_n1jgyyay
- - content: null
- reasoning: 3 docs. Need revenue numbers. Let's fetch each document content.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"docs = [\"Q1 Report\", \"Q2 Report\", \"Q3 Report\"]\nrevenues = {}\nfor d in docs:\n content
- = get_document(d)\n if content:\n import re\n # look for ''Revenue'' line\n m = re.search(r''Revenue\\s*[:=]\\s*\\$?([\\d,\\.]+)'',
- content, re.IGNORECASE)\n if m:\n amount = int(m.group(1).replace('','',''''))\n revenues[d]
- = amount\n else:\n revenues[d] = None\nprint(revenues)\nprint(''total'', sum(v for v in revenues.values()
- if v))\n"}'
- name: execute_code
- id: call_o1d3emy8
- type: function
- - content: '{"code":"docs = [\"Q1 Report\", \"Q2 Report\", \"Q3 Report\"]\nrevenues = {}\nfor d in docs:\n content
- = get_document(d)\n if content:\n import re\n # look for ''Revenue'' line\n m = re.search(r''Revenue\\s*[:=]\\s*\\$?([\\d,\\.]+)'',
- content, re.IGNORECASE)\n if m:\n amount = int(m.group(1).replace('','',''''))\n revenues[d]
- = amount\n else:\n revenues[d] = None\nprint(revenues)\nprint(''total'', sum(v for v in revenues.values()
- if v))\n","stdout":"","stderr":"ModuleNotFoundError: No module named ''re''","success":false}'
- role: tool
- tool_call_id: call_o1d3emy8
- model: gpt-oss
- reasoning_effort: low
- stream: false
- tool_choice: auto
- tools:
- - function:
- description: |-
- Execute Python code in a sandboxed interpreter.
-
- The code has access to haiku.rag functions (search, list_documents,
- get_document, get_chunk, llm).
-
- Use print() to output results.
-
- Structured result with success status, stdout, and stderr.
-
- name: execute_code
- parameters:
- additionalProperties: false
- properties:
- code:
- description: Python code to execute.
- type: string
- required:
- - code
- type: object
- strict: true
- type: function
- - function:
- description: Result from RLM agent execution.
- name: final_result
- parameters:
- additionalProperties: false
- properties:
- answer:
- description: The answer to the user's question
- type: string
- program:
- description: The final consolidated program
- type: string
- required:
- - answer
- - program
- type: object
- strict: true
- type: function
- uri: http://localhost:11434/v1/chat/completions
+ encoding_format: base64
+ input:
+ - Q4 Report
+ model: qwen3-embedding:4b
+ uri: http://localhost:11434/v1/embeddings
response:
headers:
- content-length:
- - '950'
content-type:
- application/json
+ transfer-encoding:
+ - chunked
parsed_body:
- choices:
- - finish_reason: tool_calls
+ data:
+ - embedding: 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
index: 0
- message:
- content: ''
- reasoning: Need import re before.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"import re\ndocs = [\"Q1 Report\", \"Q2 Report\", \"Q3 Report\"]\nrevenues = {}\nfor d in
- docs:\n content = get_document(d)\n if content:\n m = re.search(r''Revenue\\s*[:=]\\s*\\$?([\\d,\\.]+)'',
- content, re.IGNORECASE)\n if m:\n amount = int(m.group(1).replace('','',''''))\n revenues[d]
- = amount\n else:\n revenues[d] = None\nprint(revenues)\nprint(''total'', sum(v for v in
- revenues.values() if v))\n"}'
- name: execute_code
- id: call_4pboc6op
- index: 0
- type: function
- created: 1771336284
- id: chatcmpl-42
- model: gpt-oss
- object: chat.completion
- system_fingerprint: fp_ollama
+ object: embedding
+ model: qwen3-embedding:4b
+ object: list
usage:
- completion_tokens: 169
- prompt_tokens: 2341
- total_tokens: 2510
+ prompt_tokens: 4
+ total_tokens: 4
status:
code: 200
message: OK
@@ -992,7 +678,7 @@ interactions:
connection:
- keep-alive
content-length:
- - '11526'
+ - '9530'
content-type:
- application/json
host:
@@ -1009,7 +695,7 @@ interactions:
- search("query") ✓ CORRECT
- from haiku.rag import search ✗ WRONG - will fail
- You have access to a sandboxed Python environment with these haiku.rag functions (use them directly, no imports needed):
+ You have access to a sandboxed Python interpreter with these haiku.rag functions (use them directly, no imports needed):
## Available Functions
@@ -1025,10 +711,10 @@ interactions:
Get the full text content of a document by ID, title, or URI.
Returns the document content as a string, or None if not found.
- ### get_docling_document(id_or_title) -> DoclingDocument | None
- Get the structured DoclingDocument object for advanced analysis.
- Returns a DoclingDocument object, or None if not found.
- See "DoclingDocument API" section below for how to use it.
+ ### get_chunk(chunk_id) -> dict | None
+ Get a specific chunk by its ID (from search results).
+ Returns dict with keys: chunk_id, content, document_id, document_title, headings, page_numbers, labels
+ Use this to retrieve full chunk details and metadata for citation.
### llm(prompt) -> str
Call an LLM directly with the given prompt. Returns the response as a string.
@@ -1045,8 +731,13 @@ interactions:
```
Check if it exists with: `if 'documents' in dir(): ...`
- ## Standard Library Modules
- You can import any Python standard library module.
+ ## Available Python Features
+
+ The interpreter supports: variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, try/except, and the `json` module.
+
+ Not supported: imports (other than `json`), class definitions, generators/yield, match statements, decorators, `with` statements.
+
+ For pattern matching or text extraction, use string methods (`str.split`, `str.find`, `str.startswith`, `in` operator) or the `llm()` function.
## Strategy Guide
@@ -1054,51 +745,9 @@ interactions:
2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
- 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and use collections.
+ 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and data structures.
6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
- 7. **Cite Your Sources**: Track which documents/chunks informed your answer for citation.
-
- ## DoclingDocument API
-
- When you call `get_docling_document(id_or_title)`, you get a DoclingDocument object for structured document analysis.
-
- ### Properties
- - `doc.texts` - List of all text items (paragraphs, headings, etc.)
- - `doc.tables` - List of all tables
- - `doc.pictures` - List of all pictures/figures
- - `doc.name` - Document name
-
- ### Methods
- - `doc.iterate_items(with_groups=False)` - Iterate all items with hierarchy level
- Returns tuples of (item, level) where level is nesting depth
- - `doc.export_to_markdown()` - Export entire document as markdown string
-
- ### Text Item Properties
- - `item.text` - The text content
- - `item.label` - Type: title, paragraph, section_header, list_item, etc. (lowercase enum values)
- - `item.prov` - Provenance (page numbers, bounding boxes)
-
- ### Table Access
- - `table.data.num_rows`, `table.data.num_cols` - Dimensions
- - `table.data.table_cells` - List of TableCell objects
- - `cell.text`, `cell.start_row_offset_idx`, `cell.start_col_offset_idx`
-
- ### Example Usage
- ```python
- doc = get_docling_document("My Document")
-
- # Get all headings
- headings = [t.text for t in doc.texts if "header" in str(t.label)]
-
- # Iterate with structure
- for item, level in doc.iterate_items():
- print(" " * level + item.text[:50])
-
- # Extract table data
- for table in doc.tables:
- for cell in table.data.table_cells:
- print(f"Row {cell.start_row_offset_idx}, Col {cell.start_col_offset_idx}: {cell.text}")
- ```
+ 7. **Cite Your Sources**: Use get_chunk() to retrieve chunk metadata for citations. Track which documents/chunks informed your answer.
## Example Patterns
@@ -1114,23 +763,31 @@ interactions:
print(f"Total: {count}")
```
- ### Aggregating data across documents
+ ### Extracting data with llm()
```python
- import re
numbers = []
results = search("financial data", limit=20)
for r in results:
- matches = re.findall(r'\$([\d,]+)', r['content'])
- for m in matches:
- numbers.append(int(m.replace(',', '')))
- print(f"Average: ${sum(numbers)/len(numbers):,.2f}")
+ extracted = llm(f"Extract all dollar amounts from this text as a comma-separated list of numbers (no $ signs): {r['content']}")
+ for part in extracted.split(','):
+ part = part.strip().replace(',', '')
+ if part.isdigit():
+ numbers.append(int(part))
+ if numbers:
+ print(f"Average: {sum(numbers) / len(numbers)}")
+ ```
+
+ ### Using search results with get_chunk for citations
+ ```python
+ results = search("safety requirements", limit=5)
+ for r in results:
+ chunk = get_chunk(r['chunk_id'])
+ print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}")
```
### Using llm() for classification
```python
- # Get document content
content = get_document("Q1 Report")
- # Use llm() to classify sentiment
sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
print(sentiment)
```
@@ -1158,61 +815,43 @@ interactions:
- content: What is the total revenue across all quarterly reports?
role: user
- content: null
- reasoning: We need to search for quarterly reports revenue sections. Let's search "quarterly report revenue"
+ reasoning: Need to search for quarterly reports. Likely files like "Q1 Report", "Quarterly Report". Search for "quarterly
+ report revenue".
role: assistant
tool_calls:
- function:
- arguments: '{"code":"# Search for revenue in quarterly reports\nresults = search(\"quarterly report revenue\",
- limit=50)\nprint(len(results))\nfor i, r in enumerate(results[:10]):\n print(i, r[''document_title''], r[''page_numbers''],
- r[''score''])\n"}'
+ arguments: '{"code":"results = search(\"quarterly report revenue\", limit=20)\nprint(len(results))\nfor r in results:\n print(r[''document_title''],
+ r[''chunk_id''], r[''content''][:200])\n"}'
name: execute_code
- id: call_n1jgyyay
+ id: call_djn3wl0z
type: function
- - content: '{"code":"# Search for revenue in quarterly reports\nresults = search(\"quarterly report revenue\", limit=50)\nprint(len(results))\nfor
- i, r in enumerate(results[:10]):\n print(i, r[''document_title''], r[''page_numbers''], r[''score''])\n","stdout":"3\n0
- Q1 Report [] 0.03226645663380623\n1 Q2 Report [] 0.03226645663380623\n2 Q3 Report [] 0.032258063554763794\n","stderr":"","success":true}'
+ - content: '{"code":"results = search(\"quarterly report revenue\", limit=20)\nprint(len(results))\nfor r in results:\n print(r[''document_title''],
+ r[''chunk_id''], r[''content''][:200])\n","stdout":"3\nQ1 Report 3b154808-f8fa-4322-ad0c-4d462b54f59a Sales report
+ Q1: Revenue was $100,000.\nQ2 Report debfe200-93d6-4c8d-beed-5e244b0474dd Sales report Q2: Revenue was $150,000.\nQ3
+ Report 9b7ef62a-b01e-48aa-92ca-927284307fcf Sales report Q3: Revenue was $200,000.\n","stderr":"","success":true}'
role: tool
- tool_call_id: call_n1jgyyay
+ tool_call_id: call_djn3wl0z
- content: null
- reasoning: 3 docs. Need revenue numbers. Let's fetch each document content.
+ reasoning: We need total revenue across all quarterly reports. There are Q1, Q2, Q3. Probably Q4 as well. Search for
+ Q4.
role: assistant
tool_calls:
- function:
- arguments: '{"code":"docs = [\"Q1 Report\", \"Q2 Report\", \"Q3 Report\"]\nrevenues = {}\nfor d in docs:\n content
- = get_document(d)\n if content:\n import re\n # look for ''Revenue'' line\n m = re.search(r''Revenue\\s*[:=]\\s*\\$?([\\d,\\.]+)'',
- content, re.IGNORECASE)\n if m:\n amount = int(m.group(1).replace('','',''''))\n revenues[d]
- = amount\n else:\n revenues[d] = None\nprint(revenues)\nprint(''total'', sum(v for v in revenues.values()
- if v))\n"}'
+ arguments: '{"code":"results = search(\"Q4 Report\", limit=10)\nprint(results)\n"}'
name: execute_code
- id: call_o1d3emy8
+ id: call_ls6idggl
type: function
- - content: '{"code":"docs = [\"Q1 Report\", \"Q2 Report\", \"Q3 Report\"]\nrevenues = {}\nfor d in docs:\n content
- = get_document(d)\n if content:\n import re\n # look for ''Revenue'' line\n m = re.search(r''Revenue\\s*[:=]\\s*\\$?([\\d,\\.]+)'',
- content, re.IGNORECASE)\n if m:\n amount = int(m.group(1).replace('','',''''))\n revenues[d]
- = amount\n else:\n revenues[d] = None\nprint(revenues)\nprint(''total'', sum(v for v in revenues.values()
- if v))\n","stdout":"","stderr":"ModuleNotFoundError: No module named ''re''","success":false}'
+ - content: '{"code":"results = search(\"Q4 Report\", limit=10)\nprint(results)\n","stdout":"[{''chunk_id'': ''3b154808-f8fa-4322-ad0c-4d462b54f59a'',
+ ''content'': ''Sales report Q1: Revenue was $100,000.'', ''document_id'': ''23ce9e44-5cd4-4e67-99d7-41bc69250ea8'',
+ ''document_title'': ''Q1 Report'', ''document_uri'': None, ''score'': 0.03226645663380623, ''page_numbers'': [],
+ ''headings'': None}, {''chunk_id'': ''debfe200-93d6-4c8d-beed-5e244b0474dd'', ''content'': ''Sales report Q2: Revenue
+ was $150,000.'', ''document_id'': ''a1856b41-de85-41af-9515-ddfa4ded5682'', ''document_title'': ''Q2 Report'', ''document_uri'':
+ None, ''score'': 0.03226645663380623, ''page_numbers'': [], ''headings'': None}, {''chunk_id'': ''9b7ef62a-b01e-48aa-92ca-927284307fcf'',
+ ''content'': ''Sales report Q3: Revenue was $200,000.'', ''document_id'': ''6ef871e0-c379-4af0-8b72-ef617ade0dea'',
+ ''document_title'': ''Q3 Report'', ''document_uri'': None, ''score'': 0.032258063554763794, ''page_numbers'': [],
+ ''headings'': None}]\n","stderr":"","success":true}'
role: tool
- tool_call_id: call_o1d3emy8
- - content: null
- reasoning: Need import re before.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"import re\ndocs = [\"Q1 Report\", \"Q2 Report\", \"Q3 Report\"]\nrevenues = {}\nfor d in
- docs:\n content = get_document(d)\n if content:\n m = re.search(r''Revenue\\s*[:=]\\s*\\$?([\\d,\\.]+)'',
- content, re.IGNORECASE)\n if m:\n amount = int(m.group(1).replace('','',''''))\n revenues[d]
- = amount\n else:\n revenues[d] = None\nprint(revenues)\nprint(''total'', sum(v for v in revenues.values()
- if v))\n"}'
- name: execute_code
- id: call_4pboc6op
- type: function
- - content: '{"code":"import re\ndocs = [\"Q1 Report\", \"Q2 Report\", \"Q3 Report\"]\nrevenues = {}\nfor d in docs:\n content
- = get_document(d)\n if content:\n m = re.search(r''Revenue\\s*[:=]\\s*\\$?([\\d,\\.]+)'', content, re.IGNORECASE)\n if
- m:\n amount = int(m.group(1).replace('','',''''))\n revenues[d] = amount\n else:\n revenues[d]
- = None\nprint(revenues)\nprint(''total'', sum(v for v in revenues.values() if v))\n","stdout":"","stderr":"ModuleNotFoundError:
- No module named ''re''","success":false}'
- role: tool
- tool_call_id: call_4pboc6op
+ tool_call_id: call_ls6idggl
model: gpt-oss
reasoning_effort: low
stream: false
@@ -1263,1424 +902,7 @@ interactions:
response:
headers:
content-length:
- - '683'
- content-type:
- - application/json
- parsed_body:
- choices:
- - finish_reason: tool_calls
- index: 0
- message:
- content: ''
- reasoning: It seems re not available? maybe need to import as re? but error. maybe namespace doesn't allow re; but
- standard library typically available. Might need to use import sys? Let's check import availability.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"import sys\nprint(sys.modules.keys())\n"}'
- name: execute_code
- id: call_k6jze9bj
- index: 0
- type: function
- created: 1771336286
- id: chatcmpl-204
- model: gpt-oss
- object: chat.completion
- system_fingerprint: fp_ollama
- usage:
- completion_tokens: 73
- prompt_tokens: 2688
- total_tokens: 2761
- status:
- code: 200
- message: OK
-- request:
- headers:
- accept:
- - application/json
- accept-encoding:
- - gzip, deflate, zstd
- connection:
- - keep-alive
- content-length:
- - '12167'
- content-type:
- - application/json
- host:
- - localhost:11434
- method: POST
- parsed_body:
- messages:
- - content: |-
- You are a Recursive Language Model (RLM) agent that solves complex research questions by writing and executing Python code.
-
- IMPORTANT: You MUST use the `execute_code` tool to run Python code. The functions described below are ONLY available inside the execute_code tool - you cannot access them any other way. Always execute code to answer questions; do not just describe what code would do.
-
- CRITICAL: Inside execute_code, these functions are ALREADY available in the namespace. Do NOT import them - just use them directly:
- - search("query") ✓ CORRECT
- - from haiku.rag import search ✗ WRONG - will fail
-
- You have access to a sandboxed Python environment with these haiku.rag functions (use them directly, no imports needed):
-
- ## Available Functions
-
- ### search(query, limit=10) -> list[dict]
- Search the knowledge base using hybrid search (vector + full-text).
- Returns list of dicts with keys: chunk_id, content, document_id, document_title, document_uri, score, page_numbers, headings
-
- ### list_documents(limit=10, offset=0) -> list[dict]
- List available documents in the knowledge base.
- Returns list of dicts with keys: id, title, uri, created_at
-
- ### get_document(id_or_title) -> str | None
- Get the full text content of a document by ID, title, or URI.
- Returns the document content as a string, or None if not found.
-
- ### get_docling_document(id_or_title) -> DoclingDocument | None
- Get the structured DoclingDocument object for advanced analysis.
- Returns a DoclingDocument object, or None if not found.
- See "DoclingDocument API" section below for how to use it.
-
- ### llm(prompt) -> str
- Call an LLM directly with the given prompt. Returns the response as a string.
- Use this for classification, summarization, extraction, or any task where you
- already have the content and just need LLM reasoning.
-
- ## Pre-loaded Documents Variable
-
- If documents were pre-loaded for this session, a `documents` variable is available:
- ```python
- # documents is a list of dicts with keys: id, title, uri, content
- for doc in documents:
- print(doc['title'], len(doc['content']))
- ```
- Check if it exists with: `if 'documents' in dir(): ...`
-
- ## Standard Library Modules
- You can import any Python standard library module.
-
- ## Strategy Guide
-
- 1. **Explore First**: Start by listing documents or searching to understand what's available. Document names may differ from filenames (e.g., "tbmed593.pdf" might be stored as "TB MED 593" or similar).
- 2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
- 3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
- 4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
- 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and use collections.
- 6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
- 7. **Cite Your Sources**: Track which documents/chunks informed your answer for citation.
-
- ## DoclingDocument API
-
- When you call `get_docling_document(id_or_title)`, you get a DoclingDocument object for structured document analysis.
-
- ### Properties
- - `doc.texts` - List of all text items (paragraphs, headings, etc.)
- - `doc.tables` - List of all tables
- - `doc.pictures` - List of all pictures/figures
- - `doc.name` - Document name
-
- ### Methods
- - `doc.iterate_items(with_groups=False)` - Iterate all items with hierarchy level
- Returns tuples of (item, level) where level is nesting depth
- - `doc.export_to_markdown()` - Export entire document as markdown string
-
- ### Text Item Properties
- - `item.text` - The text content
- - `item.label` - Type: title, paragraph, section_header, list_item, etc. (lowercase enum values)
- - `item.prov` - Provenance (page numbers, bounding boxes)
-
- ### Table Access
- - `table.data.num_rows`, `table.data.num_cols` - Dimensions
- - `table.data.table_cells` - List of TableCell objects
- - `cell.text`, `cell.start_row_offset_idx`, `cell.start_col_offset_idx`
-
- ### Example Usage
- ```python
- doc = get_docling_document("My Document")
-
- # Get all headings
- headings = [t.text for t in doc.texts if "header" in str(t.label)]
-
- # Iterate with structure
- for item, level in doc.iterate_items():
- print(" " * level + item.text[:50])
-
- # Extract table data
- for table in doc.tables:
- for cell in table.data.table_cells:
- print(f"Row {cell.start_row_offset_idx}, Col {cell.start_col_offset_idx}: {cell.text}")
- ```
-
- ## Example Patterns
-
- ### Counting documents matching a condition
- ```python
- docs = list_documents(limit=100)
- count = 0
- for doc in docs:
- content = get_document(doc['id'])
- if content and 'keyword' in content.lower():
- count += 1
- print(f"Found in: {doc['title']}")
- print(f"Total: {count}")
- ```
-
- ### Aggregating data across documents
- ```python
- import re
- numbers = []
- results = search("financial data", limit=20)
- for r in results:
- matches = re.findall(r'\$([\d,]+)', r['content'])
- for m in matches:
- numbers.append(int(m.replace(',', '')))
- print(f"Average: ${sum(numbers)/len(numbers):,.2f}")
- ```
-
- ### Using llm() for classification
- ```python
- # Get document content
- content = get_document("Q1 Report")
- # Use llm() to classify sentiment
- sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
- print(sentiment)
- ```
-
- ## Workflow
-
- 1. **ALWAYS start by using execute_code** to explore the knowledge base
- 2. Run multiple code blocks as needed to gather information
- 3. After collecting data, provide your final answer
-
- ## Output Format
-
- CRITICAL: Your final response MUST be valid JSON matching this exact schema:
- ```json
- {"answer": "Your complete answer here as a string", "program": "Your final consolidated program here as a string"}
- ```
-
- - `answer`: A clear answer to the user's question with key findings and references to specific documents/chunks.
- - `program`: A single, self-contained Python program that produces the answer. Consolidate your exploratory code executions into one clean script.
-
- Do NOT return arbitrary JSON structures. Always use the exact format: {"answer": "...", "program": "..."}
-
- CRITICAL: You MUST call execute_code at least once before providing your answer. Never give up without trying to execute code first.
- role: system
- - content: What is the total revenue across all quarterly reports?
- role: user
- - content: null
- reasoning: We need to search for quarterly reports revenue sections. Let's search "quarterly report revenue"
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"# Search for revenue in quarterly reports\nresults = search(\"quarterly report revenue\",
- limit=50)\nprint(len(results))\nfor i, r in enumerate(results[:10]):\n print(i, r[''document_title''], r[''page_numbers''],
- r[''score''])\n"}'
- name: execute_code
- id: call_n1jgyyay
- type: function
- - content: '{"code":"# Search for revenue in quarterly reports\nresults = search(\"quarterly report revenue\", limit=50)\nprint(len(results))\nfor
- i, r in enumerate(results[:10]):\n print(i, r[''document_title''], r[''page_numbers''], r[''score''])\n","stdout":"3\n0
- Q1 Report [] 0.03226645663380623\n1 Q2 Report [] 0.03226645663380623\n2 Q3 Report [] 0.032258063554763794\n","stderr":"","success":true}'
- role: tool
- tool_call_id: call_n1jgyyay
- - content: null
- reasoning: 3 docs. Need revenue numbers. Let's fetch each document content.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"docs = [\"Q1 Report\", \"Q2 Report\", \"Q3 Report\"]\nrevenues = {}\nfor d in docs:\n content
- = get_document(d)\n if content:\n import re\n # look for ''Revenue'' line\n m = re.search(r''Revenue\\s*[:=]\\s*\\$?([\\d,\\.]+)'',
- content, re.IGNORECASE)\n if m:\n amount = int(m.group(1).replace('','',''''))\n revenues[d]
- = amount\n else:\n revenues[d] = None\nprint(revenues)\nprint(''total'', sum(v for v in revenues.values()
- if v))\n"}'
- name: execute_code
- id: call_o1d3emy8
- type: function
- - content: '{"code":"docs = [\"Q1 Report\", \"Q2 Report\", \"Q3 Report\"]\nrevenues = {}\nfor d in docs:\n content
- = get_document(d)\n if content:\n import re\n # look for ''Revenue'' line\n m = re.search(r''Revenue\\s*[:=]\\s*\\$?([\\d,\\.]+)'',
- content, re.IGNORECASE)\n if m:\n amount = int(m.group(1).replace('','',''''))\n revenues[d]
- = amount\n else:\n revenues[d] = None\nprint(revenues)\nprint(''total'', sum(v for v in revenues.values()
- if v))\n","stdout":"","stderr":"ModuleNotFoundError: No module named ''re''","success":false}'
- role: tool
- tool_call_id: call_o1d3emy8
- - content: null
- reasoning: Need import re before.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"import re\ndocs = [\"Q1 Report\", \"Q2 Report\", \"Q3 Report\"]\nrevenues = {}\nfor d in
- docs:\n content = get_document(d)\n if content:\n m = re.search(r''Revenue\\s*[:=]\\s*\\$?([\\d,\\.]+)'',
- content, re.IGNORECASE)\n if m:\n amount = int(m.group(1).replace('','',''''))\n revenues[d]
- = amount\n else:\n revenues[d] = None\nprint(revenues)\nprint(''total'', sum(v for v in revenues.values()
- if v))\n"}'
- name: execute_code
- id: call_4pboc6op
- type: function
- - content: '{"code":"import re\ndocs = [\"Q1 Report\", \"Q2 Report\", \"Q3 Report\"]\nrevenues = {}\nfor d in docs:\n content
- = get_document(d)\n if content:\n m = re.search(r''Revenue\\s*[:=]\\s*\\$?([\\d,\\.]+)'', content, re.IGNORECASE)\n if
- m:\n amount = int(m.group(1).replace('','',''''))\n revenues[d] = amount\n else:\n revenues[d]
- = None\nprint(revenues)\nprint(''total'', sum(v for v in revenues.values() if v))\n","stdout":"","stderr":"ModuleNotFoundError:
- No module named ''re''","success":false}'
- role: tool
- tool_call_id: call_4pboc6op
- - content: null
- reasoning: It seems re not available? maybe need to import as re? but error. maybe namespace doesn't allow re; but
- standard library typically available. Might need to use import sys? Let's check import availability.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"import sys\nprint(sys.modules.keys())\n"}'
- name: execute_code
- id: call_k6jze9bj
- type: function
- - content: '{"code":"import sys\nprint(sys.modules.keys())\n","stdout":"","stderr":"AttributeError: ''module'' object
- has no attribute ''modules''","success":false}'
- role: tool
- tool_call_id: call_k6jze9bj
- model: gpt-oss
- reasoning_effort: low
- stream: false
- tool_choice: auto
- tools:
- - function:
- description: |-
- Execute Python code in a sandboxed interpreter.
-
- The code has access to haiku.rag functions (search, list_documents,
- get_document, get_chunk, llm).
-
- Use print() to output results.
-
- Structured result with success status, stdout, and stderr.
-
- name: execute_code
- parameters:
- additionalProperties: false
- properties:
- code:
- description: Python code to execute.
- type: string
- required:
- - code
- type: object
- strict: true
- type: function
- - function:
- description: Result from RLM agent execution.
- name: final_result
- parameters:
- additionalProperties: false
- properties:
- answer:
- description: The answer to the user's question
- type: string
- program:
- description: The final consolidated program
- type: string
- required:
- - answer
- - program
- type: object
- strict: true
- type: function
- uri: http://localhost:11434/v1/chat/completions
- response:
- headers:
- content-length:
- - '1409'
- content-type:
- - application/json
- parsed_body:
- choices:
- - finish_reason: tool_calls
- index: 0
- message:
- content: ''
- reasoning: It might not allow imports except what? Maybe we cannot import re. In that case we can use string methods.
- Let's find 'Revenue' in content manually.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"docs = [\"Q1 Report\", \"Q2 Report\", \"Q3 Report\"]\nrevenues = {}\nfor d in docs:\n content
- = get_document(d)\n if content:\n lines = content.splitlines()\n for line in lines:\n if
- ''Revenue'' in line:\n # extract digits\n digits = ''''\n for
- ch in line:\n if ch.isdigit() or ch=='','' or ch==''.'':\n digits+=ch\n try:\n amount=int(digits.replace('','',''''))\n revenues[d]=amount\n except:\n revenues[d]=None\n break\n else:\n revenues[d]=None\n else:\n revenues[d]=None\nprint(revenues)\nprint(''total'',
- sum(v for v in revenues.values() if v))\n"}'
- name: execute_code
- id: call_rj8xvv7f
- index: 0
- type: function
- created: 1771336291
- id: chatcmpl-375
- model: gpt-oss
- object: chat.completion
- system_fingerprint: fp_ollama
- usage:
- completion_tokens: 239
- prompt_tokens: 2808
- total_tokens: 3047
- status:
- code: 200
- message: OK
-- request:
- headers:
- accept:
- - application/json
- accept-encoding:
- - gzip, deflate, zstd
- connection:
- - keep-alive
- content-length:
- - '14325'
- content-type:
- - application/json
- host:
- - localhost:11434
- method: POST
- parsed_body:
- messages:
- - content: |-
- You are a Recursive Language Model (RLM) agent that solves complex research questions by writing and executing Python code.
-
- IMPORTANT: You MUST use the `execute_code` tool to run Python code. The functions described below are ONLY available inside the execute_code tool - you cannot access them any other way. Always execute code to answer questions; do not just describe what code would do.
-
- CRITICAL: Inside execute_code, these functions are ALREADY available in the namespace. Do NOT import them - just use them directly:
- - search("query") ✓ CORRECT
- - from haiku.rag import search ✗ WRONG - will fail
-
- You have access to a sandboxed Python environment with these haiku.rag functions (use them directly, no imports needed):
-
- ## Available Functions
-
- ### search(query, limit=10) -> list[dict]
- Search the knowledge base using hybrid search (vector + full-text).
- Returns list of dicts with keys: chunk_id, content, document_id, document_title, document_uri, score, page_numbers, headings
-
- ### list_documents(limit=10, offset=0) -> list[dict]
- List available documents in the knowledge base.
- Returns list of dicts with keys: id, title, uri, created_at
-
- ### get_document(id_or_title) -> str | None
- Get the full text content of a document by ID, title, or URI.
- Returns the document content as a string, or None if not found.
-
- ### get_docling_document(id_or_title) -> DoclingDocument | None
- Get the structured DoclingDocument object for advanced analysis.
- Returns a DoclingDocument object, or None if not found.
- See "DoclingDocument API" section below for how to use it.
-
- ### llm(prompt) -> str
- Call an LLM directly with the given prompt. Returns the response as a string.
- Use this for classification, summarization, extraction, or any task where you
- already have the content and just need LLM reasoning.
-
- ## Pre-loaded Documents Variable
-
- If documents were pre-loaded for this session, a `documents` variable is available:
- ```python
- # documents is a list of dicts with keys: id, title, uri, content
- for doc in documents:
- print(doc['title'], len(doc['content']))
- ```
- Check if it exists with: `if 'documents' in dir(): ...`
-
- ## Standard Library Modules
- You can import any Python standard library module.
-
- ## Strategy Guide
-
- 1. **Explore First**: Start by listing documents or searching to understand what's available. Document names may differ from filenames (e.g., "tbmed593.pdf" might be stored as "TB MED 593" or similar).
- 2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
- 3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
- 4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
- 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and use collections.
- 6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
- 7. **Cite Your Sources**: Track which documents/chunks informed your answer for citation.
-
- ## DoclingDocument API
-
- When you call `get_docling_document(id_or_title)`, you get a DoclingDocument object for structured document analysis.
-
- ### Properties
- - `doc.texts` - List of all text items (paragraphs, headings, etc.)
- - `doc.tables` - List of all tables
- - `doc.pictures` - List of all pictures/figures
- - `doc.name` - Document name
-
- ### Methods
- - `doc.iterate_items(with_groups=False)` - Iterate all items with hierarchy level
- Returns tuples of (item, level) where level is nesting depth
- - `doc.export_to_markdown()` - Export entire document as markdown string
-
- ### Text Item Properties
- - `item.text` - The text content
- - `item.label` - Type: title, paragraph, section_header, list_item, etc. (lowercase enum values)
- - `item.prov` - Provenance (page numbers, bounding boxes)
-
- ### Table Access
- - `table.data.num_rows`, `table.data.num_cols` - Dimensions
- - `table.data.table_cells` - List of TableCell objects
- - `cell.text`, `cell.start_row_offset_idx`, `cell.start_col_offset_idx`
-
- ### Example Usage
- ```python
- doc = get_docling_document("My Document")
-
- # Get all headings
- headings = [t.text for t in doc.texts if "header" in str(t.label)]
-
- # Iterate with structure
- for item, level in doc.iterate_items():
- print(" " * level + item.text[:50])
-
- # Extract table data
- for table in doc.tables:
- for cell in table.data.table_cells:
- print(f"Row {cell.start_row_offset_idx}, Col {cell.start_col_offset_idx}: {cell.text}")
- ```
-
- ## Example Patterns
-
- ### Counting documents matching a condition
- ```python
- docs = list_documents(limit=100)
- count = 0
- for doc in docs:
- content = get_document(doc['id'])
- if content and 'keyword' in content.lower():
- count += 1
- print(f"Found in: {doc['title']}")
- print(f"Total: {count}")
- ```
-
- ### Aggregating data across documents
- ```python
- import re
- numbers = []
- results = search("financial data", limit=20)
- for r in results:
- matches = re.findall(r'\$([\d,]+)', r['content'])
- for m in matches:
- numbers.append(int(m.replace(',', '')))
- print(f"Average: ${sum(numbers)/len(numbers):,.2f}")
- ```
-
- ### Using llm() for classification
- ```python
- # Get document content
- content = get_document("Q1 Report")
- # Use llm() to classify sentiment
- sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
- print(sentiment)
- ```
-
- ## Workflow
-
- 1. **ALWAYS start by using execute_code** to explore the knowledge base
- 2. Run multiple code blocks as needed to gather information
- 3. After collecting data, provide your final answer
-
- ## Output Format
-
- CRITICAL: Your final response MUST be valid JSON matching this exact schema:
- ```json
- {"answer": "Your complete answer here as a string", "program": "Your final consolidated program here as a string"}
- ```
-
- - `answer`: A clear answer to the user's question with key findings and references to specific documents/chunks.
- - `program`: A single, self-contained Python program that produces the answer. Consolidate your exploratory code executions into one clean script.
-
- Do NOT return arbitrary JSON structures. Always use the exact format: {"answer": "...", "program": "..."}
-
- CRITICAL: You MUST call execute_code at least once before providing your answer. Never give up without trying to execute code first.
- role: system
- - content: What is the total revenue across all quarterly reports?
- role: user
- - content: null
- reasoning: We need to search for quarterly reports revenue sections. Let's search "quarterly report revenue"
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"# Search for revenue in quarterly reports\nresults = search(\"quarterly report revenue\",
- limit=50)\nprint(len(results))\nfor i, r in enumerate(results[:10]):\n print(i, r[''document_title''], r[''page_numbers''],
- r[''score''])\n"}'
- name: execute_code
- id: call_n1jgyyay
- type: function
- - content: '{"code":"# Search for revenue in quarterly reports\nresults = search(\"quarterly report revenue\", limit=50)\nprint(len(results))\nfor
- i, r in enumerate(results[:10]):\n print(i, r[''document_title''], r[''page_numbers''], r[''score''])\n","stdout":"3\n0
- Q1 Report [] 0.03226645663380623\n1 Q2 Report [] 0.03226645663380623\n2 Q3 Report [] 0.032258063554763794\n","stderr":"","success":true}'
- role: tool
- tool_call_id: call_n1jgyyay
- - content: null
- reasoning: 3 docs. Need revenue numbers. Let's fetch each document content.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"docs = [\"Q1 Report\", \"Q2 Report\", \"Q3 Report\"]\nrevenues = {}\nfor d in docs:\n content
- = get_document(d)\n if content:\n import re\n # look for ''Revenue'' line\n m = re.search(r''Revenue\\s*[:=]\\s*\\$?([\\d,\\.]+)'',
- content, re.IGNORECASE)\n if m:\n amount = int(m.group(1).replace('','',''''))\n revenues[d]
- = amount\n else:\n revenues[d] = None\nprint(revenues)\nprint(''total'', sum(v for v in revenues.values()
- if v))\n"}'
- name: execute_code
- id: call_o1d3emy8
- type: function
- - content: '{"code":"docs = [\"Q1 Report\", \"Q2 Report\", \"Q3 Report\"]\nrevenues = {}\nfor d in docs:\n content
- = get_document(d)\n if content:\n import re\n # look for ''Revenue'' line\n m = re.search(r''Revenue\\s*[:=]\\s*\\$?([\\d,\\.]+)'',
- content, re.IGNORECASE)\n if m:\n amount = int(m.group(1).replace('','',''''))\n revenues[d]
- = amount\n else:\n revenues[d] = None\nprint(revenues)\nprint(''total'', sum(v for v in revenues.values()
- if v))\n","stdout":"","stderr":"ModuleNotFoundError: No module named ''re''","success":false}'
- role: tool
- tool_call_id: call_o1d3emy8
- - content: null
- reasoning: Need import re before.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"import re\ndocs = [\"Q1 Report\", \"Q2 Report\", \"Q3 Report\"]\nrevenues = {}\nfor d in
- docs:\n content = get_document(d)\n if content:\n m = re.search(r''Revenue\\s*[:=]\\s*\\$?([\\d,\\.]+)'',
- content, re.IGNORECASE)\n if m:\n amount = int(m.group(1).replace('','',''''))\n revenues[d]
- = amount\n else:\n revenues[d] = None\nprint(revenues)\nprint(''total'', sum(v for v in revenues.values()
- if v))\n"}'
- name: execute_code
- id: call_4pboc6op
- type: function
- - content: '{"code":"import re\ndocs = [\"Q1 Report\", \"Q2 Report\", \"Q3 Report\"]\nrevenues = {}\nfor d in docs:\n content
- = get_document(d)\n if content:\n m = re.search(r''Revenue\\s*[:=]\\s*\\$?([\\d,\\.]+)'', content, re.IGNORECASE)\n if
- m:\n amount = int(m.group(1).replace('','',''''))\n revenues[d] = amount\n else:\n revenues[d]
- = None\nprint(revenues)\nprint(''total'', sum(v for v in revenues.values() if v))\n","stdout":"","stderr":"ModuleNotFoundError:
- No module named ''re''","success":false}'
- role: tool
- tool_call_id: call_4pboc6op
- - content: null
- reasoning: It seems re not available? maybe need to import as re? but error. maybe namespace doesn't allow re; but
- standard library typically available. Might need to use import sys? Let's check import availability.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"import sys\nprint(sys.modules.keys())\n"}'
- name: execute_code
- id: call_k6jze9bj
- type: function
- - content: '{"code":"import sys\nprint(sys.modules.keys())\n","stdout":"","stderr":"AttributeError: ''module'' object
- has no attribute ''modules''","success":false}'
- role: tool
- tool_call_id: call_k6jze9bj
- - content: null
- reasoning: It might not allow imports except what? Maybe we cannot import re. In that case we can use string methods.
- Let's find 'Revenue' in content manually.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"docs = [\"Q1 Report\", \"Q2 Report\", \"Q3 Report\"]\nrevenues = {}\nfor d in docs:\n content
- = get_document(d)\n if content:\n lines = content.splitlines()\n for line in lines:\n if
- ''Revenue'' in line:\n # extract digits\n digits = ''''\n for ch
- in line:\n if ch.isdigit() or ch=='','' or ch==''.'':\n digits+=ch\n try:\n amount=int(digits.replace('','',''''))\n revenues[d]=amount\n except:\n revenues[d]=None\n break\n else:\n revenues[d]=None\n else:\n revenues[d]=None\nprint(revenues)\nprint(''total'',
- sum(v for v in revenues.values() if v))\n"}'
- name: execute_code
- id: call_rj8xvv7f
- type: function
- - content: '{"code":"docs = [\"Q1 Report\", \"Q2 Report\", \"Q3 Report\"]\nrevenues = {}\nfor d in docs:\n content
- = get_document(d)\n if content:\n lines = content.splitlines()\n for line in lines:\n if
- ''Revenue'' in line:\n # extract digits\n digits = ''''\n for ch in
- line:\n if ch.isdigit() or ch=='','' or ch==''.'':\n digits+=ch\n try:\n amount=int(digits.replace('','',''''))\n revenues[d]=amount\n except:\n revenues[d]=None\n break\n else:\n revenues[d]=None\n else:\n revenues[d]=None\nprint(revenues)\nprint(''total'',
- sum(v for v in revenues.values() if v))\n","stdout":"{''Q1 Report'': None, ''Q2 Report'': None, ''Q3 Report'': None}\ntotal
- 0\n","stderr":"","success":true}'
- role: tool
- tool_call_id: call_rj8xvv7f
- model: gpt-oss
- reasoning_effort: low
- stream: false
- tool_choice: auto
- tools:
- - function:
- description: |-
- Execute Python code in a sandboxed interpreter.
-
- The code has access to haiku.rag functions (search, list_documents,
- get_document, get_chunk, llm).
-
- Use print() to output results.
-
- Structured result with success status, stdout, and stderr.
-
- name: execute_code
- parameters:
- additionalProperties: false
- properties:
- code:
- description: Python code to execute.
- type: string
- required:
- - code
- type: object
- strict: true
- type: function
- - function:
- description: Result from RLM agent execution.
- name: final_result
- parameters:
- additionalProperties: false
- properties:
- answer:
- description: The answer to the user's question
- type: string
- program:
- description: The final consolidated program
- type: string
- required:
- - answer
- - program
- type: object
- strict: true
- type: function
- uri: http://localhost:11434/v1/chat/completions
- response:
- headers:
- content-length:
- - '647'
- content-type:
- - application/json
- parsed_body:
- choices:
- - finish_reason: tool_calls
- index: 0
- message:
- content: ''
- reasoning: No lines contain 'Revenue'. Let's check content.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"for d in [\"Q1 Report\", \"Q2 Report\", \"Q3 Report\"]:\n c=get_document(d)\n print(''---'',d,''---'')\n print(c[:200])\n print(''...'')\n"}'
- name: execute_code
- id: call_2qs5j6do
- index: 0
- type: function
- created: 1771336294
- id: chatcmpl-979
- model: gpt-oss
- object: chat.completion
- system_fingerprint: fp_ollama
- usage:
- completion_tokens: 87
- prompt_tokens: 3281
- total_tokens: 3368
- status:
- code: 200
- message: OK
-- request:
- headers:
- accept:
- - application/json
- accept-encoding:
- - gzip, deflate, zstd
- connection:
- - keep-alive
- content-length:
- - '15192'
- content-type:
- - application/json
- host:
- - localhost:11434
- method: POST
- parsed_body:
- messages:
- - content: |-
- You are a Recursive Language Model (RLM) agent that solves complex research questions by writing and executing Python code.
-
- IMPORTANT: You MUST use the `execute_code` tool to run Python code. The functions described below are ONLY available inside the execute_code tool - you cannot access them any other way. Always execute code to answer questions; do not just describe what code would do.
-
- CRITICAL: Inside execute_code, these functions are ALREADY available in the namespace. Do NOT import them - just use them directly:
- - search("query") ✓ CORRECT
- - from haiku.rag import search ✗ WRONG - will fail
-
- You have access to a sandboxed Python environment with these haiku.rag functions (use them directly, no imports needed):
-
- ## Available Functions
-
- ### search(query, limit=10) -> list[dict]
- Search the knowledge base using hybrid search (vector + full-text).
- Returns list of dicts with keys: chunk_id, content, document_id, document_title, document_uri, score, page_numbers, headings
-
- ### list_documents(limit=10, offset=0) -> list[dict]
- List available documents in the knowledge base.
- Returns list of dicts with keys: id, title, uri, created_at
-
- ### get_document(id_or_title) -> str | None
- Get the full text content of a document by ID, title, or URI.
- Returns the document content as a string, or None if not found.
-
- ### get_docling_document(id_or_title) -> DoclingDocument | None
- Get the structured DoclingDocument object for advanced analysis.
- Returns a DoclingDocument object, or None if not found.
- See "DoclingDocument API" section below for how to use it.
-
- ### llm(prompt) -> str
- Call an LLM directly with the given prompt. Returns the response as a string.
- Use this for classification, summarization, extraction, or any task where you
- already have the content and just need LLM reasoning.
-
- ## Pre-loaded Documents Variable
-
- If documents were pre-loaded for this session, a `documents` variable is available:
- ```python
- # documents is a list of dicts with keys: id, title, uri, content
- for doc in documents:
- print(doc['title'], len(doc['content']))
- ```
- Check if it exists with: `if 'documents' in dir(): ...`
-
- ## Standard Library Modules
- You can import any Python standard library module.
-
- ## Strategy Guide
-
- 1. **Explore First**: Start by listing documents or searching to understand what's available. Document names may differ from filenames (e.g., "tbmed593.pdf" might be stored as "TB MED 593" or similar).
- 2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
- 3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
- 4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
- 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and use collections.
- 6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
- 7. **Cite Your Sources**: Track which documents/chunks informed your answer for citation.
-
- ## DoclingDocument API
-
- When you call `get_docling_document(id_or_title)`, you get a DoclingDocument object for structured document analysis.
-
- ### Properties
- - `doc.texts` - List of all text items (paragraphs, headings, etc.)
- - `doc.tables` - List of all tables
- - `doc.pictures` - List of all pictures/figures
- - `doc.name` - Document name
-
- ### Methods
- - `doc.iterate_items(with_groups=False)` - Iterate all items with hierarchy level
- Returns tuples of (item, level) where level is nesting depth
- - `doc.export_to_markdown()` - Export entire document as markdown string
-
- ### Text Item Properties
- - `item.text` - The text content
- - `item.label` - Type: title, paragraph, section_header, list_item, etc. (lowercase enum values)
- - `item.prov` - Provenance (page numbers, bounding boxes)
-
- ### Table Access
- - `table.data.num_rows`, `table.data.num_cols` - Dimensions
- - `table.data.table_cells` - List of TableCell objects
- - `cell.text`, `cell.start_row_offset_idx`, `cell.start_col_offset_idx`
-
- ### Example Usage
- ```python
- doc = get_docling_document("My Document")
-
- # Get all headings
- headings = [t.text for t in doc.texts if "header" in str(t.label)]
-
- # Iterate with structure
- for item, level in doc.iterate_items():
- print(" " * level + item.text[:50])
-
- # Extract table data
- for table in doc.tables:
- for cell in table.data.table_cells:
- print(f"Row {cell.start_row_offset_idx}, Col {cell.start_col_offset_idx}: {cell.text}")
- ```
-
- ## Example Patterns
-
- ### Counting documents matching a condition
- ```python
- docs = list_documents(limit=100)
- count = 0
- for doc in docs:
- content = get_document(doc['id'])
- if content and 'keyword' in content.lower():
- count += 1
- print(f"Found in: {doc['title']}")
- print(f"Total: {count}")
- ```
-
- ### Aggregating data across documents
- ```python
- import re
- numbers = []
- results = search("financial data", limit=20)
- for r in results:
- matches = re.findall(r'\$([\d,]+)', r['content'])
- for m in matches:
- numbers.append(int(m.replace(',', '')))
- print(f"Average: ${sum(numbers)/len(numbers):,.2f}")
- ```
-
- ### Using llm() for classification
- ```python
- # Get document content
- content = get_document("Q1 Report")
- # Use llm() to classify sentiment
- sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
- print(sentiment)
- ```
-
- ## Workflow
-
- 1. **ALWAYS start by using execute_code** to explore the knowledge base
- 2. Run multiple code blocks as needed to gather information
- 3. After collecting data, provide your final answer
-
- ## Output Format
-
- CRITICAL: Your final response MUST be valid JSON matching this exact schema:
- ```json
- {"answer": "Your complete answer here as a string", "program": "Your final consolidated program here as a string"}
- ```
-
- - `answer`: A clear answer to the user's question with key findings and references to specific documents/chunks.
- - `program`: A single, self-contained Python program that produces the answer. Consolidate your exploratory code executions into one clean script.
-
- Do NOT return arbitrary JSON structures. Always use the exact format: {"answer": "...", "program": "..."}
-
- CRITICAL: You MUST call execute_code at least once before providing your answer. Never give up without trying to execute code first.
- role: system
- - content: What is the total revenue across all quarterly reports?
- role: user
- - content: null
- reasoning: We need to search for quarterly reports revenue sections. Let's search "quarterly report revenue"
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"# Search for revenue in quarterly reports\nresults = search(\"quarterly report revenue\",
- limit=50)\nprint(len(results))\nfor i, r in enumerate(results[:10]):\n print(i, r[''document_title''], r[''page_numbers''],
- r[''score''])\n"}'
- name: execute_code
- id: call_n1jgyyay
- type: function
- - content: '{"code":"# Search for revenue in quarterly reports\nresults = search(\"quarterly report revenue\", limit=50)\nprint(len(results))\nfor
- i, r in enumerate(results[:10]):\n print(i, r[''document_title''], r[''page_numbers''], r[''score''])\n","stdout":"3\n0
- Q1 Report [] 0.03226645663380623\n1 Q2 Report [] 0.03226645663380623\n2 Q3 Report [] 0.032258063554763794\n","stderr":"","success":true}'
- role: tool
- tool_call_id: call_n1jgyyay
- - content: null
- reasoning: 3 docs. Need revenue numbers. Let's fetch each document content.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"docs = [\"Q1 Report\", \"Q2 Report\", \"Q3 Report\"]\nrevenues = {}\nfor d in docs:\n content
- = get_document(d)\n if content:\n import re\n # look for ''Revenue'' line\n m = re.search(r''Revenue\\s*[:=]\\s*\\$?([\\d,\\.]+)'',
- content, re.IGNORECASE)\n if m:\n amount = int(m.group(1).replace('','',''''))\n revenues[d]
- = amount\n else:\n revenues[d] = None\nprint(revenues)\nprint(''total'', sum(v for v in revenues.values()
- if v))\n"}'
- name: execute_code
- id: call_o1d3emy8
- type: function
- - content: '{"code":"docs = [\"Q1 Report\", \"Q2 Report\", \"Q3 Report\"]\nrevenues = {}\nfor d in docs:\n content
- = get_document(d)\n if content:\n import re\n # look for ''Revenue'' line\n m = re.search(r''Revenue\\s*[:=]\\s*\\$?([\\d,\\.]+)'',
- content, re.IGNORECASE)\n if m:\n amount = int(m.group(1).replace('','',''''))\n revenues[d]
- = amount\n else:\n revenues[d] = None\nprint(revenues)\nprint(''total'', sum(v for v in revenues.values()
- if v))\n","stdout":"","stderr":"ModuleNotFoundError: No module named ''re''","success":false}'
- role: tool
- tool_call_id: call_o1d3emy8
- - content: null
- reasoning: Need import re before.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"import re\ndocs = [\"Q1 Report\", \"Q2 Report\", \"Q3 Report\"]\nrevenues = {}\nfor d in
- docs:\n content = get_document(d)\n if content:\n m = re.search(r''Revenue\\s*[:=]\\s*\\$?([\\d,\\.]+)'',
- content, re.IGNORECASE)\n if m:\n amount = int(m.group(1).replace('','',''''))\n revenues[d]
- = amount\n else:\n revenues[d] = None\nprint(revenues)\nprint(''total'', sum(v for v in revenues.values()
- if v))\n"}'
- name: execute_code
- id: call_4pboc6op
- type: function
- - content: '{"code":"import re\ndocs = [\"Q1 Report\", \"Q2 Report\", \"Q3 Report\"]\nrevenues = {}\nfor d in docs:\n content
- = get_document(d)\n if content:\n m = re.search(r''Revenue\\s*[:=]\\s*\\$?([\\d,\\.]+)'', content, re.IGNORECASE)\n if
- m:\n amount = int(m.group(1).replace('','',''''))\n revenues[d] = amount\n else:\n revenues[d]
- = None\nprint(revenues)\nprint(''total'', sum(v for v in revenues.values() if v))\n","stdout":"","stderr":"ModuleNotFoundError:
- No module named ''re''","success":false}'
- role: tool
- tool_call_id: call_4pboc6op
- - content: null
- reasoning: It seems re not available? maybe need to import as re? but error. maybe namespace doesn't allow re; but
- standard library typically available. Might need to use import sys? Let's check import availability.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"import sys\nprint(sys.modules.keys())\n"}'
- name: execute_code
- id: call_k6jze9bj
- type: function
- - content: '{"code":"import sys\nprint(sys.modules.keys())\n","stdout":"","stderr":"AttributeError: ''module'' object
- has no attribute ''modules''","success":false}'
- role: tool
- tool_call_id: call_k6jze9bj
- - content: null
- reasoning: It might not allow imports except what? Maybe we cannot import re. In that case we can use string methods.
- Let's find 'Revenue' in content manually.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"docs = [\"Q1 Report\", \"Q2 Report\", \"Q3 Report\"]\nrevenues = {}\nfor d in docs:\n content
- = get_document(d)\n if content:\n lines = content.splitlines()\n for line in lines:\n if
- ''Revenue'' in line:\n # extract digits\n digits = ''''\n for ch
- in line:\n if ch.isdigit() or ch=='','' or ch==''.'':\n digits+=ch\n try:\n amount=int(digits.replace('','',''''))\n revenues[d]=amount\n except:\n revenues[d]=None\n break\n else:\n revenues[d]=None\n else:\n revenues[d]=None\nprint(revenues)\nprint(''total'',
- sum(v for v in revenues.values() if v))\n"}'
- name: execute_code
- id: call_rj8xvv7f
- type: function
- - content: '{"code":"docs = [\"Q1 Report\", \"Q2 Report\", \"Q3 Report\"]\nrevenues = {}\nfor d in docs:\n content
- = get_document(d)\n if content:\n lines = content.splitlines()\n for line in lines:\n if
- ''Revenue'' in line:\n # extract digits\n digits = ''''\n for ch in
- line:\n if ch.isdigit() or ch=='','' or ch==''.'':\n digits+=ch\n try:\n amount=int(digits.replace('','',''''))\n revenues[d]=amount\n except:\n revenues[d]=None\n break\n else:\n revenues[d]=None\n else:\n revenues[d]=None\nprint(revenues)\nprint(''total'',
- sum(v for v in revenues.values() if v))\n","stdout":"{''Q1 Report'': None, ''Q2 Report'': None, ''Q3 Report'': None}\ntotal
- 0\n","stderr":"","success":true}'
- role: tool
- tool_call_id: call_rj8xvv7f
- - content: null
- reasoning: No lines contain 'Revenue'. Let's check content.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"for d in [\"Q1 Report\", \"Q2 Report\", \"Q3 Report\"]:\n c=get_document(d)\n print(''---'',d,''---'')\n print(c[:200])\n print(''...'')\n"}'
- name: execute_code
- id: call_2qs5j6do
- type: function
- - content: '{"code":"for d in [\"Q1 Report\", \"Q2 Report\", \"Q3 Report\"]:\n c=get_document(d)\n print(''---'',d,''---'')\n print(c[:200])\n print(''...'')\n","stdout":"---
- Q1 Report ---\nSales report Q1: Revenue was $100,000.\n...\n--- Q2 Report ---\nSales report Q2: Revenue was $150,000.\n...\n---
- Q3 Report ---\nSales report Q3: Revenue was $200,000.\n...\n","stderr":"","success":true}'
- role: tool
- tool_call_id: call_2qs5j6do
- model: gpt-oss
- reasoning_effort: low
- stream: false
- tool_choice: auto
- tools:
- - function:
- description: |-
- Execute Python code in a sandboxed interpreter.
-
- The code has access to haiku.rag functions (search, list_documents,
- get_document, get_chunk, llm).
-
- Use print() to output results.
-
- Structured result with success status, stdout, and stderr.
-
- name: execute_code
- parameters:
- additionalProperties: false
- properties:
- code:
- description: Python code to execute.
- type: string
- required:
- - code
- type: object
- strict: true
- type: function
- - function:
- description: Result from RLM agent execution.
- name: final_result
- parameters:
- additionalProperties: false
- properties:
- answer:
- description: The answer to the user's question
- type: string
- program:
- description: The final consolidated program
- type: string
- required:
- - answer
- - program
- type: object
- strict: true
- type: function
- uri: http://localhost:11434/v1/chat/completions
- response:
- headers:
- content-length:
- - '1001'
- content-type:
- - application/json
- parsed_body:
- choices:
- - finish_reason: tool_calls
- index: 0
- message:
- content: ''
- reasoning: The content has "Revenue was $". So we can extract. Search for "$" pattern. Use simple.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"def extract_revenue(text):\n # find ''$''\n idx = text.find(''$'')\n if idx==-1:\n return
- None\n start=idx+1\n end=start\n while end\u003clen(text) and (text[end].isdigit() or text[end]=='',''):\n end+=1\n num=text[start:end].replace('','','''')\n return
- int(num)\n\nrev={}\nfor d in [''Q1 Report'',''Q2 Report'',''Q3 Report'']:\n c=get_document(d)\n rev[d]=extract_revenue(c)\nprint(rev)\nprint(''total'',
- sum(rev.values()))\n"}'
- name: execute_code
- id: call_miqfjj3y
- index: 0
- type: function
- created: 1771336299
- id: chatcmpl-75
- model: gpt-oss
- object: chat.completion
- system_fingerprint: fp_ollama
- usage:
- completion_tokens: 190
- prompt_tokens: 3512
- total_tokens: 3702
- status:
- code: 200
- message: OK
-- request:
- headers:
- accept:
- - application/json
- accept-encoding:
- - gzip, deflate, zstd
- connection:
- - keep-alive
- content-length:
- - '16598'
- content-type:
- - application/json
- host:
- - localhost:11434
- method: POST
- parsed_body:
- messages:
- - content: |-
- You are a Recursive Language Model (RLM) agent that solves complex research questions by writing and executing Python code.
-
- IMPORTANT: You MUST use the `execute_code` tool to run Python code. The functions described below are ONLY available inside the execute_code tool - you cannot access them any other way. Always execute code to answer questions; do not just describe what code would do.
-
- CRITICAL: Inside execute_code, these functions are ALREADY available in the namespace. Do NOT import them - just use them directly:
- - search("query") ✓ CORRECT
- - from haiku.rag import search ✗ WRONG - will fail
-
- You have access to a sandboxed Python environment with these haiku.rag functions (use them directly, no imports needed):
-
- ## Available Functions
-
- ### search(query, limit=10) -> list[dict]
- Search the knowledge base using hybrid search (vector + full-text).
- Returns list of dicts with keys: chunk_id, content, document_id, document_title, document_uri, score, page_numbers, headings
-
- ### list_documents(limit=10, offset=0) -> list[dict]
- List available documents in the knowledge base.
- Returns list of dicts with keys: id, title, uri, created_at
-
- ### get_document(id_or_title) -> str | None
- Get the full text content of a document by ID, title, or URI.
- Returns the document content as a string, or None if not found.
-
- ### get_docling_document(id_or_title) -> DoclingDocument | None
- Get the structured DoclingDocument object for advanced analysis.
- Returns a DoclingDocument object, or None if not found.
- See "DoclingDocument API" section below for how to use it.
-
- ### llm(prompt) -> str
- Call an LLM directly with the given prompt. Returns the response as a string.
- Use this for classification, summarization, extraction, or any task where you
- already have the content and just need LLM reasoning.
-
- ## Pre-loaded Documents Variable
-
- If documents were pre-loaded for this session, a `documents` variable is available:
- ```python
- # documents is a list of dicts with keys: id, title, uri, content
- for doc in documents:
- print(doc['title'], len(doc['content']))
- ```
- Check if it exists with: `if 'documents' in dir(): ...`
-
- ## Standard Library Modules
- You can import any Python standard library module.
-
- ## Strategy Guide
-
- 1. **Explore First**: Start by listing documents or searching to understand what's available. Document names may differ from filenames (e.g., "tbmed593.pdf" might be stored as "TB MED 593" or similar).
- 2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
- 3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
- 4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
- 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and use collections.
- 6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
- 7. **Cite Your Sources**: Track which documents/chunks informed your answer for citation.
-
- ## DoclingDocument API
-
- When you call `get_docling_document(id_or_title)`, you get a DoclingDocument object for structured document analysis.
-
- ### Properties
- - `doc.texts` - List of all text items (paragraphs, headings, etc.)
- - `doc.tables` - List of all tables
- - `doc.pictures` - List of all pictures/figures
- - `doc.name` - Document name
-
- ### Methods
- - `doc.iterate_items(with_groups=False)` - Iterate all items with hierarchy level
- Returns tuples of (item, level) where level is nesting depth
- - `doc.export_to_markdown()` - Export entire document as markdown string
-
- ### Text Item Properties
- - `item.text` - The text content
- - `item.label` - Type: title, paragraph, section_header, list_item, etc. (lowercase enum values)
- - `item.prov` - Provenance (page numbers, bounding boxes)
-
- ### Table Access
- - `table.data.num_rows`, `table.data.num_cols` - Dimensions
- - `table.data.table_cells` - List of TableCell objects
- - `cell.text`, `cell.start_row_offset_idx`, `cell.start_col_offset_idx`
-
- ### Example Usage
- ```python
- doc = get_docling_document("My Document")
-
- # Get all headings
- headings = [t.text for t in doc.texts if "header" in str(t.label)]
-
- # Iterate with structure
- for item, level in doc.iterate_items():
- print(" " * level + item.text[:50])
-
- # Extract table data
- for table in doc.tables:
- for cell in table.data.table_cells:
- print(f"Row {cell.start_row_offset_idx}, Col {cell.start_col_offset_idx}: {cell.text}")
- ```
-
- ## Example Patterns
-
- ### Counting documents matching a condition
- ```python
- docs = list_documents(limit=100)
- count = 0
- for doc in docs:
- content = get_document(doc['id'])
- if content and 'keyword' in content.lower():
- count += 1
- print(f"Found in: {doc['title']}")
- print(f"Total: {count}")
- ```
-
- ### Aggregating data across documents
- ```python
- import re
- numbers = []
- results = search("financial data", limit=20)
- for r in results:
- matches = re.findall(r'\$([\d,]+)', r['content'])
- for m in matches:
- numbers.append(int(m.replace(',', '')))
- print(f"Average: ${sum(numbers)/len(numbers):,.2f}")
- ```
-
- ### Using llm() for classification
- ```python
- # Get document content
- content = get_document("Q1 Report")
- # Use llm() to classify sentiment
- sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
- print(sentiment)
- ```
-
- ## Workflow
-
- 1. **ALWAYS start by using execute_code** to explore the knowledge base
- 2. Run multiple code blocks as needed to gather information
- 3. After collecting data, provide your final answer
-
- ## Output Format
-
- CRITICAL: Your final response MUST be valid JSON matching this exact schema:
- ```json
- {"answer": "Your complete answer here as a string", "program": "Your final consolidated program here as a string"}
- ```
-
- - `answer`: A clear answer to the user's question with key findings and references to specific documents/chunks.
- - `program`: A single, self-contained Python program that produces the answer. Consolidate your exploratory code executions into one clean script.
-
- Do NOT return arbitrary JSON structures. Always use the exact format: {"answer": "...", "program": "..."}
-
- CRITICAL: You MUST call execute_code at least once before providing your answer. Never give up without trying to execute code first.
- role: system
- - content: What is the total revenue across all quarterly reports?
- role: user
- - content: null
- reasoning: We need to search for quarterly reports revenue sections. Let's search "quarterly report revenue"
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"# Search for revenue in quarterly reports\nresults = search(\"quarterly report revenue\",
- limit=50)\nprint(len(results))\nfor i, r in enumerate(results[:10]):\n print(i, r[''document_title''], r[''page_numbers''],
- r[''score''])\n"}'
- name: execute_code
- id: call_n1jgyyay
- type: function
- - content: '{"code":"# Search for revenue in quarterly reports\nresults = search(\"quarterly report revenue\", limit=50)\nprint(len(results))\nfor
- i, r in enumerate(results[:10]):\n print(i, r[''document_title''], r[''page_numbers''], r[''score''])\n","stdout":"3\n0
- Q1 Report [] 0.03226645663380623\n1 Q2 Report [] 0.03226645663380623\n2 Q3 Report [] 0.032258063554763794\n","stderr":"","success":true}'
- role: tool
- tool_call_id: call_n1jgyyay
- - content: null
- reasoning: 3 docs. Need revenue numbers. Let's fetch each document content.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"docs = [\"Q1 Report\", \"Q2 Report\", \"Q3 Report\"]\nrevenues = {}\nfor d in docs:\n content
- = get_document(d)\n if content:\n import re\n # look for ''Revenue'' line\n m = re.search(r''Revenue\\s*[:=]\\s*\\$?([\\d,\\.]+)'',
- content, re.IGNORECASE)\n if m:\n amount = int(m.group(1).replace('','',''''))\n revenues[d]
- = amount\n else:\n revenues[d] = None\nprint(revenues)\nprint(''total'', sum(v for v in revenues.values()
- if v))\n"}'
- name: execute_code
- id: call_o1d3emy8
- type: function
- - content: '{"code":"docs = [\"Q1 Report\", \"Q2 Report\", \"Q3 Report\"]\nrevenues = {}\nfor d in docs:\n content
- = get_document(d)\n if content:\n import re\n # look for ''Revenue'' line\n m = re.search(r''Revenue\\s*[:=]\\s*\\$?([\\d,\\.]+)'',
- content, re.IGNORECASE)\n if m:\n amount = int(m.group(1).replace('','',''''))\n revenues[d]
- = amount\n else:\n revenues[d] = None\nprint(revenues)\nprint(''total'', sum(v for v in revenues.values()
- if v))\n","stdout":"","stderr":"ModuleNotFoundError: No module named ''re''","success":false}'
- role: tool
- tool_call_id: call_o1d3emy8
- - content: null
- reasoning: Need import re before.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"import re\ndocs = [\"Q1 Report\", \"Q2 Report\", \"Q3 Report\"]\nrevenues = {}\nfor d in
- docs:\n content = get_document(d)\n if content:\n m = re.search(r''Revenue\\s*[:=]\\s*\\$?([\\d,\\.]+)'',
- content, re.IGNORECASE)\n if m:\n amount = int(m.group(1).replace('','',''''))\n revenues[d]
- = amount\n else:\n revenues[d] = None\nprint(revenues)\nprint(''total'', sum(v for v in revenues.values()
- if v))\n"}'
- name: execute_code
- id: call_4pboc6op
- type: function
- - content: '{"code":"import re\ndocs = [\"Q1 Report\", \"Q2 Report\", \"Q3 Report\"]\nrevenues = {}\nfor d in docs:\n content
- = get_document(d)\n if content:\n m = re.search(r''Revenue\\s*[:=]\\s*\\$?([\\d,\\.]+)'', content, re.IGNORECASE)\n if
- m:\n amount = int(m.group(1).replace('','',''''))\n revenues[d] = amount\n else:\n revenues[d]
- = None\nprint(revenues)\nprint(''total'', sum(v for v in revenues.values() if v))\n","stdout":"","stderr":"ModuleNotFoundError:
- No module named ''re''","success":false}'
- role: tool
- tool_call_id: call_4pboc6op
- - content: null
- reasoning: It seems re not available? maybe need to import as re? but error. maybe namespace doesn't allow re; but
- standard library typically available. Might need to use import sys? Let's check import availability.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"import sys\nprint(sys.modules.keys())\n"}'
- name: execute_code
- id: call_k6jze9bj
- type: function
- - content: '{"code":"import sys\nprint(sys.modules.keys())\n","stdout":"","stderr":"AttributeError: ''module'' object
- has no attribute ''modules''","success":false}'
- role: tool
- tool_call_id: call_k6jze9bj
- - content: null
- reasoning: It might not allow imports except what? Maybe we cannot import re. In that case we can use string methods.
- Let's find 'Revenue' in content manually.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"docs = [\"Q1 Report\", \"Q2 Report\", \"Q3 Report\"]\nrevenues = {}\nfor d in docs:\n content
- = get_document(d)\n if content:\n lines = content.splitlines()\n for line in lines:\n if
- ''Revenue'' in line:\n # extract digits\n digits = ''''\n for ch
- in line:\n if ch.isdigit() or ch=='','' or ch==''.'':\n digits+=ch\n try:\n amount=int(digits.replace('','',''''))\n revenues[d]=amount\n except:\n revenues[d]=None\n break\n else:\n revenues[d]=None\n else:\n revenues[d]=None\nprint(revenues)\nprint(''total'',
- sum(v for v in revenues.values() if v))\n"}'
- name: execute_code
- id: call_rj8xvv7f
- type: function
- - content: '{"code":"docs = [\"Q1 Report\", \"Q2 Report\", \"Q3 Report\"]\nrevenues = {}\nfor d in docs:\n content
- = get_document(d)\n if content:\n lines = content.splitlines()\n for line in lines:\n if
- ''Revenue'' in line:\n # extract digits\n digits = ''''\n for ch in
- line:\n if ch.isdigit() or ch=='','' or ch==''.'':\n digits+=ch\n try:\n amount=int(digits.replace('','',''''))\n revenues[d]=amount\n except:\n revenues[d]=None\n break\n else:\n revenues[d]=None\n else:\n revenues[d]=None\nprint(revenues)\nprint(''total'',
- sum(v for v in revenues.values() if v))\n","stdout":"{''Q1 Report'': None, ''Q2 Report'': None, ''Q3 Report'': None}\ntotal
- 0\n","stderr":"","success":true}'
- role: tool
- tool_call_id: call_rj8xvv7f
- - content: null
- reasoning: No lines contain 'Revenue'. Let's check content.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"for d in [\"Q1 Report\", \"Q2 Report\", \"Q3 Report\"]:\n c=get_document(d)\n print(''---'',d,''---'')\n print(c[:200])\n print(''...'')\n"}'
- name: execute_code
- id: call_2qs5j6do
- type: function
- - content: '{"code":"for d in [\"Q1 Report\", \"Q2 Report\", \"Q3 Report\"]:\n c=get_document(d)\n print(''---'',d,''---'')\n print(c[:200])\n print(''...'')\n","stdout":"---
- Q1 Report ---\nSales report Q1: Revenue was $100,000.\n...\n--- Q2 Report ---\nSales report Q2: Revenue was $150,000.\n...\n---
- Q3 Report ---\nSales report Q3: Revenue was $200,000.\n...\n","stderr":"","success":true}'
- role: tool
- tool_call_id: call_2qs5j6do
- - content: null
- reasoning: The content has "Revenue was $". So we can extract. Search for "$" pattern. Use simple.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"def extract_revenue(text):\n # find ''$''\n idx = text.find(''$'')\n if idx==-1:\n return
- None\n start=idx+1\n end=start\n while end\u003clen(text) and (text[end].isdigit() or text[end]=='',''):\n end+=1\n num=text[start:end].replace('','','''')\n return
- int(num)\n\nrev={}\nfor d in [''Q1 Report'',''Q2 Report'',''Q3 Report'']:\n c=get_document(d)\n rev[d]=extract_revenue(c)\nprint(rev)\nprint(''total'',
- sum(rev.values()))\n"}'
- name: execute_code
- id: call_miqfjj3y
- type: function
- - content: '{"code":"def extract_revenue(text):\n # find ''$''\n idx = text.find(''$'')\n if idx==-1:\n return
- None\n start=idx+1\n end=start\n while endExecute Python code in a sandboxed interpreter.
-
- The code has access to haiku.rag functions (search, list_documents,
- get_document, get_chunk, llm).
-
- Use print() to output results.
-
- Structured result with success status, stdout, and stderr.
-
- name: execute_code
- parameters:
- additionalProperties: false
- properties:
- code:
- description: Python code to execute.
- type: string
- required:
- - code
- type: object
- strict: true
- type: function
- - function:
- description: Result from RLM agent execution.
- name: final_result
- parameters:
- additionalProperties: false
- properties:
- answer:
- description: The answer to the user's question
- type: string
- program:
- description: The final consolidated program
- type: string
- required:
- - answer
- - program
- type: object
- strict: true
- type: function
- uri: http://localhost:11434/v1/chat/completions
- response:
- headers:
- content-length:
- - '1382'
+ - '1186'
content-type:
- application/json
parsed_body:
@@ -2688,25 +910,24 @@ interactions:
- finish_reason: stop
index: 0
message:
- content: '{"answer":"The total revenue across the Q1, Q2, and Q3 quarterly reports is $450,000. Q1 Report contributed
- $100,000, Q2 Report contributed $150,000, and Q3 Report contributed $200,000.","program":"def extract_revenue(text):\n #
- Find first dollar sign and extract contiguous digits and commas\n idx = text.find(''$'')\n if idx == -1:\n return
- None\n start = idx + 1\n end = start\n while end < len(text) and (text[end].isdigit() or text[end] ==
- '',''):\n end += 1\n num = text[start:end].replace('','', '''')\n return int(num)\n\n# List of quarterly
- report titles\nquarterly_reports = [''Q1 Report'', ''Q2 Report'', ''Q3 Report'']\nrevenues = {}\nfor title in
- quarterly_reports:\n content = get_document(title)\n if content:\n revenues[title] = extract_revenue(content)\n else:\n revenues[title]
- = None\n\n# Calculate total revenue\ntotal_revenue = sum(v for v in revenues.values() if v is not None)\n\nprint(''Revenue
- per quarter:'', revenues)\nprint(''Total revenue across all quarters: $'', total_revenue)\n"}'
+ content: '{"answer":"The total revenue reported across all quarterly reports is **$450,000**.\n\nBreakdown:\n- Q1
+ Report: $100,000\n- Q2 Report: $150,000\n- Q3 Report: $200,000\n\n(Quarter 4 is not present in the current knowledge
+ base.)","program":"# Calculate total revenue across all quarterly reports\nresults = search(\"quarterly report
+ revenue\", limit=20)\nrevenues = []\nfor r in results:\n # Extract numeric amount from the content string\n amount_str
+ = llm(f\"Extract the dollar amount from this text (no $ sign or commas): {r[''content'']}\")\n # Clean and
+ convert to integer\n amount = int(amount_str.strip().replace('','', ''''))\n revenues.append(amount)\n#
+ Compute total\ntotal = sum(revenues)\nprint(f\"Total revenue: ${total:,}\")"}'
+ reasoning: No Q4 in search; maybe none. So total revenue = 100k+150k+200k=450k. Provide answer and program.
role: assistant
- created: 1771336306
- id: chatcmpl-707
+ created: 1771336715
+ id: chatcmpl-973
model: gpt-oss
object: chat.completion
system_fingerprint: fp_ollama
usage:
- completion_tokens: 293
- prompt_tokens: 3907
- total_tokens: 4200
+ completion_tokens: 243
+ prompt_tokens: 2296
+ total_tokens: 2539
status:
code: 200
message: OK
diff --git a/tests/cassettes/test_rlm/TestClientRLMIntegration.test_rlm_count_documents.yaml b/tests/cassettes/test_rlm/TestClientRLMIntegration.test_rlm_count_documents.yaml
index 16307d3e..2bca76ac 100644
--- a/tests/cassettes/test_rlm/TestClientRLMIntegration.test_rlm_count_documents.yaml
+++ b/tests/cassettes/test_rlm/TestClientRLMIntegration.test_rlm_count_documents.yaml
@@ -128,7 +128,7 @@ interactions:
connection:
- keep-alive
content-length:
- - '7719'
+ - '7083'
content-type:
- application/json
host:
@@ -145,7 +145,7 @@ interactions:
- search("query") ✓ CORRECT
- from haiku.rag import search ✗ WRONG - will fail
- You have access to a sandboxed Python environment with these haiku.rag functions (use them directly, no imports needed):
+ You have access to a sandboxed Python interpreter with these haiku.rag functions (use them directly, no imports needed):
## Available Functions
@@ -161,10 +161,10 @@ interactions:
Get the full text content of a document by ID, title, or URI.
Returns the document content as a string, or None if not found.
- ### get_docling_document(id_or_title) -> DoclingDocument | None
- Get the structured DoclingDocument object for advanced analysis.
- Returns a DoclingDocument object, or None if not found.
- See "DoclingDocument API" section below for how to use it.
+ ### get_chunk(chunk_id) -> dict | None
+ Get a specific chunk by its ID (from search results).
+ Returns dict with keys: chunk_id, content, document_id, document_title, headings, page_numbers, labels
+ Use this to retrieve full chunk details and metadata for citation.
### llm(prompt) -> str
Call an LLM directly with the given prompt. Returns the response as a string.
@@ -181,8 +181,13 @@ interactions:
```
Check if it exists with: `if 'documents' in dir(): ...`
- ## Standard Library Modules
- You can import any Python standard library module.
+ ## Available Python Features
+
+ The interpreter supports: variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, try/except, and the `json` module.
+
+ Not supported: imports (other than `json`), class definitions, generators/yield, match statements, decorators, `with` statements.
+
+ For pattern matching or text extraction, use string methods (`str.split`, `str.find`, `str.startswith`, `in` operator) or the `llm()` function.
## Strategy Guide
@@ -190,51 +195,9 @@ interactions:
2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
- 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and use collections.
+ 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and data structures.
6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
- 7. **Cite Your Sources**: Track which documents/chunks informed your answer for citation.
-
- ## DoclingDocument API
-
- When you call `get_docling_document(id_or_title)`, you get a DoclingDocument object for structured document analysis.
-
- ### Properties
- - `doc.texts` - List of all text items (paragraphs, headings, etc.)
- - `doc.tables` - List of all tables
- - `doc.pictures` - List of all pictures/figures
- - `doc.name` - Document name
-
- ### Methods
- - `doc.iterate_items(with_groups=False)` - Iterate all items with hierarchy level
- Returns tuples of (item, level) where level is nesting depth
- - `doc.export_to_markdown()` - Export entire document as markdown string
-
- ### Text Item Properties
- - `item.text` - The text content
- - `item.label` - Type: title, paragraph, section_header, list_item, etc. (lowercase enum values)
- - `item.prov` - Provenance (page numbers, bounding boxes)
-
- ### Table Access
- - `table.data.num_rows`, `table.data.num_cols` - Dimensions
- - `table.data.table_cells` - List of TableCell objects
- - `cell.text`, `cell.start_row_offset_idx`, `cell.start_col_offset_idx`
-
- ### Example Usage
- ```python
- doc = get_docling_document("My Document")
-
- # Get all headings
- headings = [t.text for t in doc.texts if "header" in str(t.label)]
-
- # Iterate with structure
- for item, level in doc.iterate_items():
- print(" " * level + item.text[:50])
-
- # Extract table data
- for table in doc.tables:
- for cell in table.data.table_cells:
- print(f"Row {cell.start_row_offset_idx}, Col {cell.start_col_offset_idx}: {cell.text}")
- ```
+ 7. **Cite Your Sources**: Use get_chunk() to retrieve chunk metadata for citations. Track which documents/chunks informed your answer.
## Example Patterns
@@ -250,23 +213,31 @@ interactions:
print(f"Total: {count}")
```
- ### Aggregating data across documents
+ ### Extracting data with llm()
```python
- import re
numbers = []
results = search("financial data", limit=20)
for r in results:
- matches = re.findall(r'\$([\d,]+)', r['content'])
- for m in matches:
- numbers.append(int(m.replace(',', '')))
- print(f"Average: ${sum(numbers)/len(numbers):,.2f}")
+ extracted = llm(f"Extract all dollar amounts from this text as a comma-separated list of numbers (no $ signs): {r['content']}")
+ for part in extracted.split(','):
+ part = part.strip().replace(',', '')
+ if part.isdigit():
+ numbers.append(int(part))
+ if numbers:
+ print(f"Average: {sum(numbers) / len(numbers)}")
+ ```
+
+ ### Using search results with get_chunk for citations
+ ```python
+ results = search("safety requirements", limit=5)
+ for r in results:
+ chunk = get_chunk(r['chunk_id'])
+ print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}")
```
### Using llm() for classification
```python
- # Get document content
content = get_document("Q1 Report")
- # Use llm() to classify sentiment
sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
print(sentiment)
```
@@ -343,7 +314,7 @@ interactions:
response:
headers:
content-length:
- - '585'
+ - '552'
content-type:
- application/json
parsed_body:
@@ -352,24 +323,24 @@ interactions:
index: 0
message:
content: ''
- reasoning: We need to list documents via list_documents to count.
+ reasoning: Need to list documents.
role: assistant
tool_calls:
- function:
- arguments: '{"code":"docs = list_documents(limit=1000)\nprint(len(docs))\nfor d in docs:\n print(d[''title''])"}'
+ arguments: '{"code":"docs=list_documents(limit=1000)\nprint(len(docs))\nfor d in docs:\n print(d[''title''])"}'
name: execute_code
- id: call_1he6vvcy
+ id: call_tumky965
index: 0
type: function
- created: 1771336260
- id: chatcmpl-619
+ created: 1771336699
+ id: chatcmpl-910
model: gpt-oss
object: chat.completion
system_fingerprint: fp_ollama
usage:
- completion_tokens: 63
- prompt_tokens: 1734
- total_tokens: 1797
+ completion_tokens: 56
+ prompt_tokens: 1562
+ total_tokens: 1618
status:
code: 200
message: OK
@@ -382,7 +353,7 @@ interactions:
connection:
- keep-alive
content-length:
- - '8283'
+ - '7612'
content-type:
- application/json
host:
@@ -399,7 +370,7 @@ interactions:
- search("query") ✓ CORRECT
- from haiku.rag import search ✗ WRONG - will fail
- You have access to a sandboxed Python environment with these haiku.rag functions (use them directly, no imports needed):
+ You have access to a sandboxed Python interpreter with these haiku.rag functions (use them directly, no imports needed):
## Available Functions
@@ -415,10 +386,10 @@ interactions:
Get the full text content of a document by ID, title, or URI.
Returns the document content as a string, or None if not found.
- ### get_docling_document(id_or_title) -> DoclingDocument | None
- Get the structured DoclingDocument object for advanced analysis.
- Returns a DoclingDocument object, or None if not found.
- See "DoclingDocument API" section below for how to use it.
+ ### get_chunk(chunk_id) -> dict | None
+ Get a specific chunk by its ID (from search results).
+ Returns dict with keys: chunk_id, content, document_id, document_title, headings, page_numbers, labels
+ Use this to retrieve full chunk details and metadata for citation.
### llm(prompt) -> str
Call an LLM directly with the given prompt. Returns the response as a string.
@@ -435,8 +406,13 @@ interactions:
```
Check if it exists with: `if 'documents' in dir(): ...`
- ## Standard Library Modules
- You can import any Python standard library module.
+ ## Available Python Features
+
+ The interpreter supports: variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, try/except, and the `json` module.
+
+ Not supported: imports (other than `json`), class definitions, generators/yield, match statements, decorators, `with` statements.
+
+ For pattern matching or text extraction, use string methods (`str.split`, `str.find`, `str.startswith`, `in` operator) or the `llm()` function.
## Strategy Guide
@@ -444,51 +420,9 @@ interactions:
2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
- 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and use collections.
+ 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and data structures.
6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
- 7. **Cite Your Sources**: Track which documents/chunks informed your answer for citation.
-
- ## DoclingDocument API
-
- When you call `get_docling_document(id_or_title)`, you get a DoclingDocument object for structured document analysis.
-
- ### Properties
- - `doc.texts` - List of all text items (paragraphs, headings, etc.)
- - `doc.tables` - List of all tables
- - `doc.pictures` - List of all pictures/figures
- - `doc.name` - Document name
-
- ### Methods
- - `doc.iterate_items(with_groups=False)` - Iterate all items with hierarchy level
- Returns tuples of (item, level) where level is nesting depth
- - `doc.export_to_markdown()` - Export entire document as markdown string
-
- ### Text Item Properties
- - `item.text` - The text content
- - `item.label` - Type: title, paragraph, section_header, list_item, etc. (lowercase enum values)
- - `item.prov` - Provenance (page numbers, bounding boxes)
-
- ### Table Access
- - `table.data.num_rows`, `table.data.num_cols` - Dimensions
- - `table.data.table_cells` - List of TableCell objects
- - `cell.text`, `cell.start_row_offset_idx`, `cell.start_col_offset_idx`
-
- ### Example Usage
- ```python
- doc = get_docling_document("My Document")
-
- # Get all headings
- headings = [t.text for t in doc.texts if "header" in str(t.label)]
-
- # Iterate with structure
- for item, level in doc.iterate_items():
- print(" " * level + item.text[:50])
-
- # Extract table data
- for table in doc.tables:
- for cell in table.data.table_cells:
- print(f"Row {cell.start_row_offset_idx}, Col {cell.start_col_offset_idx}: {cell.text}")
- ```
+ 7. **Cite Your Sources**: Use get_chunk() to retrieve chunk metadata for citations. Track which documents/chunks informed your answer.
## Example Patterns
@@ -504,23 +438,31 @@ interactions:
print(f"Total: {count}")
```
- ### Aggregating data across documents
+ ### Extracting data with llm()
```python
- import re
numbers = []
results = search("financial data", limit=20)
for r in results:
- matches = re.findall(r'\$([\d,]+)', r['content'])
- for m in matches:
- numbers.append(int(m.replace(',', '')))
- print(f"Average: ${sum(numbers)/len(numbers):,.2f}")
+ extracted = llm(f"Extract all dollar amounts from this text as a comma-separated list of numbers (no $ signs): {r['content']}")
+ for part in extracted.split(','):
+ part = part.strip().replace(',', '')
+ if part.isdigit():
+ numbers.append(int(part))
+ if numbers:
+ print(f"Average: {sum(numbers) / len(numbers)}")
+ ```
+
+ ### Using search results with get_chunk for citations
+ ```python
+ results = search("safety requirements", limit=5)
+ for r in results:
+ chunk = get_chunk(r['chunk_id'])
+ print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}")
```
### Using llm() for classification
```python
- # Get document content
content = get_document("Q1 Report")
- # Use llm() to classify sentiment
sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
print(sentiment)
```
@@ -548,18 +490,18 @@ interactions:
- content: How many documents are in the database?
role: user
- content: null
- reasoning: We need to list documents via list_documents to count.
+ reasoning: Need to list documents.
role: assistant
tool_calls:
- function:
- arguments: '{"code":"docs = list_documents(limit=1000)\nprint(len(docs))\nfor d in docs:\n print(d[''title''])"}'
+ arguments: '{"code":"docs=list_documents(limit=1000)\nprint(len(docs))\nfor d in docs:\n print(d[''title''])"}'
name: execute_code
- id: call_1he6vvcy
+ id: call_tumky965
type: function
- - content: '{"code":"docs = list_documents(limit=1000)\nprint(len(docs))\nfor d in docs:\n print(d[''title''])","stdout":"3\nDoc
+ - content: '{"code":"docs=list_documents(limit=1000)\nprint(len(docs))\nfor d in docs:\n print(d[''title''])","stdout":"3\nDoc
1\nDoc 2\nDoc 3\n","stderr":"","success":true}'
role: tool
- tool_call_id: call_1he6vvcy
+ tool_call_id: call_tumky965
model: gpt-oss
reasoning_effort: low
stream: false
@@ -610,7 +552,7 @@ interactions:
response:
headers:
content-length:
- - '577'
+ - '517'
content-type:
- application/json
parsed_body:
@@ -618,19 +560,18 @@ interactions:
- finish_reason: stop
index: 0
message:
- content: '{"answer":"There are 3 documents in the database. They are titled: Doc 1, Doc 2, and Doc 3.","program":"#
- Count documents in the database\n\ndocs = list_documents(limit=1000)\nprint(f\"Number of documents: {len(docs)}\")\nfor
- d in docs:\n print(f\"- {d[''title'']}\")"}'
+ content: '{"answer":"There are 3 documents in the database.","program":"docs = list_documents(limit=1000)\nprint(f\"Number
+ of documents: {len(docs)}\")\nfor doc in docs:\n print(f\"- {doc[''title'']} (ID: {doc[''id'']})\")"}'
role: assistant
- created: 1771336262
- id: chatcmpl-256
+ created: 1771336701
+ id: chatcmpl-57
model: gpt-oss
object: chat.completion
system_fingerprint: fp_ollama
usage:
- completion_tokens: 91
- prompt_tokens: 1864
- total_tokens: 1955
+ completion_tokens: 82
+ prompt_tokens: 1684
+ total_tokens: 1766
status:
code: 200
message: OK
diff --git a/tests/cassettes/test_rlm/TestClientRLMIntegration.test_rlm_search_and_extract.yaml b/tests/cassettes/test_rlm/TestClientRLMIntegration.test_rlm_search_and_extract.yaml
index 2798044f..05633044 100644
--- a/tests/cassettes/test_rlm/TestClientRLMIntegration.test_rlm_search_and_extract.yaml
+++ b/tests/cassettes/test_rlm/TestClientRLMIntegration.test_rlm_search_and_extract.yaml
@@ -182,7 +182,7 @@ interactions:
connection:
- keep-alive
content-length:
- - '7814'
+ - '7178'
content-type:
- application/json
host:
@@ -199,7 +199,7 @@ interactions:
- search("query") ✓ CORRECT
- from haiku.rag import search ✗ WRONG - will fail
- You have access to a sandboxed Python environment with these haiku.rag functions (use them directly, no imports needed):
+ You have access to a sandboxed Python interpreter with these haiku.rag functions (use them directly, no imports needed):
## Available Functions
@@ -215,10 +215,10 @@ interactions:
Get the full text content of a document by ID, title, or URI.
Returns the document content as a string, or None if not found.
- ### get_docling_document(id_or_title) -> DoclingDocument | None
- Get the structured DoclingDocument object for advanced analysis.
- Returns a DoclingDocument object, or None if not found.
- See "DoclingDocument API" section below for how to use it.
+ ### get_chunk(chunk_id) -> dict | None
+ Get a specific chunk by its ID (from search results).
+ Returns dict with keys: chunk_id, content, document_id, document_title, headings, page_numbers, labels
+ Use this to retrieve full chunk details and metadata for citation.
### llm(prompt) -> str
Call an LLM directly with the given prompt. Returns the response as a string.
@@ -235,8 +235,13 @@ interactions:
```
Check if it exists with: `if 'documents' in dir(): ...`
- ## Standard Library Modules
- You can import any Python standard library module.
+ ## Available Python Features
+
+ The interpreter supports: variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, try/except, and the `json` module.
+
+ Not supported: imports (other than `json`), class definitions, generators/yield, match statements, decorators, `with` statements.
+
+ For pattern matching or text extraction, use string methods (`str.split`, `str.find`, `str.startswith`, `in` operator) or the `llm()` function.
## Strategy Guide
@@ -244,51 +249,9 @@ interactions:
2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
- 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and use collections.
+ 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and data structures.
6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
- 7. **Cite Your Sources**: Track which documents/chunks informed your answer for citation.
-
- ## DoclingDocument API
-
- When you call `get_docling_document(id_or_title)`, you get a DoclingDocument object for structured document analysis.
-
- ### Properties
- - `doc.texts` - List of all text items (paragraphs, headings, etc.)
- - `doc.tables` - List of all tables
- - `doc.pictures` - List of all pictures/figures
- - `doc.name` - Document name
-
- ### Methods
- - `doc.iterate_items(with_groups=False)` - Iterate all items with hierarchy level
- Returns tuples of (item, level) where level is nesting depth
- - `doc.export_to_markdown()` - Export entire document as markdown string
-
- ### Text Item Properties
- - `item.text` - The text content
- - `item.label` - Type: title, paragraph, section_header, list_item, etc. (lowercase enum values)
- - `item.prov` - Provenance (page numbers, bounding boxes)
-
- ### Table Access
- - `table.data.num_rows`, `table.data.num_cols` - Dimensions
- - `table.data.table_cells` - List of TableCell objects
- - `cell.text`, `cell.start_row_offset_idx`, `cell.start_col_offset_idx`
-
- ### Example Usage
- ```python
- doc = get_docling_document("My Document")
-
- # Get all headings
- headings = [t.text for t in doc.texts if "header" in str(t.label)]
-
- # Iterate with structure
- for item, level in doc.iterate_items():
- print(" " * level + item.text[:50])
-
- # Extract table data
- for table in doc.tables:
- for cell in table.data.table_cells:
- print(f"Row {cell.start_row_offset_idx}, Col {cell.start_col_offset_idx}: {cell.text}")
- ```
+ 7. **Cite Your Sources**: Use get_chunk() to retrieve chunk metadata for citations. Track which documents/chunks informed your answer.
## Example Patterns
@@ -304,23 +267,31 @@ interactions:
print(f"Total: {count}")
```
- ### Aggregating data across documents
+ ### Extracting data with llm()
```python
- import re
numbers = []
results = search("financial data", limit=20)
for r in results:
- matches = re.findall(r'\$([\d,]+)', r['content'])
- for m in matches:
- numbers.append(int(m.replace(',', '')))
- print(f"Average: ${sum(numbers)/len(numbers):,.2f}")
+ extracted = llm(f"Extract all dollar amounts from this text as a comma-separated list of numbers (no $ signs): {r['content']}")
+ for part in extracted.split(','):
+ part = part.strip().replace(',', '')
+ if part.isdigit():
+ numbers.append(int(part))
+ if numbers:
+ print(f"Average: {sum(numbers) / len(numbers)}")
+ ```
+
+ ### Using search results with get_chunk for citations
+ ```python
+ results = search("safety requirements", limit=5)
+ for r in results:
+ chunk = get_chunk(r['chunk_id'])
+ print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}")
```
### Using llm() for classification
```python
- # Get document content
content = get_document("Q1 Report")
- # Use llm() to classify sentiment
sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
print(sentiment)
```
@@ -398,7 +369,7 @@ interactions:
response:
headers:
content-length:
- - '666'
+ - '601'
content-type:
- application/json
parsed_body:
@@ -407,25 +378,25 @@ interactions:
index: 0
message:
content: ''
- reasoning: We need to search for content about document element types or labels. Use search.
+ reasoning: Need to search.
role: assistant
tool_calls:
- function:
- arguments: '{"code":"results = search(''document element types'', limit=20)\nfor r in results:\n print(r[''document_title''],
- r[''page_numbers''], r[''content''][:200])\n"}'
+ arguments: '{"code":"results = search(\"document element types\", limit=20)\nfor r in results:\n print(r[''chunk_id''],
+ r[''document_title''], r[''content''][:200])\n"}'
name: execute_code
- id: call_w56yspv7
+ id: call_a5m383mw
index: 0
type: function
- created: 1771336374
- id: chatcmpl-596
+ created: 1771336770
+ id: chatcmpl-12
model: gpt-oss
object: chat.completion
system_fingerprint: fp_ollama
usage:
- completion_tokens: 79
- prompt_tokens: 1750
- total_tokens: 1829
+ completion_tokens: 67
+ prompt_tokens: 1578
+ total_tokens: 1645
status:
code: 200
message: OK
@@ -478,7 +449,7 @@ interactions:
connection:
- keep-alive
content-length:
- - '12099'
+ - '11962'
content-type:
- application/json
host:
@@ -495,7 +466,7 @@ interactions:
- search("query") ✓ CORRECT
- from haiku.rag import search ✗ WRONG - will fail
- You have access to a sandboxed Python environment with these haiku.rag functions (use them directly, no imports needed):
+ You have access to a sandboxed Python interpreter with these haiku.rag functions (use them directly, no imports needed):
## Available Functions
@@ -511,10 +482,10 @@ interactions:
Get the full text content of a document by ID, title, or URI.
Returns the document content as a string, or None if not found.
- ### get_docling_document(id_or_title) -> DoclingDocument | None
- Get the structured DoclingDocument object for advanced analysis.
- Returns a DoclingDocument object, or None if not found.
- See "DoclingDocument API" section below for how to use it.
+ ### get_chunk(chunk_id) -> dict | None
+ Get a specific chunk by its ID (from search results).
+ Returns dict with keys: chunk_id, content, document_id, document_title, headings, page_numbers, labels
+ Use this to retrieve full chunk details and metadata for citation.
### llm(prompt) -> str
Call an LLM directly with the given prompt. Returns the response as a string.
@@ -531,8 +502,13 @@ interactions:
```
Check if it exists with: `if 'documents' in dir(): ...`
- ## Standard Library Modules
- You can import any Python standard library module.
+ ## Available Python Features
+
+ The interpreter supports: variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, try/except, and the `json` module.
+
+ Not supported: imports (other than `json`), class definitions, generators/yield, match statements, decorators, `with` statements.
+
+ For pattern matching or text extraction, use string methods (`str.split`, `str.find`, `str.startswith`, `in` operator) or the `llm()` function.
## Strategy Guide
@@ -540,51 +516,9 @@ interactions:
2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
- 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and use collections.
+ 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and data structures.
6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
- 7. **Cite Your Sources**: Track which documents/chunks informed your answer for citation.
-
- ## DoclingDocument API
-
- When you call `get_docling_document(id_or_title)`, you get a DoclingDocument object for structured document analysis.
-
- ### Properties
- - `doc.texts` - List of all text items (paragraphs, headings, etc.)
- - `doc.tables` - List of all tables
- - `doc.pictures` - List of all pictures/figures
- - `doc.name` - Document name
-
- ### Methods
- - `doc.iterate_items(with_groups=False)` - Iterate all items with hierarchy level
- Returns tuples of (item, level) where level is nesting depth
- - `doc.export_to_markdown()` - Export entire document as markdown string
-
- ### Text Item Properties
- - `item.text` - The text content
- - `item.label` - Type: title, paragraph, section_header, list_item, etc. (lowercase enum values)
- - `item.prov` - Provenance (page numbers, bounding boxes)
-
- ### Table Access
- - `table.data.num_rows`, `table.data.num_cols` - Dimensions
- - `table.data.table_cells` - List of TableCell objects
- - `cell.text`, `cell.start_row_offset_idx`, `cell.start_col_offset_idx`
-
- ### Example Usage
- ```python
- doc = get_docling_document("My Document")
-
- # Get all headings
- headings = [t.text for t in doc.texts if "header" in str(t.label)]
-
- # Iterate with structure
- for item, level in doc.iterate_items():
- print(" " * level + item.text[:50])
-
- # Extract table data
- for table in doc.tables:
- for cell in table.data.table_cells:
- print(f"Row {cell.start_row_offset_idx}, Col {cell.start_col_offset_idx}: {cell.text}")
- ```
+ 7. **Cite Your Sources**: Use get_chunk() to retrieve chunk metadata for citations. Track which documents/chunks informed your answer.
## Example Patterns
@@ -600,23 +534,31 @@ interactions:
print(f"Total: {count}")
```
- ### Aggregating data across documents
+ ### Extracting data with llm()
```python
- import re
numbers = []
results = search("financial data", limit=20)
for r in results:
- matches = re.findall(r'\$([\d,]+)', r['content'])
- for m in matches:
- numbers.append(int(m.replace(',', '')))
- print(f"Average: ${sum(numbers)/len(numbers):,.2f}")
+ extracted = llm(f"Extract all dollar amounts from this text as a comma-separated list of numbers (no $ signs): {r['content']}")
+ for part in extracted.split(','):
+ part = part.strip().replace(',', '')
+ if part.isdigit():
+ numbers.append(int(part))
+ if numbers:
+ print(f"Average: {sum(numbers) / len(numbers)}")
+ ```
+
+ ### Using search results with get_chunk for citations
+ ```python
+ results = search("safety requirements", limit=5)
+ for r in results:
+ chunk = get_chunk(r['chunk_id'])
+ print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}")
```
### Using llm() for classification
```python
- # Get document content
content = get_document("Q1 Report")
- # Use llm() to classify sentiment
sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
print(sentiment)
```
@@ -645,50 +587,53 @@ interactions:
mentioned? List them all.
role: user
- content: null
- reasoning: We need to search for content about document element types or labels. Use search.
+ reasoning: Need to search.
role: assistant
tool_calls:
- function:
- arguments: '{"code":"results = search(''document element types'', limit=20)\nfor r in results:\n print(r[''document_title''],
- r[''page_numbers''], r[''content''][:200])\n"}'
+ arguments: '{"code":"results = search(\"document element types\", limit=20)\nfor r in results:\n print(r[''chunk_id''],
+ r[''document_title''], r[''content''][:200])\n"}'
name: execute_code
- id: call_w56yspv7
+ id: call_a5m383mw
type: function
- - content: '{"code":"results = search(''document element types'', limit=20)\nfor r in results:\n print(r[''document_title''],
- r[''page_numbers''], r[''content''][:200])\n","stdout":"None [1] Phase 2: Label selection and guideline. We reviewed
- the collected documents and identified the most common structural features they exhibit. This was achieved by identifying
- recurrent layout elements \nNone [1] Phase 1: Data selection and preparation. Our inclusion criteria for documents
- were described in Section 3. A large effort went into ensuring that all documents are free to use. The data sources
- includ\nNone [1] $_{Affiliation}$, as seen in DocBank, are often only distinguishable by discriminating on\nPreparation
- work included uploading and parsing the sourced PDF documents in the Corpus Conversion Service (CC\nNone [1] \nTable
- 1: DocLayNet dataset overview. Along with the frequency of each class label, we present the relative occurrence
- (as % of row \"Total\") in the train, test and validation sets. The inter-annotator\nNone [1] Page-footer, triple
- inter-annotator mAP @ 0.5-0.95 (%).Ten = 96-98. Page-header, Count = 58022. Page-header, % of Total.Train = 5.10.
- Page-header, % of Total.Test = 6.70. Page-header, % of Total.Val =\nNone [1] 0.5-0.95 (%).Law = 87-94. Section-header,
- triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 69-73. Section-header, triple inter-annotator mAP @ 0.5-0.95 (%).Ten
- = 78-86. Table, Count = 34733. Table, % o\nNone [1] Caption, Count = 22524. Caption, % of Total.Train = 2.04. Caption,
- % of Total.Test = 1.77. Caption, % of Total.Val = 2.32. Caption, triple inter-annotator mAP @ 0.5-0.95 (%).All =
- 84-89. Caption, trip\nNone [1] inter-annotator mAP @ 0.5-0.95 (%).Pat = 66-71. Picture, triple inter-annotator mAP
- @ 0.5-0.95 (%).Ten = 59-76. Section-header, Count = 142884. Section-header, % of Total.Train = 12.60. Section-header\nNone
- [1] n/a. Footnote, Count = 6318. Footnote, % of Total.Train = 0.60. Footnote, % of Total.Test = 0.31. Footnote,
- % of Total.Val = 0.58. Footnote, triple inter-annotator mAP @ 0.5-0.95 (%).All = 83-91. Foot\nNone [1] 185660. List-item,
- % of Total.Train = 17.19. List-item, % of Total.Test = 13.34. List-item, % of Total.Val = 15.82. List-item, triple
- inter-annotator mAP @ 0.5-0.95 (%).All = 87-88. List-item, triple \nNone [1] 0.5-0.95 (%).Ten = 93-95. Page-footer,
- Count = 70878. Page-footer, % of Total.Train = 6.51. Page-footer, % of Total.Test = 5.58. Page-footer, % of Total.Val
- = 6.00. Page-footer, triple inter-annotator\nNone [1] 89-93. Text, triple inter-annotator mAP @ 0.5-0.95 (%).Law
- = 87-92. Text, triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 71-79. Text, triple inter-annotator mAP @ 0.5-0.95
- (%).Ten = 87-95. Title, Cou\nNone [1] 0.5-0.95 (%).Pat = 97-99. Page-header, triple inter-annotator mAP @ 0.5-0.95
- (%).Ten = 81-86. Picture, Count = 45976. Picture, % of Total.Train = 4.21. Picture, % of Total.Test = 2.78. Picture,
- % of T\nNone [1] (%).Sci = 94-100. Title, triple inter-annotator mAP @ 0.5-0.95 (%).Law = 82-96. Title, triple inter-annotator
- mAP @ 0.5-0.95 (%).Pat = 68-79. Title, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 24-\nNone [1] inter-annotator
- mAP @ 0.5-0.95 (%).Law = 58-80. Table, triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 79-84. Table, triple inter-annotator
- mAP @ 0.5-0.95 (%).Ten = 70-85. Text, Count = 510377. Text, \nNone [1] = 82-97. Formula, Count = 25027. Formula,
- % of Total.Train = 2.25. Formula, % of Total.Test = 1.90. Formula, % of Total.Val = 2.96. Formula, triple inter-annotator
- mAP @ 0.5-0.95 (%).All = 83-85. Form\nNone [1] mAP @ 0.5-0.95 (%).Sci = 89-94. Total, triple inter-annotator mAP
- @ 0.5-0.95 (%).Law = 86-91. Total, triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 71-76. Total, triple inter-annotator
- mAP @ 0.5-0.95\n","stderr":"","success":true}'
+ - content: '{"code":"results = search(\"document element types\", limit=20)\nfor r in results:\n print(r[''chunk_id''],
+ r[''document_title''], r[''content''][:200])\n","stdout":"baf79253-e710-41e4-9afc-cd53a58b14e6 None Phase 2: Label
+ selection and guideline. We reviewed the collected documents and identified the most common structural features
+ they exhibit. This was achieved by identifying recurrent layout elements \n6377d621-2597-4bda-ba58-11dc72b87477
+ None Phase 1: Data selection and preparation. Our inclusion criteria for documents were described in Section 3.
+ A large effort went into ensuring that all documents are free to use. The data sources includ\n1cbffceb-fa16-434f-a168-54ad860b1c21
+ None $_{Affiliation}$, as seen in DocBank, are often only distinguishable by discriminating on\nPreparation work
+ included uploading and parsing the sourced PDF documents in the Corpus Conversion Service (CC\n44a5a4d6-7f42-46d2-9115-4dda6b6d62ee
+ None \nTable 1: DocLayNet dataset overview. Along with the frequency of each class label, we present the relative
+ occurrence (as % of row \"Total\") in the train, test and validation sets. The inter-annotator\nfe743e25-0740-423c-9f26-d42e658269f2
+ None Page-footer, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 96-98. Page-header, Count = 58022. Page-header,
+ % of Total.Train = 5.10. Page-header, % of Total.Test = 6.70. Page-header, % of Total.Val =\ned730761-c984-419a-9fdd-06fd37aa632a
+ None 0.5-0.95 (%).Law = 87-94. Section-header, triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 69-73. Section-header,
+ triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 78-86. Table, Count = 34733. Table, % o\na669db88-1bd8-46f3-87ec-078b74151258
+ None Caption, Count = 22524. Caption, % of Total.Train = 2.04. Caption, % of Total.Test = 1.77. Caption, % of Total.Val
+ = 2.32. Caption, triple inter-annotator mAP @ 0.5-0.95 (%).All = 84-89. Caption, trip\n38cf7b9f-3947-45f2-a66a-5ef4d89a88c2
+ None inter-annotator mAP @ 0.5-0.95 (%).Pat = 66-71. Picture, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 59-76.
+ Section-header, Count = 142884. Section-header, % of Total.Train = 12.60. Section-header\n26781f97-2e94-4270-85e7-84670a7971f2
+ None n/a. Footnote, Count = 6318. Footnote, % of Total.Train = 0.60. Footnote, % of Total.Test = 0.31. Footnote,
+ % of Total.Val = 0.58. Footnote, triple inter-annotator mAP @ 0.5-0.95 (%).All = 83-91. Foot\n1eb6520a-3d81-4113-a8b1-4a045bdd581d
+ None 185660. List-item, % of Total.Train = 17.19. List-item, % of Total.Test = 13.34. List-item, % of Total.Val
+ = 15.82. List-item, triple inter-annotator mAP @ 0.5-0.95 (%).All = 87-88. List-item, triple \nec1f7ead-7b8c-46f7-926b-f0d6c942ad02
+ None 0.5-0.95 (%).Ten = 93-95. Page-footer, Count = 70878. Page-footer, % of Total.Train = 6.51. Page-footer, %
+ of Total.Test = 5.58. Page-footer, % of Total.Val = 6.00. Page-footer, triple inter-annotator\n8e924800-19a6-4096-8e36-c211ac858ffa
+ None 89-93. Text, triple inter-annotator mAP @ 0.5-0.95 (%).Law = 87-92. Text, triple inter-annotator mAP @ 0.5-0.95
+ (%).Pat = 71-79. Text, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 87-95. Title, Cou\na36c2e35-4c45-462f-8997-d800667e2c5c
+ None 0.5-0.95 (%).Pat = 97-99. Page-header, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 81-86. Picture, Count
+ = 45976. Picture, % of Total.Train = 4.21. Picture, % of Total.Test = 2.78. Picture, % of T\nd0d1d4b6-bd81-42f4-a9d1-443f8869defd
+ None (%).Sci = 94-100. Title, triple inter-annotator mAP @ 0.5-0.95 (%).Law = 82-96. Title, triple inter-annotator
+ mAP @ 0.5-0.95 (%).Pat = 68-79. Title, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 24-\n3cdae3b8-d62e-497a-908b-dac06a9b87a8
+ None inter-annotator mAP @ 0.5-0.95 (%).Law = 58-80. Table, triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 79-84.
+ Table, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 70-85. Text, Count = 510377. Text, \n525a5cca-93ee-45be-8d67-f3e54286a80f
+ None = 82-97. Formula, Count = 25027. Formula, % of Total.Train = 2.25. Formula, % of Total.Test = 1.90. Formula,
+ % of Total.Val = 2.96. Formula, triple inter-annotator mAP @ 0.5-0.95 (%).All = 83-85. Form\n5667fabf-8421-4004-bccf-9ce1b100f49a
+ None mAP @ 0.5-0.95 (%).Sci = 89-94. Total, triple inter-annotator mAP @ 0.5-0.95 (%).Law = 86-91. Total, triple
+ inter-annotator mAP @ 0.5-0.95 (%).Pat = 71-76. Total, triple inter-annotator mAP @ 0.5-0.95\n","stderr":"","success":true}'
role: tool
- tool_call_id: call_w56yspv7
+ tool_call_id: call_a5m383mw
model: gpt-oss
reasoning_effort: low
stream: false
@@ -739,7 +684,7 @@ interactions:
response:
headers:
content-length:
- - '945'
+ - '817'
content-type:
- application/json
parsed_body:
@@ -748,27 +693,27 @@ interactions:
index: 0
message:
content: ''
- reasoning: 'We see many types: Page-header, Page-footer, Section-header, Caption, Picture, List-item, Footnote,
- Text, Title, Formula etc. Let''s extract unique types. Probably need to parse for words before comma. Use regex.'
+ reasoning: 'We need list of element types. From snippets: Page-footer, Page-header, Section-header, Table, Caption,
+ Picture, Footnote, List-item, Text, Title, Formula. Also maybe others like List, Equation? Let''s search for "element
+ types" keyword.'
role: assistant
tool_calls:
- function:
- arguments: '{"code":"import re\nresults = search(''document element types'', limit=50)\ntypes=set()\nfor r in
- results:\n for line in r[''content''].split('',''):\n m = re.match(r''\\s*(\\w[\\w-]*)(?:\\s*[,\\.]|$)'',
- line)\n if m:\n types.add(m.group(1))\nprint(sorted(types))\n"}'
+ arguments: '{"code":"results = search(\"element types\", limit=20)\nfor r in results:\n print(r[''chunk_id''],
+ r[''document_title''], r[''content''][:200])\n"}'
name: execute_code
- id: call_bt2tgwxs
+ id: call_5sejw3xj
index: 0
type: function
- created: 1771336380
- id: chatcmpl-437
+ created: 1771336775
+ id: chatcmpl-749
model: gpt-oss
object: chat.completion
system_fingerprint: fp_ollama
usage:
- completion_tokens: 162
- prompt_tokens: 3198
- total_tokens: 3360
+ completion_tokens: 116
+ prompt_tokens: 3346
+ total_tokens: 3462
status:
code: 200
message: OK
@@ -781,1015 +726,7 @@ interactions:
connection:
- keep-alive
content-length:
- - '13238'
- content-type:
- - application/json
- host:
- - localhost:11434
- method: POST
- parsed_body:
- messages:
- - content: |-
- You are a Recursive Language Model (RLM) agent that solves complex research questions by writing and executing Python code.
-
- IMPORTANT: You MUST use the `execute_code` tool to run Python code. The functions described below are ONLY available inside the execute_code tool - you cannot access them any other way. Always execute code to answer questions; do not just describe what code would do.
-
- CRITICAL: Inside execute_code, these functions are ALREADY available in the namespace. Do NOT import them - just use them directly:
- - search("query") ✓ CORRECT
- - from haiku.rag import search ✗ WRONG - will fail
-
- You have access to a sandboxed Python environment with these haiku.rag functions (use them directly, no imports needed):
-
- ## Available Functions
-
- ### search(query, limit=10) -> list[dict]
- Search the knowledge base using hybrid search (vector + full-text).
- Returns list of dicts with keys: chunk_id, content, document_id, document_title, document_uri, score, page_numbers, headings
-
- ### list_documents(limit=10, offset=0) -> list[dict]
- List available documents in the knowledge base.
- Returns list of dicts with keys: id, title, uri, created_at
-
- ### get_document(id_or_title) -> str | None
- Get the full text content of a document by ID, title, or URI.
- Returns the document content as a string, or None if not found.
-
- ### get_docling_document(id_or_title) -> DoclingDocument | None
- Get the structured DoclingDocument object for advanced analysis.
- Returns a DoclingDocument object, or None if not found.
- See "DoclingDocument API" section below for how to use it.
-
- ### llm(prompt) -> str
- Call an LLM directly with the given prompt. Returns the response as a string.
- Use this for classification, summarization, extraction, or any task where you
- already have the content and just need LLM reasoning.
-
- ## Pre-loaded Documents Variable
-
- If documents were pre-loaded for this session, a `documents` variable is available:
- ```python
- # documents is a list of dicts with keys: id, title, uri, content
- for doc in documents:
- print(doc['title'], len(doc['content']))
- ```
- Check if it exists with: `if 'documents' in dir(): ...`
-
- ## Standard Library Modules
- You can import any Python standard library module.
-
- ## Strategy Guide
-
- 1. **Explore First**: Start by listing documents or searching to understand what's available. Document names may differ from filenames (e.g., "tbmed593.pdf" might be stored as "TB MED 593" or similar).
- 2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
- 3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
- 4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
- 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and use collections.
- 6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
- 7. **Cite Your Sources**: Track which documents/chunks informed your answer for citation.
-
- ## DoclingDocument API
-
- When you call `get_docling_document(id_or_title)`, you get a DoclingDocument object for structured document analysis.
-
- ### Properties
- - `doc.texts` - List of all text items (paragraphs, headings, etc.)
- - `doc.tables` - List of all tables
- - `doc.pictures` - List of all pictures/figures
- - `doc.name` - Document name
-
- ### Methods
- - `doc.iterate_items(with_groups=False)` - Iterate all items with hierarchy level
- Returns tuples of (item, level) where level is nesting depth
- - `doc.export_to_markdown()` - Export entire document as markdown string
-
- ### Text Item Properties
- - `item.text` - The text content
- - `item.label` - Type: title, paragraph, section_header, list_item, etc. (lowercase enum values)
- - `item.prov` - Provenance (page numbers, bounding boxes)
-
- ### Table Access
- - `table.data.num_rows`, `table.data.num_cols` - Dimensions
- - `table.data.table_cells` - List of TableCell objects
- - `cell.text`, `cell.start_row_offset_idx`, `cell.start_col_offset_idx`
-
- ### Example Usage
- ```python
- doc = get_docling_document("My Document")
-
- # Get all headings
- headings = [t.text for t in doc.texts if "header" in str(t.label)]
-
- # Iterate with structure
- for item, level in doc.iterate_items():
- print(" " * level + item.text[:50])
-
- # Extract table data
- for table in doc.tables:
- for cell in table.data.table_cells:
- print(f"Row {cell.start_row_offset_idx}, Col {cell.start_col_offset_idx}: {cell.text}")
- ```
-
- ## Example Patterns
-
- ### Counting documents matching a condition
- ```python
- docs = list_documents(limit=100)
- count = 0
- for doc in docs:
- content = get_document(doc['id'])
- if content and 'keyword' in content.lower():
- count += 1
- print(f"Found in: {doc['title']}")
- print(f"Total: {count}")
- ```
-
- ### Aggregating data across documents
- ```python
- import re
- numbers = []
- results = search("financial data", limit=20)
- for r in results:
- matches = re.findall(r'\$([\d,]+)', r['content'])
- for m in matches:
- numbers.append(int(m.replace(',', '')))
- print(f"Average: ${sum(numbers)/len(numbers):,.2f}")
- ```
-
- ### Using llm() for classification
- ```python
- # Get document content
- content = get_document("Q1 Report")
- # Use llm() to classify sentiment
- sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
- print(sentiment)
- ```
-
- ## Workflow
-
- 1. **ALWAYS start by using execute_code** to explore the knowledge base
- 2. Run multiple code blocks as needed to gather information
- 3. After collecting data, provide your final answer
-
- ## Output Format
-
- CRITICAL: Your final response MUST be valid JSON matching this exact schema:
- ```json
- {"answer": "Your complete answer here as a string", "program": "Your final consolidated program here as a string"}
- ```
-
- - `answer`: A clear answer to the user's question with key findings and references to specific documents/chunks.
- - `program`: A single, self-contained Python program that produces the answer. Consolidate your exploratory code executions into one clean script.
-
- Do NOT return arbitrary JSON structures. Always use the exact format: {"answer": "...", "program": "..."}
-
- CRITICAL: You MUST call execute_code at least once before providing your answer. Never give up without trying to execute code first.
- role: system
- - content: Search for content about document element types or labels. What are all the different document element types
- mentioned? List them all.
- role: user
- - content: null
- reasoning: We need to search for content about document element types or labels. Use search.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"results = search(''document element types'', limit=20)\nfor r in results:\n print(r[''document_title''],
- r[''page_numbers''], r[''content''][:200])\n"}'
- name: execute_code
- id: call_w56yspv7
- type: function
- - content: '{"code":"results = search(''document element types'', limit=20)\nfor r in results:\n print(r[''document_title''],
- r[''page_numbers''], r[''content''][:200])\n","stdout":"None [1] Phase 2: Label selection and guideline. We reviewed
- the collected documents and identified the most common structural features they exhibit. This was achieved by identifying
- recurrent layout elements \nNone [1] Phase 1: Data selection and preparation. Our inclusion criteria for documents
- were described in Section 3. A large effort went into ensuring that all documents are free to use. The data sources
- includ\nNone [1] $_{Affiliation}$, as seen in DocBank, are often only distinguishable by discriminating on\nPreparation
- work included uploading and parsing the sourced PDF documents in the Corpus Conversion Service (CC\nNone [1] \nTable
- 1: DocLayNet dataset overview. Along with the frequency of each class label, we present the relative occurrence
- (as % of row \"Total\") in the train, test and validation sets. The inter-annotator\nNone [1] Page-footer, triple
- inter-annotator mAP @ 0.5-0.95 (%).Ten = 96-98. Page-header, Count = 58022. Page-header, % of Total.Train = 5.10.
- Page-header, % of Total.Test = 6.70. Page-header, % of Total.Val =\nNone [1] 0.5-0.95 (%).Law = 87-94. Section-header,
- triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 69-73. Section-header, triple inter-annotator mAP @ 0.5-0.95 (%).Ten
- = 78-86. Table, Count = 34733. Table, % o\nNone [1] Caption, Count = 22524. Caption, % of Total.Train = 2.04. Caption,
- % of Total.Test = 1.77. Caption, % of Total.Val = 2.32. Caption, triple inter-annotator mAP @ 0.5-0.95 (%).All =
- 84-89. Caption, trip\nNone [1] inter-annotator mAP @ 0.5-0.95 (%).Pat = 66-71. Picture, triple inter-annotator mAP
- @ 0.5-0.95 (%).Ten = 59-76. Section-header, Count = 142884. Section-header, % of Total.Train = 12.60. Section-header\nNone
- [1] n/a. Footnote, Count = 6318. Footnote, % of Total.Train = 0.60. Footnote, % of Total.Test = 0.31. Footnote,
- % of Total.Val = 0.58. Footnote, triple inter-annotator mAP @ 0.5-0.95 (%).All = 83-91. Foot\nNone [1] 185660. List-item,
- % of Total.Train = 17.19. List-item, % of Total.Test = 13.34. List-item, % of Total.Val = 15.82. List-item, triple
- inter-annotator mAP @ 0.5-0.95 (%).All = 87-88. List-item, triple \nNone [1] 0.5-0.95 (%).Ten = 93-95. Page-footer,
- Count = 70878. Page-footer, % of Total.Train = 6.51. Page-footer, % of Total.Test = 5.58. Page-footer, % of Total.Val
- = 6.00. Page-footer, triple inter-annotator\nNone [1] 89-93. Text, triple inter-annotator mAP @ 0.5-0.95 (%).Law
- = 87-92. Text, triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 71-79. Text, triple inter-annotator mAP @ 0.5-0.95
- (%).Ten = 87-95. Title, Cou\nNone [1] 0.5-0.95 (%).Pat = 97-99. Page-header, triple inter-annotator mAP @ 0.5-0.95
- (%).Ten = 81-86. Picture, Count = 45976. Picture, % of Total.Train = 4.21. Picture, % of Total.Test = 2.78. Picture,
- % of T\nNone [1] (%).Sci = 94-100. Title, triple inter-annotator mAP @ 0.5-0.95 (%).Law = 82-96. Title, triple inter-annotator
- mAP @ 0.5-0.95 (%).Pat = 68-79. Title, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 24-\nNone [1] inter-annotator
- mAP @ 0.5-0.95 (%).Law = 58-80. Table, triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 79-84. Table, triple inter-annotator
- mAP @ 0.5-0.95 (%).Ten = 70-85. Text, Count = 510377. Text, \nNone [1] = 82-97. Formula, Count = 25027. Formula,
- % of Total.Train = 2.25. Formula, % of Total.Test = 1.90. Formula, % of Total.Val = 2.96. Formula, triple inter-annotator
- mAP @ 0.5-0.95 (%).All = 83-85. Form\nNone [1] mAP @ 0.5-0.95 (%).Sci = 89-94. Total, triple inter-annotator mAP
- @ 0.5-0.95 (%).Law = 86-91. Total, triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 71-76. Total, triple inter-annotator
- mAP @ 0.5-0.95\n","stderr":"","success":true}'
- role: tool
- tool_call_id: call_w56yspv7
- - content: null
- reasoning: 'We see many types: Page-header, Page-footer, Section-header, Caption, Picture, List-item, Footnote, Text,
- Title, Formula etc. Let''s extract unique types. Probably need to parse for words before comma. Use regex.'
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"import re\nresults = search(''document element types'', limit=50)\ntypes=set()\nfor r in
- results:\n for line in r[''content''].split('',''):\n m = re.match(r''\\s*(\\w[\\w-]*)(?:\\s*[,\\.]|$)'',
- line)\n if m:\n types.add(m.group(1))\nprint(sorted(types))\n"}'
- name: execute_code
- id: call_bt2tgwxs
- type: function
- - content: '{"code":"import re\nresults = search(''document element types'', limit=50)\ntypes=set()\nfor r in results:\n for
- line in r[''content''].split('',''):\n m = re.match(r''\\s*(\\w[\\w-]*)(?:\\s*[,\\.]|$)'', line)\n if
- m:\n types.add(m.group(1))\nprint(sorted(types))\n","stdout":"","stderr":"ModuleNotFoundError: No module
- named ''re''","success":false}'
- role: tool
- tool_call_id: call_bt2tgwxs
- model: gpt-oss
- reasoning_effort: low
- stream: false
- tool_choice: auto
- tools:
- - function:
- description: |-
- Execute Python code in a sandboxed interpreter.
-
- The code has access to haiku.rag functions (search, list_documents,
- get_document, get_chunk, llm).
-
- Use print() to output results.
-
- Structured result with success status, stdout, and stderr.
-
- name: execute_code
- parameters:
- additionalProperties: false
- properties:
- code:
- description: Python code to execute.
- type: string
- required:
- - code
- type: object
- strict: true
- type: function
- - function:
- description: Result from RLM agent execution.
- name: final_result
- parameters:
- additionalProperties: false
- properties:
- answer:
- description: The answer to the user's question
- type: string
- program:
- description: The final consolidated program
- type: string
- required:
- - answer
- - program
- type: object
- strict: true
- type: function
- uri: http://localhost:11434/v1/chat/completions
- response:
- headers:
- content-length:
- - '749'
- content-type:
- - application/json
- parsed_body:
- choices:
- - finish_reason: tool_calls
- index: 0
- message:
- content: ''
- reasoning: Need import re.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"import re\nresults = search(''document element types'', limit=50)\ntypes=set()\nfor r in
- results:\n for line in r[''content''].split('',''):\n m = re.match(r''\\s*(\\w[\\w-]*)(?:\\s*[,\\.]|$)'',
- line)\n if m:\n types.add(m.group(1))\nprint(sorted(types))\n"}'
- name: execute_code
- id: call_iflcvbbl
- index: 0
- type: function
- created: 1771336383
- id: chatcmpl-954
- model: gpt-oss
- object: chat.completion
- system_fingerprint: fp_ollama
- usage:
- completion_tokens: 118
- prompt_tokens: 3486
- total_tokens: 3604
- status:
- code: 200
- message: OK
-- request:
- headers:
- accept:
- - application/json
- accept-encoding:
- - gzip, deflate, zstd
- connection:
- - keep-alive
- content-length:
- - '14181'
- content-type:
- - application/json
- host:
- - localhost:11434
- method: POST
- parsed_body:
- messages:
- - content: |-
- You are a Recursive Language Model (RLM) agent that solves complex research questions by writing and executing Python code.
-
- IMPORTANT: You MUST use the `execute_code` tool to run Python code. The functions described below are ONLY available inside the execute_code tool - you cannot access them any other way. Always execute code to answer questions; do not just describe what code would do.
-
- CRITICAL: Inside execute_code, these functions are ALREADY available in the namespace. Do NOT import them - just use them directly:
- - search("query") ✓ CORRECT
- - from haiku.rag import search ✗ WRONG - will fail
-
- You have access to a sandboxed Python environment with these haiku.rag functions (use them directly, no imports needed):
-
- ## Available Functions
-
- ### search(query, limit=10) -> list[dict]
- Search the knowledge base using hybrid search (vector + full-text).
- Returns list of dicts with keys: chunk_id, content, document_id, document_title, document_uri, score, page_numbers, headings
-
- ### list_documents(limit=10, offset=0) -> list[dict]
- List available documents in the knowledge base.
- Returns list of dicts with keys: id, title, uri, created_at
-
- ### get_document(id_or_title) -> str | None
- Get the full text content of a document by ID, title, or URI.
- Returns the document content as a string, or None if not found.
-
- ### get_docling_document(id_or_title) -> DoclingDocument | None
- Get the structured DoclingDocument object for advanced analysis.
- Returns a DoclingDocument object, or None if not found.
- See "DoclingDocument API" section below for how to use it.
-
- ### llm(prompt) -> str
- Call an LLM directly with the given prompt. Returns the response as a string.
- Use this for classification, summarization, extraction, or any task where you
- already have the content and just need LLM reasoning.
-
- ## Pre-loaded Documents Variable
-
- If documents were pre-loaded for this session, a `documents` variable is available:
- ```python
- # documents is a list of dicts with keys: id, title, uri, content
- for doc in documents:
- print(doc['title'], len(doc['content']))
- ```
- Check if it exists with: `if 'documents' in dir(): ...`
-
- ## Standard Library Modules
- You can import any Python standard library module.
-
- ## Strategy Guide
-
- 1. **Explore First**: Start by listing documents or searching to understand what's available. Document names may differ from filenames (e.g., "tbmed593.pdf" might be stored as "TB MED 593" or similar).
- 2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
- 3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
- 4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
- 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and use collections.
- 6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
- 7. **Cite Your Sources**: Track which documents/chunks informed your answer for citation.
-
- ## DoclingDocument API
-
- When you call `get_docling_document(id_or_title)`, you get a DoclingDocument object for structured document analysis.
-
- ### Properties
- - `doc.texts` - List of all text items (paragraphs, headings, etc.)
- - `doc.tables` - List of all tables
- - `doc.pictures` - List of all pictures/figures
- - `doc.name` - Document name
-
- ### Methods
- - `doc.iterate_items(with_groups=False)` - Iterate all items with hierarchy level
- Returns tuples of (item, level) where level is nesting depth
- - `doc.export_to_markdown()` - Export entire document as markdown string
-
- ### Text Item Properties
- - `item.text` - The text content
- - `item.label` - Type: title, paragraph, section_header, list_item, etc. (lowercase enum values)
- - `item.prov` - Provenance (page numbers, bounding boxes)
-
- ### Table Access
- - `table.data.num_rows`, `table.data.num_cols` - Dimensions
- - `table.data.table_cells` - List of TableCell objects
- - `cell.text`, `cell.start_row_offset_idx`, `cell.start_col_offset_idx`
-
- ### Example Usage
- ```python
- doc = get_docling_document("My Document")
-
- # Get all headings
- headings = [t.text for t in doc.texts if "header" in str(t.label)]
-
- # Iterate with structure
- for item, level in doc.iterate_items():
- print(" " * level + item.text[:50])
-
- # Extract table data
- for table in doc.tables:
- for cell in table.data.table_cells:
- print(f"Row {cell.start_row_offset_idx}, Col {cell.start_col_offset_idx}: {cell.text}")
- ```
-
- ## Example Patterns
-
- ### Counting documents matching a condition
- ```python
- docs = list_documents(limit=100)
- count = 0
- for doc in docs:
- content = get_document(doc['id'])
- if content and 'keyword' in content.lower():
- count += 1
- print(f"Found in: {doc['title']}")
- print(f"Total: {count}")
- ```
-
- ### Aggregating data across documents
- ```python
- import re
- numbers = []
- results = search("financial data", limit=20)
- for r in results:
- matches = re.findall(r'\$([\d,]+)', r['content'])
- for m in matches:
- numbers.append(int(m.replace(',', '')))
- print(f"Average: ${sum(numbers)/len(numbers):,.2f}")
- ```
-
- ### Using llm() for classification
- ```python
- # Get document content
- content = get_document("Q1 Report")
- # Use llm() to classify sentiment
- sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
- print(sentiment)
- ```
-
- ## Workflow
-
- 1. **ALWAYS start by using execute_code** to explore the knowledge base
- 2. Run multiple code blocks as needed to gather information
- 3. After collecting data, provide your final answer
-
- ## Output Format
-
- CRITICAL: Your final response MUST be valid JSON matching this exact schema:
- ```json
- {"answer": "Your complete answer here as a string", "program": "Your final consolidated program here as a string"}
- ```
-
- - `answer`: A clear answer to the user's question with key findings and references to specific documents/chunks.
- - `program`: A single, self-contained Python program that produces the answer. Consolidate your exploratory code executions into one clean script.
-
- Do NOT return arbitrary JSON structures. Always use the exact format: {"answer": "...", "program": "..."}
-
- CRITICAL: You MUST call execute_code at least once before providing your answer. Never give up without trying to execute code first.
- role: system
- - content: Search for content about document element types or labels. What are all the different document element types
- mentioned? List them all.
- role: user
- - content: null
- reasoning: We need to search for content about document element types or labels. Use search.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"results = search(''document element types'', limit=20)\nfor r in results:\n print(r[''document_title''],
- r[''page_numbers''], r[''content''][:200])\n"}'
- name: execute_code
- id: call_w56yspv7
- type: function
- - content: '{"code":"results = search(''document element types'', limit=20)\nfor r in results:\n print(r[''document_title''],
- r[''page_numbers''], r[''content''][:200])\n","stdout":"None [1] Phase 2: Label selection and guideline. We reviewed
- the collected documents and identified the most common structural features they exhibit. This was achieved by identifying
- recurrent layout elements \nNone [1] Phase 1: Data selection and preparation. Our inclusion criteria for documents
- were described in Section 3. A large effort went into ensuring that all documents are free to use. The data sources
- includ\nNone [1] $_{Affiliation}$, as seen in DocBank, are often only distinguishable by discriminating on\nPreparation
- work included uploading and parsing the sourced PDF documents in the Corpus Conversion Service (CC\nNone [1] \nTable
- 1: DocLayNet dataset overview. Along with the frequency of each class label, we present the relative occurrence
- (as % of row \"Total\") in the train, test and validation sets. The inter-annotator\nNone [1] Page-footer, triple
- inter-annotator mAP @ 0.5-0.95 (%).Ten = 96-98. Page-header, Count = 58022. Page-header, % of Total.Train = 5.10.
- Page-header, % of Total.Test = 6.70. Page-header, % of Total.Val =\nNone [1] 0.5-0.95 (%).Law = 87-94. Section-header,
- triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 69-73. Section-header, triple inter-annotator mAP @ 0.5-0.95 (%).Ten
- = 78-86. Table, Count = 34733. Table, % o\nNone [1] Caption, Count = 22524. Caption, % of Total.Train = 2.04. Caption,
- % of Total.Test = 1.77. Caption, % of Total.Val = 2.32. Caption, triple inter-annotator mAP @ 0.5-0.95 (%).All =
- 84-89. Caption, trip\nNone [1] inter-annotator mAP @ 0.5-0.95 (%).Pat = 66-71. Picture, triple inter-annotator mAP
- @ 0.5-0.95 (%).Ten = 59-76. Section-header, Count = 142884. Section-header, % of Total.Train = 12.60. Section-header\nNone
- [1] n/a. Footnote, Count = 6318. Footnote, % of Total.Train = 0.60. Footnote, % of Total.Test = 0.31. Footnote,
- % of Total.Val = 0.58. Footnote, triple inter-annotator mAP @ 0.5-0.95 (%).All = 83-91. Foot\nNone [1] 185660. List-item,
- % of Total.Train = 17.19. List-item, % of Total.Test = 13.34. List-item, % of Total.Val = 15.82. List-item, triple
- inter-annotator mAP @ 0.5-0.95 (%).All = 87-88. List-item, triple \nNone [1] 0.5-0.95 (%).Ten = 93-95. Page-footer,
- Count = 70878. Page-footer, % of Total.Train = 6.51. Page-footer, % of Total.Test = 5.58. Page-footer, % of Total.Val
- = 6.00. Page-footer, triple inter-annotator\nNone [1] 89-93. Text, triple inter-annotator mAP @ 0.5-0.95 (%).Law
- = 87-92. Text, triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 71-79. Text, triple inter-annotator mAP @ 0.5-0.95
- (%).Ten = 87-95. Title, Cou\nNone [1] 0.5-0.95 (%).Pat = 97-99. Page-header, triple inter-annotator mAP @ 0.5-0.95
- (%).Ten = 81-86. Picture, Count = 45976. Picture, % of Total.Train = 4.21. Picture, % of Total.Test = 2.78. Picture,
- % of T\nNone [1] (%).Sci = 94-100. Title, triple inter-annotator mAP @ 0.5-0.95 (%).Law = 82-96. Title, triple inter-annotator
- mAP @ 0.5-0.95 (%).Pat = 68-79. Title, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 24-\nNone [1] inter-annotator
- mAP @ 0.5-0.95 (%).Law = 58-80. Table, triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 79-84. Table, triple inter-annotator
- mAP @ 0.5-0.95 (%).Ten = 70-85. Text, Count = 510377. Text, \nNone [1] = 82-97. Formula, Count = 25027. Formula,
- % of Total.Train = 2.25. Formula, % of Total.Test = 1.90. Formula, % of Total.Val = 2.96. Formula, triple inter-annotator
- mAP @ 0.5-0.95 (%).All = 83-85. Form\nNone [1] mAP @ 0.5-0.95 (%).Sci = 89-94. Total, triple inter-annotator mAP
- @ 0.5-0.95 (%).Law = 86-91. Total, triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 71-76. Total, triple inter-annotator
- mAP @ 0.5-0.95\n","stderr":"","success":true}'
- role: tool
- tool_call_id: call_w56yspv7
- - content: null
- reasoning: 'We see many types: Page-header, Page-footer, Section-header, Caption, Picture, List-item, Footnote, Text,
- Title, Formula etc. Let''s extract unique types. Probably need to parse for words before comma. Use regex.'
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"import re\nresults = search(''document element types'', limit=50)\ntypes=set()\nfor r in
- results:\n for line in r[''content''].split('',''):\n m = re.match(r''\\s*(\\w[\\w-]*)(?:\\s*[,\\.]|$)'',
- line)\n if m:\n types.add(m.group(1))\nprint(sorted(types))\n"}'
- name: execute_code
- id: call_bt2tgwxs
- type: function
- - content: '{"code":"import re\nresults = search(''document element types'', limit=50)\ntypes=set()\nfor r in results:\n for
- line in r[''content''].split('',''):\n m = re.match(r''\\s*(\\w[\\w-]*)(?:\\s*[,\\.]|$)'', line)\n if
- m:\n types.add(m.group(1))\nprint(sorted(types))\n","stdout":"","stderr":"ModuleNotFoundError: No module
- named ''re''","success":false}'
- role: tool
- tool_call_id: call_bt2tgwxs
- - content: null
- reasoning: Need import re.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"import re\nresults = search(''document element types'', limit=50)\ntypes=set()\nfor r in
- results:\n for line in r[''content''].split('',''):\n m = re.match(r''\\s*(\\w[\\w-]*)(?:\\s*[,\\.]|$)'',
- line)\n if m:\n types.add(m.group(1))\nprint(sorted(types))\n"}'
- name: execute_code
- id: call_iflcvbbl
- type: function
- - content: '{"code":"import re\nresults = search(''document element types'', limit=50)\ntypes=set()\nfor r in results:\n for
- line in r[''content''].split('',''):\n m = re.match(r''\\s*(\\w[\\w-]*)(?:\\s*[,\\.]|$)'', line)\n if
- m:\n types.add(m.group(1))\nprint(sorted(types))\n","stdout":"","stderr":"ModuleNotFoundError: No module
- named ''re''","success":false}'
- role: tool
- tool_call_id: call_iflcvbbl
- model: gpt-oss
- reasoning_effort: low
- stream: false
- tool_choice: auto
- tools:
- - function:
- description: |-
- Execute Python code in a sandboxed interpreter.
-
- The code has access to haiku.rag functions (search, list_documents,
- get_document, get_chunk, llm).
-
- Use print() to output results.
-
- Structured result with success status, stdout, and stderr.
-
- name: execute_code
- parameters:
- additionalProperties: false
- properties:
- code:
- description: Python code to execute.
- type: string
- required:
- - code
- type: object
- strict: true
- type: function
- - function:
- description: Result from RLM agent execution.
- name: final_result
- parameters:
- additionalProperties: false
- properties:
- answer:
- description: The answer to the user's question
- type: string
- program:
- description: The final consolidated program
- type: string
- required:
- - answer
- - program
- type: object
- strict: true
- type: function
- uri: http://localhost:11434/v1/chat/completions
- response:
- headers:
- content-length:
- - '717'
- content-type:
- - application/json
- parsed_body:
- choices:
- - finish_reason: tool_calls
- index: 0
- message:
- content: ''
- reasoning: Maybe re is not available? But standard library re should exist. Maybe import is required differently.
- It's standard. Could be restricted? Try import re inside same code. That we did. Maybe environment prohibits re?
- Let's try a simple import re example.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"import re\nprint(''ok'')\n"}'
- name: execute_code
- id: call_1uala4jy
- index: 0
- type: function
- created: 1771336386
- id: chatcmpl-540
- model: gpt-oss
- object: chat.completion
- system_fingerprint: fp_ollama
- usage:
- completion_tokens: 82
- prompt_tokens: 3730
- total_tokens: 3812
- status:
- code: 200
- message: OK
-- request:
- headers:
- accept:
- - application/json
- accept-encoding:
- - gzip, deflate, zstd
- connection:
- - keep-alive
- content-length:
- - '14824'
- content-type:
- - application/json
- host:
- - localhost:11434
- method: POST
- parsed_body:
- messages:
- - content: |-
- You are a Recursive Language Model (RLM) agent that solves complex research questions by writing and executing Python code.
-
- IMPORTANT: You MUST use the `execute_code` tool to run Python code. The functions described below are ONLY available inside the execute_code tool - you cannot access them any other way. Always execute code to answer questions; do not just describe what code would do.
-
- CRITICAL: Inside execute_code, these functions are ALREADY available in the namespace. Do NOT import them - just use them directly:
- - search("query") ✓ CORRECT
- - from haiku.rag import search ✗ WRONG - will fail
-
- You have access to a sandboxed Python environment with these haiku.rag functions (use them directly, no imports needed):
-
- ## Available Functions
-
- ### search(query, limit=10) -> list[dict]
- Search the knowledge base using hybrid search (vector + full-text).
- Returns list of dicts with keys: chunk_id, content, document_id, document_title, document_uri, score, page_numbers, headings
-
- ### list_documents(limit=10, offset=0) -> list[dict]
- List available documents in the knowledge base.
- Returns list of dicts with keys: id, title, uri, created_at
-
- ### get_document(id_or_title) -> str | None
- Get the full text content of a document by ID, title, or URI.
- Returns the document content as a string, or None if not found.
-
- ### get_docling_document(id_or_title) -> DoclingDocument | None
- Get the structured DoclingDocument object for advanced analysis.
- Returns a DoclingDocument object, or None if not found.
- See "DoclingDocument API" section below for how to use it.
-
- ### llm(prompt) -> str
- Call an LLM directly with the given prompt. Returns the response as a string.
- Use this for classification, summarization, extraction, or any task where you
- already have the content and just need LLM reasoning.
-
- ## Pre-loaded Documents Variable
-
- If documents were pre-loaded for this session, a `documents` variable is available:
- ```python
- # documents is a list of dicts with keys: id, title, uri, content
- for doc in documents:
- print(doc['title'], len(doc['content']))
- ```
- Check if it exists with: `if 'documents' in dir(): ...`
-
- ## Standard Library Modules
- You can import any Python standard library module.
-
- ## Strategy Guide
-
- 1. **Explore First**: Start by listing documents or searching to understand what's available. Document names may differ from filenames (e.g., "tbmed593.pdf" might be stored as "TB MED 593" or similar).
- 2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
- 3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
- 4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
- 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and use collections.
- 6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
- 7. **Cite Your Sources**: Track which documents/chunks informed your answer for citation.
-
- ## DoclingDocument API
-
- When you call `get_docling_document(id_or_title)`, you get a DoclingDocument object for structured document analysis.
-
- ### Properties
- - `doc.texts` - List of all text items (paragraphs, headings, etc.)
- - `doc.tables` - List of all tables
- - `doc.pictures` - List of all pictures/figures
- - `doc.name` - Document name
-
- ### Methods
- - `doc.iterate_items(with_groups=False)` - Iterate all items with hierarchy level
- Returns tuples of (item, level) where level is nesting depth
- - `doc.export_to_markdown()` - Export entire document as markdown string
-
- ### Text Item Properties
- - `item.text` - The text content
- - `item.label` - Type: title, paragraph, section_header, list_item, etc. (lowercase enum values)
- - `item.prov` - Provenance (page numbers, bounding boxes)
-
- ### Table Access
- - `table.data.num_rows`, `table.data.num_cols` - Dimensions
- - `table.data.table_cells` - List of TableCell objects
- - `cell.text`, `cell.start_row_offset_idx`, `cell.start_col_offset_idx`
-
- ### Example Usage
- ```python
- doc = get_docling_document("My Document")
-
- # Get all headings
- headings = [t.text for t in doc.texts if "header" in str(t.label)]
-
- # Iterate with structure
- for item, level in doc.iterate_items():
- print(" " * level + item.text[:50])
-
- # Extract table data
- for table in doc.tables:
- for cell in table.data.table_cells:
- print(f"Row {cell.start_row_offset_idx}, Col {cell.start_col_offset_idx}: {cell.text}")
- ```
-
- ## Example Patterns
-
- ### Counting documents matching a condition
- ```python
- docs = list_documents(limit=100)
- count = 0
- for doc in docs:
- content = get_document(doc['id'])
- if content and 'keyword' in content.lower():
- count += 1
- print(f"Found in: {doc['title']}")
- print(f"Total: {count}")
- ```
-
- ### Aggregating data across documents
- ```python
- import re
- numbers = []
- results = search("financial data", limit=20)
- for r in results:
- matches = re.findall(r'\$([\d,]+)', r['content'])
- for m in matches:
- numbers.append(int(m.replace(',', '')))
- print(f"Average: ${sum(numbers)/len(numbers):,.2f}")
- ```
-
- ### Using llm() for classification
- ```python
- # Get document content
- content = get_document("Q1 Report")
- # Use llm() to classify sentiment
- sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
- print(sentiment)
- ```
-
- ## Workflow
-
- 1. **ALWAYS start by using execute_code** to explore the knowledge base
- 2. Run multiple code blocks as needed to gather information
- 3. After collecting data, provide your final answer
-
- ## Output Format
-
- CRITICAL: Your final response MUST be valid JSON matching this exact schema:
- ```json
- {"answer": "Your complete answer here as a string", "program": "Your final consolidated program here as a string"}
- ```
-
- - `answer`: A clear answer to the user's question with key findings and references to specific documents/chunks.
- - `program`: A single, self-contained Python program that produces the answer. Consolidate your exploratory code executions into one clean script.
-
- Do NOT return arbitrary JSON structures. Always use the exact format: {"answer": "...", "program": "..."}
-
- CRITICAL: You MUST call execute_code at least once before providing your answer. Never give up without trying to execute code first.
- role: system
- - content: Search for content about document element types or labels. What are all the different document element types
- mentioned? List them all.
- role: user
- - content: null
- reasoning: We need to search for content about document element types or labels. Use search.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"results = search(''document element types'', limit=20)\nfor r in results:\n print(r[''document_title''],
- r[''page_numbers''], r[''content''][:200])\n"}'
- name: execute_code
- id: call_w56yspv7
- type: function
- - content: '{"code":"results = search(''document element types'', limit=20)\nfor r in results:\n print(r[''document_title''],
- r[''page_numbers''], r[''content''][:200])\n","stdout":"None [1] Phase 2: Label selection and guideline. We reviewed
- the collected documents and identified the most common structural features they exhibit. This was achieved by identifying
- recurrent layout elements \nNone [1] Phase 1: Data selection and preparation. Our inclusion criteria for documents
- were described in Section 3. A large effort went into ensuring that all documents are free to use. The data sources
- includ\nNone [1] $_{Affiliation}$, as seen in DocBank, are often only distinguishable by discriminating on\nPreparation
- work included uploading and parsing the sourced PDF documents in the Corpus Conversion Service (CC\nNone [1] \nTable
- 1: DocLayNet dataset overview. Along with the frequency of each class label, we present the relative occurrence
- (as % of row \"Total\") in the train, test and validation sets. The inter-annotator\nNone [1] Page-footer, triple
- inter-annotator mAP @ 0.5-0.95 (%).Ten = 96-98. Page-header, Count = 58022. Page-header, % of Total.Train = 5.10.
- Page-header, % of Total.Test = 6.70. Page-header, % of Total.Val =\nNone [1] 0.5-0.95 (%).Law = 87-94. Section-header,
- triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 69-73. Section-header, triple inter-annotator mAP @ 0.5-0.95 (%).Ten
- = 78-86. Table, Count = 34733. Table, % o\nNone [1] Caption, Count = 22524. Caption, % of Total.Train = 2.04. Caption,
- % of Total.Test = 1.77. Caption, % of Total.Val = 2.32. Caption, triple inter-annotator mAP @ 0.5-0.95 (%).All =
- 84-89. Caption, trip\nNone [1] inter-annotator mAP @ 0.5-0.95 (%).Pat = 66-71. Picture, triple inter-annotator mAP
- @ 0.5-0.95 (%).Ten = 59-76. Section-header, Count = 142884. Section-header, % of Total.Train = 12.60. Section-header\nNone
- [1] n/a. Footnote, Count = 6318. Footnote, % of Total.Train = 0.60. Footnote, % of Total.Test = 0.31. Footnote,
- % of Total.Val = 0.58. Footnote, triple inter-annotator mAP @ 0.5-0.95 (%).All = 83-91. Foot\nNone [1] 185660. List-item,
- % of Total.Train = 17.19. List-item, % of Total.Test = 13.34. List-item, % of Total.Val = 15.82. List-item, triple
- inter-annotator mAP @ 0.5-0.95 (%).All = 87-88. List-item, triple \nNone [1] 0.5-0.95 (%).Ten = 93-95. Page-footer,
- Count = 70878. Page-footer, % of Total.Train = 6.51. Page-footer, % of Total.Test = 5.58. Page-footer, % of Total.Val
- = 6.00. Page-footer, triple inter-annotator\nNone [1] 89-93. Text, triple inter-annotator mAP @ 0.5-0.95 (%).Law
- = 87-92. Text, triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 71-79. Text, triple inter-annotator mAP @ 0.5-0.95
- (%).Ten = 87-95. Title, Cou\nNone [1] 0.5-0.95 (%).Pat = 97-99. Page-header, triple inter-annotator mAP @ 0.5-0.95
- (%).Ten = 81-86. Picture, Count = 45976. Picture, % of Total.Train = 4.21. Picture, % of Total.Test = 2.78. Picture,
- % of T\nNone [1] (%).Sci = 94-100. Title, triple inter-annotator mAP @ 0.5-0.95 (%).Law = 82-96. Title, triple inter-annotator
- mAP @ 0.5-0.95 (%).Pat = 68-79. Title, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 24-\nNone [1] inter-annotator
- mAP @ 0.5-0.95 (%).Law = 58-80. Table, triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 79-84. Table, triple inter-annotator
- mAP @ 0.5-0.95 (%).Ten = 70-85. Text, Count = 510377. Text, \nNone [1] = 82-97. Formula, Count = 25027. Formula,
- % of Total.Train = 2.25. Formula, % of Total.Test = 1.90. Formula, % of Total.Val = 2.96. Formula, triple inter-annotator
- mAP @ 0.5-0.95 (%).All = 83-85. Form\nNone [1] mAP @ 0.5-0.95 (%).Sci = 89-94. Total, triple inter-annotator mAP
- @ 0.5-0.95 (%).Law = 86-91. Total, triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 71-76. Total, triple inter-annotator
- mAP @ 0.5-0.95\n","stderr":"","success":true}'
- role: tool
- tool_call_id: call_w56yspv7
- - content: null
- reasoning: 'We see many types: Page-header, Page-footer, Section-header, Caption, Picture, List-item, Footnote, Text,
- Title, Formula etc. Let''s extract unique types. Probably need to parse for words before comma. Use regex.'
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"import re\nresults = search(''document element types'', limit=50)\ntypes=set()\nfor r in
- results:\n for line in r[''content''].split('',''):\n m = re.match(r''\\s*(\\w[\\w-]*)(?:\\s*[,\\.]|$)'',
- line)\n if m:\n types.add(m.group(1))\nprint(sorted(types))\n"}'
- name: execute_code
- id: call_bt2tgwxs
- type: function
- - content: '{"code":"import re\nresults = search(''document element types'', limit=50)\ntypes=set()\nfor r in results:\n for
- line in r[''content''].split('',''):\n m = re.match(r''\\s*(\\w[\\w-]*)(?:\\s*[,\\.]|$)'', line)\n if
- m:\n types.add(m.group(1))\nprint(sorted(types))\n","stdout":"","stderr":"ModuleNotFoundError: No module
- named ''re''","success":false}'
- role: tool
- tool_call_id: call_bt2tgwxs
- - content: null
- reasoning: Need import re.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"import re\nresults = search(''document element types'', limit=50)\ntypes=set()\nfor r in
- results:\n for line in r[''content''].split('',''):\n m = re.match(r''\\s*(\\w[\\w-]*)(?:\\s*[,\\.]|$)'',
- line)\n if m:\n types.add(m.group(1))\nprint(sorted(types))\n"}'
- name: execute_code
- id: call_iflcvbbl
- type: function
- - content: '{"code":"import re\nresults = search(''document element types'', limit=50)\ntypes=set()\nfor r in results:\n for
- line in r[''content''].split('',''):\n m = re.match(r''\\s*(\\w[\\w-]*)(?:\\s*[,\\.]|$)'', line)\n if
- m:\n types.add(m.group(1))\nprint(sorted(types))\n","stdout":"","stderr":"ModuleNotFoundError: No module
- named ''re''","success":false}'
- role: tool
- tool_call_id: call_iflcvbbl
- - content: null
- reasoning: Maybe re is not available? But standard library re should exist. Maybe import is required differently.
- It's standard. Could be restricted? Try import re inside same code. That we did. Maybe environment prohibits re?
- Let's try a simple import re example.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"import re\nprint(''ok'')\n"}'
- name: execute_code
- id: call_1uala4jy
- type: function
- - content: '{"code":"import re\nprint(''ok'')\n","stdout":"","stderr":"ModuleNotFoundError: No module named ''re''","success":false}'
- role: tool
- tool_call_id: call_1uala4jy
- model: gpt-oss
- reasoning_effort: low
- stream: false
- tool_choice: auto
- tools:
- - function:
- description: |-
- Execute Python code in a sandboxed interpreter.
-
- The code has access to haiku.rag functions (search, list_documents,
- get_document, get_chunk, llm).
-
- Use print() to output results.
-
- Structured result with success status, stdout, and stderr.
-
- name: execute_code
- parameters:
- additionalProperties: false
- properties:
- code:
- description: Python code to execute.
- type: string
- required:
- - code
- type: object
- strict: true
- type: function
- - function:
- description: Result from RLM agent execution.
- name: final_result
- parameters:
- additionalProperties: false
- properties:
- answer:
- description: The answer to the user's question
- type: string
- program:
- description: The final consolidated program
- type: string
- required:
- - answer
- - program
- type: object
- strict: true
- type: function
- uri: http://localhost:11434/v1/chat/completions
- response:
- headers:
- content-length:
- - '761'
- content-type:
- - application/json
- parsed_body:
- choices:
- - finish_reason: tool_calls
- index: 0
- message:
- content: ''
- reasoning: 'No re? Try using import regex? Probably not. Alternative: use simple split on whitespace and punctuation.
- We can manually list known types from content. Let''s search for "label" and extract.'
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"results = search(''label'', limit=20)\nfor r in results:\n print(r[''document_title''],
- r[''page_numbers''], r[''content''][:200])\n"}'
- name: execute_code
- id: call_mputujfq
- index: 0
- type: function
- created: 1771336388
- id: chatcmpl-224
- model: gpt-oss
- object: chat.completion
- system_fingerprint: fp_ollama
- usage:
- completion_tokens: 100
- prompt_tokens: 3856
- total_tokens: 3956
- status:
- code: 200
- message: OK
-- request:
- headers:
- accept:
- - application/json
- accept-encoding:
- - gzip, deflate, zstd
- connection:
- - keep-alive
- content-length:
- - '75'
+ - '83'
content-type:
- application/json
host:
@@ -1798,7 +735,7 @@ interactions:
parsed_body:
encoding_format: base64
input:
- - label
+ - element types
model: qwen3-embedding:4b
uri: http://localhost:11434/v1/embeddings
response:
@@ -1809,14 +746,14 @@ interactions:
- chunked
parsed_body:
data:
- - embedding: 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
+ - embedding: 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
index: 0
object: embedding
model: qwen3-embedding:4b
object: list
usage:
- prompt_tokens: 2
- total_tokens: 2
+ prompt_tokens: 3
+ total_tokens: 3
status:
code: 200
message: OK
@@ -1829,7 +766,7 @@ interactions:
connection:
- keep-alive
content-length:
- - '19186'
+ - '16951'
content-type:
- application/json
host:
@@ -1846,7 +783,7 @@ interactions:
- search("query") ✓ CORRECT
- from haiku.rag import search ✗ WRONG - will fail
- You have access to a sandboxed Python environment with these haiku.rag functions (use them directly, no imports needed):
+ You have access to a sandboxed Python interpreter with these haiku.rag functions (use them directly, no imports needed):
## Available Functions
@@ -1862,10 +799,10 @@ interactions:
Get the full text content of a document by ID, title, or URI.
Returns the document content as a string, or None if not found.
- ### get_docling_document(id_or_title) -> DoclingDocument | None
- Get the structured DoclingDocument object for advanced analysis.
- Returns a DoclingDocument object, or None if not found.
- See "DoclingDocument API" section below for how to use it.
+ ### get_chunk(chunk_id) -> dict | None
+ Get a specific chunk by its ID (from search results).
+ Returns dict with keys: chunk_id, content, document_id, document_title, headings, page_numbers, labels
+ Use this to retrieve full chunk details and metadata for citation.
### llm(prompt) -> str
Call an LLM directly with the given prompt. Returns the response as a string.
@@ -1882,8 +819,13 @@ interactions:
```
Check if it exists with: `if 'documents' in dir(): ...`
- ## Standard Library Modules
- You can import any Python standard library module.
+ ## Available Python Features
+
+ The interpreter supports: variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, try/except, and the `json` module.
+
+ Not supported: imports (other than `json`), class definitions, generators/yield, match statements, decorators, `with` statements.
+
+ For pattern matching or text extraction, use string methods (`str.split`, `str.find`, `str.startswith`, `in` operator) or the `llm()` function.
## Strategy Guide
@@ -1891,51 +833,9 @@ interactions:
2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
- 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and use collections.
+ 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and data structures.
6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
- 7. **Cite Your Sources**: Track which documents/chunks informed your answer for citation.
-
- ## DoclingDocument API
-
- When you call `get_docling_document(id_or_title)`, you get a DoclingDocument object for structured document analysis.
-
- ### Properties
- - `doc.texts` - List of all text items (paragraphs, headings, etc.)
- - `doc.tables` - List of all tables
- - `doc.pictures` - List of all pictures/figures
- - `doc.name` - Document name
-
- ### Methods
- - `doc.iterate_items(with_groups=False)` - Iterate all items with hierarchy level
- Returns tuples of (item, level) where level is nesting depth
- - `doc.export_to_markdown()` - Export entire document as markdown string
-
- ### Text Item Properties
- - `item.text` - The text content
- - `item.label` - Type: title, paragraph, section_header, list_item, etc. (lowercase enum values)
- - `item.prov` - Provenance (page numbers, bounding boxes)
-
- ### Table Access
- - `table.data.num_rows`, `table.data.num_cols` - Dimensions
- - `table.data.table_cells` - List of TableCell objects
- - `cell.text`, `cell.start_row_offset_idx`, `cell.start_col_offset_idx`
-
- ### Example Usage
- ```python
- doc = get_docling_document("My Document")
-
- # Get all headings
- headings = [t.text for t in doc.texts if "header" in str(t.label)]
-
- # Iterate with structure
- for item, level in doc.iterate_items():
- print(" " * level + item.text[:50])
-
- # Extract table data
- for table in doc.tables:
- for cell in table.data.table_cells:
- print(f"Row {cell.start_row_offset_idx}, Col {cell.start_col_offset_idx}: {cell.text}")
- ```
+ 7. **Cite Your Sources**: Use get_chunk() to retrieve chunk metadata for citations. Track which documents/chunks informed your answer.
## Example Patterns
@@ -1951,23 +851,31 @@ interactions:
print(f"Total: {count}")
```
- ### Aggregating data across documents
+ ### Extracting data with llm()
```python
- import re
numbers = []
results = search("financial data", limit=20)
for r in results:
- matches = re.findall(r'\$([\d,]+)', r['content'])
- for m in matches:
- numbers.append(int(m.replace(',', '')))
- print(f"Average: ${sum(numbers)/len(numbers):,.2f}")
+ extracted = llm(f"Extract all dollar amounts from this text as a comma-separated list of numbers (no $ signs): {r['content']}")
+ for part in extracted.split(','):
+ part = part.strip().replace(',', '')
+ if part.isdigit():
+ numbers.append(int(part))
+ if numbers:
+ print(f"Average: {sum(numbers) / len(numbers)}")
+ ```
+
+ ### Using search results with get_chunk for citations
+ ```python
+ results = search("safety requirements", limit=5)
+ for r in results:
+ chunk = get_chunk(r['chunk_id'])
+ print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}")
```
### Using llm() for classification
```python
- # Get document content
content = get_document("Q1 Report")
- # Use llm() to classify sentiment
sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
print(sentiment)
```
@@ -1996,145 +904,103 @@ interactions:
mentioned? List them all.
role: user
- content: null
- reasoning: We need to search for content about document element types or labels. Use search.
+ reasoning: Need to search.
role: assistant
tool_calls:
- function:
- arguments: '{"code":"results = search(''document element types'', limit=20)\nfor r in results:\n print(r[''document_title''],
- r[''page_numbers''], r[''content''][:200])\n"}'
+ arguments: '{"code":"results = search(\"document element types\", limit=20)\nfor r in results:\n print(r[''chunk_id''],
+ r[''document_title''], r[''content''][:200])\n"}'
name: execute_code
- id: call_w56yspv7
+ id: call_a5m383mw
type: function
- - content: '{"code":"results = search(''document element types'', limit=20)\nfor r in results:\n print(r[''document_title''],
- r[''page_numbers''], r[''content''][:200])\n","stdout":"None [1] Phase 2: Label selection and guideline. We reviewed
- the collected documents and identified the most common structural features they exhibit. This was achieved by identifying
- recurrent layout elements \nNone [1] Phase 1: Data selection and preparation. Our inclusion criteria for documents
- were described in Section 3. A large effort went into ensuring that all documents are free to use. The data sources
- includ\nNone [1] $_{Affiliation}$, as seen in DocBank, are often only distinguishable by discriminating on\nPreparation
- work included uploading and parsing the sourced PDF documents in the Corpus Conversion Service (CC\nNone [1] \nTable
- 1: DocLayNet dataset overview. Along with the frequency of each class label, we present the relative occurrence
- (as % of row \"Total\") in the train, test and validation sets. The inter-annotator\nNone [1] Page-footer, triple
- inter-annotator mAP @ 0.5-0.95 (%).Ten = 96-98. Page-header, Count = 58022. Page-header, % of Total.Train = 5.10.
- Page-header, % of Total.Test = 6.70. Page-header, % of Total.Val =\nNone [1] 0.5-0.95 (%).Law = 87-94. Section-header,
- triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 69-73. Section-header, triple inter-annotator mAP @ 0.5-0.95 (%).Ten
- = 78-86. Table, Count = 34733. Table, % o\nNone [1] Caption, Count = 22524. Caption, % of Total.Train = 2.04. Caption,
- % of Total.Test = 1.77. Caption, % of Total.Val = 2.32. Caption, triple inter-annotator mAP @ 0.5-0.95 (%).All =
- 84-89. Caption, trip\nNone [1] inter-annotator mAP @ 0.5-0.95 (%).Pat = 66-71. Picture, triple inter-annotator mAP
- @ 0.5-0.95 (%).Ten = 59-76. Section-header, Count = 142884. Section-header, % of Total.Train = 12.60. Section-header\nNone
- [1] n/a. Footnote, Count = 6318. Footnote, % of Total.Train = 0.60. Footnote, % of Total.Test = 0.31. Footnote,
- % of Total.Val = 0.58. Footnote, triple inter-annotator mAP @ 0.5-0.95 (%).All = 83-91. Foot\nNone [1] 185660. List-item,
- % of Total.Train = 17.19. List-item, % of Total.Test = 13.34. List-item, % of Total.Val = 15.82. List-item, triple
- inter-annotator mAP @ 0.5-0.95 (%).All = 87-88. List-item, triple \nNone [1] 0.5-0.95 (%).Ten = 93-95. Page-footer,
- Count = 70878. Page-footer, % of Total.Train = 6.51. Page-footer, % of Total.Test = 5.58. Page-footer, % of Total.Val
- = 6.00. Page-footer, triple inter-annotator\nNone [1] 89-93. Text, triple inter-annotator mAP @ 0.5-0.95 (%).Law
- = 87-92. Text, triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 71-79. Text, triple inter-annotator mAP @ 0.5-0.95
- (%).Ten = 87-95. Title, Cou\nNone [1] 0.5-0.95 (%).Pat = 97-99. Page-header, triple inter-annotator mAP @ 0.5-0.95
- (%).Ten = 81-86. Picture, Count = 45976. Picture, % of Total.Train = 4.21. Picture, % of Total.Test = 2.78. Picture,
- % of T\nNone [1] (%).Sci = 94-100. Title, triple inter-annotator mAP @ 0.5-0.95 (%).Law = 82-96. Title, triple inter-annotator
- mAP @ 0.5-0.95 (%).Pat = 68-79. Title, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 24-\nNone [1] inter-annotator
- mAP @ 0.5-0.95 (%).Law = 58-80. Table, triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 79-84. Table, triple inter-annotator
- mAP @ 0.5-0.95 (%).Ten = 70-85. Text, Count = 510377. Text, \nNone [1] = 82-97. Formula, Count = 25027. Formula,
- % of Total.Train = 2.25. Formula, % of Total.Test = 1.90. Formula, % of Total.Val = 2.96. Formula, triple inter-annotator
- mAP @ 0.5-0.95 (%).All = 83-85. Form\nNone [1] mAP @ 0.5-0.95 (%).Sci = 89-94. Total, triple inter-annotator mAP
- @ 0.5-0.95 (%).Law = 86-91. Total, triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 71-76. Total, triple inter-annotator
- mAP @ 0.5-0.95\n","stderr":"","success":true}'
+ - content: '{"code":"results = search(\"document element types\", limit=20)\nfor r in results:\n print(r[''chunk_id''],
+ r[''document_title''], r[''content''][:200])\n","stdout":"baf79253-e710-41e4-9afc-cd53a58b14e6 None Phase 2: Label
+ selection and guideline. We reviewed the collected documents and identified the most common structural features
+ they exhibit. This was achieved by identifying recurrent layout elements \n6377d621-2597-4bda-ba58-11dc72b87477
+ None Phase 1: Data selection and preparation. Our inclusion criteria for documents were described in Section 3.
+ A large effort went into ensuring that all documents are free to use. The data sources includ\n1cbffceb-fa16-434f-a168-54ad860b1c21
+ None $_{Affiliation}$, as seen in DocBank, are often only distinguishable by discriminating on\nPreparation work
+ included uploading and parsing the sourced PDF documents in the Corpus Conversion Service (CC\n44a5a4d6-7f42-46d2-9115-4dda6b6d62ee
+ None \nTable 1: DocLayNet dataset overview. Along with the frequency of each class label, we present the relative
+ occurrence (as % of row \"Total\") in the train, test and validation sets. The inter-annotator\nfe743e25-0740-423c-9f26-d42e658269f2
+ None Page-footer, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 96-98. Page-header, Count = 58022. Page-header,
+ % of Total.Train = 5.10. Page-header, % of Total.Test = 6.70. Page-header, % of Total.Val =\ned730761-c984-419a-9fdd-06fd37aa632a
+ None 0.5-0.95 (%).Law = 87-94. Section-header, triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 69-73. Section-header,
+ triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 78-86. Table, Count = 34733. Table, % o\na669db88-1bd8-46f3-87ec-078b74151258
+ None Caption, Count = 22524. Caption, % of Total.Train = 2.04. Caption, % of Total.Test = 1.77. Caption, % of Total.Val
+ = 2.32. Caption, triple inter-annotator mAP @ 0.5-0.95 (%).All = 84-89. Caption, trip\n38cf7b9f-3947-45f2-a66a-5ef4d89a88c2
+ None inter-annotator mAP @ 0.5-0.95 (%).Pat = 66-71. Picture, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 59-76.
+ Section-header, Count = 142884. Section-header, % of Total.Train = 12.60. Section-header\n26781f97-2e94-4270-85e7-84670a7971f2
+ None n/a. Footnote, Count = 6318. Footnote, % of Total.Train = 0.60. Footnote, % of Total.Test = 0.31. Footnote,
+ % of Total.Val = 0.58. Footnote, triple inter-annotator mAP @ 0.5-0.95 (%).All = 83-91. Foot\n1eb6520a-3d81-4113-a8b1-4a045bdd581d
+ None 185660. List-item, % of Total.Train = 17.19. List-item, % of Total.Test = 13.34. List-item, % of Total.Val
+ = 15.82. List-item, triple inter-annotator mAP @ 0.5-0.95 (%).All = 87-88. List-item, triple \nec1f7ead-7b8c-46f7-926b-f0d6c942ad02
+ None 0.5-0.95 (%).Ten = 93-95. Page-footer, Count = 70878. Page-footer, % of Total.Train = 6.51. Page-footer, %
+ of Total.Test = 5.58. Page-footer, % of Total.Val = 6.00. Page-footer, triple inter-annotator\n8e924800-19a6-4096-8e36-c211ac858ffa
+ None 89-93. Text, triple inter-annotator mAP @ 0.5-0.95 (%).Law = 87-92. Text, triple inter-annotator mAP @ 0.5-0.95
+ (%).Pat = 71-79. Text, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 87-95. Title, Cou\na36c2e35-4c45-462f-8997-d800667e2c5c
+ None 0.5-0.95 (%).Pat = 97-99. Page-header, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 81-86. Picture, Count
+ = 45976. Picture, % of Total.Train = 4.21. Picture, % of Total.Test = 2.78. Picture, % of T\nd0d1d4b6-bd81-42f4-a9d1-443f8869defd
+ None (%).Sci = 94-100. Title, triple inter-annotator mAP @ 0.5-0.95 (%).Law = 82-96. Title, triple inter-annotator
+ mAP @ 0.5-0.95 (%).Pat = 68-79. Title, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 24-\n3cdae3b8-d62e-497a-908b-dac06a9b87a8
+ None inter-annotator mAP @ 0.5-0.95 (%).Law = 58-80. Table, triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 79-84.
+ Table, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 70-85. Text, Count = 510377. Text, \n525a5cca-93ee-45be-8d67-f3e54286a80f
+ None = 82-97. Formula, Count = 25027. Formula, % of Total.Train = 2.25. Formula, % of Total.Test = 1.90. Formula,
+ % of Total.Val = 2.96. Formula, triple inter-annotator mAP @ 0.5-0.95 (%).All = 83-85. Form\n5667fabf-8421-4004-bccf-9ce1b100f49a
+ None mAP @ 0.5-0.95 (%).Sci = 89-94. Total, triple inter-annotator mAP @ 0.5-0.95 (%).Law = 86-91. Total, triple
+ inter-annotator mAP @ 0.5-0.95 (%).Pat = 71-76. Total, triple inter-annotator mAP @ 0.5-0.95\n","stderr":"","success":true}'
role: tool
- tool_call_id: call_w56yspv7
+ tool_call_id: call_a5m383mw
- content: null
- reasoning: 'We see many types: Page-header, Page-footer, Section-header, Caption, Picture, List-item, Footnote, Text,
- Title, Formula etc. Let''s extract unique types. Probably need to parse for words before comma. Use regex.'
+ reasoning: 'We need list of element types. From snippets: Page-footer, Page-header, Section-header, Table, Caption,
+ Picture, Footnote, List-item, Text, Title, Formula. Also maybe others like List, Equation? Let''s search for "element
+ types" keyword.'
role: assistant
tool_calls:
- function:
- arguments: '{"code":"import re\nresults = search(''document element types'', limit=50)\ntypes=set()\nfor r in
- results:\n for line in r[''content''].split('',''):\n m = re.match(r''\\s*(\\w[\\w-]*)(?:\\s*[,\\.]|$)'',
- line)\n if m:\n types.add(m.group(1))\nprint(sorted(types))\n"}'
+ arguments: '{"code":"results = search(\"element types\", limit=20)\nfor r in results:\n print(r[''chunk_id''],
+ r[''document_title''], r[''content''][:200])\n"}'
name: execute_code
- id: call_bt2tgwxs
+ id: call_5sejw3xj
type: function
- - content: '{"code":"import re\nresults = search(''document element types'', limit=50)\ntypes=set()\nfor r in results:\n for
- line in r[''content''].split('',''):\n m = re.match(r''\\s*(\\w[\\w-]*)(?:\\s*[,\\.]|$)'', line)\n if
- m:\n types.add(m.group(1))\nprint(sorted(types))\n","stdout":"","stderr":"ModuleNotFoundError: No module
- named ''re''","success":false}'
+ - content: '{"code":"results = search(\"element types\", limit=20)\nfor r in results:\n print(r[''chunk_id''], r[''document_title''],
+ r[''content''][:200])\n","stdout":"baf79253-e710-41e4-9afc-cd53a58b14e6 None Phase 2: Label selection and guideline.
+ We reviewed the collected documents and identified the most common structural features they exhibit. This was achieved
+ by identifying recurrent layout elements \n26781f97-2e94-4270-85e7-84670a7971f2 None n/a. Footnote, Count = 6318.
+ Footnote, % of Total.Train = 0.60. Footnote, % of Total.Test = 0.31. Footnote, % of Total.Val = 0.58. Footnote,
+ triple inter-annotator mAP @ 0.5-0.95 (%).All = 83-91. Foot\nd0d1d4b6-bd81-42f4-a9d1-443f8869defd None (%).Sci =
+ 94-100. Title, triple inter-annotator mAP @ 0.5-0.95 (%).Law = 82-96. Title, triple inter-annotator mAP @ 0.5-0.95
+ (%).Pat = 68-79. Title, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 24-\na669db88-1bd8-46f3-87ec-078b74151258
+ None Caption, Count = 22524. Caption, % of Total.Train = 2.04. Caption, % of Total.Test = 1.77. Caption, % of Total.Val
+ = 2.32. Caption, triple inter-annotator mAP @ 0.5-0.95 (%).All = 84-89. Caption, trip\nfe743e25-0740-423c-9f26-d42e658269f2
+ None Page-footer, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 96-98. Page-header, Count = 58022. Page-header,
+ % of Total.Train = 5.10. Page-header, % of Total.Test = 6.70. Page-header, % of Total.Val =\ned730761-c984-419a-9fdd-06fd37aa632a
+ None 0.5-0.95 (%).Law = 87-94. Section-header, triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 69-73. Section-header,
+ triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 78-86. Table, Count = 34733. Table, % o\n1eb6520a-3d81-4113-a8b1-4a045bdd581d
+ None 185660. List-item, % of Total.Train = 17.19. List-item, % of Total.Test = 13.34. List-item, % of Total.Val
+ = 15.82. List-item, triple inter-annotator mAP @ 0.5-0.95 (%).All = 87-88. List-item, triple \n525a5cca-93ee-45be-8d67-f3e54286a80f
+ None = 82-97. Formula, Count = 25027. Formula, % of Total.Train = 2.25. Formula, % of Total.Test = 1.90. Formula,
+ % of Total.Val = 2.96. Formula, triple inter-annotator mAP @ 0.5-0.95 (%).All = 83-85. Form\n38cf7b9f-3947-45f2-a66a-5ef4d89a88c2
+ None inter-annotator mAP @ 0.5-0.95 (%).Pat = 66-71. Picture, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 59-76.
+ Section-header, Count = 142884. Section-header, % of Total.Train = 12.60. Section-header\na36c2e35-4c45-462f-8997-d800667e2c5c
+ None 0.5-0.95 (%).Pat = 97-99. Page-header, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 81-86. Picture, Count
+ = 45976. Picture, % of Total.Train = 4.21. Picture, % of Total.Test = 2.78. Picture, % of T\n8e924800-19a6-4096-8e36-c211ac858ffa
+ None 89-93. Text, triple inter-annotator mAP @ 0.5-0.95 (%).Law = 87-92. Text, triple inter-annotator mAP @ 0.5-0.95
+ (%).Pat = 71-79. Text, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 87-95. Title, Cou\nec1f7ead-7b8c-46f7-926b-f0d6c942ad02
+ None 0.5-0.95 (%).Ten = 93-95. Page-footer, Count = 70878. Page-footer, % of Total.Train = 6.51. Page-footer, %
+ of Total.Test = 5.58. Page-footer, % of Total.Val = 6.00. Page-footer, triple inter-annotator\n44a5a4d6-7f42-46d2-9115-4dda6b6d62ee
+ None \nTable 1: DocLayNet dataset overview. Along with the frequency of each class label, we present the relative
+ occurrence (as % of row \"Total\") in the train, test and validation sets. The inter-annotator\n3cdae3b8-d62e-497a-908b-dac06a9b87a8
+ None inter-annotator mAP @ 0.5-0.95 (%).Law = 58-80. Table, triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 79-84.
+ Table, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 70-85. Text, Count = 510377. Text, \n6377d621-2597-4bda-ba58-11dc72b87477
+ None Phase 1: Data selection and preparation. Our inclusion criteria for documents were described in Section 3.
+ A large effort went into ensuring that all documents are free to use. The data sources includ\n1cbffceb-fa16-434f-a168-54ad860b1c21
+ None $_{Affiliation}$, as seen in DocBank, are often only distinguishable by discriminating on\nPreparation work
+ included uploading and parsing the sourced PDF documents in the Corpus Conversion Service (CC\n5667fabf-8421-4004-bccf-9ce1b100f49a
+ None mAP @ 0.5-0.95 (%).Sci = 89-94. Total, triple inter-annotator mAP @ 0.5-0.95 (%).Law = 86-91. Total, triple
+ inter-annotator mAP @ 0.5-0.95 (%).Pat = 71-76. Total, triple inter-annotator mAP @ 0.5-0.95\n","stderr":"","success":true}'
role: tool
- tool_call_id: call_bt2tgwxs
- - content: null
- reasoning: Need import re.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"import re\nresults = search(''document element types'', limit=50)\ntypes=set()\nfor r in
- results:\n for line in r[''content''].split('',''):\n m = re.match(r''\\s*(\\w[\\w-]*)(?:\\s*[,\\.]|$)'',
- line)\n if m:\n types.add(m.group(1))\nprint(sorted(types))\n"}'
- name: execute_code
- id: call_iflcvbbl
- type: function
- - content: '{"code":"import re\nresults = search(''document element types'', limit=50)\ntypes=set()\nfor r in results:\n for
- line in r[''content''].split('',''):\n m = re.match(r''\\s*(\\w[\\w-]*)(?:\\s*[,\\.]|$)'', line)\n if
- m:\n types.add(m.group(1))\nprint(sorted(types))\n","stdout":"","stderr":"ModuleNotFoundError: No module
- named ''re''","success":false}'
- role: tool
- tool_call_id: call_iflcvbbl
- - content: null
- reasoning: Maybe re is not available? But standard library re should exist. Maybe import is required differently.
- It's standard. Could be restricted? Try import re inside same code. That we did. Maybe environment prohibits re?
- Let's try a simple import re example.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"import re\nprint(''ok'')\n"}'
- name: execute_code
- id: call_1uala4jy
- type: function
- - content: '{"code":"import re\nprint(''ok'')\n","stdout":"","stderr":"ModuleNotFoundError: No module named ''re''","success":false}'
- role: tool
- tool_call_id: call_1uala4jy
- - content: null
- reasoning: 'No re? Try using import regex? Probably not. Alternative: use simple split on whitespace and punctuation.
- We can manually list known types from content. Let''s search for "label" and extract.'
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"results = search(''label'', limit=20)\nfor r in results:\n print(r[''document_title''],
- r[''page_numbers''], r[''content''][:200])\n"}'
- name: execute_code
- id: call_mputujfq
- type: function
- - content: '{"code":"results = search(''label'', limit=20)\nfor r in results:\n print(r[''document_title''], r[''page_numbers''],
- r[''content''][:200])\n","stdout":"None [1] Phase 2: Label selection and guideline. We reviewed the collected documents
- and identified the most common structural features they exhibit. This was achieved by identifying recurrent layout
- elements \nNone [1] \nTable 1: DocLayNet dataset overview. Along with the frequency of each class label, we present
- the relative occurrence (as % of row \"Total\") in the train, test and validation sets. The inter-annotator\nNone
- [1] mAP @ 0.5-0.95 (%).Sci = 89-94. Total, triple inter-annotator mAP @ 0.5-0.95 (%).Law = 86-91. Total, triple
- inter-annotator mAP @ 0.5-0.95 (%).Pat = 71-76. Total, triple inter-annotator mAP @ 0.5-0.95\nNone [1] Caption,
- Count = 22524. Caption, % of Total.Train = 2.04. Caption, % of Total.Test = 1.77. Caption, % of Total.Val = 2.32.
- Caption, triple inter-annotator mAP @ 0.5-0.95 (%).All = 84-89. Caption, trip\nNone [1] Page-footer, triple inter-annotator
- mAP @ 0.5-0.95 (%).Ten = 96-98. Page-header, Count = 58022. Page-header, % of Total.Train = 5.10. Page-header, %
- of Total.Test = 6.70. Page-header, % of Total.Val =\nNone [1] (%).Sci = 94-100. Title, triple inter-annotator mAP
- @ 0.5-0.95 (%).Law = 82-96. Title, triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 68-79. Title, triple inter-annotator
- mAP @ 0.5-0.95 (%).Ten = 24-\nNone [1] n/a. Footnote, Count = 6318. Footnote, % of Total.Train = 0.60. Footnote,
- % of Total.Test = 0.31. Footnote, % of Total.Val = 0.58. Footnote, triple inter-annotator mAP @ 0.5-0.95 (%).All
- = 83-91. Foot\nNone [1] 0.5-0.95 (%).Law = 87-94. Section-header, triple inter-annotator mAP @ 0.5-0.95 (%).Pat
- = 69-73. Section-header, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 78-86. Table, Count = 34733. Table, % o\nNone
- [1] inter-annotator mAP @ 0.5-0.95 (%).Pat = 66-71. Picture, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 59-76.
- Section-header, Count = 142884. Section-header, % of Total.Train = 12.60. Section-header\nNone [1] 0.5-0.95 (%).Pat
- = 97-99. Page-header, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 81-86. Picture, Count = 45976. Picture, %
- of Total.Train = 4.21. Picture, % of Total.Test = 2.78. Picture, % of T\nNone [1] 185660. List-item, % of Total.Train
- = 17.19. List-item, % of Total.Test = 13.34. List-item, % of Total.Val = 15.82. List-item, triple inter-annotator
- mAP @ 0.5-0.95 (%).All = 87-88. List-item, triple \nNone [1] inter-annotator mAP @ 0.5-0.95 (%).Law = 58-80. Table,
- triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 79-84. Table, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 70-85.
- Text, Count = 510377. Text, \nNone [1] 89-93. Text, triple inter-annotator mAP @ 0.5-0.95 (%).Law = 87-92. Text,
- triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 71-79. Text, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 87-95.
- Title, Cou\nNone [1] 0.5-0.95 (%).Ten = 93-95. Page-footer, Count = 70878. Page-footer, % of Total.Train = 6.51.
- Page-footer, % of Total.Test = 5.58. Page-footer, % of Total.Val = 6.00. Page-footer, triple inter-annotator\nNone
- [1] = 82-97. Formula, Count = 25027. Formula, % of Total.Train = 2.25. Formula, % of Total.Test = 1.90. Formula,
- % of Total.Val = 2.96. Formula, triple inter-annotator mAP @ 0.5-0.95 (%).All = 83-85. Form\nNone [1] Phase 1: Data
- selection and preparation. Our inclusion criteria for documents were described in Section 3. A large effort went
- into ensuring that all documents are free to use. The data sources includ\nNone [1] $_{Affiliation}$, as seen in
- DocBank, are often only distinguishable by discriminating on\nPreparation work included uploading and parsing the
- sourced PDF documents in the Corpus Conversion Service (CC\n","stderr":"","success":true}'
- role: tool
- tool_call_id: call_mputujfq
+ tool_call_id: call_5sejw3xj
model: gpt-oss
reasoning_effort: low
stream: false
@@ -2185,955 +1051,7 @@ interactions:
response:
headers:
content-length:
- - '838'
- content-type:
- - application/json
- parsed_body:
- choices:
- - finish_reason: tool_calls
- index: 0
- message:
- content: ''
- reasoning: Collect unique words before comma from lines like "Caption, Count =". Let's parse manually.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"texts = []\nresults = search(''label'', limit=20)\nfor r in results:\n texts.append(r[''content''])\n#
- simple split by comma\nparts = [p.strip() for c in texts for p in c.split('','')]\ntypes=set()\nfor p in parts:\n if
- p:\n w = p.split()[0]\n types.add(w)\nprint(sorted(types))\n"}'
- name: execute_code
- id: call_lya9szfq
- index: 0
- type: function
- created: 1771336394
- id: chatcmpl-712
- model: gpt-oss
- object: chat.completion
- system_fingerprint: fp_ollama
- usage:
- completion_tokens: 133
- prompt_tokens: 5323
- total_tokens: 5456
- status:
- code: 200
- message: OK
-- request:
- headers:
- accept:
- - application/json
- accept-encoding:
- - gzip, deflate, zstd
- connection:
- - keep-alive
- content-length:
- - '75'
- content-type:
- - application/json
- host:
- - localhost:11434
- method: POST
- parsed_body:
- encoding_format: base64
- input:
- - label
- model: qwen3-embedding:4b
- uri: http://localhost:11434/v1/embeddings
- response:
- headers:
- content-type:
- - application/json
- transfer-encoding:
- - chunked
- parsed_body:
- data:
- - embedding: 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
- index: 0
- object: embedding
- model: qwen3-embedding:4b
- object: list
- usage:
- prompt_tokens: 2
- total_tokens: 2
- status:
- code: 200
- message: OK
-- request:
- headers:
- accept:
- - application/json
- accept-encoding:
- - gzip, deflate, zstd
- connection:
- - keep-alive
- content-length:
- - '20638'
- content-type:
- - application/json
- host:
- - localhost:11434
- method: POST
- parsed_body:
- messages:
- - content: |-
- You are a Recursive Language Model (RLM) agent that solves complex research questions by writing and executing Python code.
-
- IMPORTANT: You MUST use the `execute_code` tool to run Python code. The functions described below are ONLY available inside the execute_code tool - you cannot access them any other way. Always execute code to answer questions; do not just describe what code would do.
-
- CRITICAL: Inside execute_code, these functions are ALREADY available in the namespace. Do NOT import them - just use them directly:
- - search("query") ✓ CORRECT
- - from haiku.rag import search ✗ WRONG - will fail
-
- You have access to a sandboxed Python environment with these haiku.rag functions (use them directly, no imports needed):
-
- ## Available Functions
-
- ### search(query, limit=10) -> list[dict]
- Search the knowledge base using hybrid search (vector + full-text).
- Returns list of dicts with keys: chunk_id, content, document_id, document_title, document_uri, score, page_numbers, headings
-
- ### list_documents(limit=10, offset=0) -> list[dict]
- List available documents in the knowledge base.
- Returns list of dicts with keys: id, title, uri, created_at
-
- ### get_document(id_or_title) -> str | None
- Get the full text content of a document by ID, title, or URI.
- Returns the document content as a string, or None if not found.
-
- ### get_docling_document(id_or_title) -> DoclingDocument | None
- Get the structured DoclingDocument object for advanced analysis.
- Returns a DoclingDocument object, or None if not found.
- See "DoclingDocument API" section below for how to use it.
-
- ### llm(prompt) -> str
- Call an LLM directly with the given prompt. Returns the response as a string.
- Use this for classification, summarization, extraction, or any task where you
- already have the content and just need LLM reasoning.
-
- ## Pre-loaded Documents Variable
-
- If documents were pre-loaded for this session, a `documents` variable is available:
- ```python
- # documents is a list of dicts with keys: id, title, uri, content
- for doc in documents:
- print(doc['title'], len(doc['content']))
- ```
- Check if it exists with: `if 'documents' in dir(): ...`
-
- ## Standard Library Modules
- You can import any Python standard library module.
-
- ## Strategy Guide
-
- 1. **Explore First**: Start by listing documents or searching to understand what's available. Document names may differ from filenames (e.g., "tbmed593.pdf" might be stored as "TB MED 593" or similar).
- 2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
- 3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
- 4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
- 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and use collections.
- 6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
- 7. **Cite Your Sources**: Track which documents/chunks informed your answer for citation.
-
- ## DoclingDocument API
-
- When you call `get_docling_document(id_or_title)`, you get a DoclingDocument object for structured document analysis.
-
- ### Properties
- - `doc.texts` - List of all text items (paragraphs, headings, etc.)
- - `doc.tables` - List of all tables
- - `doc.pictures` - List of all pictures/figures
- - `doc.name` - Document name
-
- ### Methods
- - `doc.iterate_items(with_groups=False)` - Iterate all items with hierarchy level
- Returns tuples of (item, level) where level is nesting depth
- - `doc.export_to_markdown()` - Export entire document as markdown string
-
- ### Text Item Properties
- - `item.text` - The text content
- - `item.label` - Type: title, paragraph, section_header, list_item, etc. (lowercase enum values)
- - `item.prov` - Provenance (page numbers, bounding boxes)
-
- ### Table Access
- - `table.data.num_rows`, `table.data.num_cols` - Dimensions
- - `table.data.table_cells` - List of TableCell objects
- - `cell.text`, `cell.start_row_offset_idx`, `cell.start_col_offset_idx`
-
- ### Example Usage
- ```python
- doc = get_docling_document("My Document")
-
- # Get all headings
- headings = [t.text for t in doc.texts if "header" in str(t.label)]
-
- # Iterate with structure
- for item, level in doc.iterate_items():
- print(" " * level + item.text[:50])
-
- # Extract table data
- for table in doc.tables:
- for cell in table.data.table_cells:
- print(f"Row {cell.start_row_offset_idx}, Col {cell.start_col_offset_idx}: {cell.text}")
- ```
-
- ## Example Patterns
-
- ### Counting documents matching a condition
- ```python
- docs = list_documents(limit=100)
- count = 0
- for doc in docs:
- content = get_document(doc['id'])
- if content and 'keyword' in content.lower():
- count += 1
- print(f"Found in: {doc['title']}")
- print(f"Total: {count}")
- ```
-
- ### Aggregating data across documents
- ```python
- import re
- numbers = []
- results = search("financial data", limit=20)
- for r in results:
- matches = re.findall(r'\$([\d,]+)', r['content'])
- for m in matches:
- numbers.append(int(m.replace(',', '')))
- print(f"Average: ${sum(numbers)/len(numbers):,.2f}")
- ```
-
- ### Using llm() for classification
- ```python
- # Get document content
- content = get_document("Q1 Report")
- # Use llm() to classify sentiment
- sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
- print(sentiment)
- ```
-
- ## Workflow
-
- 1. **ALWAYS start by using execute_code** to explore the knowledge base
- 2. Run multiple code blocks as needed to gather information
- 3. After collecting data, provide your final answer
-
- ## Output Format
-
- CRITICAL: Your final response MUST be valid JSON matching this exact schema:
- ```json
- {"answer": "Your complete answer here as a string", "program": "Your final consolidated program here as a string"}
- ```
-
- - `answer`: A clear answer to the user's question with key findings and references to specific documents/chunks.
- - `program`: A single, self-contained Python program that produces the answer. Consolidate your exploratory code executions into one clean script.
-
- Do NOT return arbitrary JSON structures. Always use the exact format: {"answer": "...", "program": "..."}
-
- CRITICAL: You MUST call execute_code at least once before providing your answer. Never give up without trying to execute code first.
- role: system
- - content: Search for content about document element types or labels. What are all the different document element types
- mentioned? List them all.
- role: user
- - content: null
- reasoning: We need to search for content about document element types or labels. Use search.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"results = search(''document element types'', limit=20)\nfor r in results:\n print(r[''document_title''],
- r[''page_numbers''], r[''content''][:200])\n"}'
- name: execute_code
- id: call_w56yspv7
- type: function
- - content: '{"code":"results = search(''document element types'', limit=20)\nfor r in results:\n print(r[''document_title''],
- r[''page_numbers''], r[''content''][:200])\n","stdout":"None [1] Phase 2: Label selection and guideline. We reviewed
- the collected documents and identified the most common structural features they exhibit. This was achieved by identifying
- recurrent layout elements \nNone [1] Phase 1: Data selection and preparation. Our inclusion criteria for documents
- were described in Section 3. A large effort went into ensuring that all documents are free to use. The data sources
- includ\nNone [1] $_{Affiliation}$, as seen in DocBank, are often only distinguishable by discriminating on\nPreparation
- work included uploading and parsing the sourced PDF documents in the Corpus Conversion Service (CC\nNone [1] \nTable
- 1: DocLayNet dataset overview. Along with the frequency of each class label, we present the relative occurrence
- (as % of row \"Total\") in the train, test and validation sets. The inter-annotator\nNone [1] Page-footer, triple
- inter-annotator mAP @ 0.5-0.95 (%).Ten = 96-98. Page-header, Count = 58022. Page-header, % of Total.Train = 5.10.
- Page-header, % of Total.Test = 6.70. Page-header, % of Total.Val =\nNone [1] 0.5-0.95 (%).Law = 87-94. Section-header,
- triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 69-73. Section-header, triple inter-annotator mAP @ 0.5-0.95 (%).Ten
- = 78-86. Table, Count = 34733. Table, % o\nNone [1] Caption, Count = 22524. Caption, % of Total.Train = 2.04. Caption,
- % of Total.Test = 1.77. Caption, % of Total.Val = 2.32. Caption, triple inter-annotator mAP @ 0.5-0.95 (%).All =
- 84-89. Caption, trip\nNone [1] inter-annotator mAP @ 0.5-0.95 (%).Pat = 66-71. Picture, triple inter-annotator mAP
- @ 0.5-0.95 (%).Ten = 59-76. Section-header, Count = 142884. Section-header, % of Total.Train = 12.60. Section-header\nNone
- [1] n/a. Footnote, Count = 6318. Footnote, % of Total.Train = 0.60. Footnote, % of Total.Test = 0.31. Footnote,
- % of Total.Val = 0.58. Footnote, triple inter-annotator mAP @ 0.5-0.95 (%).All = 83-91. Foot\nNone [1] 185660. List-item,
- % of Total.Train = 17.19. List-item, % of Total.Test = 13.34. List-item, % of Total.Val = 15.82. List-item, triple
- inter-annotator mAP @ 0.5-0.95 (%).All = 87-88. List-item, triple \nNone [1] 0.5-0.95 (%).Ten = 93-95. Page-footer,
- Count = 70878. Page-footer, % of Total.Train = 6.51. Page-footer, % of Total.Test = 5.58. Page-footer, % of Total.Val
- = 6.00. Page-footer, triple inter-annotator\nNone [1] 89-93. Text, triple inter-annotator mAP @ 0.5-0.95 (%).Law
- = 87-92. Text, triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 71-79. Text, triple inter-annotator mAP @ 0.5-0.95
- (%).Ten = 87-95. Title, Cou\nNone [1] 0.5-0.95 (%).Pat = 97-99. Page-header, triple inter-annotator mAP @ 0.5-0.95
- (%).Ten = 81-86. Picture, Count = 45976. Picture, % of Total.Train = 4.21. Picture, % of Total.Test = 2.78. Picture,
- % of T\nNone [1] (%).Sci = 94-100. Title, triple inter-annotator mAP @ 0.5-0.95 (%).Law = 82-96. Title, triple inter-annotator
- mAP @ 0.5-0.95 (%).Pat = 68-79. Title, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 24-\nNone [1] inter-annotator
- mAP @ 0.5-0.95 (%).Law = 58-80. Table, triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 79-84. Table, triple inter-annotator
- mAP @ 0.5-0.95 (%).Ten = 70-85. Text, Count = 510377. Text, \nNone [1] = 82-97. Formula, Count = 25027. Formula,
- % of Total.Train = 2.25. Formula, % of Total.Test = 1.90. Formula, % of Total.Val = 2.96. Formula, triple inter-annotator
- mAP @ 0.5-0.95 (%).All = 83-85. Form\nNone [1] mAP @ 0.5-0.95 (%).Sci = 89-94. Total, triple inter-annotator mAP
- @ 0.5-0.95 (%).Law = 86-91. Total, triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 71-76. Total, triple inter-annotator
- mAP @ 0.5-0.95\n","stderr":"","success":true}'
- role: tool
- tool_call_id: call_w56yspv7
- - content: null
- reasoning: 'We see many types: Page-header, Page-footer, Section-header, Caption, Picture, List-item, Footnote, Text,
- Title, Formula etc. Let''s extract unique types. Probably need to parse for words before comma. Use regex.'
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"import re\nresults = search(''document element types'', limit=50)\ntypes=set()\nfor r in
- results:\n for line in r[''content''].split('',''):\n m = re.match(r''\\s*(\\w[\\w-]*)(?:\\s*[,\\.]|$)'',
- line)\n if m:\n types.add(m.group(1))\nprint(sorted(types))\n"}'
- name: execute_code
- id: call_bt2tgwxs
- type: function
- - content: '{"code":"import re\nresults = search(''document element types'', limit=50)\ntypes=set()\nfor r in results:\n for
- line in r[''content''].split('',''):\n m = re.match(r''\\s*(\\w[\\w-]*)(?:\\s*[,\\.]|$)'', line)\n if
- m:\n types.add(m.group(1))\nprint(sorted(types))\n","stdout":"","stderr":"ModuleNotFoundError: No module
- named ''re''","success":false}'
- role: tool
- tool_call_id: call_bt2tgwxs
- - content: null
- reasoning: Need import re.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"import re\nresults = search(''document element types'', limit=50)\ntypes=set()\nfor r in
- results:\n for line in r[''content''].split('',''):\n m = re.match(r''\\s*(\\w[\\w-]*)(?:\\s*[,\\.]|$)'',
- line)\n if m:\n types.add(m.group(1))\nprint(sorted(types))\n"}'
- name: execute_code
- id: call_iflcvbbl
- type: function
- - content: '{"code":"import re\nresults = search(''document element types'', limit=50)\ntypes=set()\nfor r in results:\n for
- line in r[''content''].split('',''):\n m = re.match(r''\\s*(\\w[\\w-]*)(?:\\s*[,\\.]|$)'', line)\n if
- m:\n types.add(m.group(1))\nprint(sorted(types))\n","stdout":"","stderr":"ModuleNotFoundError: No module
- named ''re''","success":false}'
- role: tool
- tool_call_id: call_iflcvbbl
- - content: null
- reasoning: Maybe re is not available? But standard library re should exist. Maybe import is required differently.
- It's standard. Could be restricted? Try import re inside same code. That we did. Maybe environment prohibits re?
- Let's try a simple import re example.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"import re\nprint(''ok'')\n"}'
- name: execute_code
- id: call_1uala4jy
- type: function
- - content: '{"code":"import re\nprint(''ok'')\n","stdout":"","stderr":"ModuleNotFoundError: No module named ''re''","success":false}'
- role: tool
- tool_call_id: call_1uala4jy
- - content: null
- reasoning: 'No re? Try using import regex? Probably not. Alternative: use simple split on whitespace and punctuation.
- We can manually list known types from content. Let''s search for "label" and extract.'
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"results = search(''label'', limit=20)\nfor r in results:\n print(r[''document_title''],
- r[''page_numbers''], r[''content''][:200])\n"}'
- name: execute_code
- id: call_mputujfq
- type: function
- - content: '{"code":"results = search(''label'', limit=20)\nfor r in results:\n print(r[''document_title''], r[''page_numbers''],
- r[''content''][:200])\n","stdout":"None [1] Phase 2: Label selection and guideline. We reviewed the collected documents
- and identified the most common structural features they exhibit. This was achieved by identifying recurrent layout
- elements \nNone [1] \nTable 1: DocLayNet dataset overview. Along with the frequency of each class label, we present
- the relative occurrence (as % of row \"Total\") in the train, test and validation sets. The inter-annotator\nNone
- [1] mAP @ 0.5-0.95 (%).Sci = 89-94. Total, triple inter-annotator mAP @ 0.5-0.95 (%).Law = 86-91. Total, triple
- inter-annotator mAP @ 0.5-0.95 (%).Pat = 71-76. Total, triple inter-annotator mAP @ 0.5-0.95\nNone [1] Caption,
- Count = 22524. Caption, % of Total.Train = 2.04. Caption, % of Total.Test = 1.77. Caption, % of Total.Val = 2.32.
- Caption, triple inter-annotator mAP @ 0.5-0.95 (%).All = 84-89. Caption, trip\nNone [1] Page-footer, triple inter-annotator
- mAP @ 0.5-0.95 (%).Ten = 96-98. Page-header, Count = 58022. Page-header, % of Total.Train = 5.10. Page-header, %
- of Total.Test = 6.70. Page-header, % of Total.Val =\nNone [1] (%).Sci = 94-100. Title, triple inter-annotator mAP
- @ 0.5-0.95 (%).Law = 82-96. Title, triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 68-79. Title, triple inter-annotator
- mAP @ 0.5-0.95 (%).Ten = 24-\nNone [1] n/a. Footnote, Count = 6318. Footnote, % of Total.Train = 0.60. Footnote,
- % of Total.Test = 0.31. Footnote, % of Total.Val = 0.58. Footnote, triple inter-annotator mAP @ 0.5-0.95 (%).All
- = 83-91. Foot\nNone [1] 0.5-0.95 (%).Law = 87-94. Section-header, triple inter-annotator mAP @ 0.5-0.95 (%).Pat
- = 69-73. Section-header, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 78-86. Table, Count = 34733. Table, % o\nNone
- [1] inter-annotator mAP @ 0.5-0.95 (%).Pat = 66-71. Picture, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 59-76.
- Section-header, Count = 142884. Section-header, % of Total.Train = 12.60. Section-header\nNone [1] 0.5-0.95 (%).Pat
- = 97-99. Page-header, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 81-86. Picture, Count = 45976. Picture, %
- of Total.Train = 4.21. Picture, % of Total.Test = 2.78. Picture, % of T\nNone [1] 185660. List-item, % of Total.Train
- = 17.19. List-item, % of Total.Test = 13.34. List-item, % of Total.Val = 15.82. List-item, triple inter-annotator
- mAP @ 0.5-0.95 (%).All = 87-88. List-item, triple \nNone [1] inter-annotator mAP @ 0.5-0.95 (%).Law = 58-80. Table,
- triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 79-84. Table, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 70-85.
- Text, Count = 510377. Text, \nNone [1] 89-93. Text, triple inter-annotator mAP @ 0.5-0.95 (%).Law = 87-92. Text,
- triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 71-79. Text, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 87-95.
- Title, Cou\nNone [1] 0.5-0.95 (%).Ten = 93-95. Page-footer, Count = 70878. Page-footer, % of Total.Train = 6.51.
- Page-footer, % of Total.Test = 5.58. Page-footer, % of Total.Val = 6.00. Page-footer, triple inter-annotator\nNone
- [1] = 82-97. Formula, Count = 25027. Formula, % of Total.Train = 2.25. Formula, % of Total.Test = 1.90. Formula,
- % of Total.Val = 2.96. Formula, triple inter-annotator mAP @ 0.5-0.95 (%).All = 83-85. Form\nNone [1] Phase 1: Data
- selection and preparation. Our inclusion criteria for documents were described in Section 3. A large effort went
- into ensuring that all documents are free to use. The data sources includ\nNone [1] $_{Affiliation}$, as seen in
- DocBank, are often only distinguishable by discriminating on\nPreparation work included uploading and parsing the
- sourced PDF documents in the Corpus Conversion Service (CC\n","stderr":"","success":true}'
- role: tool
- tool_call_id: call_mputujfq
- - content: null
- reasoning: Collect unique words before comma from lines like "Caption, Count =". Let's parse manually.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"texts = []\nresults = search(''label'', limit=20)\nfor r in results:\n texts.append(r[''content''])\n#
- simple split by comma\nparts = [p.strip() for c in texts for p in c.split('','')]\ntypes=set()\nfor p in parts:\n if
- p:\n w = p.split()[0]\n types.add(w)\nprint(sorted(types))\n"}'
- name: execute_code
- id: call_lya9szfq
- type: function
- - content: '{"code":"texts = []\nresults = search(''label'', limit=20)\nfor r in results:\n texts.append(r[''content''])\n#
- simple split by comma\nparts = [p.strip() for c in texts for p in c.split('','')]\ntypes=set()\nfor p in parts:\n if
- p:\n w = p.split()[0]\n types.add(w)\nprint(sorted(types))\n","stdout":"[''$_{Affiliation}$'', ''$_{Footnote}$'',
- ''$_{Formula}$'', ''$_{List-item}$'', ''$_{Page-header}$'', ''$_{Picture}$'', ''$_{Section-header}$'', ''$_{Table}$'',
- ''$_{Text}$'', ''%'', ''(%).Sci'', ''(2)'', ''(3)'', ''0.5-0.95'', ''185660.'', ''89-93.'', ''='', ''Caption'',
- ''Count'', ''Page-$_{footer}$'', ''Page-footer'', ''Phase'', ''Table'', ''a'', ''and'', ''are'', ''as'', ''company'',
- ''from'', ''government'', ''inter-annotator'', ''mAP'', ''n/a.'', ''such'', ''test'', ''triple'', ''we'', ''which'',
- ''while'', ''with'']\n","stderr":"","success":true}'
- role: tool
- tool_call_id: call_lya9szfq
- model: gpt-oss
- reasoning_effort: low
- stream: false
- tool_choice: auto
- tools:
- - function:
- description: |-
- Execute Python code in a sandboxed interpreter.
-
- The code has access to haiku.rag functions (search, list_documents,
- get_document, get_chunk, llm).
-
- Use print() to output results.
-
- Structured result with success status, stdout, and stderr.
-
- name: execute_code
- parameters:
- additionalProperties: false
- properties:
- code:
- description: Python code to execute.
- type: string
- required:
- - code
- type: object
- strict: true
- type: function
- - function:
- description: Result from RLM agent execution.
- name: final_result
- parameters:
- additionalProperties: false
- properties:
- answer:
- description: The answer to the user's question
- type: string
- program:
- description: The final consolidated program
- type: string
- required:
- - answer
- - program
- type: object
- strict: true
- type: function
- uri: http://localhost:11434/v1/chat/completions
- response:
- headers:
- content-length:
- - '771'
- content-type:
- - application/json
- parsed_body:
- choices:
- - finish_reason: tool_calls
- index: 0
- message:
- content: ''
- reasoning: 'This extraction not good. Instead use known types from dataset: Page-header, Page-footer, Section-header,
- Caption, Picture, List-item, Footnote, Text, Title, Formula, Table. Also maybe Abstract? Let''s search ''Abstract''.'
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"results = search(''Abstract'', limit=10)\nfor r in results:\n print(r[''document_title''],
- r[''content''][:200])\n"}'
- name: execute_code
- id: call_lzf7e2ob
- index: 0
- type: function
- created: 1771336397
- id: chatcmpl-80
- model: gpt-oss
- object: chat.completion
- system_fingerprint: fp_ollama
- usage:
- completion_tokens: 104
- prompt_tokens: 5746
- total_tokens: 5850
- status:
- code: 200
- message: OK
-- request:
- headers:
- accept:
- - application/json
- accept-encoding:
- - gzip, deflate, zstd
- connection:
- - keep-alive
- content-length:
- - '78'
- content-type:
- - application/json
- host:
- - localhost:11434
- method: POST
- parsed_body:
- encoding_format: base64
- input:
- - Abstract
- model: qwen3-embedding:4b
- uri: http://localhost:11434/v1/embeddings
- response:
- headers:
- content-type:
- - application/json
- transfer-encoding:
- - chunked
- parsed_body:
- data:
- - embedding: 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
- index: 0
- object: embedding
- model: qwen3-embedding:4b
- object: list
- usage:
- prompt_tokens: 2
- total_tokens: 2
- status:
- code: 200
- message: OK
-- request:
- headers:
- accept:
- - application/json
- accept-encoding:
- - gzip, deflate, zstd
- connection:
- - keep-alive
- content-length:
- - '23461'
- content-type:
- - application/json
- host:
- - localhost:11434
- method: POST
- parsed_body:
- messages:
- - content: |-
- You are a Recursive Language Model (RLM) agent that solves complex research questions by writing and executing Python code.
-
- IMPORTANT: You MUST use the `execute_code` tool to run Python code. The functions described below are ONLY available inside the execute_code tool - you cannot access them any other way. Always execute code to answer questions; do not just describe what code would do.
-
- CRITICAL: Inside execute_code, these functions are ALREADY available in the namespace. Do NOT import them - just use them directly:
- - search("query") ✓ CORRECT
- - from haiku.rag import search ✗ WRONG - will fail
-
- You have access to a sandboxed Python environment with these haiku.rag functions (use them directly, no imports needed):
-
- ## Available Functions
-
- ### search(query, limit=10) -> list[dict]
- Search the knowledge base using hybrid search (vector + full-text).
- Returns list of dicts with keys: chunk_id, content, document_id, document_title, document_uri, score, page_numbers, headings
-
- ### list_documents(limit=10, offset=0) -> list[dict]
- List available documents in the knowledge base.
- Returns list of dicts with keys: id, title, uri, created_at
-
- ### get_document(id_or_title) -> str | None
- Get the full text content of a document by ID, title, or URI.
- Returns the document content as a string, or None if not found.
-
- ### get_docling_document(id_or_title) -> DoclingDocument | None
- Get the structured DoclingDocument object for advanced analysis.
- Returns a DoclingDocument object, or None if not found.
- See "DoclingDocument API" section below for how to use it.
-
- ### llm(prompt) -> str
- Call an LLM directly with the given prompt. Returns the response as a string.
- Use this for classification, summarization, extraction, or any task where you
- already have the content and just need LLM reasoning.
-
- ## Pre-loaded Documents Variable
-
- If documents were pre-loaded for this session, a `documents` variable is available:
- ```python
- # documents is a list of dicts with keys: id, title, uri, content
- for doc in documents:
- print(doc['title'], len(doc['content']))
- ```
- Check if it exists with: `if 'documents' in dir(): ...`
-
- ## Standard Library Modules
- You can import any Python standard library module.
-
- ## Strategy Guide
-
- 1. **Explore First**: Start by listing documents or searching to understand what's available. Document names may differ from filenames (e.g., "tbmed593.pdf" might be stored as "TB MED 593" or similar).
- 2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
- 3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
- 4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
- 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and use collections.
- 6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
- 7. **Cite Your Sources**: Track which documents/chunks informed your answer for citation.
-
- ## DoclingDocument API
-
- When you call `get_docling_document(id_or_title)`, you get a DoclingDocument object for structured document analysis.
-
- ### Properties
- - `doc.texts` - List of all text items (paragraphs, headings, etc.)
- - `doc.tables` - List of all tables
- - `doc.pictures` - List of all pictures/figures
- - `doc.name` - Document name
-
- ### Methods
- - `doc.iterate_items(with_groups=False)` - Iterate all items with hierarchy level
- Returns tuples of (item, level) where level is nesting depth
- - `doc.export_to_markdown()` - Export entire document as markdown string
-
- ### Text Item Properties
- - `item.text` - The text content
- - `item.label` - Type: title, paragraph, section_header, list_item, etc. (lowercase enum values)
- - `item.prov` - Provenance (page numbers, bounding boxes)
-
- ### Table Access
- - `table.data.num_rows`, `table.data.num_cols` - Dimensions
- - `table.data.table_cells` - List of TableCell objects
- - `cell.text`, `cell.start_row_offset_idx`, `cell.start_col_offset_idx`
-
- ### Example Usage
- ```python
- doc = get_docling_document("My Document")
-
- # Get all headings
- headings = [t.text for t in doc.texts if "header" in str(t.label)]
-
- # Iterate with structure
- for item, level in doc.iterate_items():
- print(" " * level + item.text[:50])
-
- # Extract table data
- for table in doc.tables:
- for cell in table.data.table_cells:
- print(f"Row {cell.start_row_offset_idx}, Col {cell.start_col_offset_idx}: {cell.text}")
- ```
-
- ## Example Patterns
-
- ### Counting documents matching a condition
- ```python
- docs = list_documents(limit=100)
- count = 0
- for doc in docs:
- content = get_document(doc['id'])
- if content and 'keyword' in content.lower():
- count += 1
- print(f"Found in: {doc['title']}")
- print(f"Total: {count}")
- ```
-
- ### Aggregating data across documents
- ```python
- import re
- numbers = []
- results = search("financial data", limit=20)
- for r in results:
- matches = re.findall(r'\$([\d,]+)', r['content'])
- for m in matches:
- numbers.append(int(m.replace(',', '')))
- print(f"Average: ${sum(numbers)/len(numbers):,.2f}")
- ```
-
- ### Using llm() for classification
- ```python
- # Get document content
- content = get_document("Q1 Report")
- # Use llm() to classify sentiment
- sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
- print(sentiment)
- ```
-
- ## Workflow
-
- 1. **ALWAYS start by using execute_code** to explore the knowledge base
- 2. Run multiple code blocks as needed to gather information
- 3. After collecting data, provide your final answer
-
- ## Output Format
-
- CRITICAL: Your final response MUST be valid JSON matching this exact schema:
- ```json
- {"answer": "Your complete answer here as a string", "program": "Your final consolidated program here as a string"}
- ```
-
- - `answer`: A clear answer to the user's question with key findings and references to specific documents/chunks.
- - `program`: A single, self-contained Python program that produces the answer. Consolidate your exploratory code executions into one clean script.
-
- Do NOT return arbitrary JSON structures. Always use the exact format: {"answer": "...", "program": "..."}
-
- CRITICAL: You MUST call execute_code at least once before providing your answer. Never give up without trying to execute code first.
- role: system
- - content: Search for content about document element types or labels. What are all the different document element types
- mentioned? List them all.
- role: user
- - content: null
- reasoning: We need to search for content about document element types or labels. Use search.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"results = search(''document element types'', limit=20)\nfor r in results:\n print(r[''document_title''],
- r[''page_numbers''], r[''content''][:200])\n"}'
- name: execute_code
- id: call_w56yspv7
- type: function
- - content: '{"code":"results = search(''document element types'', limit=20)\nfor r in results:\n print(r[''document_title''],
- r[''page_numbers''], r[''content''][:200])\n","stdout":"None [1] Phase 2: Label selection and guideline. We reviewed
- the collected documents and identified the most common structural features they exhibit. This was achieved by identifying
- recurrent layout elements \nNone [1] Phase 1: Data selection and preparation. Our inclusion criteria for documents
- were described in Section 3. A large effort went into ensuring that all documents are free to use. The data sources
- includ\nNone [1] $_{Affiliation}$, as seen in DocBank, are often only distinguishable by discriminating on\nPreparation
- work included uploading and parsing the sourced PDF documents in the Corpus Conversion Service (CC\nNone [1] \nTable
- 1: DocLayNet dataset overview. Along with the frequency of each class label, we present the relative occurrence
- (as % of row \"Total\") in the train, test and validation sets. The inter-annotator\nNone [1] Page-footer, triple
- inter-annotator mAP @ 0.5-0.95 (%).Ten = 96-98. Page-header, Count = 58022. Page-header, % of Total.Train = 5.10.
- Page-header, % of Total.Test = 6.70. Page-header, % of Total.Val =\nNone [1] 0.5-0.95 (%).Law = 87-94. Section-header,
- triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 69-73. Section-header, triple inter-annotator mAP @ 0.5-0.95 (%).Ten
- = 78-86. Table, Count = 34733. Table, % o\nNone [1] Caption, Count = 22524. Caption, % of Total.Train = 2.04. Caption,
- % of Total.Test = 1.77. Caption, % of Total.Val = 2.32. Caption, triple inter-annotator mAP @ 0.5-0.95 (%).All =
- 84-89. Caption, trip\nNone [1] inter-annotator mAP @ 0.5-0.95 (%).Pat = 66-71. Picture, triple inter-annotator mAP
- @ 0.5-0.95 (%).Ten = 59-76. Section-header, Count = 142884. Section-header, % of Total.Train = 12.60. Section-header\nNone
- [1] n/a. Footnote, Count = 6318. Footnote, % of Total.Train = 0.60. Footnote, % of Total.Test = 0.31. Footnote,
- % of Total.Val = 0.58. Footnote, triple inter-annotator mAP @ 0.5-0.95 (%).All = 83-91. Foot\nNone [1] 185660. List-item,
- % of Total.Train = 17.19. List-item, % of Total.Test = 13.34. List-item, % of Total.Val = 15.82. List-item, triple
- inter-annotator mAP @ 0.5-0.95 (%).All = 87-88. List-item, triple \nNone [1] 0.5-0.95 (%).Ten = 93-95. Page-footer,
- Count = 70878. Page-footer, % of Total.Train = 6.51. Page-footer, % of Total.Test = 5.58. Page-footer, % of Total.Val
- = 6.00. Page-footer, triple inter-annotator\nNone [1] 89-93. Text, triple inter-annotator mAP @ 0.5-0.95 (%).Law
- = 87-92. Text, triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 71-79. Text, triple inter-annotator mAP @ 0.5-0.95
- (%).Ten = 87-95. Title, Cou\nNone [1] 0.5-0.95 (%).Pat = 97-99. Page-header, triple inter-annotator mAP @ 0.5-0.95
- (%).Ten = 81-86. Picture, Count = 45976. Picture, % of Total.Train = 4.21. Picture, % of Total.Test = 2.78. Picture,
- % of T\nNone [1] (%).Sci = 94-100. Title, triple inter-annotator mAP @ 0.5-0.95 (%).Law = 82-96. Title, triple inter-annotator
- mAP @ 0.5-0.95 (%).Pat = 68-79. Title, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 24-\nNone [1] inter-annotator
- mAP @ 0.5-0.95 (%).Law = 58-80. Table, triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 79-84. Table, triple inter-annotator
- mAP @ 0.5-0.95 (%).Ten = 70-85. Text, Count = 510377. Text, \nNone [1] = 82-97. Formula, Count = 25027. Formula,
- % of Total.Train = 2.25. Formula, % of Total.Test = 1.90. Formula, % of Total.Val = 2.96. Formula, triple inter-annotator
- mAP @ 0.5-0.95 (%).All = 83-85. Form\nNone [1] mAP @ 0.5-0.95 (%).Sci = 89-94. Total, triple inter-annotator mAP
- @ 0.5-0.95 (%).Law = 86-91. Total, triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 71-76. Total, triple inter-annotator
- mAP @ 0.5-0.95\n","stderr":"","success":true}'
- role: tool
- tool_call_id: call_w56yspv7
- - content: null
- reasoning: 'We see many types: Page-header, Page-footer, Section-header, Caption, Picture, List-item, Footnote, Text,
- Title, Formula etc. Let''s extract unique types. Probably need to parse for words before comma. Use regex.'
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"import re\nresults = search(''document element types'', limit=50)\ntypes=set()\nfor r in
- results:\n for line in r[''content''].split('',''):\n m = re.match(r''\\s*(\\w[\\w-]*)(?:\\s*[,\\.]|$)'',
- line)\n if m:\n types.add(m.group(1))\nprint(sorted(types))\n"}'
- name: execute_code
- id: call_bt2tgwxs
- type: function
- - content: '{"code":"import re\nresults = search(''document element types'', limit=50)\ntypes=set()\nfor r in results:\n for
- line in r[''content''].split('',''):\n m = re.match(r''\\s*(\\w[\\w-]*)(?:\\s*[,\\.]|$)'', line)\n if
- m:\n types.add(m.group(1))\nprint(sorted(types))\n","stdout":"","stderr":"ModuleNotFoundError: No module
- named ''re''","success":false}'
- role: tool
- tool_call_id: call_bt2tgwxs
- - content: null
- reasoning: Need import re.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"import re\nresults = search(''document element types'', limit=50)\ntypes=set()\nfor r in
- results:\n for line in r[''content''].split('',''):\n m = re.match(r''\\s*(\\w[\\w-]*)(?:\\s*[,\\.]|$)'',
- line)\n if m:\n types.add(m.group(1))\nprint(sorted(types))\n"}'
- name: execute_code
- id: call_iflcvbbl
- type: function
- - content: '{"code":"import re\nresults = search(''document element types'', limit=50)\ntypes=set()\nfor r in results:\n for
- line in r[''content''].split('',''):\n m = re.match(r''\\s*(\\w[\\w-]*)(?:\\s*[,\\.]|$)'', line)\n if
- m:\n types.add(m.group(1))\nprint(sorted(types))\n","stdout":"","stderr":"ModuleNotFoundError: No module
- named ''re''","success":false}'
- role: tool
- tool_call_id: call_iflcvbbl
- - content: null
- reasoning: Maybe re is not available? But standard library re should exist. Maybe import is required differently.
- It's standard. Could be restricted? Try import re inside same code. That we did. Maybe environment prohibits re?
- Let's try a simple import re example.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"import re\nprint(''ok'')\n"}'
- name: execute_code
- id: call_1uala4jy
- type: function
- - content: '{"code":"import re\nprint(''ok'')\n","stdout":"","stderr":"ModuleNotFoundError: No module named ''re''","success":false}'
- role: tool
- tool_call_id: call_1uala4jy
- - content: null
- reasoning: 'No re? Try using import regex? Probably not. Alternative: use simple split on whitespace and punctuation.
- We can manually list known types from content. Let''s search for "label" and extract.'
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"results = search(''label'', limit=20)\nfor r in results:\n print(r[''document_title''],
- r[''page_numbers''], r[''content''][:200])\n"}'
- name: execute_code
- id: call_mputujfq
- type: function
- - content: '{"code":"results = search(''label'', limit=20)\nfor r in results:\n print(r[''document_title''], r[''page_numbers''],
- r[''content''][:200])\n","stdout":"None [1] Phase 2: Label selection and guideline. We reviewed the collected documents
- and identified the most common structural features they exhibit. This was achieved by identifying recurrent layout
- elements \nNone [1] \nTable 1: DocLayNet dataset overview. Along with the frequency of each class label, we present
- the relative occurrence (as % of row \"Total\") in the train, test and validation sets. The inter-annotator\nNone
- [1] mAP @ 0.5-0.95 (%).Sci = 89-94. Total, triple inter-annotator mAP @ 0.5-0.95 (%).Law = 86-91. Total, triple
- inter-annotator mAP @ 0.5-0.95 (%).Pat = 71-76. Total, triple inter-annotator mAP @ 0.5-0.95\nNone [1] Caption,
- Count = 22524. Caption, % of Total.Train = 2.04. Caption, % of Total.Test = 1.77. Caption, % of Total.Val = 2.32.
- Caption, triple inter-annotator mAP @ 0.5-0.95 (%).All = 84-89. Caption, trip\nNone [1] Page-footer, triple inter-annotator
- mAP @ 0.5-0.95 (%).Ten = 96-98. Page-header, Count = 58022. Page-header, % of Total.Train = 5.10. Page-header, %
- of Total.Test = 6.70. Page-header, % of Total.Val =\nNone [1] (%).Sci = 94-100. Title, triple inter-annotator mAP
- @ 0.5-0.95 (%).Law = 82-96. Title, triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 68-79. Title, triple inter-annotator
- mAP @ 0.5-0.95 (%).Ten = 24-\nNone [1] n/a. Footnote, Count = 6318. Footnote, % of Total.Train = 0.60. Footnote,
- % of Total.Test = 0.31. Footnote, % of Total.Val = 0.58. Footnote, triple inter-annotator mAP @ 0.5-0.95 (%).All
- = 83-91. Foot\nNone [1] 0.5-0.95 (%).Law = 87-94. Section-header, triple inter-annotator mAP @ 0.5-0.95 (%).Pat
- = 69-73. Section-header, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 78-86. Table, Count = 34733. Table, % o\nNone
- [1] inter-annotator mAP @ 0.5-0.95 (%).Pat = 66-71. Picture, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 59-76.
- Section-header, Count = 142884. Section-header, % of Total.Train = 12.60. Section-header\nNone [1] 0.5-0.95 (%).Pat
- = 97-99. Page-header, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 81-86. Picture, Count = 45976. Picture, %
- of Total.Train = 4.21. Picture, % of Total.Test = 2.78. Picture, % of T\nNone [1] 185660. List-item, % of Total.Train
- = 17.19. List-item, % of Total.Test = 13.34. List-item, % of Total.Val = 15.82. List-item, triple inter-annotator
- mAP @ 0.5-0.95 (%).All = 87-88. List-item, triple \nNone [1] inter-annotator mAP @ 0.5-0.95 (%).Law = 58-80. Table,
- triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 79-84. Table, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 70-85.
- Text, Count = 510377. Text, \nNone [1] 89-93. Text, triple inter-annotator mAP @ 0.5-0.95 (%).Law = 87-92. Text,
- triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 71-79. Text, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 87-95.
- Title, Cou\nNone [1] 0.5-0.95 (%).Ten = 93-95. Page-footer, Count = 70878. Page-footer, % of Total.Train = 6.51.
- Page-footer, % of Total.Test = 5.58. Page-footer, % of Total.Val = 6.00. Page-footer, triple inter-annotator\nNone
- [1] = 82-97. Formula, Count = 25027. Formula, % of Total.Train = 2.25. Formula, % of Total.Test = 1.90. Formula,
- % of Total.Val = 2.96. Formula, triple inter-annotator mAP @ 0.5-0.95 (%).All = 83-85. Form\nNone [1] Phase 1: Data
- selection and preparation. Our inclusion criteria for documents were described in Section 3. A large effort went
- into ensuring that all documents are free to use. The data sources includ\nNone [1] $_{Affiliation}$, as seen in
- DocBank, are often only distinguishable by discriminating on\nPreparation work included uploading and parsing the
- sourced PDF documents in the Corpus Conversion Service (CC\n","stderr":"","success":true}'
- role: tool
- tool_call_id: call_mputujfq
- - content: null
- reasoning: Collect unique words before comma from lines like "Caption, Count =". Let's parse manually.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"texts = []\nresults = search(''label'', limit=20)\nfor r in results:\n texts.append(r[''content''])\n#
- simple split by comma\nparts = [p.strip() for c in texts for p in c.split('','')]\ntypes=set()\nfor p in parts:\n if
- p:\n w = p.split()[0]\n types.add(w)\nprint(sorted(types))\n"}'
- name: execute_code
- id: call_lya9szfq
- type: function
- - content: '{"code":"texts = []\nresults = search(''label'', limit=20)\nfor r in results:\n texts.append(r[''content''])\n#
- simple split by comma\nparts = [p.strip() for c in texts for p in c.split('','')]\ntypes=set()\nfor p in parts:\n if
- p:\n w = p.split()[0]\n types.add(w)\nprint(sorted(types))\n","stdout":"[''$_{Affiliation}$'', ''$_{Footnote}$'',
- ''$_{Formula}$'', ''$_{List-item}$'', ''$_{Page-header}$'', ''$_{Picture}$'', ''$_{Section-header}$'', ''$_{Table}$'',
- ''$_{Text}$'', ''%'', ''(%).Sci'', ''(2)'', ''(3)'', ''0.5-0.95'', ''185660.'', ''89-93.'', ''='', ''Caption'',
- ''Count'', ''Page-$_{footer}$'', ''Page-footer'', ''Phase'', ''Table'', ''a'', ''and'', ''are'', ''as'', ''company'',
- ''from'', ''government'', ''inter-annotator'', ''mAP'', ''n/a.'', ''such'', ''test'', ''triple'', ''we'', ''which'',
- ''while'', ''with'']\n","stderr":"","success":true}'
- role: tool
- tool_call_id: call_lya9szfq
- - content: null
- reasoning: 'This extraction not good. Instead use known types from dataset: Page-header, Page-footer, Section-header,
- Caption, Picture, List-item, Footnote, Text, Title, Formula, Table. Also maybe Abstract? Let''s search ''Abstract''.'
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"results = search(''Abstract'', limit=10)\nfor r in results:\n print(r[''document_title''],
- r[''content''][:200])\n"}'
- name: execute_code
- id: call_lzf7e2ob
- type: function
- - content: '{"code":"results = search(''Abstract'', limit=10)\nfor r in results:\n print(r[''document_title''], r[''content''][:200])\n","stdout":"None
- Phase 2: Label selection and guideline. We reviewed the collected documents and identified the most common structural
- features they exhibit. This was achieved by identifying recurrent layout elements \nNone inter-annotator mAP @ 0.5-0.95
- (%).Pat = 66-71. Picture, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 59-76. Section-header, Count = 142884.
- Section-header, % of Total.Train = 12.60. Section-header\nNone Page-footer, triple inter-annotator mAP @ 0.5-0.95
- (%).Ten = 96-98. Page-header, Count = 58022. Page-header, % of Total.Train = 5.10. Page-header, % of Total.Test
- = 6.70. Page-header, % of Total.Val =\nNone Caption, Count = 22524. Caption, % of Total.Train = 2.04. Caption, %
- of Total.Test = 1.77. Caption, % of Total.Val = 2.32. Caption, triple inter-annotator mAP @ 0.5-0.95 (%).All = 84-89.
- Caption, trip\nNone n/a. Footnote, Count = 6318. Footnote, % of Total.Train = 0.60. Footnote, % of Total.Test =
- 0.31. Footnote, % of Total.Val = 0.58. Footnote, triple inter-annotator mAP @ 0.5-0.95 (%).All = 83-91. Foot\nNone
- (%).Sci = 94-100. Title, triple inter-annotator mAP @ 0.5-0.95 (%).Law = 82-96. Title, triple inter-annotator mAP
- @ 0.5-0.95 (%).Pat = 68-79. Title, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 24-\nNone 0.5-0.95 (%).Law =
- 87-94. Section-header, triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 69-73. Section-header, triple inter-annotator
- mAP @ 0.5-0.95 (%).Ten = 78-86. Table, Count = 34733. Table, % o\nNone 89-93. Text, triple inter-annotator mAP @
- 0.5-0.95 (%).Law = 87-92. Text, triple inter-annotator mAP @ 0.5-0.95 (%).Pat = 71-79. Text, triple inter-annotator
- mAP @ 0.5-0.95 (%).Ten = 87-95. Title, Cou\nNone 0.5-0.95 (%).Ten = 93-95. Page-footer, Count = 70878. Page-footer,
- % of Total.Train = 6.51. Page-footer, % of Total.Test = 5.58. Page-footer, % of Total.Val = 6.00. Page-footer, triple
- inter-annotator\nNone 0.5-0.95 (%).Pat = 97-99. Page-header, triple inter-annotator mAP @ 0.5-0.95 (%).Ten = 81-86.
- Picture, Count = 45976. Picture, % of Total.Train = 4.21. Picture, % of Total.Test = 2.78. Picture, % of T\n","stderr":"","success":true}'
- role: tool
- tool_call_id: call_lzf7e2ob
- model: gpt-oss
- reasoning_effort: low
- stream: false
- tool_choice: auto
- tools:
- - function:
- description: |-
- Execute Python code in a sandboxed interpreter.
-
- The code has access to haiku.rag functions (search, list_documents,
- get_document, get_chunk, llm).
-
- Use print() to output results.
-
- Structured result with success status, stdout, and stderr.
-
- name: execute_code
- parameters:
- additionalProperties: false
- properties:
- code:
- description: Python code to execute.
- type: string
- required:
- - code
- type: object
- strict: true
- type: function
- - function:
- description: Result from RLM agent execution.
- name: final_result
- parameters:
- additionalProperties: false
- properties:
- answer:
- description: The answer to the user's question
- type: string
- program:
- description: The final consolidated program
- type: string
- required:
- - answer
- - program
- type: object
- strict: true
- type: function
- uri: http://localhost:11434/v1/chat/completions
- response:
- headers:
- content-length:
- - '1184'
+ - '1744'
content-type:
- application/json
parsed_body:
@@ -3141,24 +1059,28 @@ interactions:
- finish_reason: stop
index: 0
message:
- content: '{"answer":"All the element types that appear in the documents are:\n- Page-header\n- Page-footer\n- Section-header\n-
- Caption\n- Picture\n- List-item\n- Footnote\n- Text\n- Title\n- Formula\n- Table","program":"import json\nfrom
- collections import Counter\n\n# Gather list of element types found in document metadata\n# For this exercise we
- only use the hard‑coded types extracted from the search results\n# as the environment does not provide regex or
- string processing modules.\ntypes = [''Page-header'', ''Page-footer'', ''Section-header'', ''Caption'', ''Picture'',
- ''List-item'', ''Footnote'', ''Text'', ''Title'', ''Formula'', ''Table'']\nprint(json.dumps(types, indent=2))"}'
- reasoning: 'Given time, I''ll compile list: Page-header, Page-footer, Section-header, Caption, Picture, List-item,
- Footnote, Text, Title, Formula, Table. Also maybe Abstract? Not sure. I''ll answer with those.'
+ content: '{"answer":"The documents we examined list the following distinct document‑element types (each appearing
+ as a separate class label in the annotation datasets):\n\n1. Page‑header\n2. Page‑footer\n3. Section‑header\n4.
+ Table\n5. Caption\n6. Picture\n7. Footnote\n8. List‑item\n9. Text\n10. Title\n11. Formula\n\nThese are the unique
+ element categories identified across the datasets (DocLayNet/DocBank annotations).","program":"# Consolidated
+ program to extract unique document element types\n# Search for fragments that mention ‘label’ or ‘class’ to capture
+ element names\nresults = search(\"class label\", limit=50)\nunique_types = set()\nfor r in results:\n # find
+ capitalized words followed by ‘label’ or ‘class’\n chunk = get_chunk(r[''chunk_id''])\n if not chunk:\n continue\n text
+ = chunk[''content'']\n # simple split to capture possible types\n for word in text.split():\n if
+ word.lower().startswith((''page-header'',''page-footer'',''section-header'',''table'',''caption'',''picture'',''footnote'',''list-item'',''text'',''title'',''formula'')):\n unique_types.add(word.replace(\"–\",\"-\").strip('',''))\n#
+ print the unique element types\nprint(sorted(unique_types))"}'
+ reasoning: 'Thus list: Page-header, Page-footer, Section-header, Table, Caption, Picture, Footnote, List-item, Text,
+ Title, Formula. Also maybe "Formula" is element type. Provide list.'
role: assistant
- created: 1771336404
- id: chatcmpl-221
+ created: 1771336786
+ id: chatcmpl-766
model: gpt-oss
object: chat.completion
system_fingerprint: fp_ollama
usage:
- completion_tokens: 218
- prompt_tokens: 6684
- total_tokens: 6902
+ completion_tokens: 346
+ prompt_tokens: 5162
+ total_tokens: 5508
status:
code: 200
message: OK
diff --git a/tests/cassettes/test_rlm/TestClientRLMIntegration.test_rlm_search_and_get_chunk.yaml b/tests/cassettes/test_rlm/TestClientRLMIntegration.test_rlm_search_and_get_chunk.yaml
index c5e370ac..6b9d38df 100644
--- a/tests/cassettes/test_rlm/TestClientRLMIntegration.test_rlm_search_and_get_chunk.yaml
+++ b/tests/cassettes/test_rlm/TestClientRLMIntegration.test_rlm_search_and_get_chunk.yaml
@@ -48,7 +48,7 @@ interactions:
connection:
- keep-alive
content-length:
- - '7753'
+ - '7117'
content-type:
- application/json
host:
@@ -65,7 +65,7 @@ interactions:
- search("query") ✓ CORRECT
- from haiku.rag import search ✗ WRONG - will fail
- You have access to a sandboxed Python environment with these haiku.rag functions (use them directly, no imports needed):
+ You have access to a sandboxed Python interpreter with these haiku.rag functions (use them directly, no imports needed):
## Available Functions
@@ -81,10 +81,10 @@ interactions:
Get the full text content of a document by ID, title, or URI.
Returns the document content as a string, or None if not found.
- ### get_docling_document(id_or_title) -> DoclingDocument | None
- Get the structured DoclingDocument object for advanced analysis.
- Returns a DoclingDocument object, or None if not found.
- See "DoclingDocument API" section below for how to use it.
+ ### get_chunk(chunk_id) -> dict | None
+ Get a specific chunk by its ID (from search results).
+ Returns dict with keys: chunk_id, content, document_id, document_title, headings, page_numbers, labels
+ Use this to retrieve full chunk details and metadata for citation.
### llm(prompt) -> str
Call an LLM directly with the given prompt. Returns the response as a string.
@@ -101,8 +101,13 @@ interactions:
```
Check if it exists with: `if 'documents' in dir(): ...`
- ## Standard Library Modules
- You can import any Python standard library module.
+ ## Available Python Features
+
+ The interpreter supports: variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, try/except, and the `json` module.
+
+ Not supported: imports (other than `json`), class definitions, generators/yield, match statements, decorators, `with` statements.
+
+ For pattern matching or text extraction, use string methods (`str.split`, `str.find`, `str.startswith`, `in` operator) or the `llm()` function.
## Strategy Guide
@@ -110,51 +115,9 @@ interactions:
2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
- 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and use collections.
+ 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and data structures.
6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
- 7. **Cite Your Sources**: Track which documents/chunks informed your answer for citation.
-
- ## DoclingDocument API
-
- When you call `get_docling_document(id_or_title)`, you get a DoclingDocument object for structured document analysis.
-
- ### Properties
- - `doc.texts` - List of all text items (paragraphs, headings, etc.)
- - `doc.tables` - List of all tables
- - `doc.pictures` - List of all pictures/figures
- - `doc.name` - Document name
-
- ### Methods
- - `doc.iterate_items(with_groups=False)` - Iterate all items with hierarchy level
- Returns tuples of (item, level) where level is nesting depth
- - `doc.export_to_markdown()` - Export entire document as markdown string
-
- ### Text Item Properties
- - `item.text` - The text content
- - `item.label` - Type: title, paragraph, section_header, list_item, etc. (lowercase enum values)
- - `item.prov` - Provenance (page numbers, bounding boxes)
-
- ### Table Access
- - `table.data.num_rows`, `table.data.num_cols` - Dimensions
- - `table.data.table_cells` - List of TableCell objects
- - `cell.text`, `cell.start_row_offset_idx`, `cell.start_col_offset_idx`
-
- ### Example Usage
- ```python
- doc = get_docling_document("My Document")
-
- # Get all headings
- headings = [t.text for t in doc.texts if "header" in str(t.label)]
-
- # Iterate with structure
- for item, level in doc.iterate_items():
- print(" " * level + item.text[:50])
-
- # Extract table data
- for table in doc.tables:
- for cell in table.data.table_cells:
- print(f"Row {cell.start_row_offset_idx}, Col {cell.start_col_offset_idx}: {cell.text}")
- ```
+ 7. **Cite Your Sources**: Use get_chunk() to retrieve chunk metadata for citations. Track which documents/chunks informed your answer.
## Example Patterns
@@ -170,23 +133,31 @@ interactions:
print(f"Total: {count}")
```
- ### Aggregating data across documents
+ ### Extracting data with llm()
```python
- import re
numbers = []
results = search("financial data", limit=20)
for r in results:
- matches = re.findall(r'\$([\d,]+)', r['content'])
- for m in matches:
- numbers.append(int(m.replace(',', '')))
- print(f"Average: ${sum(numbers)/len(numbers):,.2f}")
+ extracted = llm(f"Extract all dollar amounts from this text as a comma-separated list of numbers (no $ signs): {r['content']}")
+ for part in extracted.split(','):
+ part = part.strip().replace(',', '')
+ if part.isdigit():
+ numbers.append(int(part))
+ if numbers:
+ print(f"Average: {sum(numbers) / len(numbers)}")
+ ```
+
+ ### Using search results with get_chunk for citations
+ ```python
+ results = search("safety requirements", limit=5)
+ for r in results:
+ chunk = get_chunk(r['chunk_id'])
+ print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}")
```
### Using llm() for classification
```python
- # Get document content
content = get_document("Q1 Report")
- # Use llm() to classify sentiment
sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
print(sentiment)
```
@@ -263,7 +234,7 @@ interactions:
response:
headers:
content-length:
- - '495'
+ - '593'
content-type:
- application/json
parsed_body:
@@ -272,24 +243,25 @@ interactions:
index: 0
message:
content: ''
- reasoning: We need to search for animals.
+ reasoning: We need to search.
role: assistant
tool_calls:
- function:
- arguments: '{"code":"search(''animals'', limit=10)"}'
+ arguments: '{"code":"results = search(\"animals\", limit=10)\nfor r in results:\n print(r[''document_title''],
+ r[''chunk_id''], r[''page_numbers''][:5])\n"}'
name: execute_code
- id: call_mn3wbs1z
+ id: call_tpl0j81u
index: 0
type: function
- created: 1771336318
- id: chatcmpl-377
+ created: 1771336724
+ id: chatcmpl-115
model: gpt-oss
object: chat.completion
system_fingerprint: fp_ollama
usage:
- completion_tokens: 38
- prompt_tokens: 1740
- total_tokens: 1778
+ completion_tokens: 67
+ prompt_tokens: 1568
+ total_tokens: 1635
status:
code: 200
message: OK
@@ -342,7 +314,7 @@ interactions:
connection:
- keep-alive
content-length:
- - '8426'
+ - '7760'
content-type:
- application/json
host:
@@ -359,7 +331,7 @@ interactions:
- search("query") ✓ CORRECT
- from haiku.rag import search ✗ WRONG - will fail
- You have access to a sandboxed Python environment with these haiku.rag functions (use them directly, no imports needed):
+ You have access to a sandboxed Python interpreter with these haiku.rag functions (use them directly, no imports needed):
## Available Functions
@@ -375,10 +347,10 @@ interactions:
Get the full text content of a document by ID, title, or URI.
Returns the document content as a string, or None if not found.
- ### get_docling_document(id_or_title) -> DoclingDocument | None
- Get the structured DoclingDocument object for advanced analysis.
- Returns a DoclingDocument object, or None if not found.
- See "DoclingDocument API" section below for how to use it.
+ ### get_chunk(chunk_id) -> dict | None
+ Get a specific chunk by its ID (from search results).
+ Returns dict with keys: chunk_id, content, document_id, document_title, headings, page_numbers, labels
+ Use this to retrieve full chunk details and metadata for citation.
### llm(prompt) -> str
Call an LLM directly with the given prompt. Returns the response as a string.
@@ -395,8 +367,13 @@ interactions:
```
Check if it exists with: `if 'documents' in dir(): ...`
- ## Standard Library Modules
- You can import any Python standard library module.
+ ## Available Python Features
+
+ The interpreter supports: variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, try/except, and the `json` module.
+
+ Not supported: imports (other than `json`), class definitions, generators/yield, match statements, decorators, `with` statements.
+
+ For pattern matching or text extraction, use string methods (`str.split`, `str.find`, `str.startswith`, `in` operator) or the `llm()` function.
## Strategy Guide
@@ -404,51 +381,9 @@ interactions:
2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
- 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and use collections.
+ 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and data structures.
6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
- 7. **Cite Your Sources**: Track which documents/chunks informed your answer for citation.
-
- ## DoclingDocument API
-
- When you call `get_docling_document(id_or_title)`, you get a DoclingDocument object for structured document analysis.
-
- ### Properties
- - `doc.texts` - List of all text items (paragraphs, headings, etc.)
- - `doc.tables` - List of all tables
- - `doc.pictures` - List of all pictures/figures
- - `doc.name` - Document name
-
- ### Methods
- - `doc.iterate_items(with_groups=False)` - Iterate all items with hierarchy level
- Returns tuples of (item, level) where level is nesting depth
- - `doc.export_to_markdown()` - Export entire document as markdown string
-
- ### Text Item Properties
- - `item.text` - The text content
- - `item.label` - Type: title, paragraph, section_header, list_item, etc. (lowercase enum values)
- - `item.prov` - Provenance (page numbers, bounding boxes)
-
- ### Table Access
- - `table.data.num_rows`, `table.data.num_cols` - Dimensions
- - `table.data.table_cells` - List of TableCell objects
- - `cell.text`, `cell.start_row_offset_idx`, `cell.start_col_offset_idx`
-
- ### Example Usage
- ```python
- doc = get_docling_document("My Document")
-
- # Get all headings
- headings = [t.text for t in doc.texts if "header" in str(t.label)]
-
- # Iterate with structure
- for item, level in doc.iterate_items():
- print(" " * level + item.text[:50])
-
- # Extract table data
- for table in doc.tables:
- for cell in table.data.table_cells:
- print(f"Row {cell.start_row_offset_idx}, Col {cell.start_col_offset_idx}: {cell.text}")
- ```
+ 7. **Cite Your Sources**: Use get_chunk() to retrieve chunk metadata for citations. Track which documents/chunks informed your answer.
## Example Patterns
@@ -464,23 +399,31 @@ interactions:
print(f"Total: {count}")
```
- ### Aggregating data across documents
+ ### Extracting data with llm()
```python
- import re
numbers = []
results = search("financial data", limit=20)
for r in results:
- matches = re.findall(r'\$([\d,]+)', r['content'])
- for m in matches:
- numbers.append(int(m.replace(',', '')))
- print(f"Average: ${sum(numbers)/len(numbers):,.2f}")
+ extracted = llm(f"Extract all dollar amounts from this text as a comma-separated list of numbers (no $ signs): {r['content']}")
+ for part in extracted.split(','):
+ part = part.strip().replace(',', '')
+ if part.isdigit():
+ numbers.append(int(part))
+ if numbers:
+ print(f"Average: {sum(numbers) / len(numbers)}")
+ ```
+
+ ### Using search results with get_chunk for citations
+ ```python
+ results = search("safety requirements", limit=5)
+ for r in results:
+ chunk = get_chunk(r['chunk_id'])
+ print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}")
```
### Using llm() for classification
```python
- # Get document content
content = get_document("Q1 Report")
- # Use llm() to classify sentiment
sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
print(sentiment)
```
@@ -508,20 +451,19 @@ interactions:
- content: Search for content about animals and tell me which document it came from.
role: user
- content: null
- reasoning: We need to search for animals.
+ reasoning: We need to search.
role: assistant
tool_calls:
- function:
- arguments: '{"code":"search(''animals'', limit=10)"}'
+ arguments: '{"code":"results = search(\"animals\", limit=10)\nfor r in results:\n print(r[''document_title''],
+ r[''chunk_id''], r[''page_numbers''][:5])\n"}'
name: execute_code
- id: call_mn3wbs1z
+ id: call_tpl0j81u
type: function
- - content: '{"code":"search(''animals'', limit=10)","stdout":"[{''chunk_id'': ''4f359f54-704f-45f9-87cc-a9b324bd8c94'',
- ''content'': ''The quick brown fox jumps over the lazy dog.'', ''document_id'': ''d85af197-c6f8-4ca5-893d-e6b2de835758'',
- ''document_title'': ''Animal Facts'', ''document_uri'': None, ''score'': 0.016393441706895828, ''page_numbers'':
- [], ''headings'': None}]","stderr":"","success":true}'
+ - content: '{"code":"results = search(\"animals\", limit=10)\nfor r in results:\n print(r[''document_title''], r[''chunk_id''],
+ r[''page_numbers''][:5])\n","stdout":"Animal Facts eb0e1e92-35b2-4f5e-8011-2a67f3dadf00 []\n","stderr":"","success":true}'
role: tool
- tool_call_id: call_mn3wbs1z
+ tool_call_id: call_tpl0j81u
model: gpt-oss
reasoning_effort: low
stream: false
@@ -572,7 +514,567 @@ interactions:
response:
headers:
content-length:
- - '585'
+ - '613'
+ content-type:
+ - application/json
+ parsed_body:
+ choices:
+ - finish_reason: tool_calls
+ index: 0
+ message:
+ content: ''
+ reasoning: Only one result? Let's list maybe more.
+ role: assistant
+ tool_calls:
+ - function:
+ arguments: '{"code":"results = search(\"animals\", limit=20)\nfor r in results:\n print(r[''document_title''],
+ r[''chunk_id''], r[''page_numbers''][:5])\n"}'
+ name: execute_code
+ id: call_orialuab
+ index: 0
+ type: function
+ created: 1771336725
+ id: chatcmpl-99
+ model: gpt-oss
+ object: chat.completion
+ system_fingerprint: fp_ollama
+ usage:
+ completion_tokens: 71
+ prompt_tokens: 1730
+ total_tokens: 1801
+ status:
+ code: 200
+ message: OK
+- request:
+ headers:
+ accept:
+ - application/json
+ accept-encoding:
+ - gzip, deflate, zstd
+ connection:
+ - keep-alive
+ content-length:
+ - '77'
+ content-type:
+ - application/json
+ host:
+ - localhost:11434
+ method: POST
+ parsed_body:
+ encoding_format: base64
+ input:
+ - animals
+ model: qwen3-embedding:4b
+ uri: http://localhost:11434/v1/embeddings
+ response:
+ headers:
+ content-type:
+ - application/json
+ transfer-encoding:
+ - chunked
+ parsed_body:
+ data:
+ - embedding: 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
+ index: 0
+ object: embedding
+ model: qwen3-embedding:4b
+ object: list
+ usage:
+ prompt_tokens: 2
+ total_tokens: 2
+ status:
+ code: 200
+ message: OK
+- request:
+ headers:
+ accept:
+ - application/json
+ accept-encoding:
+ - gzip, deflate, zstd
+ connection:
+ - keep-alive
+ content-length:
+ - '8424'
+ content-type:
+ - application/json
+ host:
+ - localhost:11434
+ method: POST
+ parsed_body:
+ messages:
+ - content: |-
+ You are a Recursive Language Model (RLM) agent that solves complex research questions by writing and executing Python code.
+
+ IMPORTANT: You MUST use the `execute_code` tool to run Python code. The functions described below are ONLY available inside the execute_code tool - you cannot access them any other way. Always execute code to answer questions; do not just describe what code would do.
+
+ CRITICAL: Inside execute_code, these functions are ALREADY available in the namespace. Do NOT import them - just use them directly:
+ - search("query") ✓ CORRECT
+ - from haiku.rag import search ✗ WRONG - will fail
+
+ You have access to a sandboxed Python interpreter with these haiku.rag functions (use them directly, no imports needed):
+
+ ## Available Functions
+
+ ### search(query, limit=10) -> list[dict]
+ Search the knowledge base using hybrid search (vector + full-text).
+ Returns list of dicts with keys: chunk_id, content, document_id, document_title, document_uri, score, page_numbers, headings
+
+ ### list_documents(limit=10, offset=0) -> list[dict]
+ List available documents in the knowledge base.
+ Returns list of dicts with keys: id, title, uri, created_at
+
+ ### get_document(id_or_title) -> str | None
+ Get the full text content of a document by ID, title, or URI.
+ Returns the document content as a string, or None if not found.
+
+ ### get_chunk(chunk_id) -> dict | None
+ Get a specific chunk by its ID (from search results).
+ Returns dict with keys: chunk_id, content, document_id, document_title, headings, page_numbers, labels
+ Use this to retrieve full chunk details and metadata for citation.
+
+ ### llm(prompt) -> str
+ Call an LLM directly with the given prompt. Returns the response as a string.
+ Use this for classification, summarization, extraction, or any task where you
+ already have the content and just need LLM reasoning.
+
+ ## Pre-loaded Documents Variable
+
+ If documents were pre-loaded for this session, a `documents` variable is available:
+ ```python
+ # documents is a list of dicts with keys: id, title, uri, content
+ for doc in documents:
+ print(doc['title'], len(doc['content']))
+ ```
+ Check if it exists with: `if 'documents' in dir(): ...`
+
+ ## Available Python Features
+
+ The interpreter supports: variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, try/except, and the `json` module.
+
+ Not supported: imports (other than `json`), class definitions, generators/yield, match statements, decorators, `with` statements.
+
+ For pattern matching or text extraction, use string methods (`str.split`, `str.find`, `str.startswith`, `in` operator) or the `llm()` function.
+
+ ## Strategy Guide
+
+ 1. **Explore First**: Start by listing documents or searching to understand what's available. Document names may differ from filenames (e.g., "tbmed593.pdf" might be stored as "TB MED 593" or similar).
+ 2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
+ 3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
+ 4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
+ 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and data structures.
+ 6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
+ 7. **Cite Your Sources**: Use get_chunk() to retrieve chunk metadata for citations. Track which documents/chunks informed your answer.
+
+ ## Example Patterns
+
+ ### Counting documents matching a condition
+ ```python
+ docs = list_documents(limit=100)
+ count = 0
+ for doc in docs:
+ content = get_document(doc['id'])
+ if content and 'keyword' in content.lower():
+ count += 1
+ print(f"Found in: {doc['title']}")
+ print(f"Total: {count}")
+ ```
+
+ ### Extracting data with llm()
+ ```python
+ numbers = []
+ results = search("financial data", limit=20)
+ for r in results:
+ extracted = llm(f"Extract all dollar amounts from this text as a comma-separated list of numbers (no $ signs): {r['content']}")
+ for part in extracted.split(','):
+ part = part.strip().replace(',', '')
+ if part.isdigit():
+ numbers.append(int(part))
+ if numbers:
+ print(f"Average: {sum(numbers) / len(numbers)}")
+ ```
+
+ ### Using search results with get_chunk for citations
+ ```python
+ results = search("safety requirements", limit=5)
+ for r in results:
+ chunk = get_chunk(r['chunk_id'])
+ print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}")
+ ```
+
+ ### Using llm() for classification
+ ```python
+ content = get_document("Q1 Report")
+ sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
+ print(sentiment)
+ ```
+
+ ## Workflow
+
+ 1. **ALWAYS start by using execute_code** to explore the knowledge base
+ 2. Run multiple code blocks as needed to gather information
+ 3. After collecting data, provide your final answer
+
+ ## Output Format
+
+ CRITICAL: Your final response MUST be valid JSON matching this exact schema:
+ ```json
+ {"answer": "Your complete answer here as a string", "program": "Your final consolidated program here as a string"}
+ ```
+
+ - `answer`: A clear answer to the user's question with key findings and references to specific documents/chunks.
+ - `program`: A single, self-contained Python program that produces the answer. Consolidate your exploratory code executions into one clean script.
+
+ Do NOT return arbitrary JSON structures. Always use the exact format: {"answer": "...", "program": "..."}
+
+ CRITICAL: You MUST call execute_code at least once before providing your answer. Never give up without trying to execute code first.
+ role: system
+ - content: Search for content about animals and tell me which document it came from.
+ role: user
+ - content: null
+ reasoning: We need to search.
+ role: assistant
+ tool_calls:
+ - function:
+ arguments: '{"code":"results = search(\"animals\", limit=10)\nfor r in results:\n print(r[''document_title''],
+ r[''chunk_id''], r[''page_numbers''][:5])\n"}'
+ name: execute_code
+ id: call_tpl0j81u
+ type: function
+ - content: '{"code":"results = search(\"animals\", limit=10)\nfor r in results:\n print(r[''document_title''], r[''chunk_id''],
+ r[''page_numbers''][:5])\n","stdout":"Animal Facts eb0e1e92-35b2-4f5e-8011-2a67f3dadf00 []\n","stderr":"","success":true}'
+ role: tool
+ tool_call_id: call_tpl0j81u
+ - content: null
+ reasoning: Only one result? Let's list maybe more.
+ role: assistant
+ tool_calls:
+ - function:
+ arguments: '{"code":"results = search(\"animals\", limit=20)\nfor r in results:\n print(r[''document_title''],
+ r[''chunk_id''], r[''page_numbers''][:5])\n"}'
+ name: execute_code
+ id: call_orialuab
+ type: function
+ - content: '{"code":"results = search(\"animals\", limit=20)\nfor r in results:\n print(r[''document_title''], r[''chunk_id''],
+ r[''page_numbers''][:5])\n","stdout":"Animal Facts eb0e1e92-35b2-4f5e-8011-2a67f3dadf00 []\n","stderr":"","success":true}'
+ role: tool
+ tool_call_id: call_orialuab
+ model: gpt-oss
+ reasoning_effort: low
+ stream: false
+ tool_choice: auto
+ tools:
+ - function:
+ description: |-
+ Execute Python code in a sandboxed interpreter.
+
+ The code has access to haiku.rag functions (search, list_documents,
+ get_document, get_chunk, llm).
+
+ Use print() to output results.
+
+ Structured result with success status, stdout, and stderr.
+
+ name: execute_code
+ parameters:
+ additionalProperties: false
+ properties:
+ code:
+ description: Python code to execute.
+ type: string
+ required:
+ - code
+ type: object
+ strict: true
+ type: function
+ - function:
+ description: Result from RLM agent execution.
+ name: final_result
+ parameters:
+ additionalProperties: false
+ properties:
+ answer:
+ description: The answer to the user's question
+ type: string
+ program:
+ description: The final consolidated program
+ type: string
+ required:
+ - answer
+ - program
+ type: object
+ strict: true
+ type: function
+ uri: http://localhost:11434/v1/chat/completions
+ response:
+ headers:
+ content-length:
+ - '583'
+ content-type:
+ - application/json
+ parsed_body:
+ choices:
+ - finish_reason: tool_calls
+ index: 0
+ message:
+ content: ''
+ reasoning: Only that document returned. Let's get chunk details.
+ role: assistant
+ tool_calls:
+ - function:
+ arguments: '{"code":"chunk = get_chunk(''eb0e1e92-35b2-4f5e-8011-2a67f3dadf00'')\nprint(chunk[''content''][:200])\n"}'
+ name: execute_code
+ id: call_hgxkkg8j
+ index: 0
+ type: function
+ created: 1771336727
+ id: chatcmpl-276
+ model: gpt-oss
+ object: chat.completion
+ system_fingerprint: fp_ollama
+ usage:
+ completion_tokens: 78
+ prompt_tokens: 1896
+ total_tokens: 1974
+ status:
+ code: 200
+ message: OK
+- request:
+ headers:
+ accept:
+ - application/json
+ accept-encoding:
+ - gzip, deflate, zstd
+ connection:
+ - keep-alive
+ content-length:
+ - '9004'
+ content-type:
+ - application/json
+ host:
+ - localhost:11434
+ method: POST
+ parsed_body:
+ messages:
+ - content: |-
+ You are a Recursive Language Model (RLM) agent that solves complex research questions by writing and executing Python code.
+
+ IMPORTANT: You MUST use the `execute_code` tool to run Python code. The functions described below are ONLY available inside the execute_code tool - you cannot access them any other way. Always execute code to answer questions; do not just describe what code would do.
+
+ CRITICAL: Inside execute_code, these functions are ALREADY available in the namespace. Do NOT import them - just use them directly:
+ - search("query") ✓ CORRECT
+ - from haiku.rag import search ✗ WRONG - will fail
+
+ You have access to a sandboxed Python interpreter with these haiku.rag functions (use them directly, no imports needed):
+
+ ## Available Functions
+
+ ### search(query, limit=10) -> list[dict]
+ Search the knowledge base using hybrid search (vector + full-text).
+ Returns list of dicts with keys: chunk_id, content, document_id, document_title, document_uri, score, page_numbers, headings
+
+ ### list_documents(limit=10, offset=0) -> list[dict]
+ List available documents in the knowledge base.
+ Returns list of dicts with keys: id, title, uri, created_at
+
+ ### get_document(id_or_title) -> str | None
+ Get the full text content of a document by ID, title, or URI.
+ Returns the document content as a string, or None if not found.
+
+ ### get_chunk(chunk_id) -> dict | None
+ Get a specific chunk by its ID (from search results).
+ Returns dict with keys: chunk_id, content, document_id, document_title, headings, page_numbers, labels
+ Use this to retrieve full chunk details and metadata for citation.
+
+ ### llm(prompt) -> str
+ Call an LLM directly with the given prompt. Returns the response as a string.
+ Use this for classification, summarization, extraction, or any task where you
+ already have the content and just need LLM reasoning.
+
+ ## Pre-loaded Documents Variable
+
+ If documents were pre-loaded for this session, a `documents` variable is available:
+ ```python
+ # documents is a list of dicts with keys: id, title, uri, content
+ for doc in documents:
+ print(doc['title'], len(doc['content']))
+ ```
+ Check if it exists with: `if 'documents' in dir(): ...`
+
+ ## Available Python Features
+
+ The interpreter supports: variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, try/except, and the `json` module.
+
+ Not supported: imports (other than `json`), class definitions, generators/yield, match statements, decorators, `with` statements.
+
+ For pattern matching or text extraction, use string methods (`str.split`, `str.find`, `str.startswith`, `in` operator) or the `llm()` function.
+
+ ## Strategy Guide
+
+ 1. **Explore First**: Start by listing documents or searching to understand what's available. Document names may differ from filenames (e.g., "tbmed593.pdf" might be stored as "TB MED 593" or similar).
+ 2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
+ 3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
+ 4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
+ 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and data structures.
+ 6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
+ 7. **Cite Your Sources**: Use get_chunk() to retrieve chunk metadata for citations. Track which documents/chunks informed your answer.
+
+ ## Example Patterns
+
+ ### Counting documents matching a condition
+ ```python
+ docs = list_documents(limit=100)
+ count = 0
+ for doc in docs:
+ content = get_document(doc['id'])
+ if content and 'keyword' in content.lower():
+ count += 1
+ print(f"Found in: {doc['title']}")
+ print(f"Total: {count}")
+ ```
+
+ ### Extracting data with llm()
+ ```python
+ numbers = []
+ results = search("financial data", limit=20)
+ for r in results:
+ extracted = llm(f"Extract all dollar amounts from this text as a comma-separated list of numbers (no $ signs): {r['content']}")
+ for part in extracted.split(','):
+ part = part.strip().replace(',', '')
+ if part.isdigit():
+ numbers.append(int(part))
+ if numbers:
+ print(f"Average: {sum(numbers) / len(numbers)}")
+ ```
+
+ ### Using search results with get_chunk for citations
+ ```python
+ results = search("safety requirements", limit=5)
+ for r in results:
+ chunk = get_chunk(r['chunk_id'])
+ print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}")
+ ```
+
+ ### Using llm() for classification
+ ```python
+ content = get_document("Q1 Report")
+ sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
+ print(sentiment)
+ ```
+
+ ## Workflow
+
+ 1. **ALWAYS start by using execute_code** to explore the knowledge base
+ 2. Run multiple code blocks as needed to gather information
+ 3. After collecting data, provide your final answer
+
+ ## Output Format
+
+ CRITICAL: Your final response MUST be valid JSON matching this exact schema:
+ ```json
+ {"answer": "Your complete answer here as a string", "program": "Your final consolidated program here as a string"}
+ ```
+
+ - `answer`: A clear answer to the user's question with key findings and references to specific documents/chunks.
+ - `program`: A single, self-contained Python program that produces the answer. Consolidate your exploratory code executions into one clean script.
+
+ Do NOT return arbitrary JSON structures. Always use the exact format: {"answer": "...", "program": "..."}
+
+ CRITICAL: You MUST call execute_code at least once before providing your answer. Never give up without trying to execute code first.
+ role: system
+ - content: Search for content about animals and tell me which document it came from.
+ role: user
+ - content: null
+ reasoning: We need to search.
+ role: assistant
+ tool_calls:
+ - function:
+ arguments: '{"code":"results = search(\"animals\", limit=10)\nfor r in results:\n print(r[''document_title''],
+ r[''chunk_id''], r[''page_numbers''][:5])\n"}'
+ name: execute_code
+ id: call_tpl0j81u
+ type: function
+ - content: '{"code":"results = search(\"animals\", limit=10)\nfor r in results:\n print(r[''document_title''], r[''chunk_id''],
+ r[''page_numbers''][:5])\n","stdout":"Animal Facts eb0e1e92-35b2-4f5e-8011-2a67f3dadf00 []\n","stderr":"","success":true}'
+ role: tool
+ tool_call_id: call_tpl0j81u
+ - content: null
+ reasoning: Only one result? Let's list maybe more.
+ role: assistant
+ tool_calls:
+ - function:
+ arguments: '{"code":"results = search(\"animals\", limit=20)\nfor r in results:\n print(r[''document_title''],
+ r[''chunk_id''], r[''page_numbers''][:5])\n"}'
+ name: execute_code
+ id: call_orialuab
+ type: function
+ - content: '{"code":"results = search(\"animals\", limit=20)\nfor r in results:\n print(r[''document_title''], r[''chunk_id''],
+ r[''page_numbers''][:5])\n","stdout":"Animal Facts eb0e1e92-35b2-4f5e-8011-2a67f3dadf00 []\n","stderr":"","success":true}'
+ role: tool
+ tool_call_id: call_orialuab
+ - content: null
+ reasoning: Only that document returned. Let's get chunk details.
+ role: assistant
+ tool_calls:
+ - function:
+ arguments: '{"code":"chunk = get_chunk(''eb0e1e92-35b2-4f5e-8011-2a67f3dadf00'')\nprint(chunk[''content''][:200])\n"}'
+ name: execute_code
+ id: call_hgxkkg8j
+ type: function
+ - content: '{"code":"chunk = get_chunk(''eb0e1e92-35b2-4f5e-8011-2a67f3dadf00'')\nprint(chunk[''content''][:200])\n","stdout":"The
+ quick brown fox jumps over the lazy dog.\n","stderr":"","success":true}'
+ role: tool
+ tool_call_id: call_hgxkkg8j
+ model: gpt-oss
+ reasoning_effort: low
+ stream: false
+ tool_choice: auto
+ tools:
+ - function:
+ description: |-
+ Execute Python code in a sandboxed interpreter.
+
+ The code has access to haiku.rag functions (search, list_documents,
+ get_document, get_chunk, llm).
+
+ Use print() to output results.
+
+ Structured result with success status, stdout, and stderr.
+
+ name: execute_code
+ parameters:
+ additionalProperties: false
+ properties:
+ code:
+ description: Python code to execute.
+ type: string
+ required:
+ - code
+ type: object
+ strict: true
+ type: function
+ - function:
+ description: Result from RLM agent execution.
+ name: final_result
+ parameters:
+ additionalProperties: false
+ properties:
+ answer:
+ description: The answer to the user's question
+ type: string
+ program:
+ description: The final consolidated program
+ type: string
+ required:
+ - answer
+ - program
+ type: object
+ strict: true
+ type: function
+ uri: http://localhost:11434/v1/chat/completions
+ response:
+ headers:
+ content-length:
+ - '1164'
content-type:
- application/json
parsed_body:
@@ -580,20 +1082,24 @@ interactions:
- finish_reason: stop
index: 0
message:
- content: '{"answer":"The search found a chunk about animals in a document titled \"Animal Facts\" (document_id:
- d85af197-c6f8-4ca5-893d-e6b2de835758). The chunk content is: \"The quick brown fox jumps over the lazy dog.\"","program":"search(''animals'',
- limit=10)"}'
- reasoning: Return in JSON.
+ content: '{"answer":"The search for the term \"animals\" returned content from a single document titled **\"Animal
+ Facts\"** (document ID: eb0e1e92-35b2-4f5e-8011-2a67f3dadf00). The retrieved chunk contains the sentence: \"The
+ quick brown fox jumps over the lazy dog.\".","program":"# Search for the term \"animals\"\nresults = search(\"animals\",
+ limit=20)\n# Print document titles and chunk IDs for reference\nfor r in results:\n print(f\"Document: {r[''document_title'']},
+ Chunk ID: {r[''chunk_id'']}\")\n\n# Retrieve the first (and only) chunk from the search results\nchunk = get_chunk(results[0][''chunk_id''])\nprint(\"\\n---
+ Chunk Content ---\")\nprint(chunk[''content''][:200])\n# The chunk content is the source of the animal-related
+ text.\n"}'
+ reasoning: 'Content is placeholder. So answer: document "Animal Facts". Provide code.'
role: assistant
- created: 1771336320
- id: chatcmpl-438
+ created: 1771336733
+ id: chatcmpl-589
model: gpt-oss
object: chat.completion
system_fingerprint: fp_ollama
usage:
- completion_tokens: 99
- prompt_tokens: 1920
- total_tokens: 2019
+ completion_tokens: 240
+ prompt_tokens: 2054
+ total_tokens: 2294
status:
code: 200
message: OK
diff --git a/tests/cassettes/test_rlm/TestClientRLMIntegration.test_rlm_semantic_analysis_with_llm.yaml b/tests/cassettes/test_rlm/TestClientRLMIntegration.test_rlm_semantic_analysis_with_llm.yaml
index dab917b4..0141f655 100644
--- a/tests/cassettes/test_rlm/TestClientRLMIntegration.test_rlm_semantic_analysis_with_llm.yaml
+++ b/tests/cassettes/test_rlm/TestClientRLMIntegration.test_rlm_semantic_analysis_with_llm.yaml
@@ -131,7 +131,7 @@ interactions:
connection:
- keep-alive
content-length:
- - '7781'
+ - '7145'
content-type:
- application/json
host:
@@ -148,7 +148,7 @@ interactions:
- search("query") ✓ CORRECT
- from haiku.rag import search ✗ WRONG - will fail
- You have access to a sandboxed Python environment with these haiku.rag functions (use them directly, no imports needed):
+ You have access to a sandboxed Python interpreter with these haiku.rag functions (use them directly, no imports needed):
## Available Functions
@@ -164,10 +164,10 @@ interactions:
Get the full text content of a document by ID, title, or URI.
Returns the document content as a string, or None if not found.
- ### get_docling_document(id_or_title) -> DoclingDocument | None
- Get the structured DoclingDocument object for advanced analysis.
- Returns a DoclingDocument object, or None if not found.
- See "DoclingDocument API" section below for how to use it.
+ ### get_chunk(chunk_id) -> dict | None
+ Get a specific chunk by its ID (from search results).
+ Returns dict with keys: chunk_id, content, document_id, document_title, headings, page_numbers, labels
+ Use this to retrieve full chunk details and metadata for citation.
### llm(prompt) -> str
Call an LLM directly with the given prompt. Returns the response as a string.
@@ -184,8 +184,13 @@ interactions:
```
Check if it exists with: `if 'documents' in dir(): ...`
- ## Standard Library Modules
- You can import any Python standard library module.
+ ## Available Python Features
+
+ The interpreter supports: variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, try/except, and the `json` module.
+
+ Not supported: imports (other than `json`), class definitions, generators/yield, match statements, decorators, `with` statements.
+
+ For pattern matching or text extraction, use string methods (`str.split`, `str.find`, `str.startswith`, `in` operator) or the `llm()` function.
## Strategy Guide
@@ -193,51 +198,9 @@ interactions:
2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
- 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and use collections.
+ 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and data structures.
6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
- 7. **Cite Your Sources**: Track which documents/chunks informed your answer for citation.
-
- ## DoclingDocument API
-
- When you call `get_docling_document(id_or_title)`, you get a DoclingDocument object for structured document analysis.
-
- ### Properties
- - `doc.texts` - List of all text items (paragraphs, headings, etc.)
- - `doc.tables` - List of all tables
- - `doc.pictures` - List of all pictures/figures
- - `doc.name` - Document name
-
- ### Methods
- - `doc.iterate_items(with_groups=False)` - Iterate all items with hierarchy level
- Returns tuples of (item, level) where level is nesting depth
- - `doc.export_to_markdown()` - Export entire document as markdown string
-
- ### Text Item Properties
- - `item.text` - The text content
- - `item.label` - Type: title, paragraph, section_header, list_item, etc. (lowercase enum values)
- - `item.prov` - Provenance (page numbers, bounding boxes)
-
- ### Table Access
- - `table.data.num_rows`, `table.data.num_cols` - Dimensions
- - `table.data.table_cells` - List of TableCell objects
- - `cell.text`, `cell.start_row_offset_idx`, `cell.start_col_offset_idx`
-
- ### Example Usage
- ```python
- doc = get_docling_document("My Document")
-
- # Get all headings
- headings = [t.text for t in doc.texts if "header" in str(t.label)]
-
- # Iterate with structure
- for item, level in doc.iterate_items():
- print(" " * level + item.text[:50])
-
- # Extract table data
- for table in doc.tables:
- for cell in table.data.table_cells:
- print(f"Row {cell.start_row_offset_idx}, Col {cell.start_col_offset_idx}: {cell.text}")
- ```
+ 7. **Cite Your Sources**: Use get_chunk() to retrieve chunk metadata for citations. Track which documents/chunks informed your answer.
## Example Patterns
@@ -253,23 +216,31 @@ interactions:
print(f"Total: {count}")
```
- ### Aggregating data across documents
+ ### Extracting data with llm()
```python
- import re
numbers = []
results = search("financial data", limit=20)
for r in results:
- matches = re.findall(r'\$([\d,]+)', r['content'])
- for m in matches:
- numbers.append(int(m.replace(',', '')))
- print(f"Average: ${sum(numbers)/len(numbers):,.2f}")
+ extracted = llm(f"Extract all dollar amounts from this text as a comma-separated list of numbers (no $ signs): {r['content']}")
+ for part in extracted.split(','):
+ part = part.strip().replace(',', '')
+ if part.isdigit():
+ numbers.append(int(part))
+ if numbers:
+ print(f"Average: {sum(numbers) / len(numbers)}")
+ ```
+
+ ### Using search results with get_chunk for citations
+ ```python
+ results = search("safety requirements", limit=5)
+ for r in results:
+ chunk = get_chunk(r['chunk_id'])
+ print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}")
```
### Using llm() for classification
```python
- # Get document content
content = get_document("Q1 Report")
- # Use llm() to classify sentiment
sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
print(sentiment)
```
@@ -346,244 +317,7 @@ interactions:
response:
headers:
content-length:
- - '184'
- content-type:
- - application/json
- parsed_body:
- error:
- code: null
- message: 'error parsing tool call: raw=''{"code":"search(''quarterly update'', limit=20)"'', err=unexpected end of
- JSON input'
- param: null
- type: api_error
- status:
- code: 500
- message: Internal Server Error
-- request:
- headers:
- accept:
- - application/json
- accept-encoding:
- - gzip, deflate, zstd
- connection:
- - keep-alive
- content-length:
- - '7781'
- content-type:
- - application/json
- host:
- - localhost:11434
- method: POST
- parsed_body:
- messages:
- - content: |-
- You are a Recursive Language Model (RLM) agent that solves complex research questions by writing and executing Python code.
-
- IMPORTANT: You MUST use the `execute_code` tool to run Python code. The functions described below are ONLY available inside the execute_code tool - you cannot access them any other way. Always execute code to answer questions; do not just describe what code would do.
-
- CRITICAL: Inside execute_code, these functions are ALREADY available in the namespace. Do NOT import them - just use them directly:
- - search("query") ✓ CORRECT
- - from haiku.rag import search ✗ WRONG - will fail
-
- You have access to a sandboxed Python environment with these haiku.rag functions (use them directly, no imports needed):
-
- ## Available Functions
-
- ### search(query, limit=10) -> list[dict]
- Search the knowledge base using hybrid search (vector + full-text).
- Returns list of dicts with keys: chunk_id, content, document_id, document_title, document_uri, score, page_numbers, headings
-
- ### list_documents(limit=10, offset=0) -> list[dict]
- List available documents in the knowledge base.
- Returns list of dicts with keys: id, title, uri, created_at
-
- ### get_document(id_or_title) -> str | None
- Get the full text content of a document by ID, title, or URI.
- Returns the document content as a string, or None if not found.
-
- ### get_docling_document(id_or_title) -> DoclingDocument | None
- Get the structured DoclingDocument object for advanced analysis.
- Returns a DoclingDocument object, or None if not found.
- See "DoclingDocument API" section below for how to use it.
-
- ### llm(prompt) -> str
- Call an LLM directly with the given prompt. Returns the response as a string.
- Use this for classification, summarization, extraction, or any task where you
- already have the content and just need LLM reasoning.
-
- ## Pre-loaded Documents Variable
-
- If documents were pre-loaded for this session, a `documents` variable is available:
- ```python
- # documents is a list of dicts with keys: id, title, uri, content
- for doc in documents:
- print(doc['title'], len(doc['content']))
- ```
- Check if it exists with: `if 'documents' in dir(): ...`
-
- ## Standard Library Modules
- You can import any Python standard library module.
-
- ## Strategy Guide
-
- 1. **Explore First**: Start by listing documents or searching to understand what's available. Document names may differ from filenames (e.g., "tbmed593.pdf" might be stored as "TB MED 593" or similar).
- 2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
- 3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
- 4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
- 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and use collections.
- 6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
- 7. **Cite Your Sources**: Track which documents/chunks informed your answer for citation.
-
- ## DoclingDocument API
-
- When you call `get_docling_document(id_or_title)`, you get a DoclingDocument object for structured document analysis.
-
- ### Properties
- - `doc.texts` - List of all text items (paragraphs, headings, etc.)
- - `doc.tables` - List of all tables
- - `doc.pictures` - List of all pictures/figures
- - `doc.name` - Document name
-
- ### Methods
- - `doc.iterate_items(with_groups=False)` - Iterate all items with hierarchy level
- Returns tuples of (item, level) where level is nesting depth
- - `doc.export_to_markdown()` - Export entire document as markdown string
-
- ### Text Item Properties
- - `item.text` - The text content
- - `item.label` - Type: title, paragraph, section_header, list_item, etc. (lowercase enum values)
- - `item.prov` - Provenance (page numbers, bounding boxes)
-
- ### Table Access
- - `table.data.num_rows`, `table.data.num_cols` - Dimensions
- - `table.data.table_cells` - List of TableCell objects
- - `cell.text`, `cell.start_row_offset_idx`, `cell.start_col_offset_idx`
-
- ### Example Usage
- ```python
- doc = get_docling_document("My Document")
-
- # Get all headings
- headings = [t.text for t in doc.texts if "header" in str(t.label)]
-
- # Iterate with structure
- for item, level in doc.iterate_items():
- print(" " * level + item.text[:50])
-
- # Extract table data
- for table in doc.tables:
- for cell in table.data.table_cells:
- print(f"Row {cell.start_row_offset_idx}, Col {cell.start_col_offset_idx}: {cell.text}")
- ```
-
- ## Example Patterns
-
- ### Counting documents matching a condition
- ```python
- docs = list_documents(limit=100)
- count = 0
- for doc in docs:
- content = get_document(doc['id'])
- if content and 'keyword' in content.lower():
- count += 1
- print(f"Found in: {doc['title']}")
- print(f"Total: {count}")
- ```
-
- ### Aggregating data across documents
- ```python
- import re
- numbers = []
- results = search("financial data", limit=20)
- for r in results:
- matches = re.findall(r'\$([\d,]+)', r['content'])
- for m in matches:
- numbers.append(int(m.replace(',', '')))
- print(f"Average: ${sum(numbers)/len(numbers):,.2f}")
- ```
-
- ### Using llm() for classification
- ```python
- # Get document content
- content = get_document("Q1 Report")
- # Use llm() to classify sentiment
- sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
- print(sentiment)
- ```
-
- ## Workflow
-
- 1. **ALWAYS start by using execute_code** to explore the knowledge base
- 2. Run multiple code blocks as needed to gather information
- 3. After collecting data, provide your final answer
-
- ## Output Format
-
- CRITICAL: Your final response MUST be valid JSON matching this exact schema:
- ```json
- {"answer": "Your complete answer here as a string", "program": "Your final consolidated program here as a string"}
- ```
-
- - `answer`: A clear answer to the user's question with key findings and references to specific documents/chunks.
- - `program`: A single, self-contained Python program that produces the answer. Consolidate your exploratory code executions into one clean script.
-
- Do NOT return arbitrary JSON structures. Always use the exact format: {"answer": "...", "program": "..."}
-
- CRITICAL: You MUST call execute_code at least once before providing your answer. Never give up without trying to execute code first.
- role: system
- - content: Analyze the sentiment of each quarterly update. How many quarters were positive, negative, and mixed?
- role: user
- model: gpt-oss
- reasoning_effort: low
- stream: false
- tool_choice: auto
- tools:
- - function:
- description: |-
- Execute Python code in a sandboxed interpreter.
-
- The code has access to haiku.rag functions (search, list_documents,
- get_document, get_chunk, llm).
-
- Use print() to output results.
-
- Structured result with success status, stdout, and stderr.
-
- name: execute_code
- parameters:
- additionalProperties: false
- properties:
- code:
- description: Python code to execute.
- type: string
- required:
- - code
- type: object
- strict: true
- type: function
- - function:
- description: Result from RLM agent execution.
- name: final_result
- parameters:
- additionalProperties: false
- properties:
- answer:
- description: The answer to the user's question
- type: string
- program:
- description: The final consolidated program
- type: string
- required:
- - answer
- - program
- type: object
- strict: true
- type: function
- uri: http://localhost:11434/v1/chat/completions
- response:
- headers:
- content-length:
- - '656'
+ - '636'
content-type:
- application/json
parsed_body:
@@ -592,25 +326,25 @@ interactions:
index: 0
message:
content: ''
- reasoning: We need documents about quarterly updates. Search.
+ reasoning: Need to find quarterly updates documents. Search.
role: assistant
tool_calls:
- function:
- arguments: '{"code":"results = search(\"quarterly update\", limit=50)\nprint(len(results))\nfor r in results[:5]:\n print(r[''document_title''],
- r[''page_numbers''][:3], r[''score''])\n"}'
+ arguments: '{"code":"results = search(\"quarterly update\", limit=20)\nprint(len(results))\nfor r in results:\n print(r[''document_title''],
+ r[''page_numbers''])\n"}'
name: execute_code
- id: call_0g817w8v
+ id: call_19rh51up
index: 0
type: function
- created: 1771336329
- id: chatcmpl-80
+ created: 1771336739
+ id: chatcmpl-862
model: gpt-oss
object: chat.completion
system_fingerprint: fp_ollama
usage:
- completion_tokens: 81
- prompt_tokens: 1745
- total_tokens: 1826
+ completion_tokens: 71
+ prompt_tokens: 1573
+ total_tokens: 1644
status:
code: 200
message: OK
@@ -663,7 +397,7 @@ interactions:
connection:
- keep-alive
content-length:
- - '8576'
+ - '7837'
content-type:
- application/json
host:
@@ -680,7 +414,7 @@ interactions:
- search("query") ✓ CORRECT
- from haiku.rag import search ✗ WRONG - will fail
- You have access to a sandboxed Python environment with these haiku.rag functions (use them directly, no imports needed):
+ You have access to a sandboxed Python interpreter with these haiku.rag functions (use them directly, no imports needed):
## Available Functions
@@ -696,10 +430,10 @@ interactions:
Get the full text content of a document by ID, title, or URI.
Returns the document content as a string, or None if not found.
- ### get_docling_document(id_or_title) -> DoclingDocument | None
- Get the structured DoclingDocument object for advanced analysis.
- Returns a DoclingDocument object, or None if not found.
- See "DoclingDocument API" section below for how to use it.
+ ### get_chunk(chunk_id) -> dict | None
+ Get a specific chunk by its ID (from search results).
+ Returns dict with keys: chunk_id, content, document_id, document_title, headings, page_numbers, labels
+ Use this to retrieve full chunk details and metadata for citation.
### llm(prompt) -> str
Call an LLM directly with the given prompt. Returns the response as a string.
@@ -716,8 +450,13 @@ interactions:
```
Check if it exists with: `if 'documents' in dir(): ...`
- ## Standard Library Modules
- You can import any Python standard library module.
+ ## Available Python Features
+
+ The interpreter supports: variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, try/except, and the `json` module.
+
+ Not supported: imports (other than `json`), class definitions, generators/yield, match statements, decorators, `with` statements.
+
+ For pattern matching or text extraction, use string methods (`str.split`, `str.find`, `str.startswith`, `in` operator) or the `llm()` function.
## Strategy Guide
@@ -725,51 +464,9 @@ interactions:
2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
- 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and use collections.
+ 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and data structures.
6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
- 7. **Cite Your Sources**: Track which documents/chunks informed your answer for citation.
-
- ## DoclingDocument API
-
- When you call `get_docling_document(id_or_title)`, you get a DoclingDocument object for structured document analysis.
-
- ### Properties
- - `doc.texts` - List of all text items (paragraphs, headings, etc.)
- - `doc.tables` - List of all tables
- - `doc.pictures` - List of all pictures/figures
- - `doc.name` - Document name
-
- ### Methods
- - `doc.iterate_items(with_groups=False)` - Iterate all items with hierarchy level
- Returns tuples of (item, level) where level is nesting depth
- - `doc.export_to_markdown()` - Export entire document as markdown string
-
- ### Text Item Properties
- - `item.text` - The text content
- - `item.label` - Type: title, paragraph, section_header, list_item, etc. (lowercase enum values)
- - `item.prov` - Provenance (page numbers, bounding boxes)
-
- ### Table Access
- - `table.data.num_rows`, `table.data.num_cols` - Dimensions
- - `table.data.table_cells` - List of TableCell objects
- - `cell.text`, `cell.start_row_offset_idx`, `cell.start_col_offset_idx`
-
- ### Example Usage
- ```python
- doc = get_docling_document("My Document")
-
- # Get all headings
- headings = [t.text for t in doc.texts if "header" in str(t.label)]
-
- # Iterate with structure
- for item, level in doc.iterate_items():
- print(" " * level + item.text[:50])
-
- # Extract table data
- for table in doc.tables:
- for cell in table.data.table_cells:
- print(f"Row {cell.start_row_offset_idx}, Col {cell.start_col_offset_idx}: {cell.text}")
- ```
+ 7. **Cite Your Sources**: Use get_chunk() to retrieve chunk metadata for citations. Track which documents/chunks informed your answer.
## Example Patterns
@@ -785,23 +482,31 @@ interactions:
print(f"Total: {count}")
```
- ### Aggregating data across documents
+ ### Extracting data with llm()
```python
- import re
numbers = []
results = search("financial data", limit=20)
for r in results:
- matches = re.findall(r'\$([\d,]+)', r['content'])
- for m in matches:
- numbers.append(int(m.replace(',', '')))
- print(f"Average: ${sum(numbers)/len(numbers):,.2f}")
+ extracted = llm(f"Extract all dollar amounts from this text as a comma-separated list of numbers (no $ signs): {r['content']}")
+ for part in extracted.split(','):
+ part = part.strip().replace(',', '')
+ if part.isdigit():
+ numbers.append(int(part))
+ if numbers:
+ print(f"Average: {sum(numbers) / len(numbers)}")
+ ```
+
+ ### Using search results with get_chunk for citations
+ ```python
+ results = search("safety requirements", limit=5)
+ for r in results:
+ chunk = get_chunk(r['chunk_id'])
+ print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}")
```
### Using llm() for classification
```python
- # Get document content
content = get_document("Q1 Report")
- # Use llm() to classify sentiment
sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
print(sentiment)
```
@@ -829,20 +534,19 @@ interactions:
- content: Analyze the sentiment of each quarterly update. How many quarters were positive, negative, and mixed?
role: user
- content: null
- reasoning: We need documents about quarterly updates. Search.
+ reasoning: Need to find quarterly updates documents. Search.
role: assistant
tool_calls:
- function:
- arguments: '{"code":"results = search(\"quarterly update\", limit=50)\nprint(len(results))\nfor r in results[:5]:\n print(r[''document_title''],
- r[''page_numbers''][:3], r[''score''])\n"}'
+ arguments: '{"code":"results = search(\"quarterly update\", limit=20)\nprint(len(results))\nfor r in results:\n print(r[''document_title''],
+ r[''page_numbers''])\n"}'
name: execute_code
- id: call_0g817w8v
+ id: call_19rh51up
type: function
- - content: '{"code":"results = search(\"quarterly update\", limit=50)\nprint(len(results))\nfor r in results[:5]:\n print(r[''document_title''],
- r[''page_numbers''][:3], r[''score''])\n","stdout":"3\nQ3 Update [] 0.032786883413791656\nQ2 Update [] 0.032258063554763794\nQ1
- Update [] 0.01587301678955555\n","stderr":"","success":true}'
+ - content: '{"code":"results = search(\"quarterly update\", limit=20)\nprint(len(results))\nfor r in results:\n print(r[''document_title''],
+ r[''page_numbers''])\n","stdout":"3\nQ3 Update []\nQ2 Update []\nQ1 Update []\n","stderr":"","success":true}'
role: tool
- tool_call_id: call_0g817w8v
+ tool_call_id: call_19rh51up
model: gpt-oss
reasoning_effort: low
stream: false
@@ -893,7 +597,7 @@ interactions:
response:
headers:
content-length:
- - '763'
+ - '884'
content-type:
- application/json
parsed_body:
@@ -902,26 +606,27 @@ interactions:
index: 0
message:
content: ''
- reasoning: 'Three documents: Q1, Q2, Q3. We need sentiment. Use llm on each content.'
+ reasoning: We have three documents. Use get_document. Need sentiment. Use llm.
role: assistant
tool_calls:
- function:
- arguments: '{"code":"for quarter in [''Q1 Update'',''Q2 Update'',''Q3 Update'']:\n content = get_document(quarter)\n resp
- = llm(f\"Classify the sentiment of this quarterly update text as positive, negative, or mixed.\\n\\n{content}\")\n print(quarter,
- resp)\n"}'
+ arguments: '{"code":"docs = [''Q1 Update'',''Q2 Update'',''Q3 Update'']\nimport json\nresults = {}\nfor d in
+ docs:\n content = get_document(d)\n if content:\n sentiment = llm(f\"Classify the overall sentiment
+ of this quarterly update as ''positive'', ''negative'', or ''mixed'': {content[:2000]}\")\n results[d]
+ = sentiment.strip().lower()\nprint(json.dumps(results,indent=2))\n"}'
name: execute_code
- id: call_stqc44dk
+ id: call_5w95m1y7
index: 0
type: function
- created: 1771336331
- id: chatcmpl-88
+ created: 1771336741
+ id: chatcmpl-639
model: gpt-oss
object: chat.completion
system_fingerprint: fp_ollama
usage:
- completion_tokens: 115
- prompt_tokens: 1943
- total_tokens: 2058
+ completion_tokens: 141
+ prompt_tokens: 1727
+ total_tokens: 1868
status:
code: 200
message: OK
@@ -934,7 +639,7 @@ interactions:
connection:
- keep-alive
content-length:
- - '342'
+ - '9000'
content-type:
- application/json
host:
@@ -943,37 +648,245 @@ interactions:
parsed_body:
messages:
- content: |-
- Classify the sentiment of this quarterly update text as positive, negative, or mixed.
+ You are a Recursive Language Model (RLM) agent that solves complex research questions by writing and executing Python code.
- The new product launch exceeded expectations. Sales grew 40% and customer feedback has been overwhelmingly positive. Team morale is at an all-time high.
+ IMPORTANT: You MUST use the `execute_code` tool to run Python code. The functions described below are ONLY available inside the execute_code tool - you cannot access them any other way. Always execute code to answer questions; do not just describe what code would do.
+
+ CRITICAL: Inside execute_code, these functions are ALREADY available in the namespace. Do NOT import them - just use them directly:
+ - search("query") ✓ CORRECT
+ - from haiku.rag import search ✗ WRONG - will fail
+
+ You have access to a sandboxed Python interpreter with these haiku.rag functions (use them directly, no imports needed):
+
+ ## Available Functions
+
+ ### search(query, limit=10) -> list[dict]
+ Search the knowledge base using hybrid search (vector + full-text).
+ Returns list of dicts with keys: chunk_id, content, document_id, document_title, document_uri, score, page_numbers, headings
+
+ ### list_documents(limit=10, offset=0) -> list[dict]
+ List available documents in the knowledge base.
+ Returns list of dicts with keys: id, title, uri, created_at
+
+ ### get_document(id_or_title) -> str | None
+ Get the full text content of a document by ID, title, or URI.
+ Returns the document content as a string, or None if not found.
+
+ ### get_chunk(chunk_id) -> dict | None
+ Get a specific chunk by its ID (from search results).
+ Returns dict with keys: chunk_id, content, document_id, document_title, headings, page_numbers, labels
+ Use this to retrieve full chunk details and metadata for citation.
+
+ ### llm(prompt) -> str
+ Call an LLM directly with the given prompt. Returns the response as a string.
+ Use this for classification, summarization, extraction, or any task where you
+ already have the content and just need LLM reasoning.
+
+ ## Pre-loaded Documents Variable
+
+ If documents were pre-loaded for this session, a `documents` variable is available:
+ ```python
+ # documents is a list of dicts with keys: id, title, uri, content
+ for doc in documents:
+ print(doc['title'], len(doc['content']))
+ ```
+ Check if it exists with: `if 'documents' in dir(): ...`
+
+ ## Available Python Features
+
+ The interpreter supports: variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, try/except, and the `json` module.
+
+ Not supported: imports (other than `json`), class definitions, generators/yield, match statements, decorators, `with` statements.
+
+ For pattern matching or text extraction, use string methods (`str.split`, `str.find`, `str.startswith`, `in` operator) or the `llm()` function.
+
+ ## Strategy Guide
+
+ 1. **Explore First**: Start by listing documents or searching to understand what's available. Document names may differ from filenames (e.g., "tbmed593.pdf" might be stored as "TB MED 593" or similar).
+ 2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
+ 3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
+ 4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
+ 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and data structures.
+ 6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
+ 7. **Cite Your Sources**: Use get_chunk() to retrieve chunk metadata for citations. Track which documents/chunks informed your answer.
+
+ ## Example Patterns
+
+ ### Counting documents matching a condition
+ ```python
+ docs = list_documents(limit=100)
+ count = 0
+ for doc in docs:
+ content = get_document(doc['id'])
+ if content and 'keyword' in content.lower():
+ count += 1
+ print(f"Found in: {doc['title']}")
+ print(f"Total: {count}")
+ ```
+
+ ### Extracting data with llm()
+ ```python
+ numbers = []
+ results = search("financial data", limit=20)
+ for r in results:
+ extracted = llm(f"Extract all dollar amounts from this text as a comma-separated list of numbers (no $ signs): {r['content']}")
+ for part in extracted.split(','):
+ part = part.strip().replace(',', '')
+ if part.isdigit():
+ numbers.append(int(part))
+ if numbers:
+ print(f"Average: {sum(numbers) / len(numbers)}")
+ ```
+
+ ### Using search results with get_chunk for citations
+ ```python
+ results = search("safety requirements", limit=5)
+ for r in results:
+ chunk = get_chunk(r['chunk_id'])
+ print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}")
+ ```
+
+ ### Using llm() for classification
+ ```python
+ content = get_document("Q1 Report")
+ sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
+ print(sentiment)
+ ```
+
+ ## Workflow
+
+ 1. **ALWAYS start by using execute_code** to explore the knowledge base
+ 2. Run multiple code blocks as needed to gather information
+ 3. After collecting data, provide your final answer
+
+ ## Output Format
+
+ CRITICAL: Your final response MUST be valid JSON matching this exact schema:
+ ```json
+ {"answer": "Your complete answer here as a string", "program": "Your final consolidated program here as a string"}
+ ```
+
+ - `answer`: A clear answer to the user's question with key findings and references to specific documents/chunks.
+ - `program`: A single, self-contained Python program that produces the answer. Consolidate your exploratory code executions into one clean script.
+
+ Do NOT return arbitrary JSON structures. Always use the exact format: {"answer": "...", "program": "..."}
+
+ CRITICAL: You MUST call execute_code at least once before providing your answer. Never give up without trying to execute code first.
+ role: system
+ - content: Analyze the sentiment of each quarterly update. How many quarters were positive, negative, and mixed?
role: user
+ - content: null
+ reasoning: Need to find quarterly updates documents. Search.
+ role: assistant
+ tool_calls:
+ - function:
+ arguments: '{"code":"results = search(\"quarterly update\", limit=20)\nprint(len(results))\nfor r in results:\n print(r[''document_title''],
+ r[''page_numbers''])\n"}'
+ name: execute_code
+ id: call_19rh51up
+ type: function
+ - content: '{"code":"results = search(\"quarterly update\", limit=20)\nprint(len(results))\nfor r in results:\n print(r[''document_title''],
+ r[''page_numbers''])\n","stdout":"3\nQ3 Update []\nQ2 Update []\nQ1 Update []\n","stderr":"","success":true}'
+ role: tool
+ tool_call_id: call_19rh51up
+ - content: null
+ reasoning: We have three documents. Use get_document. Need sentiment. Use llm.
+ role: assistant
+ tool_calls:
+ - function:
+ arguments: '{"code":"docs = [''Q1 Update'',''Q2 Update'',''Q3 Update'']\nimport json\nresults = {}\nfor d in docs:\n content
+ = get_document(d)\n if content:\n sentiment = llm(f\"Classify the overall sentiment of this quarterly
+ update as ''positive'', ''negative'', or ''mixed'': {content[:2000]}\")\n results[d] = sentiment.strip().lower()\nprint(json.dumps(results,indent=2))\n"}'
+ name: execute_code
+ id: call_5w95m1y7
+ type: function
+ - content: '{"code":"docs = [''Q1 Update'',''Q2 Update'',''Q3 Update'']\nimport json\nresults = {}\nfor d in docs:\n content
+ = get_document(d)\n if content:\n sentiment = llm(f\"Classify the overall sentiment of this quarterly
+ update as ''positive'', ''negative'', or ''mixed'': {content[:2000]}\")\n results[d] = sentiment.strip().lower()\nprint(json.dumps(results,indent=2))\n","stdout":"","stderr":"ModuleNotFoundError:
+ No module named ''json''","success":false}'
+ role: tool
+ tool_call_id: call_5w95m1y7
model: gpt-oss
reasoning_effort: low
stream: false
+ tool_choice: auto
+ tools:
+ - function:
+ description: |-
+ Execute Python code in a sandboxed interpreter.
+
+ The code has access to haiku.rag functions (search, list_documents,
+ get_document, get_chunk, llm).
+
+ Use print() to output results.
+
+ Structured result with success status, stdout, and stderr.
+
+ name: execute_code
+ parameters:
+ additionalProperties: false
+ properties:
+ code:
+ description: Python code to execute.
+ type: string
+ required:
+ - code
+ type: object
+ strict: true
+ type: function
+ - function:
+ description: Result from RLM agent execution.
+ name: final_result
+ parameters:
+ additionalProperties: false
+ properties:
+ answer:
+ description: The answer to the user's question
+ type: string
+ program:
+ description: The final consolidated program
+ type: string
+ required:
+ - answer
+ - program
+ type: object
+ strict: true
+ type: function
uri: http://localhost:11434/v1/chat/completions
response:
headers:
content-length:
- - '399'
+ - '963'
content-type:
- application/json
parsed_body:
choices:
- - finish_reason: stop
+ - finish_reason: tool_calls
index: 0
message:
- content: Positive
- reasoning: 'We need sentiment classification: overall appears positive. No negative. So output positive.'
+ content: ''
+ reasoning: json module not allowed? It said imports except json allowed. Actually json is allowed. But error? maybe
+ 'json' not imported? In environment we can do import json. Let's try without import? Use str conversion. We'll
+ just print.
role: assistant
- created: 1771336332
- id: chatcmpl-540
+ tool_calls:
+ - function:
+ arguments: '{"code":"docs = [''Q1 Update'',''Q2 Update'',''Q3 Update'']\nres = {}\nfor d in docs:\n c = get_document(d)\n if
+ c:\n s = llm(f\"Classify the overall sentiment of this quarterly update as ''positive'', ''negative'',
+ or ''mixed'': {c[:2000]}\")\n res[d] = s.strip().lower()\nprint(res)\n"}'
+ name: execute_code
+ id: call_1phm2kez
+ index: 0
+ type: function
+ created: 1771336745
+ id: chatcmpl-227
model: gpt-oss
object: chat.completion
system_fingerprint: fp_ollama
usage:
- completion_tokens: 27
- prompt_tokens: 113
- total_tokens: 140
+ completion_tokens: 163
+ prompt_tokens: 2005
+ total_tokens: 2168
status:
code: 200
message: OK
@@ -986,7 +899,7 @@ interactions:
connection:
- keep-alive
content-length:
- - '351'
+ - '348'
content-type:
- application/json
host:
@@ -994,10 +907,9 @@ interactions:
method: POST
parsed_body:
messages:
- - content: |-
- Classify the sentiment of this quarterly update text as positive, negative, or mixed.
-
- We faced significant challenges this quarter. Supply chain issues caused delays, and we missed our revenue target by 15%. Several key employees left the company.
+ - content: 'Classify the overall sentiment of this quarterly update as ''positive'', ''negative'', or ''mixed'': The
+ new product launch exceeded expectations. Sales grew 40% and customer feedback has been overwhelmingly positive.
+ Team morale is at an all-time high.'
role: user
model: gpt-oss
reasoning_effort: low
@@ -1006,7 +918,7 @@ interactions:
response:
headers:
content-length:
- - '374'
+ - '335'
content-type:
- application/json
parsed_body:
@@ -1014,18 +926,120 @@ interactions:
- finish_reason: stop
index: 0
message:
- content: '**Sentiment:** **Negative**'
- reasoning: Need sentiment classification. Text is negative.
+ content: '**Sentiment:** Positive'
+ reasoning: All positive.
role: assistant
- created: 1771336333
- id: chatcmpl-627
+ created: 1771336746
+ id: chatcmpl-350
+ model: gpt-oss
+ object: chat.completion
+ system_fingerprint: fp_ollama
+ usage:
+ completion_tokens: 18
+ prompt_tokens: 116
+ total_tokens: 134
+ status:
+ code: 200
+ message: OK
+- request:
+ headers:
+ accept:
+ - application/json
+ accept-encoding:
+ - gzip, deflate, zstd
+ connection:
+ - keep-alive
+ content-length:
+ - '357'
+ content-type:
+ - application/json
+ host:
+ - localhost:11434
+ method: POST
+ parsed_body:
+ messages:
+ - content: 'Classify the overall sentiment of this quarterly update as ''positive'', ''negative'', or ''mixed'': We
+ faced significant challenges this quarter. Supply chain issues caused delays, and we missed our revenue target by
+ 15%. Several key employees left the company.'
+ role: user
+ model: gpt-oss
+ reasoning_effort: low
+ stream: false
+ uri: http://localhost:11434/v1/chat/completions
+ response:
+ headers:
+ content-length:
+ - '368'
+ content-type:
+ - application/json
+ parsed_body:
+ choices:
+ - finish_reason: stop
+ index: 0
+ message:
+ content: '**Sentiment: negative**'
+ reasoning: We detect negativity. So classify as negative.
+ role: assistant
+ created: 1771336747
+ id: chatcmpl-663
model: gpt-oss
object: chat.completion
system_fingerprint: fp_ollama
usage:
completion_tokens: 25
+ prompt_tokens: 117
+ total_tokens: 142
+ status:
+ code: 200
+ message: OK
+- request:
+ headers:
+ accept:
+ - application/json
+ accept-encoding:
+ - gzip, deflate, zstd
+ connection:
+ - keep-alive
+ content-length:
+ - '364'
+ content-type:
+ - application/json
+ host:
+ - localhost:11434
+ method: POST
+ parsed_body:
+ messages:
+ - content: 'Classify the overall sentiment of this quarterly update as ''positive'', ''negative'', or ''mixed'': Mixed
+ results this quarter. While product quality improved, marketing campaigns underperformed. Revenue was flat compared
+ to last year but customer retention increased.'
+ role: user
+ model: gpt-oss
+ reasoning_effort: low
+ stream: false
+ uri: http://localhost:11434/v1/chat/completions
+ response:
+ headers:
+ content-length:
+ - '394'
+ content-type:
+ - application/json
+ parsed_body:
+ choices:
+ - finish_reason: stop
+ index: 0
+ message:
+ content: '**Mixed**'
+ reasoning: Need to decide sentiment. Mixed results, some positive, some negative. Likely 'mixed'.
+ role: assistant
+ created: 1771336747
+ id: chatcmpl-126
+ model: gpt-oss
+ object: chat.completion
+ system_fingerprint: fp_ollama
+ usage:
+ completion_tokens: 32
prompt_tokens: 114
- total_tokens: 139
+ total_tokens: 146
status:
code: 200
message: OK
@@ -1038,7 +1052,7 @@ interactions:
connection:
- keep-alive
content-length:
- - '358'
+ - '10226'
content-type:
- application/json
host:
@@ -1047,18 +1061,527 @@ interactions:
parsed_body:
messages:
- content: |-
- Classify the sentiment of this quarterly update text as positive, negative, or mixed.
+ You are a Recursive Language Model (RLM) agent that solves complex research questions by writing and executing Python code.
- Mixed results this quarter. While product quality improved, marketing campaigns underperformed. Revenue was flat compared to last year but customer retention increased.
+ IMPORTANT: You MUST use the `execute_code` tool to run Python code. The functions described below are ONLY available inside the execute_code tool - you cannot access them any other way. Always execute code to answer questions; do not just describe what code would do.
+
+ CRITICAL: Inside execute_code, these functions are ALREADY available in the namespace. Do NOT import them - just use them directly:
+ - search("query") ✓ CORRECT
+ - from haiku.rag import search ✗ WRONG - will fail
+
+ You have access to a sandboxed Python interpreter with these haiku.rag functions (use them directly, no imports needed):
+
+ ## Available Functions
+
+ ### search(query, limit=10) -> list[dict]
+ Search the knowledge base using hybrid search (vector + full-text).
+ Returns list of dicts with keys: chunk_id, content, document_id, document_title, document_uri, score, page_numbers, headings
+
+ ### list_documents(limit=10, offset=0) -> list[dict]
+ List available documents in the knowledge base.
+ Returns list of dicts with keys: id, title, uri, created_at
+
+ ### get_document(id_or_title) -> str | None
+ Get the full text content of a document by ID, title, or URI.
+ Returns the document content as a string, or None if not found.
+
+ ### get_chunk(chunk_id) -> dict | None
+ Get a specific chunk by its ID (from search results).
+ Returns dict with keys: chunk_id, content, document_id, document_title, headings, page_numbers, labels
+ Use this to retrieve full chunk details and metadata for citation.
+
+ ### llm(prompt) -> str
+ Call an LLM directly with the given prompt. Returns the response as a string.
+ Use this for classification, summarization, extraction, or any task where you
+ already have the content and just need LLM reasoning.
+
+ ## Pre-loaded Documents Variable
+
+ If documents were pre-loaded for this session, a `documents` variable is available:
+ ```python
+ # documents is a list of dicts with keys: id, title, uri, content
+ for doc in documents:
+ print(doc['title'], len(doc['content']))
+ ```
+ Check if it exists with: `if 'documents' in dir(): ...`
+
+ ## Available Python Features
+
+ The interpreter supports: variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, try/except, and the `json` module.
+
+ Not supported: imports (other than `json`), class definitions, generators/yield, match statements, decorators, `with` statements.
+
+ For pattern matching or text extraction, use string methods (`str.split`, `str.find`, `str.startswith`, `in` operator) or the `llm()` function.
+
+ ## Strategy Guide
+
+ 1. **Explore First**: Start by listing documents or searching to understand what's available. Document names may differ from filenames (e.g., "tbmed593.pdf" might be stored as "TB MED 593" or similar).
+ 2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
+ 3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
+ 4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
+ 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and data structures.
+ 6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
+ 7. **Cite Your Sources**: Use get_chunk() to retrieve chunk metadata for citations. Track which documents/chunks informed your answer.
+
+ ## Example Patterns
+
+ ### Counting documents matching a condition
+ ```python
+ docs = list_documents(limit=100)
+ count = 0
+ for doc in docs:
+ content = get_document(doc['id'])
+ if content and 'keyword' in content.lower():
+ count += 1
+ print(f"Found in: {doc['title']}")
+ print(f"Total: {count}")
+ ```
+
+ ### Extracting data with llm()
+ ```python
+ numbers = []
+ results = search("financial data", limit=20)
+ for r in results:
+ extracted = llm(f"Extract all dollar amounts from this text as a comma-separated list of numbers (no $ signs): {r['content']}")
+ for part in extracted.split(','):
+ part = part.strip().replace(',', '')
+ if part.isdigit():
+ numbers.append(int(part))
+ if numbers:
+ print(f"Average: {sum(numbers) / len(numbers)}")
+ ```
+
+ ### Using search results with get_chunk for citations
+ ```python
+ results = search("safety requirements", limit=5)
+ for r in results:
+ chunk = get_chunk(r['chunk_id'])
+ print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}")
+ ```
+
+ ### Using llm() for classification
+ ```python
+ content = get_document("Q1 Report")
+ sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
+ print(sentiment)
+ ```
+
+ ## Workflow
+
+ 1. **ALWAYS start by using execute_code** to explore the knowledge base
+ 2. Run multiple code blocks as needed to gather information
+ 3. After collecting data, provide your final answer
+
+ ## Output Format
+
+ CRITICAL: Your final response MUST be valid JSON matching this exact schema:
+ ```json
+ {"answer": "Your complete answer here as a string", "program": "Your final consolidated program here as a string"}
+ ```
+
+ - `answer`: A clear answer to the user's question with key findings and references to specific documents/chunks.
+ - `program`: A single, self-contained Python program that produces the answer. Consolidate your exploratory code executions into one clean script.
+
+ Do NOT return arbitrary JSON structures. Always use the exact format: {"answer": "...", "program": "..."}
+
+ CRITICAL: You MUST call execute_code at least once before providing your answer. Never give up without trying to execute code first.
+ role: system
+ - content: Analyze the sentiment of each quarterly update. How many quarters were positive, negative, and mixed?
role: user
+ - content: null
+ reasoning: Need to find quarterly updates documents. Search.
+ role: assistant
+ tool_calls:
+ - function:
+ arguments: '{"code":"results = search(\"quarterly update\", limit=20)\nprint(len(results))\nfor r in results:\n print(r[''document_title''],
+ r[''page_numbers''])\n"}'
+ name: execute_code
+ id: call_19rh51up
+ type: function
+ - content: '{"code":"results = search(\"quarterly update\", limit=20)\nprint(len(results))\nfor r in results:\n print(r[''document_title''],
+ r[''page_numbers''])\n","stdout":"3\nQ3 Update []\nQ2 Update []\nQ1 Update []\n","stderr":"","success":true}'
+ role: tool
+ tool_call_id: call_19rh51up
+ - content: null
+ reasoning: We have three documents. Use get_document. Need sentiment. Use llm.
+ role: assistant
+ tool_calls:
+ - function:
+ arguments: '{"code":"docs = [''Q1 Update'',''Q2 Update'',''Q3 Update'']\nimport json\nresults = {}\nfor d in docs:\n content
+ = get_document(d)\n if content:\n sentiment = llm(f\"Classify the overall sentiment of this quarterly
+ update as ''positive'', ''negative'', or ''mixed'': {content[:2000]}\")\n results[d] = sentiment.strip().lower()\nprint(json.dumps(results,indent=2))\n"}'
+ name: execute_code
+ id: call_5w95m1y7
+ type: function
+ - content: '{"code":"docs = [''Q1 Update'',''Q2 Update'',''Q3 Update'']\nimport json\nresults = {}\nfor d in docs:\n content
+ = get_document(d)\n if content:\n sentiment = llm(f\"Classify the overall sentiment of this quarterly
+ update as ''positive'', ''negative'', or ''mixed'': {content[:2000]}\")\n results[d] = sentiment.strip().lower()\nprint(json.dumps(results,indent=2))\n","stdout":"","stderr":"ModuleNotFoundError:
+ No module named ''json''","success":false}'
+ role: tool
+ tool_call_id: call_5w95m1y7
+ - content: null
+ reasoning: json module not allowed? It said imports except json allowed. Actually json is allowed. But error? maybe
+ 'json' not imported? In environment we can do import json. Let's try without import? Use str conversion. We'll just
+ print.
+ role: assistant
+ tool_calls:
+ - function:
+ arguments: '{"code":"docs = [''Q1 Update'',''Q2 Update'',''Q3 Update'']\nres = {}\nfor d in docs:\n c = get_document(d)\n if
+ c:\n s = llm(f\"Classify the overall sentiment of this quarterly update as ''positive'', ''negative'',
+ or ''mixed'': {c[:2000]}\")\n res[d] = s.strip().lower()\nprint(res)\n"}'
+ name: execute_code
+ id: call_1phm2kez
+ type: function
+ - content: '{"code":"docs = [''Q1 Update'',''Q2 Update'',''Q3 Update'']\nres = {}\nfor d in docs:\n c = get_document(d)\n if
+ c:\n s = llm(f\"Classify the overall sentiment of this quarterly update as ''positive'', ''negative'', or
+ ''mixed'': {c[:2000]}\")\n res[d] = s.strip().lower()\nprint(res)\n","stdout":"{''Q1 Update'': ''**sentiment:**
+ positive'', ''Q2 Update'': ''**sentiment: negative**'', ''Q3 Update'': ''**mixed**''}\n","stderr":"","success":true}'
+ role: tool
+ tool_call_id: call_1phm2kez
model: gpt-oss
reasoning_effort: low
stream: false
+ tool_choice: auto
+ tools:
+ - function:
+ description: |-
+ Execute Python code in a sandboxed interpreter.
+
+ The code has access to haiku.rag functions (search, list_documents,
+ get_document, get_chunk, llm).
+
+ Use print() to output results.
+
+ Structured result with success status, stdout, and stderr.
+
+ name: execute_code
+ parameters:
+ additionalProperties: false
+ properties:
+ code:
+ description: Python code to execute.
+ type: string
+ required:
+ - code
+ type: object
+ strict: true
+ type: function
+ - function:
+ description: Result from RLM agent execution.
+ name: final_result
+ parameters:
+ additionalProperties: false
+ properties:
+ answer:
+ description: The answer to the user's question
+ type: string
+ program:
+ description: The final consolidated program
+ type: string
+ required:
+ - answer
+ - program
+ type: object
+ strict: true
+ type: function
uri: http://localhost:11434/v1/chat/completions
response:
headers:
content-length:
- - '428'
+ - '753'
+ content-type:
+ - application/json
+ parsed_body:
+ choices:
+ - finish_reason: tool_calls
+ index: 0
+ message:
+ content: ''
+ reasoning: Parse results. Count.
+ role: assistant
+ tool_calls:
+ - function:
+ arguments: '{"code":"res={''Q1 Update'': ''**sentiment:** positive'', ''Q2 Update'': ''**sentiment: negative**'',
+ ''Q3 Update'': ''**mixed**''}\npos=sum(1 for v in res.values() if ''positive'' in v)\nneg=sum(1 for v in res.values()
+ if ''negative'' in v)\nmix=sum(1 for v in res.values() if ''mixed'' in v)\nprint(pos,neg,mix)\n"}'
+ name: execute_code
+ id: call_8y20oq7z
+ index: 0
+ type: function
+ created: 1771336753
+ id: chatcmpl-869
+ model: gpt-oss
+ object: chat.completion
+ system_fingerprint: fp_ollama
+ usage:
+ completion_tokens: 125
+ prompt_tokens: 2318
+ total_tokens: 2443
+ status:
+ code: 200
+ message: OK
+- request:
+ headers:
+ accept:
+ - application/json
+ accept-encoding:
+ - gzip, deflate, zstd
+ connection:
+ - keep-alive
+ content-length:
+ - '11137'
+ content-type:
+ - application/json
+ host:
+ - localhost:11434
+ method: POST
+ parsed_body:
+ messages:
+ - content: |-
+ You are a Recursive Language Model (RLM) agent that solves complex research questions by writing and executing Python code.
+
+ IMPORTANT: You MUST use the `execute_code` tool to run Python code. The functions described below are ONLY available inside the execute_code tool - you cannot access them any other way. Always execute code to answer questions; do not just describe what code would do.
+
+ CRITICAL: Inside execute_code, these functions are ALREADY available in the namespace. Do NOT import them - just use them directly:
+ - search("query") ✓ CORRECT
+ - from haiku.rag import search ✗ WRONG - will fail
+
+ You have access to a sandboxed Python interpreter with these haiku.rag functions (use them directly, no imports needed):
+
+ ## Available Functions
+
+ ### search(query, limit=10) -> list[dict]
+ Search the knowledge base using hybrid search (vector + full-text).
+ Returns list of dicts with keys: chunk_id, content, document_id, document_title, document_uri, score, page_numbers, headings
+
+ ### list_documents(limit=10, offset=0) -> list[dict]
+ List available documents in the knowledge base.
+ Returns list of dicts with keys: id, title, uri, created_at
+
+ ### get_document(id_or_title) -> str | None
+ Get the full text content of a document by ID, title, or URI.
+ Returns the document content as a string, or None if not found.
+
+ ### get_chunk(chunk_id) -> dict | None
+ Get a specific chunk by its ID (from search results).
+ Returns dict with keys: chunk_id, content, document_id, document_title, headings, page_numbers, labels
+ Use this to retrieve full chunk details and metadata for citation.
+
+ ### llm(prompt) -> str
+ Call an LLM directly with the given prompt. Returns the response as a string.
+ Use this for classification, summarization, extraction, or any task where you
+ already have the content and just need LLM reasoning.
+
+ ## Pre-loaded Documents Variable
+
+ If documents were pre-loaded for this session, a `documents` variable is available:
+ ```python
+ # documents is a list of dicts with keys: id, title, uri, content
+ for doc in documents:
+ print(doc['title'], len(doc['content']))
+ ```
+ Check if it exists with: `if 'documents' in dir(): ...`
+
+ ## Available Python Features
+
+ The interpreter supports: variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, try/except, and the `json` module.
+
+ Not supported: imports (other than `json`), class definitions, generators/yield, match statements, decorators, `with` statements.
+
+ For pattern matching or text extraction, use string methods (`str.split`, `str.find`, `str.startswith`, `in` operator) or the `llm()` function.
+
+ ## Strategy Guide
+
+ 1. **Explore First**: Start by listing documents or searching to understand what's available. Document names may differ from filenames (e.g., "tbmed593.pdf" might be stored as "TB MED 593" or similar).
+ 2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
+ 3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
+ 4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
+ 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and data structures.
+ 6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
+ 7. **Cite Your Sources**: Use get_chunk() to retrieve chunk metadata for citations. Track which documents/chunks informed your answer.
+
+ ## Example Patterns
+
+ ### Counting documents matching a condition
+ ```python
+ docs = list_documents(limit=100)
+ count = 0
+ for doc in docs:
+ content = get_document(doc['id'])
+ if content and 'keyword' in content.lower():
+ count += 1
+ print(f"Found in: {doc['title']}")
+ print(f"Total: {count}")
+ ```
+
+ ### Extracting data with llm()
+ ```python
+ numbers = []
+ results = search("financial data", limit=20)
+ for r in results:
+ extracted = llm(f"Extract all dollar amounts from this text as a comma-separated list of numbers (no $ signs): {r['content']}")
+ for part in extracted.split(','):
+ part = part.strip().replace(',', '')
+ if part.isdigit():
+ numbers.append(int(part))
+ if numbers:
+ print(f"Average: {sum(numbers) / len(numbers)}")
+ ```
+
+ ### Using search results with get_chunk for citations
+ ```python
+ results = search("safety requirements", limit=5)
+ for r in results:
+ chunk = get_chunk(r['chunk_id'])
+ print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}")
+ ```
+
+ ### Using llm() for classification
+ ```python
+ content = get_document("Q1 Report")
+ sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
+ print(sentiment)
+ ```
+
+ ## Workflow
+
+ 1. **ALWAYS start by using execute_code** to explore the knowledge base
+ 2. Run multiple code blocks as needed to gather information
+ 3. After collecting data, provide your final answer
+
+ ## Output Format
+
+ CRITICAL: Your final response MUST be valid JSON matching this exact schema:
+ ```json
+ {"answer": "Your complete answer here as a string", "program": "Your final consolidated program here as a string"}
+ ```
+
+ - `answer`: A clear answer to the user's question with key findings and references to specific documents/chunks.
+ - `program`: A single, self-contained Python program that produces the answer. Consolidate your exploratory code executions into one clean script.
+
+ Do NOT return arbitrary JSON structures. Always use the exact format: {"answer": "...", "program": "..."}
+
+ CRITICAL: You MUST call execute_code at least once before providing your answer. Never give up without trying to execute code first.
+ role: system
+ - content: Analyze the sentiment of each quarterly update. How many quarters were positive, negative, and mixed?
+ role: user
+ - content: null
+ reasoning: Need to find quarterly updates documents. Search.
+ role: assistant
+ tool_calls:
+ - function:
+ arguments: '{"code":"results = search(\"quarterly update\", limit=20)\nprint(len(results))\nfor r in results:\n print(r[''document_title''],
+ r[''page_numbers''])\n"}'
+ name: execute_code
+ id: call_19rh51up
+ type: function
+ - content: '{"code":"results = search(\"quarterly update\", limit=20)\nprint(len(results))\nfor r in results:\n print(r[''document_title''],
+ r[''page_numbers''])\n","stdout":"3\nQ3 Update []\nQ2 Update []\nQ1 Update []\n","stderr":"","success":true}'
+ role: tool
+ tool_call_id: call_19rh51up
+ - content: null
+ reasoning: We have three documents. Use get_document. Need sentiment. Use llm.
+ role: assistant
+ tool_calls:
+ - function:
+ arguments: '{"code":"docs = [''Q1 Update'',''Q2 Update'',''Q3 Update'']\nimport json\nresults = {}\nfor d in docs:\n content
+ = get_document(d)\n if content:\n sentiment = llm(f\"Classify the overall sentiment of this quarterly
+ update as ''positive'', ''negative'', or ''mixed'': {content[:2000]}\")\n results[d] = sentiment.strip().lower()\nprint(json.dumps(results,indent=2))\n"}'
+ name: execute_code
+ id: call_5w95m1y7
+ type: function
+ - content: '{"code":"docs = [''Q1 Update'',''Q2 Update'',''Q3 Update'']\nimport json\nresults = {}\nfor d in docs:\n content
+ = get_document(d)\n if content:\n sentiment = llm(f\"Classify the overall sentiment of this quarterly
+ update as ''positive'', ''negative'', or ''mixed'': {content[:2000]}\")\n results[d] = sentiment.strip().lower()\nprint(json.dumps(results,indent=2))\n","stdout":"","stderr":"ModuleNotFoundError:
+ No module named ''json''","success":false}'
+ role: tool
+ tool_call_id: call_5w95m1y7
+ - content: null
+ reasoning: json module not allowed? It said imports except json allowed. Actually json is allowed. But error? maybe
+ 'json' not imported? In environment we can do import json. Let's try without import? Use str conversion. We'll just
+ print.
+ role: assistant
+ tool_calls:
+ - function:
+ arguments: '{"code":"docs = [''Q1 Update'',''Q2 Update'',''Q3 Update'']\nres = {}\nfor d in docs:\n c = get_document(d)\n if
+ c:\n s = llm(f\"Classify the overall sentiment of this quarterly update as ''positive'', ''negative'',
+ or ''mixed'': {c[:2000]}\")\n res[d] = s.strip().lower()\nprint(res)\n"}'
+ name: execute_code
+ id: call_1phm2kez
+ type: function
+ - content: '{"code":"docs = [''Q1 Update'',''Q2 Update'',''Q3 Update'']\nres = {}\nfor d in docs:\n c = get_document(d)\n if
+ c:\n s = llm(f\"Classify the overall sentiment of this quarterly update as ''positive'', ''negative'', or
+ ''mixed'': {c[:2000]}\")\n res[d] = s.strip().lower()\nprint(res)\n","stdout":"{''Q1 Update'': ''**sentiment:**
+ positive'', ''Q2 Update'': ''**sentiment: negative**'', ''Q3 Update'': ''**mixed**''}\n","stderr":"","success":true}'
+ role: tool
+ tool_call_id: call_1phm2kez
+ - content: null
+ reasoning: Parse results. Count.
+ role: assistant
+ tool_calls:
+ - function:
+ arguments: '{"code":"res={''Q1 Update'': ''**sentiment:** positive'', ''Q2 Update'': ''**sentiment: negative**'',
+ ''Q3 Update'': ''**mixed**''}\npos=sum(1 for v in res.values() if ''positive'' in v)\nneg=sum(1 for v in res.values()
+ if ''negative'' in v)\nmix=sum(1 for v in res.values() if ''mixed'' in v)\nprint(pos,neg,mix)\n"}'
+ name: execute_code
+ id: call_8y20oq7z
+ type: function
+ - content: '{"code":"res={''Q1 Update'': ''**sentiment:** positive'', ''Q2 Update'': ''**sentiment: negative**'', ''Q3
+ Update'': ''**mixed**''}\npos=sum(1 for v in res.values() if ''positive'' in v)\nneg=sum(1 for v in res.values()
+ if ''negative'' in v)\nmix=sum(1 for v in res.values() if ''mixed'' in v)\nprint(pos,neg,mix)\n","stdout":"1 1 1\n","stderr":"","success":true}'
+ role: tool
+ tool_call_id: call_8y20oq7z
+ model: gpt-oss
+ reasoning_effort: low
+ stream: false
+ tool_choice: auto
+ tools:
+ - function:
+ description: |-
+ Execute Python code in a sandboxed interpreter.
+
+ The code has access to haiku.rag functions (search, list_documents,
+ get_document, get_chunk, llm).
+
+ Use print() to output results.
+
+ Structured result with success status, stdout, and stderr.
+
+ name: execute_code
+ parameters:
+ additionalProperties: false
+ properties:
+ code:
+ description: Python code to execute.
+ type: string
+ required:
+ - code
+ type: object
+ strict: true
+ type: function
+ - function:
+ description: Result from RLM agent execution.
+ name: final_result
+ parameters:
+ additionalProperties: false
+ properties:
+ answer:
+ description: The answer to the user's question
+ type: string
+ program:
+ description: The final consolidated program
+ type: string
+ required:
+ - answer
+ - program
+ type: object
+ strict: true
+ type: function
+ uri: http://localhost:11434/v1/chat/completions
+ response:
+ headers:
+ content-length:
+ - '1239'
content-type:
- application/json
parsed_body:
@@ -1066,1620 +1589,24 @@ interactions:
- finish_reason: stop
index: 0
message:
- content: Mixed (the update contains both positive and negative elements)
- reasoning: We need decide sentiment. Mixed positives and negatives. So mixed.
+ content: '{"answer":"Among the three quarterly updates examined, one quarter was classified as positive (Q1 Update),
+ one as negative (Q2 Update), and one as mixed (Q3 Update).","program":"docs = [''Q1 Update'', ''Q2 Update'', ''Q3
+ Update'']\n# Retrieve document contents\ncontents = {}\nfor d in docs:\n c = get_document(d)\n if c:\n contents[d]
+ = c\n# Classify sentiment using the LLM\nsentiment = {}\nfor title, text in contents.items():\n s = llm(f\"Classify
+ the overall sentiment of this quarterly update as ''positive'', ''negative'', or ''mixed'': {text[:2000]}\")\n sentiment[title]
+ = s.strip().lower()\n# Count results\npositive = sum(1 for v in sentiment.values() if ''positive'' in v)\nnegative
+ = sum(1 for v in sentiment.values() if ''negative'' in v)\nmixed = sum(1 for v in sentiment.values() if ''mixed''
+ in v)\nprint(\"Positive:\", positive)\nprint(\"Negative:\", negative)\nprint(\"Mixed:\", mixed)\n"}'
role: assistant
- created: 1771336334
- id: chatcmpl-802
+ created: 1771336759
+ id: chatcmpl-715
model: gpt-oss
object: chat.completion
system_fingerprint: fp_ollama
usage:
- completion_tokens: 34
- prompt_tokens: 111
- total_tokens: 145
- status:
- code: 200
- message: OK
-- request:
- headers:
- accept:
- - application/json
- accept-encoding:
- - gzip, deflate, zstd
- connection:
- - keep-alive
- content-length:
- - '9587'
- content-type:
- - application/json
- host:
- - localhost:11434
- method: POST
- parsed_body:
- messages:
- - content: |-
- You are a Recursive Language Model (RLM) agent that solves complex research questions by writing and executing Python code.
-
- IMPORTANT: You MUST use the `execute_code` tool to run Python code. The functions described below are ONLY available inside the execute_code tool - you cannot access them any other way. Always execute code to answer questions; do not just describe what code would do.
-
- CRITICAL: Inside execute_code, these functions are ALREADY available in the namespace. Do NOT import them - just use them directly:
- - search("query") ✓ CORRECT
- - from haiku.rag import search ✗ WRONG - will fail
-
- You have access to a sandboxed Python environment with these haiku.rag functions (use them directly, no imports needed):
-
- ## Available Functions
-
- ### search(query, limit=10) -> list[dict]
- Search the knowledge base using hybrid search (vector + full-text).
- Returns list of dicts with keys: chunk_id, content, document_id, document_title, document_uri, score, page_numbers, headings
-
- ### list_documents(limit=10, offset=0) -> list[dict]
- List available documents in the knowledge base.
- Returns list of dicts with keys: id, title, uri, created_at
-
- ### get_document(id_or_title) -> str | None
- Get the full text content of a document by ID, title, or URI.
- Returns the document content as a string, or None if not found.
-
- ### get_docling_document(id_or_title) -> DoclingDocument | None
- Get the structured DoclingDocument object for advanced analysis.
- Returns a DoclingDocument object, or None if not found.
- See "DoclingDocument API" section below for how to use it.
-
- ### llm(prompt) -> str
- Call an LLM directly with the given prompt. Returns the response as a string.
- Use this for classification, summarization, extraction, or any task where you
- already have the content and just need LLM reasoning.
-
- ## Pre-loaded Documents Variable
-
- If documents were pre-loaded for this session, a `documents` variable is available:
- ```python
- # documents is a list of dicts with keys: id, title, uri, content
- for doc in documents:
- print(doc['title'], len(doc['content']))
- ```
- Check if it exists with: `if 'documents' in dir(): ...`
-
- ## Standard Library Modules
- You can import any Python standard library module.
-
- ## Strategy Guide
-
- 1. **Explore First**: Start by listing documents or searching to understand what's available. Document names may differ from filenames (e.g., "tbmed593.pdf" might be stored as "TB MED 593" or similar).
- 2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
- 3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
- 4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
- 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and use collections.
- 6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
- 7. **Cite Your Sources**: Track which documents/chunks informed your answer for citation.
-
- ## DoclingDocument API
-
- When you call `get_docling_document(id_or_title)`, you get a DoclingDocument object for structured document analysis.
-
- ### Properties
- - `doc.texts` - List of all text items (paragraphs, headings, etc.)
- - `doc.tables` - List of all tables
- - `doc.pictures` - List of all pictures/figures
- - `doc.name` - Document name
-
- ### Methods
- - `doc.iterate_items(with_groups=False)` - Iterate all items with hierarchy level
- Returns tuples of (item, level) where level is nesting depth
- - `doc.export_to_markdown()` - Export entire document as markdown string
-
- ### Text Item Properties
- - `item.text` - The text content
- - `item.label` - Type: title, paragraph, section_header, list_item, etc. (lowercase enum values)
- - `item.prov` - Provenance (page numbers, bounding boxes)
-
- ### Table Access
- - `table.data.num_rows`, `table.data.num_cols` - Dimensions
- - `table.data.table_cells` - List of TableCell objects
- - `cell.text`, `cell.start_row_offset_idx`, `cell.start_col_offset_idx`
-
- ### Example Usage
- ```python
- doc = get_docling_document("My Document")
-
- # Get all headings
- headings = [t.text for t in doc.texts if "header" in str(t.label)]
-
- # Iterate with structure
- for item, level in doc.iterate_items():
- print(" " * level + item.text[:50])
-
- # Extract table data
- for table in doc.tables:
- for cell in table.data.table_cells:
- print(f"Row {cell.start_row_offset_idx}, Col {cell.start_col_offset_idx}: {cell.text}")
- ```
-
- ## Example Patterns
-
- ### Counting documents matching a condition
- ```python
- docs = list_documents(limit=100)
- count = 0
- for doc in docs:
- content = get_document(doc['id'])
- if content and 'keyword' in content.lower():
- count += 1
- print(f"Found in: {doc['title']}")
- print(f"Total: {count}")
- ```
-
- ### Aggregating data across documents
- ```python
- import re
- numbers = []
- results = search("financial data", limit=20)
- for r in results:
- matches = re.findall(r'\$([\d,]+)', r['content'])
- for m in matches:
- numbers.append(int(m.replace(',', '')))
- print(f"Average: ${sum(numbers)/len(numbers):,.2f}")
- ```
-
- ### Using llm() for classification
- ```python
- # Get document content
- content = get_document("Q1 Report")
- # Use llm() to classify sentiment
- sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
- print(sentiment)
- ```
-
- ## Workflow
-
- 1. **ALWAYS start by using execute_code** to explore the knowledge base
- 2. Run multiple code blocks as needed to gather information
- 3. After collecting data, provide your final answer
-
- ## Output Format
-
- CRITICAL: Your final response MUST be valid JSON matching this exact schema:
- ```json
- {"answer": "Your complete answer here as a string", "program": "Your final consolidated program here as a string"}
- ```
-
- - `answer`: A clear answer to the user's question with key findings and references to specific documents/chunks.
- - `program`: A single, self-contained Python program that produces the answer. Consolidate your exploratory code executions into one clean script.
-
- Do NOT return arbitrary JSON structures. Always use the exact format: {"answer": "...", "program": "..."}
-
- CRITICAL: You MUST call execute_code at least once before providing your answer. Never give up without trying to execute code first.
- role: system
- - content: Analyze the sentiment of each quarterly update. How many quarters were positive, negative, and mixed?
- role: user
- - content: null
- reasoning: We need documents about quarterly updates. Search.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"results = search(\"quarterly update\", limit=50)\nprint(len(results))\nfor r in results[:5]:\n print(r[''document_title''],
- r[''page_numbers''][:3], r[''score''])\n"}'
- name: execute_code
- id: call_0g817w8v
- type: function
- - content: '{"code":"results = search(\"quarterly update\", limit=50)\nprint(len(results))\nfor r in results[:5]:\n print(r[''document_title''],
- r[''page_numbers''][:3], r[''score''])\n","stdout":"3\nQ3 Update [] 0.032786883413791656\nQ2 Update [] 0.032258063554763794\nQ1
- Update [] 0.01587301678955555\n","stderr":"","success":true}'
- role: tool
- tool_call_id: call_0g817w8v
- - content: null
- reasoning: 'Three documents: Q1, Q2, Q3. We need sentiment. Use llm on each content.'
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"for quarter in [''Q1 Update'',''Q2 Update'',''Q3 Update'']:\n content = get_document(quarter)\n resp
- = llm(f\"Classify the sentiment of this quarterly update text as positive, negative, or mixed.\\n\\n{content}\")\n print(quarter,
- resp)\n"}'
- name: execute_code
- id: call_stqc44dk
- type: function
- - content: '{"code":"for quarter in [''Q1 Update'',''Q2 Update'',''Q3 Update'']:\n content = get_document(quarter)\n resp
- = llm(f\"Classify the sentiment of this quarterly update text as positive, negative, or mixed.\\n\\n{content}\")\n print(quarter,
- resp)\n","stdout":"Q1 Update Positive\nQ2 Update **Sentiment:** **Negative**\nQ3 Update Mixed (the update contains
- both positive and negative elements)\n","stderr":"","success":true}'
- role: tool
- tool_call_id: call_stqc44dk
- model: gpt-oss
- reasoning_effort: low
- stream: false
- tool_choice: auto
- tools:
- - function:
- description: |-
- Execute Python code in a sandboxed interpreter.
-
- The code has access to haiku.rag functions (search, list_documents,
- get_document, get_chunk, llm).
-
- Use print() to output results.
-
- Structured result with success status, stdout, and stderr.
-
- name: execute_code
- parameters:
- additionalProperties: false
- properties:
- code:
- description: Python code to execute.
- type: string
- required:
- - code
- type: object
- strict: true
- type: function
- - function:
- description: Result from RLM agent execution.
- name: final_result
- parameters:
- additionalProperties: false
- properties:
- answer:
- description: The answer to the user's question
- type: string
- program:
- description: The final consolidated program
- type: string
- required:
- - answer
- - program
- type: object
- strict: true
- type: function
- uri: http://localhost:11434/v1/chat/completions
- response:
- headers:
- content-length:
- - '639'
- content-type:
- - application/json
- parsed_body:
- choices:
- - finish_reason: tool_calls
- index: 0
- message:
- content: ''
- reasoning: Count.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"sentiments={''Q1 Update'':''Positive'',''Q2 Update'':''Negative'',''Q3 Update'':''Mixed''}\ncounts={''Positive'':0,''Negative'':0,''Mixed'':0}\nfor
- s in sentiments.values():\n counts[s]+=1\nprint(counts)\n"}'
- name: execute_code
- id: call_upa0r5we
- index: 0
- type: function
- created: 1771336339
- id: chatcmpl-547
- model: gpt-oss
- object: chat.completion
- system_fingerprint: fp_ollama
- usage:
- completion_tokens: 82
- prompt_tokens: 2183
- total_tokens: 2265
- status:
- code: 200
- message: OK
-- request:
- headers:
- accept:
- - application/json
- accept-encoding:
- - gzip, deflate, zstd
- connection:
- - keep-alive
- content-length:
- - '10597'
- content-type:
- - application/json
- host:
- - localhost:11434
- method: POST
- parsed_body:
- messages:
- - content: |-
- You are a Recursive Language Model (RLM) agent that solves complex research questions by writing and executing Python code.
-
- IMPORTANT: You MUST use the `execute_code` tool to run Python code. The functions described below are ONLY available inside the execute_code tool - you cannot access them any other way. Always execute code to answer questions; do not just describe what code would do.
-
- CRITICAL: Inside execute_code, these functions are ALREADY available in the namespace. Do NOT import them - just use them directly:
- - search("query") ✓ CORRECT
- - from haiku.rag import search ✗ WRONG - will fail
-
- You have access to a sandboxed Python environment with these haiku.rag functions (use them directly, no imports needed):
-
- ## Available Functions
-
- ### search(query, limit=10) -> list[dict]
- Search the knowledge base using hybrid search (vector + full-text).
- Returns list of dicts with keys: chunk_id, content, document_id, document_title, document_uri, score, page_numbers, headings
-
- ### list_documents(limit=10, offset=0) -> list[dict]
- List available documents in the knowledge base.
- Returns list of dicts with keys: id, title, uri, created_at
-
- ### get_document(id_or_title) -> str | None
- Get the full text content of a document by ID, title, or URI.
- Returns the document content as a string, or None if not found.
-
- ### get_docling_document(id_or_title) -> DoclingDocument | None
- Get the structured DoclingDocument object for advanced analysis.
- Returns a DoclingDocument object, or None if not found.
- See "DoclingDocument API" section below for how to use it.
-
- ### llm(prompt) -> str
- Call an LLM directly with the given prompt. Returns the response as a string.
- Use this for classification, summarization, extraction, or any task where you
- already have the content and just need LLM reasoning.
-
- ## Pre-loaded Documents Variable
-
- If documents were pre-loaded for this session, a `documents` variable is available:
- ```python
- # documents is a list of dicts with keys: id, title, uri, content
- for doc in documents:
- print(doc['title'], len(doc['content']))
- ```
- Check if it exists with: `if 'documents' in dir(): ...`
-
- ## Standard Library Modules
- You can import any Python standard library module.
-
- ## Strategy Guide
-
- 1. **Explore First**: Start by listing documents or searching to understand what's available. Document names may differ from filenames (e.g., "tbmed593.pdf" might be stored as "TB MED 593" or similar).
- 2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
- 3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
- 4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
- 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and use collections.
- 6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
- 7. **Cite Your Sources**: Track which documents/chunks informed your answer for citation.
-
- ## DoclingDocument API
-
- When you call `get_docling_document(id_or_title)`, you get a DoclingDocument object for structured document analysis.
-
- ### Properties
- - `doc.texts` - List of all text items (paragraphs, headings, etc.)
- - `doc.tables` - List of all tables
- - `doc.pictures` - List of all pictures/figures
- - `doc.name` - Document name
-
- ### Methods
- - `doc.iterate_items(with_groups=False)` - Iterate all items with hierarchy level
- Returns tuples of (item, level) where level is nesting depth
- - `doc.export_to_markdown()` - Export entire document as markdown string
-
- ### Text Item Properties
- - `item.text` - The text content
- - `item.label` - Type: title, paragraph, section_header, list_item, etc. (lowercase enum values)
- - `item.prov` - Provenance (page numbers, bounding boxes)
-
- ### Table Access
- - `table.data.num_rows`, `table.data.num_cols` - Dimensions
- - `table.data.table_cells` - List of TableCell objects
- - `cell.text`, `cell.start_row_offset_idx`, `cell.start_col_offset_idx`
-
- ### Example Usage
- ```python
- doc = get_docling_document("My Document")
-
- # Get all headings
- headings = [t.text for t in doc.texts if "header" in str(t.label)]
-
- # Iterate with structure
- for item, level in doc.iterate_items():
- print(" " * level + item.text[:50])
-
- # Extract table data
- for table in doc.tables:
- for cell in table.data.table_cells:
- print(f"Row {cell.start_row_offset_idx}, Col {cell.start_col_offset_idx}: {cell.text}")
- ```
-
- ## Example Patterns
-
- ### Counting documents matching a condition
- ```python
- docs = list_documents(limit=100)
- count = 0
- for doc in docs:
- content = get_document(doc['id'])
- if content and 'keyword' in content.lower():
- count += 1
- print(f"Found in: {doc['title']}")
- print(f"Total: {count}")
- ```
-
- ### Aggregating data across documents
- ```python
- import re
- numbers = []
- results = search("financial data", limit=20)
- for r in results:
- matches = re.findall(r'\$([\d,]+)', r['content'])
- for m in matches:
- numbers.append(int(m.replace(',', '')))
- print(f"Average: ${sum(numbers)/len(numbers):,.2f}")
- ```
-
- ### Using llm() for classification
- ```python
- # Get document content
- content = get_document("Q1 Report")
- # Use llm() to classify sentiment
- sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
- print(sentiment)
- ```
-
- ## Workflow
-
- 1. **ALWAYS start by using execute_code** to explore the knowledge base
- 2. Run multiple code blocks as needed to gather information
- 3. After collecting data, provide your final answer
-
- ## Output Format
-
- CRITICAL: Your final response MUST be valid JSON matching this exact schema:
- ```json
- {"answer": "Your complete answer here as a string", "program": "Your final consolidated program here as a string"}
- ```
-
- - `answer`: A clear answer to the user's question with key findings and references to specific documents/chunks.
- - `program`: A single, self-contained Python program that produces the answer. Consolidate your exploratory code executions into one clean script.
-
- Do NOT return arbitrary JSON structures. Always use the exact format: {"answer": "...", "program": "..."}
-
- CRITICAL: You MUST call execute_code at least once before providing your answer. Never give up without trying to execute code first.
- role: system
- - content: Analyze the sentiment of each quarterly update. How many quarters were positive, negative, and mixed?
- role: user
- - content: null
- reasoning: We need documents about quarterly updates. Search.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"results = search(\"quarterly update\", limit=50)\nprint(len(results))\nfor r in results[:5]:\n print(r[''document_title''],
- r[''page_numbers''][:3], r[''score''])\n"}'
- name: execute_code
- id: call_0g817w8v
- type: function
- - content: '{"code":"results = search(\"quarterly update\", limit=50)\nprint(len(results))\nfor r in results[:5]:\n print(r[''document_title''],
- r[''page_numbers''][:3], r[''score''])\n","stdout":"3\nQ3 Update [] 0.032786883413791656\nQ2 Update [] 0.032258063554763794\nQ1
- Update [] 0.01587301678955555\n","stderr":"","success":true}'
- role: tool
- tool_call_id: call_0g817w8v
- - content: null
- reasoning: 'Three documents: Q1, Q2, Q3. We need sentiment. Use llm on each content.'
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"for quarter in [''Q1 Update'',''Q2 Update'',''Q3 Update'']:\n content = get_document(quarter)\n resp
- = llm(f\"Classify the sentiment of this quarterly update text as positive, negative, or mixed.\\n\\n{content}\")\n print(quarter,
- resp)\n"}'
- name: execute_code
- id: call_stqc44dk
- type: function
- - content: '{"code":"for quarter in [''Q1 Update'',''Q2 Update'',''Q3 Update'']:\n content = get_document(quarter)\n resp
- = llm(f\"Classify the sentiment of this quarterly update text as positive, negative, or mixed.\\n\\n{content}\")\n print(quarter,
- resp)\n","stdout":"Q1 Update Positive\nQ2 Update **Sentiment:** **Negative**\nQ3 Update Mixed (the update contains
- both positive and negative elements)\n","stderr":"","success":true}'
- role: tool
- tool_call_id: call_stqc44dk
- - content: null
- reasoning: Count.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"sentiments={''Q1 Update'':''Positive'',''Q2 Update'':''Negative'',''Q3 Update'':''Mixed''}\ncounts={''Positive'':0,''Negative'':0,''Mixed'':0}\nfor
- s in sentiments.values():\n counts[s]+=1\nprint(counts)\n"}'
- name: execute_code
- id: call_upa0r5we
- type: function
- - content: '{"code":"sentiments={''Q1 Update'':''Positive'',''Q2 Update'':''Negative'',''Q3 Update'':''Mixed''}\ncounts={''Positive'':0,''Negative'':0,''Mixed'':0}\nfor
- s in sentiments.values():\n counts[s]+=1\nprint(counts)\n","stdout":"","stderr":"Expected name, got Subscript(ExprSubscript
- { node_index: NodeIndex(None), range: 158..167, value: Name(ExprName { node_index: NodeIndex(None), range: 158..164,
- id: Name(\"counts\"), ctx: Load }), slice: Name(ExprName { node_index: NodeIndex(None), range: 165..166, id: Name(\"s\"),
- ctx: Load }), ctx: Store })","success":false}'
- role: tool
- tool_call_id: call_upa0r5we
- model: gpt-oss
- reasoning_effort: low
- stream: false
- tool_choice: auto
- tools:
- - function:
- description: |-
- Execute Python code in a sandboxed interpreter.
-
- The code has access to haiku.rag functions (search, list_documents,
- get_document, get_chunk, llm).
-
- Use print() to output results.
-
- Structured result with success status, stdout, and stderr.
-
- name: execute_code
- parameters:
- additionalProperties: false
- properties:
- code:
- description: Python code to execute.
- type: string
- required:
- - code
- type: object
- strict: true
- type: function
- - function:
- description: Result from RLM agent execution.
- name: final_result
- parameters:
- additionalProperties: false
- properties:
- answer:
- description: The answer to the user's question
- type: string
- program:
- description: The final consolidated program
- type: string
- required:
- - answer
- - program
- type: object
- strict: true
- type: function
- uri: http://localhost:11434/v1/chat/completions
- response:
- headers:
- content-length:
- - '618'
- content-type:
- - application/json
- parsed_body:
- choices:
- - finish_reason: tool_calls
- index: 0
- message:
- content: ''
- reasoning: Syntax error. Use proper dict.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"sentiments=[''Positive'',''Negative'',''Mixed'']\ncounts={''Positive'':0,''Negative'':0,''Mixed'':0}\nfor
- s in sentiments:\n counts[s]+=1\nprint(counts)\n"}'
- name: execute_code
- id: call_qdq26q8o
- index: 0
- type: function
- created: 1771336341
- id: chatcmpl-905
- model: gpt-oss
- object: chat.completion
- system_fingerprint: fp_ollama
- usage:
- completion_tokens: 73
- prompt_tokens: 2439
- total_tokens: 2512
- status:
- code: 200
- message: OK
-- request:
- headers:
- accept:
- - application/json
- accept-encoding:
- - gzip, deflate, zstd
- connection:
- - keep-alive
- content-length:
- - '11541'
- content-type:
- - application/json
- host:
- - localhost:11434
- method: POST
- parsed_body:
- messages:
- - content: |-
- You are a Recursive Language Model (RLM) agent that solves complex research questions by writing and executing Python code.
-
- IMPORTANT: You MUST use the `execute_code` tool to run Python code. The functions described below are ONLY available inside the execute_code tool - you cannot access them any other way. Always execute code to answer questions; do not just describe what code would do.
-
- CRITICAL: Inside execute_code, these functions are ALREADY available in the namespace. Do NOT import them - just use them directly:
- - search("query") ✓ CORRECT
- - from haiku.rag import search ✗ WRONG - will fail
-
- You have access to a sandboxed Python environment with these haiku.rag functions (use them directly, no imports needed):
-
- ## Available Functions
-
- ### search(query, limit=10) -> list[dict]
- Search the knowledge base using hybrid search (vector + full-text).
- Returns list of dicts with keys: chunk_id, content, document_id, document_title, document_uri, score, page_numbers, headings
-
- ### list_documents(limit=10, offset=0) -> list[dict]
- List available documents in the knowledge base.
- Returns list of dicts with keys: id, title, uri, created_at
-
- ### get_document(id_or_title) -> str | None
- Get the full text content of a document by ID, title, or URI.
- Returns the document content as a string, or None if not found.
-
- ### get_docling_document(id_or_title) -> DoclingDocument | None
- Get the structured DoclingDocument object for advanced analysis.
- Returns a DoclingDocument object, or None if not found.
- See "DoclingDocument API" section below for how to use it.
-
- ### llm(prompt) -> str
- Call an LLM directly with the given prompt. Returns the response as a string.
- Use this for classification, summarization, extraction, or any task where you
- already have the content and just need LLM reasoning.
-
- ## Pre-loaded Documents Variable
-
- If documents were pre-loaded for this session, a `documents` variable is available:
- ```python
- # documents is a list of dicts with keys: id, title, uri, content
- for doc in documents:
- print(doc['title'], len(doc['content']))
- ```
- Check if it exists with: `if 'documents' in dir(): ...`
-
- ## Standard Library Modules
- You can import any Python standard library module.
-
- ## Strategy Guide
-
- 1. **Explore First**: Start by listing documents or searching to understand what's available. Document names may differ from filenames (e.g., "tbmed593.pdf" might be stored as "TB MED 593" or similar).
- 2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
- 3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
- 4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
- 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and use collections.
- 6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
- 7. **Cite Your Sources**: Track which documents/chunks informed your answer for citation.
-
- ## DoclingDocument API
-
- When you call `get_docling_document(id_or_title)`, you get a DoclingDocument object for structured document analysis.
-
- ### Properties
- - `doc.texts` - List of all text items (paragraphs, headings, etc.)
- - `doc.tables` - List of all tables
- - `doc.pictures` - List of all pictures/figures
- - `doc.name` - Document name
-
- ### Methods
- - `doc.iterate_items(with_groups=False)` - Iterate all items with hierarchy level
- Returns tuples of (item, level) where level is nesting depth
- - `doc.export_to_markdown()` - Export entire document as markdown string
-
- ### Text Item Properties
- - `item.text` - The text content
- - `item.label` - Type: title, paragraph, section_header, list_item, etc. (lowercase enum values)
- - `item.prov` - Provenance (page numbers, bounding boxes)
-
- ### Table Access
- - `table.data.num_rows`, `table.data.num_cols` - Dimensions
- - `table.data.table_cells` - List of TableCell objects
- - `cell.text`, `cell.start_row_offset_idx`, `cell.start_col_offset_idx`
-
- ### Example Usage
- ```python
- doc = get_docling_document("My Document")
-
- # Get all headings
- headings = [t.text for t in doc.texts if "header" in str(t.label)]
-
- # Iterate with structure
- for item, level in doc.iterate_items():
- print(" " * level + item.text[:50])
-
- # Extract table data
- for table in doc.tables:
- for cell in table.data.table_cells:
- print(f"Row {cell.start_row_offset_idx}, Col {cell.start_col_offset_idx}: {cell.text}")
- ```
-
- ## Example Patterns
-
- ### Counting documents matching a condition
- ```python
- docs = list_documents(limit=100)
- count = 0
- for doc in docs:
- content = get_document(doc['id'])
- if content and 'keyword' in content.lower():
- count += 1
- print(f"Found in: {doc['title']}")
- print(f"Total: {count}")
- ```
-
- ### Aggregating data across documents
- ```python
- import re
- numbers = []
- results = search("financial data", limit=20)
- for r in results:
- matches = re.findall(r'\$([\d,]+)', r['content'])
- for m in matches:
- numbers.append(int(m.replace(',', '')))
- print(f"Average: ${sum(numbers)/len(numbers):,.2f}")
- ```
-
- ### Using llm() for classification
- ```python
- # Get document content
- content = get_document("Q1 Report")
- # Use llm() to classify sentiment
- sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
- print(sentiment)
- ```
-
- ## Workflow
-
- 1. **ALWAYS start by using execute_code** to explore the knowledge base
- 2. Run multiple code blocks as needed to gather information
- 3. After collecting data, provide your final answer
-
- ## Output Format
-
- CRITICAL: Your final response MUST be valid JSON matching this exact schema:
- ```json
- {"answer": "Your complete answer here as a string", "program": "Your final consolidated program here as a string"}
- ```
-
- - `answer`: A clear answer to the user's question with key findings and references to specific documents/chunks.
- - `program`: A single, self-contained Python program that produces the answer. Consolidate your exploratory code executions into one clean script.
-
- Do NOT return arbitrary JSON structures. Always use the exact format: {"answer": "...", "program": "..."}
-
- CRITICAL: You MUST call execute_code at least once before providing your answer. Never give up without trying to execute code first.
- role: system
- - content: Analyze the sentiment of each quarterly update. How many quarters were positive, negative, and mixed?
- role: user
- - content: null
- reasoning: We need documents about quarterly updates. Search.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"results = search(\"quarterly update\", limit=50)\nprint(len(results))\nfor r in results[:5]:\n print(r[''document_title''],
- r[''page_numbers''][:3], r[''score''])\n"}'
- name: execute_code
- id: call_0g817w8v
- type: function
- - content: '{"code":"results = search(\"quarterly update\", limit=50)\nprint(len(results))\nfor r in results[:5]:\n print(r[''document_title''],
- r[''page_numbers''][:3], r[''score''])\n","stdout":"3\nQ3 Update [] 0.032786883413791656\nQ2 Update [] 0.032258063554763794\nQ1
- Update [] 0.01587301678955555\n","stderr":"","success":true}'
- role: tool
- tool_call_id: call_0g817w8v
- - content: null
- reasoning: 'Three documents: Q1, Q2, Q3. We need sentiment. Use llm on each content.'
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"for quarter in [''Q1 Update'',''Q2 Update'',''Q3 Update'']:\n content = get_document(quarter)\n resp
- = llm(f\"Classify the sentiment of this quarterly update text as positive, negative, or mixed.\\n\\n{content}\")\n print(quarter,
- resp)\n"}'
- name: execute_code
- id: call_stqc44dk
- type: function
- - content: '{"code":"for quarter in [''Q1 Update'',''Q2 Update'',''Q3 Update'']:\n content = get_document(quarter)\n resp
- = llm(f\"Classify the sentiment of this quarterly update text as positive, negative, or mixed.\\n\\n{content}\")\n print(quarter,
- resp)\n","stdout":"Q1 Update Positive\nQ2 Update **Sentiment:** **Negative**\nQ3 Update Mixed (the update contains
- both positive and negative elements)\n","stderr":"","success":true}'
- role: tool
- tool_call_id: call_stqc44dk
- - content: null
- reasoning: Count.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"sentiments={''Q1 Update'':''Positive'',''Q2 Update'':''Negative'',''Q3 Update'':''Mixed''}\ncounts={''Positive'':0,''Negative'':0,''Mixed'':0}\nfor
- s in sentiments.values():\n counts[s]+=1\nprint(counts)\n"}'
- name: execute_code
- id: call_upa0r5we
- type: function
- - content: '{"code":"sentiments={''Q1 Update'':''Positive'',''Q2 Update'':''Negative'',''Q3 Update'':''Mixed''}\ncounts={''Positive'':0,''Negative'':0,''Mixed'':0}\nfor
- s in sentiments.values():\n counts[s]+=1\nprint(counts)\n","stdout":"","stderr":"Expected name, got Subscript(ExprSubscript
- { node_index: NodeIndex(None), range: 158..167, value: Name(ExprName { node_index: NodeIndex(None), range: 158..164,
- id: Name(\"counts\"), ctx: Load }), slice: Name(ExprName { node_index: NodeIndex(None), range: 165..166, id: Name(\"s\"),
- ctx: Load }), ctx: Store })","success":false}'
- role: tool
- tool_call_id: call_upa0r5we
- - content: null
- reasoning: Syntax error. Use proper dict.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"sentiments=[''Positive'',''Negative'',''Mixed'']\ncounts={''Positive'':0,''Negative'':0,''Mixed'':0}\nfor
- s in sentiments:\n counts[s]+=1\nprint(counts)\n"}'
- name: execute_code
- id: call_qdq26q8o
- type: function
- - content: '{"code":"sentiments=[''Positive'',''Negative'',''Mixed'']\ncounts={''Positive'':0,''Negative'':0,''Mixed'':0}\nfor
- s in sentiments:\n counts[s]+=1\nprint(counts)\n","stdout":"","stderr":"Expected name, got Subscript(ExprSubscript
- { node_index: NodeIndex(None), range: 113..122, value: Name(ExprName { node_index: NodeIndex(None), range: 113..119,
- id: Name(\"counts\"), ctx: Load }), slice: Name(ExprName { node_index: NodeIndex(None), range: 120..121, id: Name(\"s\"),
- ctx: Load }), ctx: Store })","success":false}'
- role: tool
- tool_call_id: call_qdq26q8o
- model: gpt-oss
- reasoning_effort: low
- stream: false
- tool_choice: auto
- tools:
- - function:
- description: |-
- Execute Python code in a sandboxed interpreter.
-
- The code has access to haiku.rag functions (search, list_documents,
- get_document, get_chunk, llm).
-
- Use print() to output results.
-
- Structured result with success status, stdout, and stderr.
-
- name: execute_code
- parameters:
- additionalProperties: false
- properties:
- code:
- description: Python code to execute.
- type: string
- required:
- - code
- type: object
- strict: true
- type: function
- - function:
- description: Result from RLM agent execution.
- name: final_result
- parameters:
- additionalProperties: false
- properties:
- answer:
- description: The answer to the user's question
- type: string
- program:
- description: The final consolidated program
- type: string
- required:
- - answer
- - program
- type: object
- strict: true
- type: function
- uri: http://localhost:11434/v1/chat/completions
- response:
- headers:
- content-length:
- - '659'
- content-type:
- - application/json
- parsed_body:
- choices:
- - finish_reason: tool_calls
- index: 0
- message:
- content: ''
- reasoning: Forgot python version issues? Actually counts[s]+=1 fine. The error due to stray? Let's rewrite.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"counts={''Positive'':0,''Negative'':0,''Mixed'':0}\nfor s in [''Positive'',''Negative'',''Mixed'']:\n counts[s]+=1\nprint(counts)\n"}'
- name: execute_code
- id: call_990j0hbg
- index: 0
- type: function
- created: 1771336343
- id: chatcmpl-71
- model: gpt-oss
- object: chat.completion
- system_fingerprint: fp_ollama
- usage:
- completion_tokens: 82
- prompt_tokens: 2672
- total_tokens: 2754
- status:
- code: 200
- message: OK
-- request:
- headers:
- accept:
- - application/json
- accept-encoding:
- - gzip, deflate, zstd
- connection:
- - keep-alive
- content-length:
- - '12498'
- content-type:
- - application/json
- host:
- - localhost:11434
- method: POST
- parsed_body:
- messages:
- - content: |-
- You are a Recursive Language Model (RLM) agent that solves complex research questions by writing and executing Python code.
-
- IMPORTANT: You MUST use the `execute_code` tool to run Python code. The functions described below are ONLY available inside the execute_code tool - you cannot access them any other way. Always execute code to answer questions; do not just describe what code would do.
-
- CRITICAL: Inside execute_code, these functions are ALREADY available in the namespace. Do NOT import them - just use them directly:
- - search("query") ✓ CORRECT
- - from haiku.rag import search ✗ WRONG - will fail
-
- You have access to a sandboxed Python environment with these haiku.rag functions (use them directly, no imports needed):
-
- ## Available Functions
-
- ### search(query, limit=10) -> list[dict]
- Search the knowledge base using hybrid search (vector + full-text).
- Returns list of dicts with keys: chunk_id, content, document_id, document_title, document_uri, score, page_numbers, headings
-
- ### list_documents(limit=10, offset=0) -> list[dict]
- List available documents in the knowledge base.
- Returns list of dicts with keys: id, title, uri, created_at
-
- ### get_document(id_or_title) -> str | None
- Get the full text content of a document by ID, title, or URI.
- Returns the document content as a string, or None if not found.
-
- ### get_docling_document(id_or_title) -> DoclingDocument | None
- Get the structured DoclingDocument object for advanced analysis.
- Returns a DoclingDocument object, or None if not found.
- See "DoclingDocument API" section below for how to use it.
-
- ### llm(prompt) -> str
- Call an LLM directly with the given prompt. Returns the response as a string.
- Use this for classification, summarization, extraction, or any task where you
- already have the content and just need LLM reasoning.
-
- ## Pre-loaded Documents Variable
-
- If documents were pre-loaded for this session, a `documents` variable is available:
- ```python
- # documents is a list of dicts with keys: id, title, uri, content
- for doc in documents:
- print(doc['title'], len(doc['content']))
- ```
- Check if it exists with: `if 'documents' in dir(): ...`
-
- ## Standard Library Modules
- You can import any Python standard library module.
-
- ## Strategy Guide
-
- 1. **Explore First**: Start by listing documents or searching to understand what's available. Document names may differ from filenames (e.g., "tbmed593.pdf" might be stored as "TB MED 593" or similar).
- 2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
- 3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
- 4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
- 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and use collections.
- 6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
- 7. **Cite Your Sources**: Track which documents/chunks informed your answer for citation.
-
- ## DoclingDocument API
-
- When you call `get_docling_document(id_or_title)`, you get a DoclingDocument object for structured document analysis.
-
- ### Properties
- - `doc.texts` - List of all text items (paragraphs, headings, etc.)
- - `doc.tables` - List of all tables
- - `doc.pictures` - List of all pictures/figures
- - `doc.name` - Document name
-
- ### Methods
- - `doc.iterate_items(with_groups=False)` - Iterate all items with hierarchy level
- Returns tuples of (item, level) where level is nesting depth
- - `doc.export_to_markdown()` - Export entire document as markdown string
-
- ### Text Item Properties
- - `item.text` - The text content
- - `item.label` - Type: title, paragraph, section_header, list_item, etc. (lowercase enum values)
- - `item.prov` - Provenance (page numbers, bounding boxes)
-
- ### Table Access
- - `table.data.num_rows`, `table.data.num_cols` - Dimensions
- - `table.data.table_cells` - List of TableCell objects
- - `cell.text`, `cell.start_row_offset_idx`, `cell.start_col_offset_idx`
-
- ### Example Usage
- ```python
- doc = get_docling_document("My Document")
-
- # Get all headings
- headings = [t.text for t in doc.texts if "header" in str(t.label)]
-
- # Iterate with structure
- for item, level in doc.iterate_items():
- print(" " * level + item.text[:50])
-
- # Extract table data
- for table in doc.tables:
- for cell in table.data.table_cells:
- print(f"Row {cell.start_row_offset_idx}, Col {cell.start_col_offset_idx}: {cell.text}")
- ```
-
- ## Example Patterns
-
- ### Counting documents matching a condition
- ```python
- docs = list_documents(limit=100)
- count = 0
- for doc in docs:
- content = get_document(doc['id'])
- if content and 'keyword' in content.lower():
- count += 1
- print(f"Found in: {doc['title']}")
- print(f"Total: {count}")
- ```
-
- ### Aggregating data across documents
- ```python
- import re
- numbers = []
- results = search("financial data", limit=20)
- for r in results:
- matches = re.findall(r'\$([\d,]+)', r['content'])
- for m in matches:
- numbers.append(int(m.replace(',', '')))
- print(f"Average: ${sum(numbers)/len(numbers):,.2f}")
- ```
-
- ### Using llm() for classification
- ```python
- # Get document content
- content = get_document("Q1 Report")
- # Use llm() to classify sentiment
- sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
- print(sentiment)
- ```
-
- ## Workflow
-
- 1. **ALWAYS start by using execute_code** to explore the knowledge base
- 2. Run multiple code blocks as needed to gather information
- 3. After collecting data, provide your final answer
-
- ## Output Format
-
- CRITICAL: Your final response MUST be valid JSON matching this exact schema:
- ```json
- {"answer": "Your complete answer here as a string", "program": "Your final consolidated program here as a string"}
- ```
-
- - `answer`: A clear answer to the user's question with key findings and references to specific documents/chunks.
- - `program`: A single, self-contained Python program that produces the answer. Consolidate your exploratory code executions into one clean script.
-
- Do NOT return arbitrary JSON structures. Always use the exact format: {"answer": "...", "program": "..."}
-
- CRITICAL: You MUST call execute_code at least once before providing your answer. Never give up without trying to execute code first.
- role: system
- - content: Analyze the sentiment of each quarterly update. How many quarters were positive, negative, and mixed?
- role: user
- - content: null
- reasoning: We need documents about quarterly updates. Search.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"results = search(\"quarterly update\", limit=50)\nprint(len(results))\nfor r in results[:5]:\n print(r[''document_title''],
- r[''page_numbers''][:3], r[''score''])\n"}'
- name: execute_code
- id: call_0g817w8v
- type: function
- - content: '{"code":"results = search(\"quarterly update\", limit=50)\nprint(len(results))\nfor r in results[:5]:\n print(r[''document_title''],
- r[''page_numbers''][:3], r[''score''])\n","stdout":"3\nQ3 Update [] 0.032786883413791656\nQ2 Update [] 0.032258063554763794\nQ1
- Update [] 0.01587301678955555\n","stderr":"","success":true}'
- role: tool
- tool_call_id: call_0g817w8v
- - content: null
- reasoning: 'Three documents: Q1, Q2, Q3. We need sentiment. Use llm on each content.'
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"for quarter in [''Q1 Update'',''Q2 Update'',''Q3 Update'']:\n content = get_document(quarter)\n resp
- = llm(f\"Classify the sentiment of this quarterly update text as positive, negative, or mixed.\\n\\n{content}\")\n print(quarter,
- resp)\n"}'
- name: execute_code
- id: call_stqc44dk
- type: function
- - content: '{"code":"for quarter in [''Q1 Update'',''Q2 Update'',''Q3 Update'']:\n content = get_document(quarter)\n resp
- = llm(f\"Classify the sentiment of this quarterly update text as positive, negative, or mixed.\\n\\n{content}\")\n print(quarter,
- resp)\n","stdout":"Q1 Update Positive\nQ2 Update **Sentiment:** **Negative**\nQ3 Update Mixed (the update contains
- both positive and negative elements)\n","stderr":"","success":true}'
- role: tool
- tool_call_id: call_stqc44dk
- - content: null
- reasoning: Count.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"sentiments={''Q1 Update'':''Positive'',''Q2 Update'':''Negative'',''Q3 Update'':''Mixed''}\ncounts={''Positive'':0,''Negative'':0,''Mixed'':0}\nfor
- s in sentiments.values():\n counts[s]+=1\nprint(counts)\n"}'
- name: execute_code
- id: call_upa0r5we
- type: function
- - content: '{"code":"sentiments={''Q1 Update'':''Positive'',''Q2 Update'':''Negative'',''Q3 Update'':''Mixed''}\ncounts={''Positive'':0,''Negative'':0,''Mixed'':0}\nfor
- s in sentiments.values():\n counts[s]+=1\nprint(counts)\n","stdout":"","stderr":"Expected name, got Subscript(ExprSubscript
- { node_index: NodeIndex(None), range: 158..167, value: Name(ExprName { node_index: NodeIndex(None), range: 158..164,
- id: Name(\"counts\"), ctx: Load }), slice: Name(ExprName { node_index: NodeIndex(None), range: 165..166, id: Name(\"s\"),
- ctx: Load }), ctx: Store })","success":false}'
- role: tool
- tool_call_id: call_upa0r5we
- - content: null
- reasoning: Syntax error. Use proper dict.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"sentiments=[''Positive'',''Negative'',''Mixed'']\ncounts={''Positive'':0,''Negative'':0,''Mixed'':0}\nfor
- s in sentiments:\n counts[s]+=1\nprint(counts)\n"}'
- name: execute_code
- id: call_qdq26q8o
- type: function
- - content: '{"code":"sentiments=[''Positive'',''Negative'',''Mixed'']\ncounts={''Positive'':0,''Negative'':0,''Mixed'':0}\nfor
- s in sentiments:\n counts[s]+=1\nprint(counts)\n","stdout":"","stderr":"Expected name, got Subscript(ExprSubscript
- { node_index: NodeIndex(None), range: 113..122, value: Name(ExprName { node_index: NodeIndex(None), range: 113..119,
- id: Name(\"counts\"), ctx: Load }), slice: Name(ExprName { node_index: NodeIndex(None), range: 120..121, id: Name(\"s\"),
- ctx: Load }), ctx: Store })","success":false}'
- role: tool
- tool_call_id: call_qdq26q8o
- - content: null
- reasoning: Forgot python version issues? Actually counts[s]+=1 fine. The error due to stray? Let's rewrite.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"counts={''Positive'':0,''Negative'':0,''Mixed'':0}\nfor s in [''Positive'',''Negative'',''Mixed'']:\n counts[s]+=1\nprint(counts)\n"}'
- name: execute_code
- id: call_990j0hbg
- type: function
- - content: '{"code":"counts={''Positive'':0,''Negative'':0,''Mixed'':0}\nfor s in [''Positive'',''Negative'',''Mixed'']:\n counts[s]+=1\nprint(counts)\n","stdout":"","stderr":"Expected
- name, got Subscript(ExprSubscript { node_index: NodeIndex(None), range: 91..100, value: Name(ExprName { node_index:
- NodeIndex(None), range: 91..97, id: Name(\"counts\"), ctx: Load }), slice: Name(ExprName { node_index: NodeIndex(None),
- range: 98..99, id: Name(\"s\"), ctx: Load }), ctx: Store })","success":false}'
- role: tool
- tool_call_id: call_990j0hbg
- model: gpt-oss
- reasoning_effort: low
- stream: false
- tool_choice: auto
- tools:
- - function:
- description: |-
- Execute Python code in a sandboxed interpreter.
-
- The code has access to haiku.rag functions (search, list_documents,
- get_document, get_chunk, llm).
-
- Use print() to output results.
-
- Structured result with success status, stdout, and stderr.
-
- name: execute_code
- parameters:
- additionalProperties: false
- properties:
- code:
- description: Python code to execute.
- type: string
- required:
- - code
- type: object
- strict: true
- type: function
- - function:
- description: Result from RLM agent execution.
- name: final_result
- parameters:
- additionalProperties: false
- properties:
- answer:
- description: The answer to the user's question
- type: string
- program:
- description: The final consolidated program
- type: string
- required:
- - answer
- - program
- type: object
- strict: true
- type: function
- uri: http://localhost:11434/v1/chat/completions
- response:
- headers:
- content-length:
- - '784'
- content-type:
- - application/json
- parsed_body:
- choices:
- - finish_reason: tool_calls
- index: 0
- message:
- content: ''
- reasoning: It seems the environment might interpret `counts[s]` as something else? Maybe eval of counts? Could be
- that counts is overwritten? Let's print variable names.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"counts={''Positive'':0,''Negative'':0,''Mixed'':0}\nprint(''counts'',counts)\nfor s in
- [''Positive'',''Negative'',''Mixed'']:\n print(''s'',s)\n counts[s]=counts[s]+1\nprint(''final'',counts)\n"}'
- name: execute_code
- id: call_c4wf4hqw
- index: 0
- type: function
- created: 1771336346
- id: chatcmpl-313
- model: gpt-oss
- object: chat.completion
- system_fingerprint: fp_ollama
- usage:
- completion_tokens: 113
- prompt_tokens: 2909
- total_tokens: 3022
- status:
- code: 200
- message: OK
-- request:
- headers:
- accept:
- - application/json
- accept-encoding:
- - gzip, deflate, zstd
- connection:
- - keep-alive
- content-length:
- - '13465'
- content-type:
- - application/json
- host:
- - localhost:11434
- method: POST
- parsed_body:
- messages:
- - content: |-
- You are a Recursive Language Model (RLM) agent that solves complex research questions by writing and executing Python code.
-
- IMPORTANT: You MUST use the `execute_code` tool to run Python code. The functions described below are ONLY available inside the execute_code tool - you cannot access them any other way. Always execute code to answer questions; do not just describe what code would do.
-
- CRITICAL: Inside execute_code, these functions are ALREADY available in the namespace. Do NOT import them - just use them directly:
- - search("query") ✓ CORRECT
- - from haiku.rag import search ✗ WRONG - will fail
-
- You have access to a sandboxed Python environment with these haiku.rag functions (use them directly, no imports needed):
-
- ## Available Functions
-
- ### search(query, limit=10) -> list[dict]
- Search the knowledge base using hybrid search (vector + full-text).
- Returns list of dicts with keys: chunk_id, content, document_id, document_title, document_uri, score, page_numbers, headings
-
- ### list_documents(limit=10, offset=0) -> list[dict]
- List available documents in the knowledge base.
- Returns list of dicts with keys: id, title, uri, created_at
-
- ### get_document(id_or_title) -> str | None
- Get the full text content of a document by ID, title, or URI.
- Returns the document content as a string, or None if not found.
-
- ### get_docling_document(id_or_title) -> DoclingDocument | None
- Get the structured DoclingDocument object for advanced analysis.
- Returns a DoclingDocument object, or None if not found.
- See "DoclingDocument API" section below for how to use it.
-
- ### llm(prompt) -> str
- Call an LLM directly with the given prompt. Returns the response as a string.
- Use this for classification, summarization, extraction, or any task where you
- already have the content and just need LLM reasoning.
-
- ## Pre-loaded Documents Variable
-
- If documents were pre-loaded for this session, a `documents` variable is available:
- ```python
- # documents is a list of dicts with keys: id, title, uri, content
- for doc in documents:
- print(doc['title'], len(doc['content']))
- ```
- Check if it exists with: `if 'documents' in dir(): ...`
-
- ## Standard Library Modules
- You can import any Python standard library module.
-
- ## Strategy Guide
-
- 1. **Explore First**: Start by listing documents or searching to understand what's available. Document names may differ from filenames (e.g., "tbmed593.pdf" might be stored as "TB MED 593" or similar).
- 2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
- 3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
- 4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
- 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and use collections.
- 6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
- 7. **Cite Your Sources**: Track which documents/chunks informed your answer for citation.
-
- ## DoclingDocument API
-
- When you call `get_docling_document(id_or_title)`, you get a DoclingDocument object for structured document analysis.
-
- ### Properties
- - `doc.texts` - List of all text items (paragraphs, headings, etc.)
- - `doc.tables` - List of all tables
- - `doc.pictures` - List of all pictures/figures
- - `doc.name` - Document name
-
- ### Methods
- - `doc.iterate_items(with_groups=False)` - Iterate all items with hierarchy level
- Returns tuples of (item, level) where level is nesting depth
- - `doc.export_to_markdown()` - Export entire document as markdown string
-
- ### Text Item Properties
- - `item.text` - The text content
- - `item.label` - Type: title, paragraph, section_header, list_item, etc. (lowercase enum values)
- - `item.prov` - Provenance (page numbers, bounding boxes)
-
- ### Table Access
- - `table.data.num_rows`, `table.data.num_cols` - Dimensions
- - `table.data.table_cells` - List of TableCell objects
- - `cell.text`, `cell.start_row_offset_idx`, `cell.start_col_offset_idx`
-
- ### Example Usage
- ```python
- doc = get_docling_document("My Document")
-
- # Get all headings
- headings = [t.text for t in doc.texts if "header" in str(t.label)]
-
- # Iterate with structure
- for item, level in doc.iterate_items():
- print(" " * level + item.text[:50])
-
- # Extract table data
- for table in doc.tables:
- for cell in table.data.table_cells:
- print(f"Row {cell.start_row_offset_idx}, Col {cell.start_col_offset_idx}: {cell.text}")
- ```
-
- ## Example Patterns
-
- ### Counting documents matching a condition
- ```python
- docs = list_documents(limit=100)
- count = 0
- for doc in docs:
- content = get_document(doc['id'])
- if content and 'keyword' in content.lower():
- count += 1
- print(f"Found in: {doc['title']}")
- print(f"Total: {count}")
- ```
-
- ### Aggregating data across documents
- ```python
- import re
- numbers = []
- results = search("financial data", limit=20)
- for r in results:
- matches = re.findall(r'\$([\d,]+)', r['content'])
- for m in matches:
- numbers.append(int(m.replace(',', '')))
- print(f"Average: ${sum(numbers)/len(numbers):,.2f}")
- ```
-
- ### Using llm() for classification
- ```python
- # Get document content
- content = get_document("Q1 Report")
- # Use llm() to classify sentiment
- sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
- print(sentiment)
- ```
-
- ## Workflow
-
- 1. **ALWAYS start by using execute_code** to explore the knowledge base
- 2. Run multiple code blocks as needed to gather information
- 3. After collecting data, provide your final answer
-
- ## Output Format
-
- CRITICAL: Your final response MUST be valid JSON matching this exact schema:
- ```json
- {"answer": "Your complete answer here as a string", "program": "Your final consolidated program here as a string"}
- ```
-
- - `answer`: A clear answer to the user's question with key findings and references to specific documents/chunks.
- - `program`: A single, self-contained Python program that produces the answer. Consolidate your exploratory code executions into one clean script.
-
- Do NOT return arbitrary JSON structures. Always use the exact format: {"answer": "...", "program": "..."}
-
- CRITICAL: You MUST call execute_code at least once before providing your answer. Never give up without trying to execute code first.
- role: system
- - content: Analyze the sentiment of each quarterly update. How many quarters were positive, negative, and mixed?
- role: user
- - content: null
- reasoning: We need documents about quarterly updates. Search.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"results = search(\"quarterly update\", limit=50)\nprint(len(results))\nfor r in results[:5]:\n print(r[''document_title''],
- r[''page_numbers''][:3], r[''score''])\n"}'
- name: execute_code
- id: call_0g817w8v
- type: function
- - content: '{"code":"results = search(\"quarterly update\", limit=50)\nprint(len(results))\nfor r in results[:5]:\n print(r[''document_title''],
- r[''page_numbers''][:3], r[''score''])\n","stdout":"3\nQ3 Update [] 0.032786883413791656\nQ2 Update [] 0.032258063554763794\nQ1
- Update [] 0.01587301678955555\n","stderr":"","success":true}'
- role: tool
- tool_call_id: call_0g817w8v
- - content: null
- reasoning: 'Three documents: Q1, Q2, Q3. We need sentiment. Use llm on each content.'
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"for quarter in [''Q1 Update'',''Q2 Update'',''Q3 Update'']:\n content = get_document(quarter)\n resp
- = llm(f\"Classify the sentiment of this quarterly update text as positive, negative, or mixed.\\n\\n{content}\")\n print(quarter,
- resp)\n"}'
- name: execute_code
- id: call_stqc44dk
- type: function
- - content: '{"code":"for quarter in [''Q1 Update'',''Q2 Update'',''Q3 Update'']:\n content = get_document(quarter)\n resp
- = llm(f\"Classify the sentiment of this quarterly update text as positive, negative, or mixed.\\n\\n{content}\")\n print(quarter,
- resp)\n","stdout":"Q1 Update Positive\nQ2 Update **Sentiment:** **Negative**\nQ3 Update Mixed (the update contains
- both positive and negative elements)\n","stderr":"","success":true}'
- role: tool
- tool_call_id: call_stqc44dk
- - content: null
- reasoning: Count.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"sentiments={''Q1 Update'':''Positive'',''Q2 Update'':''Negative'',''Q3 Update'':''Mixed''}\ncounts={''Positive'':0,''Negative'':0,''Mixed'':0}\nfor
- s in sentiments.values():\n counts[s]+=1\nprint(counts)\n"}'
- name: execute_code
- id: call_upa0r5we
- type: function
- - content: '{"code":"sentiments={''Q1 Update'':''Positive'',''Q2 Update'':''Negative'',''Q3 Update'':''Mixed''}\ncounts={''Positive'':0,''Negative'':0,''Mixed'':0}\nfor
- s in sentiments.values():\n counts[s]+=1\nprint(counts)\n","stdout":"","stderr":"Expected name, got Subscript(ExprSubscript
- { node_index: NodeIndex(None), range: 158..167, value: Name(ExprName { node_index: NodeIndex(None), range: 158..164,
- id: Name(\"counts\"), ctx: Load }), slice: Name(ExprName { node_index: NodeIndex(None), range: 165..166, id: Name(\"s\"),
- ctx: Load }), ctx: Store })","success":false}'
- role: tool
- tool_call_id: call_upa0r5we
- - content: null
- reasoning: Syntax error. Use proper dict.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"sentiments=[''Positive'',''Negative'',''Mixed'']\ncounts={''Positive'':0,''Negative'':0,''Mixed'':0}\nfor
- s in sentiments:\n counts[s]+=1\nprint(counts)\n"}'
- name: execute_code
- id: call_qdq26q8o
- type: function
- - content: '{"code":"sentiments=[''Positive'',''Negative'',''Mixed'']\ncounts={''Positive'':0,''Negative'':0,''Mixed'':0}\nfor
- s in sentiments:\n counts[s]+=1\nprint(counts)\n","stdout":"","stderr":"Expected name, got Subscript(ExprSubscript
- { node_index: NodeIndex(None), range: 113..122, value: Name(ExprName { node_index: NodeIndex(None), range: 113..119,
- id: Name(\"counts\"), ctx: Load }), slice: Name(ExprName { node_index: NodeIndex(None), range: 120..121, id: Name(\"s\"),
- ctx: Load }), ctx: Store })","success":false}'
- role: tool
- tool_call_id: call_qdq26q8o
- - content: null
- reasoning: Forgot python version issues? Actually counts[s]+=1 fine. The error due to stray? Let's rewrite.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"counts={''Positive'':0,''Negative'':0,''Mixed'':0}\nfor s in [''Positive'',''Negative'',''Mixed'']:\n counts[s]+=1\nprint(counts)\n"}'
- name: execute_code
- id: call_990j0hbg
- type: function
- - content: '{"code":"counts={''Positive'':0,''Negative'':0,''Mixed'':0}\nfor s in [''Positive'',''Negative'',''Mixed'']:\n counts[s]+=1\nprint(counts)\n","stdout":"","stderr":"Expected
- name, got Subscript(ExprSubscript { node_index: NodeIndex(None), range: 91..100, value: Name(ExprName { node_index:
- NodeIndex(None), range: 91..97, id: Name(\"counts\"), ctx: Load }), slice: Name(ExprName { node_index: NodeIndex(None),
- range: 98..99, id: Name(\"s\"), ctx: Load }), ctx: Store })","success":false}'
- role: tool
- tool_call_id: call_990j0hbg
- - content: null
- reasoning: It seems the environment might interpret `counts[s]` as something else? Maybe eval of counts? Could be
- that counts is overwritten? Let's print variable names.
- role: assistant
- tool_calls:
- - function:
- arguments: '{"code":"counts={''Positive'':0,''Negative'':0,''Mixed'':0}\nprint(''counts'',counts)\nfor s in [''Positive'',''Negative'',''Mixed'']:\n print(''s'',s)\n counts[s]=counts[s]+1\nprint(''final'',counts)\n"}'
- name: execute_code
- id: call_c4wf4hqw
- type: function
- - content: '{"code":"counts={''Positive'':0,''Negative'':0,''Mixed'':0}\nprint(''counts'',counts)\nfor s in [''Positive'',''Negative'',''Mixed'']:\n print(''s'',s)\n counts[s]=counts[s]+1\nprint(''final'',counts)\n","stdout":"counts
- {''Positive'': 0, ''Negative'': 0, ''Mixed'': 0}\ns Positive\ns Negative\ns Mixed\nfinal {''Positive'': 1, ''Negative'':
- 1, ''Mixed'': 1}\n","stderr":"","success":true}'
- role: tool
- tool_call_id: call_c4wf4hqw
- model: gpt-oss
- reasoning_effort: low
- stream: false
- tool_choice: auto
- tools:
- - function:
- description: |-
- Execute Python code in a sandboxed interpreter.
-
- The code has access to haiku.rag functions (search, list_documents,
- get_document, get_chunk, llm).
-
- Use print() to output results.
-
- Structured result with success status, stdout, and stderr.
-
- name: execute_code
- parameters:
- additionalProperties: false
- properties:
- code:
- description: Python code to execute.
- type: string
- required:
- - code
- type: object
- strict: true
- type: function
- - function:
- description: Result from RLM agent execution.
- name: final_result
- parameters:
- additionalProperties: false
- properties:
- answer:
- description: The answer to the user's question
- type: string
- program:
- description: The final consolidated program
- type: string
- required:
- - answer
- - program
- type: object
- strict: true
- type: function
- uri: http://localhost:11434/v1/chat/completions
- response:
- headers:
- content-length:
- - '1669'
- content-type:
- - application/json
- parsed_body:
- choices:
- - finish_reason: stop
- index: 0
- message:
- content: '{"answer":"After classifying each quarterly update:\n- Positive: 1 quarter (Q1 Update)\n- Negative: 1
- quarter (Q2 Update)\n- Mixed: 1 quarter (Q3 Update)\n\nThe counts indicate that across the three available quarterly
- updates, sentiment is evenly distributed with one quarter in each category.", "program":"# -*- coding: utf-8 -*-\n#
- Sentiment analysis of quarterly updates\n\n# Define the quarterly update document titles\nquarters = [''Q1 Update'',
- ''Q2 Update'', ''Q3 Update'']\n\n# Get LLM classification for each quarter\nsentiments = []\nfor q in quarters:\n content
- = get_document(q)\n # Ask LLM to classify sentiment\n response = llm(f\"Classify the sentiment of this quarterly
- update text as positive, negative, or mixed.\\n\\n{content}\")\n # Extract the first word that matches the
- expected sentiment\n if ''Positive'' in response:\n sentiments.append(''Positive'')\n elif ''Negative''
- in response:\n sentiments.append(''Negative'')\n else:\n sentiments.append(''Mixed'')\n\n# Count
- occurrences of each sentiment\ncounts = {''Positive'': 0, ''Negative'': 0, ''Mixed'': 0}\nfor s in sentiments:\n counts[s]
- += 1\n\n# Print results\nprint(''Positive:'', counts[''Positive''])\nprint(''Negative:'', counts[''Negative''])\nprint(''Mixed:'',
- counts[''Mixed''])\n"}'
- reasoning: Okay works. So final counts 1 each.
- role: assistant
- created: 1771336355
- id: chatcmpl-205
- model: gpt-oss
- object: chat.completion
- system_fingerprint: fp_ollama
- usage:
- completion_tokens: 349
- prompt_tokens: 3152
- total_tokens: 3501
+ completion_tokens: 262
+ prompt_tokens: 2568
+ total_tokens: 2830
status:
code: 200
message: OK
diff --git a/tests/cassettes/test_rlm/TestClientRLMIntegration.test_rlm_with_filter.yaml b/tests/cassettes/test_rlm/TestClientRLMIntegration.test_rlm_with_filter.yaml
index 517649da..5f4bc8ac 100644
--- a/tests/cassettes/test_rlm/TestClientRLMIntegration.test_rlm_with_filter.yaml
+++ b/tests/cassettes/test_rlm/TestClientRLMIntegration.test_rlm_with_filter.yaml
@@ -128,7 +128,7 @@ interactions:
connection:
- keep-alive
content-length:
- - '7713'
+ - '7077'
content-type:
- application/json
host:
@@ -145,7 +145,7 @@ interactions:
- search("query") ✓ CORRECT
- from haiku.rag import search ✗ WRONG - will fail
- You have access to a sandboxed Python environment with these haiku.rag functions (use them directly, no imports needed):
+ You have access to a sandboxed Python interpreter with these haiku.rag functions (use them directly, no imports needed):
## Available Functions
@@ -161,10 +161,10 @@ interactions:
Get the full text content of a document by ID, title, or URI.
Returns the document content as a string, or None if not found.
- ### get_docling_document(id_or_title) -> DoclingDocument | None
- Get the structured DoclingDocument object for advanced analysis.
- Returns a DoclingDocument object, or None if not found.
- See "DoclingDocument API" section below for how to use it.
+ ### get_chunk(chunk_id) -> dict | None
+ Get a specific chunk by its ID (from search results).
+ Returns dict with keys: chunk_id, content, document_id, document_title, headings, page_numbers, labels
+ Use this to retrieve full chunk details and metadata for citation.
### llm(prompt) -> str
Call an LLM directly with the given prompt. Returns the response as a string.
@@ -181,8 +181,13 @@ interactions:
```
Check if it exists with: `if 'documents' in dir(): ...`
- ## Standard Library Modules
- You can import any Python standard library module.
+ ## Available Python Features
+
+ The interpreter supports: variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, try/except, and the `json` module.
+
+ Not supported: imports (other than `json`), class definitions, generators/yield, match statements, decorators, `with` statements.
+
+ For pattern matching or text extraction, use string methods (`str.split`, `str.find`, `str.startswith`, `in` operator) or the `llm()` function.
## Strategy Guide
@@ -190,51 +195,9 @@ interactions:
2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
- 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and use collections.
+ 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and data structures.
6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
- 7. **Cite Your Sources**: Track which documents/chunks informed your answer for citation.
-
- ## DoclingDocument API
-
- When you call `get_docling_document(id_or_title)`, you get a DoclingDocument object for structured document analysis.
-
- ### Properties
- - `doc.texts` - List of all text items (paragraphs, headings, etc.)
- - `doc.tables` - List of all tables
- - `doc.pictures` - List of all pictures/figures
- - `doc.name` - Document name
-
- ### Methods
- - `doc.iterate_items(with_groups=False)` - Iterate all items with hierarchy level
- Returns tuples of (item, level) where level is nesting depth
- - `doc.export_to_markdown()` - Export entire document as markdown string
-
- ### Text Item Properties
- - `item.text` - The text content
- - `item.label` - Type: title, paragraph, section_header, list_item, etc. (lowercase enum values)
- - `item.prov` - Provenance (page numbers, bounding boxes)
-
- ### Table Access
- - `table.data.num_rows`, `table.data.num_cols` - Dimensions
- - `table.data.table_cells` - List of TableCell objects
- - `cell.text`, `cell.start_row_offset_idx`, `cell.start_col_offset_idx`
-
- ### Example Usage
- ```python
- doc = get_docling_document("My Document")
-
- # Get all headings
- headings = [t.text for t in doc.texts if "header" in str(t.label)]
-
- # Iterate with structure
- for item, level in doc.iterate_items():
- print(" " * level + item.text[:50])
-
- # Extract table data
- for table in doc.tables:
- for cell in table.data.table_cells:
- print(f"Row {cell.start_row_offset_idx}, Col {cell.start_col_offset_idx}: {cell.text}")
- ```
+ 7. **Cite Your Sources**: Use get_chunk() to retrieve chunk metadata for citations. Track which documents/chunks informed your answer.
## Example Patterns
@@ -250,23 +213,31 @@ interactions:
print(f"Total: {count}")
```
- ### Aggregating data across documents
+ ### Extracting data with llm()
```python
- import re
numbers = []
results = search("financial data", limit=20)
for r in results:
- matches = re.findall(r'\$([\d,]+)', r['content'])
- for m in matches:
- numbers.append(int(m.replace(',', '')))
- print(f"Average: ${sum(numbers)/len(numbers):,.2f}")
+ extracted = llm(f"Extract all dollar amounts from this text as a comma-separated list of numbers (no $ signs): {r['content']}")
+ for part in extracted.split(','):
+ part = part.strip().replace(',', '')
+ if part.isdigit():
+ numbers.append(int(part))
+ if numbers:
+ print(f"Average: {sum(numbers) / len(numbers)}")
+ ```
+
+ ### Using search results with get_chunk for citations
+ ```python
+ results = search("safety requirements", limit=5)
+ for r in results:
+ chunk = get_chunk(r['chunk_id'])
+ print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}")
```
### Using llm() for classification
```python
- # Get document content
content = get_document("Q1 Report")
- # Use llm() to classify sentiment
sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
print(sentiment)
```
@@ -343,7 +314,7 @@ interactions:
response:
headers:
content-length:
- - '527'
+ - '526'
content-type:
- application/json
parsed_body:
@@ -352,24 +323,24 @@ interactions:
index: 0
message:
content: ''
- reasoning: Need to call list_documents to count.
+ reasoning: Need to list_documents.
role: assistant
tool_calls:
- function:
- arguments: '{"code":"docs = list_documents(limit=1000)\nprint(len(docs))"}'
+ arguments: '{"code":"docs=list_documents(limit=1000);print(len(docs)); print(docs[:3])"}'
name: execute_code
- id: call_9nd3m1g0
+ id: call_rpfcy176
index: 0
type: function
- created: 1771336314
- id: chatcmpl-255
+ created: 1771336720
+ id: chatcmpl-390
model: gpt-oss
object: chat.completion
system_fingerprint: fp_ollama
usage:
- completion_tokens: 46
- prompt_tokens: 1732
- total_tokens: 1778
+ completion_tokens: 47
+ prompt_tokens: 1560
+ total_tokens: 1607
status:
code: 200
message: OK
@@ -382,7 +353,7 @@ interactions:
connection:
- keep-alive
content-length:
- - '8154'
+ - '7655'
content-type:
- application/json
host:
@@ -399,7 +370,7 @@ interactions:
- search("query") ✓ CORRECT
- from haiku.rag import search ✗ WRONG - will fail
- You have access to a sandboxed Python environment with these haiku.rag functions (use them directly, no imports needed):
+ You have access to a sandboxed Python interpreter with these haiku.rag functions (use them directly, no imports needed):
## Available Functions
@@ -415,10 +386,10 @@ interactions:
Get the full text content of a document by ID, title, or URI.
Returns the document content as a string, or None if not found.
- ### get_docling_document(id_or_title) -> DoclingDocument | None
- Get the structured DoclingDocument object for advanced analysis.
- Returns a DoclingDocument object, or None if not found.
- See "DoclingDocument API" section below for how to use it.
+ ### get_chunk(chunk_id) -> dict | None
+ Get a specific chunk by its ID (from search results).
+ Returns dict with keys: chunk_id, content, document_id, document_title, headings, page_numbers, labels
+ Use this to retrieve full chunk details and metadata for citation.
### llm(prompt) -> str
Call an LLM directly with the given prompt. Returns the response as a string.
@@ -435,8 +406,13 @@ interactions:
```
Check if it exists with: `if 'documents' in dir(): ...`
- ## Standard Library Modules
- You can import any Python standard library module.
+ ## Available Python Features
+
+ The interpreter supports: variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, try/except, and the `json` module.
+
+ Not supported: imports (other than `json`), class definitions, generators/yield, match statements, decorators, `with` statements.
+
+ For pattern matching or text extraction, use string methods (`str.split`, `str.find`, `str.startswith`, `in` operator) or the `llm()` function.
## Strategy Guide
@@ -444,51 +420,9 @@ interactions:
2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
- 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and use collections.
+ 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and data structures.
6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
- 7. **Cite Your Sources**: Track which documents/chunks informed your answer for citation.
-
- ## DoclingDocument API
-
- When you call `get_docling_document(id_or_title)`, you get a DoclingDocument object for structured document analysis.
-
- ### Properties
- - `doc.texts` - List of all text items (paragraphs, headings, etc.)
- - `doc.tables` - List of all tables
- - `doc.pictures` - List of all pictures/figures
- - `doc.name` - Document name
-
- ### Methods
- - `doc.iterate_items(with_groups=False)` - Iterate all items with hierarchy level
- Returns tuples of (item, level) where level is nesting depth
- - `doc.export_to_markdown()` - Export entire document as markdown string
-
- ### Text Item Properties
- - `item.text` - The text content
- - `item.label` - Type: title, paragraph, section_header, list_item, etc. (lowercase enum values)
- - `item.prov` - Provenance (page numbers, bounding boxes)
-
- ### Table Access
- - `table.data.num_rows`, `table.data.num_cols` - Dimensions
- - `table.data.table_cells` - List of TableCell objects
- - `cell.text`, `cell.start_row_offset_idx`, `cell.start_col_offset_idx`
-
- ### Example Usage
- ```python
- doc = get_docling_document("My Document")
-
- # Get all headings
- headings = [t.text for t in doc.texts if "header" in str(t.label)]
-
- # Iterate with structure
- for item, level in doc.iterate_items():
- print(" " * level + item.text[:50])
-
- # Extract table data
- for table in doc.tables:
- for cell in table.data.table_cells:
- print(f"Row {cell.start_row_offset_idx}, Col {cell.start_col_offset_idx}: {cell.text}")
- ```
+ 7. **Cite Your Sources**: Use get_chunk() to retrieve chunk metadata for citations. Track which documents/chunks informed your answer.
## Example Patterns
@@ -504,23 +438,31 @@ interactions:
print(f"Total: {count}")
```
- ### Aggregating data across documents
+ ### Extracting data with llm()
```python
- import re
numbers = []
results = search("financial data", limit=20)
for r in results:
- matches = re.findall(r'\$([\d,]+)', r['content'])
- for m in matches:
- numbers.append(int(m.replace(',', '')))
- print(f"Average: ${sum(numbers)/len(numbers):,.2f}")
+ extracted = llm(f"Extract all dollar amounts from this text as a comma-separated list of numbers (no $ signs): {r['content']}")
+ for part in extracted.split(','):
+ part = part.strip().replace(',', '')
+ if part.isdigit():
+ numbers.append(int(part))
+ if numbers:
+ print(f"Average: {sum(numbers) / len(numbers)}")
+ ```
+
+ ### Using search results with get_chunk for citations
+ ```python
+ results = search("safety requirements", limit=5)
+ for r in results:
+ chunk = get_chunk(r['chunk_id'])
+ print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}")
```
### Using llm() for classification
```python
- # Get document content
content = get_document("Q1 Report")
- # Use llm() to classify sentiment
sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
print(sentiment)
```
@@ -548,17 +490,18 @@ interactions:
- content: How many documents are available?
role: user
- content: null
- reasoning: Need to call list_documents to count.
+ reasoning: Need to list_documents.
role: assistant
tool_calls:
- function:
- arguments: '{"code":"docs = list_documents(limit=1000)\nprint(len(docs))"}'
+ arguments: '{"code":"docs=list_documents(limit=1000);print(len(docs)); print(docs[:3])"}'
name: execute_code
- id: call_9nd3m1g0
+ id: call_rpfcy176
type: function
- - content: '{"code":"docs = list_documents(limit=1000)\nprint(len(docs))","stdout":"1\n","stderr":"","success":true}'
+ - content: '{"code":"docs=list_documents(limit=1000);print(len(docs)); print(docs[:3])","stdout":"1\n[{''id'': ''50c659ee-70c5-4854-a418-12ae2abdcf40'',
+ ''title'': ''Cats'', ''uri'': None, ''created_at'': ''2026-02-17 15:58:36.831575''}]\n","stderr":"","success":true}'
role: tool
- tool_call_id: call_9nd3m1g0
+ tool_call_id: call_rpfcy176
model: gpt-oss
reasoning_effort: low
stream: false
@@ -609,7 +552,7 @@ interactions:
response:
headers:
content-length:
- - '424'
+ - '453'
content-type:
- application/json
parsed_body:
@@ -617,17 +560,18 @@ interactions:
- finish_reason: stop
index: 0
message:
- content: '{"answer":"There is 1 document available in the knowledge base.","program":"docs = list_documents(limit=1000)\nprint(len(docs))"}'
+ content: '{"answer":"There is 1 document available in the knowledge base.","program":"docs=list_documents(limit=1000)\nprint(f\"Number
+ of documents: {len(docs)}\")"}'
role: assistant
- created: 1771336315
- id: chatcmpl-500
+ created: 1771336721
+ id: chatcmpl-97
model: gpt-oss
object: chat.completion
system_fingerprint: fp_ollama
usage:
- completion_tokens: 39
- prompt_tokens: 1819
- total_tokens: 1858
+ completion_tokens: 46
+ prompt_tokens: 1713
+ total_tokens: 1759
status:
code: 200
message: OK
diff --git a/tests/cassettes/test_rlm/TestClientRLMIntegration.test_rlm_with_preloaded_documents.yaml b/tests/cassettes/test_rlm/TestClientRLMIntegration.test_rlm_with_preloaded_documents.yaml
index ed4c08e1..db862e9d 100644
--- a/tests/cassettes/test_rlm/TestClientRLMIntegration.test_rlm_with_preloaded_documents.yaml
+++ b/tests/cassettes/test_rlm/TestClientRLMIntegration.test_rlm_with_preloaded_documents.yaml
@@ -88,7 +88,7 @@ interactions:
connection:
- keep-alive
content-length:
- - '7784'
+ - '7148'
content-type:
- application/json
host:
@@ -105,7 +105,7 @@ interactions:
- search("query") ✓ CORRECT
- from haiku.rag import search ✗ WRONG - will fail
- You have access to a sandboxed Python environment with these haiku.rag functions (use them directly, no imports needed):
+ You have access to a sandboxed Python interpreter with these haiku.rag functions (use them directly, no imports needed):
## Available Functions
@@ -121,10 +121,10 @@ interactions:
Get the full text content of a document by ID, title, or URI.
Returns the document content as a string, or None if not found.
- ### get_docling_document(id_or_title) -> DoclingDocument | None
- Get the structured DoclingDocument object for advanced analysis.
- Returns a DoclingDocument object, or None if not found.
- See "DoclingDocument API" section below for how to use it.
+ ### get_chunk(chunk_id) -> dict | None
+ Get a specific chunk by its ID (from search results).
+ Returns dict with keys: chunk_id, content, document_id, document_title, headings, page_numbers, labels
+ Use this to retrieve full chunk details and metadata for citation.
### llm(prompt) -> str
Call an LLM directly with the given prompt. Returns the response as a string.
@@ -141,8 +141,13 @@ interactions:
```
Check if it exists with: `if 'documents' in dir(): ...`
- ## Standard Library Modules
- You can import any Python standard library module.
+ ## Available Python Features
+
+ The interpreter supports: variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, try/except, and the `json` module.
+
+ Not supported: imports (other than `json`), class definitions, generators/yield, match statements, decorators, `with` statements.
+
+ For pattern matching or text extraction, use string methods (`str.split`, `str.find`, `str.startswith`, `in` operator) or the `llm()` function.
## Strategy Guide
@@ -150,51 +155,9 @@ interactions:
2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
- 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and use collections.
+ 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and data structures.
6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
- 7. **Cite Your Sources**: Track which documents/chunks informed your answer for citation.
-
- ## DoclingDocument API
-
- When you call `get_docling_document(id_or_title)`, you get a DoclingDocument object for structured document analysis.
-
- ### Properties
- - `doc.texts` - List of all text items (paragraphs, headings, etc.)
- - `doc.tables` - List of all tables
- - `doc.pictures` - List of all pictures/figures
- - `doc.name` - Document name
-
- ### Methods
- - `doc.iterate_items(with_groups=False)` - Iterate all items with hierarchy level
- Returns tuples of (item, level) where level is nesting depth
- - `doc.export_to_markdown()` - Export entire document as markdown string
-
- ### Text Item Properties
- - `item.text` - The text content
- - `item.label` - Type: title, paragraph, section_header, list_item, etc. (lowercase enum values)
- - `item.prov` - Provenance (page numbers, bounding boxes)
-
- ### Table Access
- - `table.data.num_rows`, `table.data.num_cols` - Dimensions
- - `table.data.table_cells` - List of TableCell objects
- - `cell.text`, `cell.start_row_offset_idx`, `cell.start_col_offset_idx`
-
- ### Example Usage
- ```python
- doc = get_docling_document("My Document")
-
- # Get all headings
- headings = [t.text for t in doc.texts if "header" in str(t.label)]
-
- # Iterate with structure
- for item, level in doc.iterate_items():
- print(" " * level + item.text[:50])
-
- # Extract table data
- for table in doc.tables:
- for cell in table.data.table_cells:
- print(f"Row {cell.start_row_offset_idx}, Col {cell.start_col_offset_idx}: {cell.text}")
- ```
+ 7. **Cite Your Sources**: Use get_chunk() to retrieve chunk metadata for citations. Track which documents/chunks informed your answer.
## Example Patterns
@@ -210,23 +173,31 @@ interactions:
print(f"Total: {count}")
```
- ### Aggregating data across documents
+ ### Extracting data with llm()
```python
- import re
numbers = []
results = search("financial data", limit=20)
for r in results:
- matches = re.findall(r'\$([\d,]+)', r['content'])
- for m in matches:
- numbers.append(int(m.replace(',', '')))
- print(f"Average: ${sum(numbers)/len(numbers):,.2f}")
+ extracted = llm(f"Extract all dollar amounts from this text as a comma-separated list of numbers (no $ signs): {r['content']}")
+ for part in extracted.split(','):
+ part = part.strip().replace(',', '')
+ if part.isdigit():
+ numbers.append(int(part))
+ if numbers:
+ print(f"Average: {sum(numbers) / len(numbers)}")
+ ```
+
+ ### Using search results with get_chunk for citations
+ ```python
+ results = search("safety requirements", limit=5)
+ for r in results:
+ chunk = get_chunk(r['chunk_id'])
+ print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}")
```
### Using llm() for classification
```python
- # Get document content
content = get_document("Q1 Report")
- # Use llm() to classify sentiment
sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
print(sentiment)
```
@@ -303,7 +274,7 @@ interactions:
response:
headers:
content-length:
- - '640'
+ - '615'
content-type:
- application/json
parsed_body:
@@ -312,25 +283,25 @@ interactions:
index: 0
message:
content: ''
- reasoning: We need to check if 'documents' variable exists.
+ reasoning: Need to inspect documents variable.
role: assistant
tool_calls:
- function:
- arguments: '{"code":"if ''documents'' in dir():\n for doc in documents:\n print(doc[''title''], len(doc[''content'']))\nelse:\n print(''documents
- variable not found'')"}'
+ arguments: '{"code":"if ''documents'' in dir():\n for doc in documents:\n print(doc[''title''], len(doc[''content'']))\nelse:\n print(''no
+ documents'')\n"}'
name: execute_code
- id: call_j4wr5u97
+ id: call_9n7burqq
index: 0
type: function
- created: 1771336411
- id: chatcmpl-46
+ created: 1771336791
+ id: chatcmpl-463
model: gpt-oss
object: chat.completion
system_fingerprint: fp_ollama
usage:
- completion_tokens: 74
- prompt_tokens: 1746
- total_tokens: 1820
+ completion_tokens: 69
+ prompt_tokens: 1574
+ total_tokens: 1643
status:
code: 200
message: OK
@@ -343,7 +314,7 @@ interactions:
connection:
- keep-alive
content-length:
- - '8475'
+ - '7800'
content-type:
- application/json
host:
@@ -360,7 +331,7 @@ interactions:
- search("query") ✓ CORRECT
- from haiku.rag import search ✗ WRONG - will fail
- You have access to a sandboxed Python environment with these haiku.rag functions (use them directly, no imports needed):
+ You have access to a sandboxed Python interpreter with these haiku.rag functions (use them directly, no imports needed):
## Available Functions
@@ -376,10 +347,10 @@ interactions:
Get the full text content of a document by ID, title, or URI.
Returns the document content as a string, or None if not found.
- ### get_docling_document(id_or_title) -> DoclingDocument | None
- Get the structured DoclingDocument object for advanced analysis.
- Returns a DoclingDocument object, or None if not found.
- See "DoclingDocument API" section below for how to use it.
+ ### get_chunk(chunk_id) -> dict | None
+ Get a specific chunk by its ID (from search results).
+ Returns dict with keys: chunk_id, content, document_id, document_title, headings, page_numbers, labels
+ Use this to retrieve full chunk details and metadata for citation.
### llm(prompt) -> str
Call an LLM directly with the given prompt. Returns the response as a string.
@@ -396,8 +367,13 @@ interactions:
```
Check if it exists with: `if 'documents' in dir(): ...`
- ## Standard Library Modules
- You can import any Python standard library module.
+ ## Available Python Features
+
+ The interpreter supports: variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, try/except, and the `json` module.
+
+ Not supported: imports (other than `json`), class definitions, generators/yield, match statements, decorators, `with` statements.
+
+ For pattern matching or text extraction, use string methods (`str.split`, `str.find`, `str.startswith`, `in` operator) or the `llm()` function.
## Strategy Guide
@@ -405,51 +381,9 @@ interactions:
2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
- 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and use collections.
+ 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and data structures.
6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
- 7. **Cite Your Sources**: Track which documents/chunks informed your answer for citation.
-
- ## DoclingDocument API
-
- When you call `get_docling_document(id_or_title)`, you get a DoclingDocument object for structured document analysis.
-
- ### Properties
- - `doc.texts` - List of all text items (paragraphs, headings, etc.)
- - `doc.tables` - List of all tables
- - `doc.pictures` - List of all pictures/figures
- - `doc.name` - Document name
-
- ### Methods
- - `doc.iterate_items(with_groups=False)` - Iterate all items with hierarchy level
- Returns tuples of (item, level) where level is nesting depth
- - `doc.export_to_markdown()` - Export entire document as markdown string
-
- ### Text Item Properties
- - `item.text` - The text content
- - `item.label` - Type: title, paragraph, section_header, list_item, etc. (lowercase enum values)
- - `item.prov` - Provenance (page numbers, bounding boxes)
-
- ### Table Access
- - `table.data.num_rows`, `table.data.num_cols` - Dimensions
- - `table.data.table_cells` - List of TableCell objects
- - `cell.text`, `cell.start_row_offset_idx`, `cell.start_col_offset_idx`
-
- ### Example Usage
- ```python
- doc = get_docling_document("My Document")
-
- # Get all headings
- headings = [t.text for t in doc.texts if "header" in str(t.label)]
-
- # Iterate with structure
- for item, level in doc.iterate_items():
- print(" " * level + item.text[:50])
-
- # Extract table data
- for table in doc.tables:
- for cell in table.data.table_cells:
- print(f"Row {cell.start_row_offset_idx}, Col {cell.start_col_offset_idx}: {cell.text}")
- ```
+ 7. **Cite Your Sources**: Use get_chunk() to retrieve chunk metadata for citations. Track which documents/chunks informed your answer.
## Example Patterns
@@ -465,23 +399,31 @@ interactions:
print(f"Total: {count}")
```
- ### Aggregating data across documents
+ ### Extracting data with llm()
```python
- import re
numbers = []
results = search("financial data", limit=20)
for r in results:
- matches = re.findall(r'\$([\d,]+)', r['content'])
- for m in matches:
- numbers.append(int(m.replace(',', '')))
- print(f"Average: ${sum(numbers)/len(numbers):,.2f}")
+ extracted = llm(f"Extract all dollar amounts from this text as a comma-separated list of numbers (no $ signs): {r['content']}")
+ for part in extracted.split(','):
+ part = part.strip().replace(',', '')
+ if part.isdigit():
+ numbers.append(int(part))
+ if numbers:
+ print(f"Average: {sum(numbers) / len(numbers)}")
+ ```
+
+ ### Using search results with get_chunk for citations
+ ```python
+ results = search("safety requirements", limit=5)
+ for r in results:
+ chunk = get_chunk(r['chunk_id'])
+ print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}")
```
### Using llm() for classification
```python
- # Get document content
content = get_document("Q1 Report")
- # Use llm() to classify sentiment
sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
print(sentiment)
```
@@ -509,19 +451,19 @@ interactions:
- content: Using the pre-loaded documents variable, tell me when was the company founded and what is their mission?
role: user
- content: null
- reasoning: We need to check if 'documents' variable exists.
+ reasoning: Need to inspect documents variable.
role: assistant
tool_calls:
- function:
- arguments: '{"code":"if ''documents'' in dir():\n for doc in documents:\n print(doc[''title''], len(doc[''content'']))\nelse:\n print(''documents
- variable not found'')"}'
+ arguments: '{"code":"if ''documents'' in dir():\n for doc in documents:\n print(doc[''title''], len(doc[''content'']))\nelse:\n print(''no
+ documents'')\n"}'
name: execute_code
- id: call_j4wr5u97
+ id: call_9n7burqq
type: function
- - content: '{"code":"if ''documents'' in dir():\n for doc in documents:\n print(doc[''title''], len(doc[''content'']))\nelse:\n print(''documents
- variable not found'')","stdout":"","stderr":"NameError: name ''dir'' is not defined","success":false}'
+ - content: '{"code":"if ''documents'' in dir():\n for doc in documents:\n print(doc[''title''], len(doc[''content'']))\nelse:\n print(''no
+ documents'')\n","stdout":"","stderr":"NameError: name ''dir'' is not defined","success":false}'
role: tool
- tool_call_id: call_j4wr5u97
+ tool_call_id: call_9n7burqq
model: gpt-oss
reasoning_effort: low
stream: false
@@ -572,7 +514,7 @@ interactions:
response:
headers:
content-length:
- - '699'
+ - '792'
content-type:
- application/json
parsed_body:
@@ -581,26 +523,27 @@ interactions:
index: 0
message:
content: ''
- reasoning: It says dir not defined, maybe need to import builtins. Use __builtins__. Let's just try list(dir())?
- But we can just check if variable exists via try/except.
+ reasoning: |+
+ I used dir; but dir is builtin. Wait error: name 'dir' is not defined? Actually dir is builtin but not available? Probably need to use locals() or globals()? Let's just check if 'documents' in globals().
+
role: assistant
tool_calls:
- function:
- arguments: '{"code":"try:\n docs=documents\n print(''exists'', len(docs))\nexcept NameError:\n print(''not
- exist'')"}'
+ arguments: '{"code":"if ''documents'' in globals():\n for doc in documents:\n print(doc[''title''],
+ len(doc[''content'']))\nelse:\n print(''no documents'')\n"}'
name: execute_code
- id: call_bclp2lct
+ id: call_hiiwsnky
index: 0
type: function
- created: 1771336414
- id: chatcmpl-351
+ created: 1771336794
+ id: chatcmpl-665
model: gpt-oss
object: chat.completion
system_fingerprint: fp_ollama
usage:
- completion_tokens: 91
- prompt_tokens: 1894
- total_tokens: 1985
+ completion_tokens: 111
+ prompt_tokens: 1717
+ total_tokens: 1828
status:
code: 200
message: OK
@@ -613,7 +556,7 @@ interactions:
connection:
- keep-alive
content-length:
- - '9146'
+ - '8636'
content-type:
- application/json
host:
@@ -630,7 +573,7 @@ interactions:
- search("query") ✓ CORRECT
- from haiku.rag import search ✗ WRONG - will fail
- You have access to a sandboxed Python environment with these haiku.rag functions (use them directly, no imports needed):
+ You have access to a sandboxed Python interpreter with these haiku.rag functions (use them directly, no imports needed):
## Available Functions
@@ -646,10 +589,10 @@ interactions:
Get the full text content of a document by ID, title, or URI.
Returns the document content as a string, or None if not found.
- ### get_docling_document(id_or_title) -> DoclingDocument | None
- Get the structured DoclingDocument object for advanced analysis.
- Returns a DoclingDocument object, or None if not found.
- See "DoclingDocument API" section below for how to use it.
+ ### get_chunk(chunk_id) -> dict | None
+ Get a specific chunk by its ID (from search results).
+ Returns dict with keys: chunk_id, content, document_id, document_title, headings, page_numbers, labels
+ Use this to retrieve full chunk details and metadata for citation.
### llm(prompt) -> str
Call an LLM directly with the given prompt. Returns the response as a string.
@@ -666,8 +609,13 @@ interactions:
```
Check if it exists with: `if 'documents' in dir(): ...`
- ## Standard Library Modules
- You can import any Python standard library module.
+ ## Available Python Features
+
+ The interpreter supports: variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, try/except, and the `json` module.
+
+ Not supported: imports (other than `json`), class definitions, generators/yield, match statements, decorators, `with` statements.
+
+ For pattern matching or text extraction, use string methods (`str.split`, `str.find`, `str.startswith`, `in` operator) or the `llm()` function.
## Strategy Guide
@@ -675,51 +623,9 @@ interactions:
2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
- 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and use collections.
+ 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and data structures.
6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
- 7. **Cite Your Sources**: Track which documents/chunks informed your answer for citation.
-
- ## DoclingDocument API
-
- When you call `get_docling_document(id_or_title)`, you get a DoclingDocument object for structured document analysis.
-
- ### Properties
- - `doc.texts` - List of all text items (paragraphs, headings, etc.)
- - `doc.tables` - List of all tables
- - `doc.pictures` - List of all pictures/figures
- - `doc.name` - Document name
-
- ### Methods
- - `doc.iterate_items(with_groups=False)` - Iterate all items with hierarchy level
- Returns tuples of (item, level) where level is nesting depth
- - `doc.export_to_markdown()` - Export entire document as markdown string
-
- ### Text Item Properties
- - `item.text` - The text content
- - `item.label` - Type: title, paragraph, section_header, list_item, etc. (lowercase enum values)
- - `item.prov` - Provenance (page numbers, bounding boxes)
-
- ### Table Access
- - `table.data.num_rows`, `table.data.num_cols` - Dimensions
- - `table.data.table_cells` - List of TableCell objects
- - `cell.text`, `cell.start_row_offset_idx`, `cell.start_col_offset_idx`
-
- ### Example Usage
- ```python
- doc = get_docling_document("My Document")
-
- # Get all headings
- headings = [t.text for t in doc.texts if "header" in str(t.label)]
-
- # Iterate with structure
- for item, level in doc.iterate_items():
- print(" " * level + item.text[:50])
-
- # Extract table data
- for table in doc.tables:
- for cell in table.data.table_cells:
- print(f"Row {cell.start_row_offset_idx}, Col {cell.start_col_offset_idx}: {cell.text}")
- ```
+ 7. **Cite Your Sources**: Use get_chunk() to retrieve chunk metadata for citations. Track which documents/chunks informed your answer.
## Example Patterns
@@ -735,23 +641,31 @@ interactions:
print(f"Total: {count}")
```
- ### Aggregating data across documents
+ ### Extracting data with llm()
```python
- import re
numbers = []
results = search("financial data", limit=20)
for r in results:
- matches = re.findall(r'\$([\d,]+)', r['content'])
- for m in matches:
- numbers.append(int(m.replace(',', '')))
- print(f"Average: ${sum(numbers)/len(numbers):,.2f}")
+ extracted = llm(f"Extract all dollar amounts from this text as a comma-separated list of numbers (no $ signs): {r['content']}")
+ for part in extracted.split(','):
+ part = part.strip().replace(',', '')
+ if part.isdigit():
+ numbers.append(int(part))
+ if numbers:
+ print(f"Average: {sum(numbers) / len(numbers)}")
+ ```
+
+ ### Using search results with get_chunk for citations
+ ```python
+ results = search("safety requirements", limit=5)
+ for r in results:
+ chunk = get_chunk(r['chunk_id'])
+ print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}")
```
### Using llm() for classification
```python
- # Get document content
content = get_document("Q1 Report")
- # Use llm() to classify sentiment
sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
print(sentiment)
```
@@ -779,34 +693,35 @@ interactions:
- content: Using the pre-loaded documents variable, tell me when was the company founded and what is their mission?
role: user
- content: null
- reasoning: We need to check if 'documents' variable exists.
+ reasoning: Need to inspect documents variable.
role: assistant
tool_calls:
- function:
- arguments: '{"code":"if ''documents'' in dir():\n for doc in documents:\n print(doc[''title''], len(doc[''content'']))\nelse:\n print(''documents
- variable not found'')"}'
+ arguments: '{"code":"if ''documents'' in dir():\n for doc in documents:\n print(doc[''title''], len(doc[''content'']))\nelse:\n print(''no
+ documents'')\n"}'
name: execute_code
- id: call_j4wr5u97
+ id: call_9n7burqq
type: function
- - content: '{"code":"if ''documents'' in dir():\n for doc in documents:\n print(doc[''title''], len(doc[''content'']))\nelse:\n print(''documents
- variable not found'')","stdout":"","stderr":"NameError: name ''dir'' is not defined","success":false}'
+ - content: '{"code":"if ''documents'' in dir():\n for doc in documents:\n print(doc[''title''], len(doc[''content'']))\nelse:\n print(''no
+ documents'')\n","stdout":"","stderr":"NameError: name ''dir'' is not defined","success":false}'
role: tool
- tool_call_id: call_j4wr5u97
+ tool_call_id: call_9n7burqq
- content: null
- reasoning: It says dir not defined, maybe need to import builtins. Use __builtins__. Let's just try list(dir())? But
- we can just check if variable exists via try/except.
+ reasoning: |+
+ I used dir; but dir is builtin. Wait error: name 'dir' is not defined? Actually dir is builtin but not available? Probably need to use locals() or globals()? Let's just check if 'documents' in globals().
+
role: assistant
tool_calls:
- function:
- arguments: '{"code":"try:\n docs=documents\n print(''exists'', len(docs))\nexcept NameError:\n print(''not
- exist'')"}'
+ arguments: '{"code":"if ''documents'' in globals():\n for doc in documents:\n print(doc[''title''],
+ len(doc[''content'']))\nelse:\n print(''no documents'')\n"}'
name: execute_code
- id: call_bclp2lct
+ id: call_hiiwsnky
type: function
- - content: '{"code":"try:\n docs=documents\n print(''exists'', len(docs))\nexcept NameError:\n print(''not
- exist'')","stdout":"exists 2\n","stderr":"","success":true}'
+ - content: '{"code":"if ''documents'' in globals():\n for doc in documents:\n print(doc[''title''], len(doc[''content'']))\nelse:\n print(''no
+ documents'')\n","stdout":"","stderr":"NameError: name ''globals'' is not defined","success":false}'
role: tool
- tool_call_id: call_bclp2lct
+ tool_call_id: call_hiiwsnky
model: gpt-oss
reasoning_effort: low
stream: false
@@ -857,7 +772,7 @@ interactions:
response:
headers:
content-length:
- - '607'
+ - '633'
content-type:
- application/json
parsed_body:
@@ -866,25 +781,25 @@ interactions:
index: 0
message:
content: ''
- reasoning: So two docs loaded. Let's view titles.
+ reasoning: Globals also not defined. Probably environment only has defined functions. We might just attempt to run
+ list_documents to see if any loaded.
role: assistant
tool_calls:
- function:
- arguments: '{"code":"for doc in documents:\n print(''Title:'', doc[''title''])\n print(''Content snippet:'',
- doc[''content''][:200])\n print(''-''*40)"}'
+ arguments: '{"code":"docs = list_documents(limit=10)\nprint(''docs'',docs)\n"}'
name: execute_code
- id: call_jzdp8w33
+ id: call_yc5ml52o
index: 0
type: function
- created: 1771336416
- id: chatcmpl-830
+ created: 1771336796
+ id: chatcmpl-948
model: gpt-oss
object: chat.completion
system_fingerprint: fp_ollama
usage:
- completion_tokens: 70
- prompt_tokens: 2043
- total_tokens: 2113
+ completion_tokens: 65
+ prompt_tokens: 1902
+ total_tokens: 1967
status:
code: 200
message: OK
@@ -897,7 +812,7 @@ interactions:
connection:
- keep-alive
content-length:
- - '10023'
+ - '9458'
content-type:
- application/json
host:
@@ -914,7 +829,7 @@ interactions:
- search("query") ✓ CORRECT
- from haiku.rag import search ✗ WRONG - will fail
- You have access to a sandboxed Python environment with these haiku.rag functions (use them directly, no imports needed):
+ You have access to a sandboxed Python interpreter with these haiku.rag functions (use them directly, no imports needed):
## Available Functions
@@ -930,10 +845,10 @@ interactions:
Get the full text content of a document by ID, title, or URI.
Returns the document content as a string, or None if not found.
- ### get_docling_document(id_or_title) -> DoclingDocument | None
- Get the structured DoclingDocument object for advanced analysis.
- Returns a DoclingDocument object, or None if not found.
- See "DoclingDocument API" section below for how to use it.
+ ### get_chunk(chunk_id) -> dict | None
+ Get a specific chunk by its ID (from search results).
+ Returns dict with keys: chunk_id, content, document_id, document_title, headings, page_numbers, labels
+ Use this to retrieve full chunk details and metadata for citation.
### llm(prompt) -> str
Call an LLM directly with the given prompt. Returns the response as a string.
@@ -950,8 +865,13 @@ interactions:
```
Check if it exists with: `if 'documents' in dir(): ...`
- ## Standard Library Modules
- You can import any Python standard library module.
+ ## Available Python Features
+
+ The interpreter supports: variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, try/except, and the `json` module.
+
+ Not supported: imports (other than `json`), class definitions, generators/yield, match statements, decorators, `with` statements.
+
+ For pattern matching or text extraction, use string methods (`str.split`, `str.find`, `str.startswith`, `in` operator) or the `llm()` function.
## Strategy Guide
@@ -959,51 +879,9 @@ interactions:
2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
- 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and use collections.
+ 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and data structures.
6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
- 7. **Cite Your Sources**: Track which documents/chunks informed your answer for citation.
-
- ## DoclingDocument API
-
- When you call `get_docling_document(id_or_title)`, you get a DoclingDocument object for structured document analysis.
-
- ### Properties
- - `doc.texts` - List of all text items (paragraphs, headings, etc.)
- - `doc.tables` - List of all tables
- - `doc.pictures` - List of all pictures/figures
- - `doc.name` - Document name
-
- ### Methods
- - `doc.iterate_items(with_groups=False)` - Iterate all items with hierarchy level
- Returns tuples of (item, level) where level is nesting depth
- - `doc.export_to_markdown()` - Export entire document as markdown string
-
- ### Text Item Properties
- - `item.text` - The text content
- - `item.label` - Type: title, paragraph, section_header, list_item, etc. (lowercase enum values)
- - `item.prov` - Provenance (page numbers, bounding boxes)
-
- ### Table Access
- - `table.data.num_rows`, `table.data.num_cols` - Dimensions
- - `table.data.table_cells` - List of TableCell objects
- - `cell.text`, `cell.start_row_offset_idx`, `cell.start_col_offset_idx`
-
- ### Example Usage
- ```python
- doc = get_docling_document("My Document")
-
- # Get all headings
- headings = [t.text for t in doc.texts if "header" in str(t.label)]
-
- # Iterate with structure
- for item, level in doc.iterate_items():
- print(" " * level + item.text[:50])
-
- # Extract table data
- for table in doc.tables:
- for cell in table.data.table_cells:
- print(f"Row {cell.start_row_offset_idx}, Col {cell.start_col_offset_idx}: {cell.text}")
- ```
+ 7. **Cite Your Sources**: Use get_chunk() to retrieve chunk metadata for citations. Track which documents/chunks informed your answer.
## Example Patterns
@@ -1019,23 +897,31 @@ interactions:
print(f"Total: {count}")
```
- ### Aggregating data across documents
+ ### Extracting data with llm()
```python
- import re
numbers = []
results = search("financial data", limit=20)
for r in results:
- matches = re.findall(r'\$([\d,]+)', r['content'])
- for m in matches:
- numbers.append(int(m.replace(',', '')))
- print(f"Average: ${sum(numbers)/len(numbers):,.2f}")
+ extracted = llm(f"Extract all dollar amounts from this text as a comma-separated list of numbers (no $ signs): {r['content']}")
+ for part in extracted.split(','):
+ part = part.strip().replace(',', '')
+ if part.isdigit():
+ numbers.append(int(part))
+ if numbers:
+ print(f"Average: {sum(numbers) / len(numbers)}")
+ ```
+
+ ### Using search results with get_chunk for citations
+ ```python
+ results = search("safety requirements", limit=5)
+ for r in results:
+ chunk = get_chunk(r['chunk_id'])
+ print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}")
```
### Using llm() for classification
```python
- # Get document content
content = get_document("Q1 Report")
- # Use llm() to classify sentiment
sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
print(sentiment)
```
@@ -1063,49 +949,50 @@ interactions:
- content: Using the pre-loaded documents variable, tell me when was the company founded and what is their mission?
role: user
- content: null
- reasoning: We need to check if 'documents' variable exists.
+ reasoning: Need to inspect documents variable.
role: assistant
tool_calls:
- function:
- arguments: '{"code":"if ''documents'' in dir():\n for doc in documents:\n print(doc[''title''], len(doc[''content'']))\nelse:\n print(''documents
- variable not found'')"}'
+ arguments: '{"code":"if ''documents'' in dir():\n for doc in documents:\n print(doc[''title''], len(doc[''content'']))\nelse:\n print(''no
+ documents'')\n"}'
name: execute_code
- id: call_j4wr5u97
+ id: call_9n7burqq
type: function
- - content: '{"code":"if ''documents'' in dir():\n for doc in documents:\n print(doc[''title''], len(doc[''content'']))\nelse:\n print(''documents
- variable not found'')","stdout":"","stderr":"NameError: name ''dir'' is not defined","success":false}'
+ - content: '{"code":"if ''documents'' in dir():\n for doc in documents:\n print(doc[''title''], len(doc[''content'']))\nelse:\n print(''no
+ documents'')\n","stdout":"","stderr":"NameError: name ''dir'' is not defined","success":false}'
role: tool
- tool_call_id: call_j4wr5u97
+ tool_call_id: call_9n7burqq
- content: null
- reasoning: It says dir not defined, maybe need to import builtins. Use __builtins__. Let's just try list(dir())? But
- we can just check if variable exists via try/except.
+ reasoning: |+
+ I used dir; but dir is builtin. Wait error: name 'dir' is not defined? Actually dir is builtin but not available? Probably need to use locals() or globals()? Let's just check if 'documents' in globals().
+
role: assistant
tool_calls:
- function:
- arguments: '{"code":"try:\n docs=documents\n print(''exists'', len(docs))\nexcept NameError:\n print(''not
- exist'')"}'
+ arguments: '{"code":"if ''documents'' in globals():\n for doc in documents:\n print(doc[''title''],
+ len(doc[''content'']))\nelse:\n print(''no documents'')\n"}'
name: execute_code
- id: call_bclp2lct
+ id: call_hiiwsnky
type: function
- - content: '{"code":"try:\n docs=documents\n print(''exists'', len(docs))\nexcept NameError:\n print(''not
- exist'')","stdout":"exists 2\n","stderr":"","success":true}'
+ - content: '{"code":"if ''documents'' in globals():\n for doc in documents:\n print(doc[''title''], len(doc[''content'']))\nelse:\n print(''no
+ documents'')\n","stdout":"","stderr":"NameError: name ''globals'' is not defined","success":false}'
role: tool
- tool_call_id: call_bclp2lct
+ tool_call_id: call_hiiwsnky
- content: null
- reasoning: So two docs loaded. Let's view titles.
+ reasoning: Globals also not defined. Probably environment only has defined functions. We might just attempt to run
+ list_documents to see if any loaded.
role: assistant
tool_calls:
- function:
- arguments: '{"code":"for doc in documents:\n print(''Title:'', doc[''title''])\n print(''Content snippet:'',
- doc[''content''][:200])\n print(''-''*40)"}'
+ arguments: '{"code":"docs = list_documents(limit=10)\nprint(''docs'',docs)\n"}'
name: execute_code
- id: call_jzdp8w33
+ id: call_yc5ml52o
type: function
- - content: '{"code":"for doc in documents:\n print(''Title:'', doc[''title''])\n print(''Content snippet:'', doc[''content''][:200])\n print(''-''*40)","stdout":"Title:
- Company History\nContent snippet: The company was founded in 1985 by Jane Smith.\n----------------------------------------\nTitle:
- Mission Statement\nContent snippet: Our mission is to make technology accessible to everyone.\n----------------------------------------\n","stderr":"","success":true}'
+ - content: '{"code":"docs = list_documents(limit=10)\nprint(''docs'',docs)\n","stdout":"docs [{''id'': ''57c6d445-39c8-4eb5-8a5e-b6c2c3096f66'',
+ ''title'': ''Company History'', ''uri'': None, ''created_at'': ''2026-02-17 15:59:48.355566''}, {''id'': ''19662d84-5066-402c-a6c1-6eb0e1e1d715'',
+ ''title'': ''Mission Statement'', ''uri'': None, ''created_at'': ''2026-02-17 15:59:50.423989''}]\n","stderr":"","success":true}'
role: tool
- tool_call_id: call_jzdp8w33
+ tool_call_id: call_yc5ml52o
model: gpt-oss
reasoning_effort: low
stream: false
@@ -1156,7 +1043,297 @@ interactions:
response:
headers:
content-length:
- - '1186'
+ - '625'
+ content-type:
+ - application/json
+ parsed_body:
+ choices:
+ - finish_reason: tool_calls
+ index: 0
+ message:
+ content: ''
+ reasoning: |+
+ Now get these docs.
+
+ role: assistant
+ tool_calls:
+ - function:
+ arguments: '{"code":"content1 = get_document(''Company History'')\ncontent2 = get_document(''Mission Statement'')\nprint(''History:'',
+ content1[:200])\nprint(''Mission:'', content2[:200])\n"}'
+ name: execute_code
+ id: call_0vdkrldb
+ index: 0
+ type: function
+ created: 1771336798
+ id: chatcmpl-879
+ model: gpt-oss
+ object: chat.completion
+ system_fingerprint: fp_ollama
+ usage:
+ completion_tokens: 72
+ prompt_tokens: 2137
+ total_tokens: 2209
+ status:
+ code: 200
+ message: OK
+- request:
+ headers:
+ accept:
+ - application/json
+ accept-encoding:
+ - gzip, deflate, zstd
+ connection:
+ - keep-alive
+ content-length:
+ - '10232'
+ content-type:
+ - application/json
+ host:
+ - localhost:11434
+ method: POST
+ parsed_body:
+ messages:
+ - content: |-
+ You are a Recursive Language Model (RLM) agent that solves complex research questions by writing and executing Python code.
+
+ IMPORTANT: You MUST use the `execute_code` tool to run Python code. The functions described below are ONLY available inside the execute_code tool - you cannot access them any other way. Always execute code to answer questions; do not just describe what code would do.
+
+ CRITICAL: Inside execute_code, these functions are ALREADY available in the namespace. Do NOT import them - just use them directly:
+ - search("query") ✓ CORRECT
+ - from haiku.rag import search ✗ WRONG - will fail
+
+ You have access to a sandboxed Python interpreter with these haiku.rag functions (use them directly, no imports needed):
+
+ ## Available Functions
+
+ ### search(query, limit=10) -> list[dict]
+ Search the knowledge base using hybrid search (vector + full-text).
+ Returns list of dicts with keys: chunk_id, content, document_id, document_title, document_uri, score, page_numbers, headings
+
+ ### list_documents(limit=10, offset=0) -> list[dict]
+ List available documents in the knowledge base.
+ Returns list of dicts with keys: id, title, uri, created_at
+
+ ### get_document(id_or_title) -> str | None
+ Get the full text content of a document by ID, title, or URI.
+ Returns the document content as a string, or None if not found.
+
+ ### get_chunk(chunk_id) -> dict | None
+ Get a specific chunk by its ID (from search results).
+ Returns dict with keys: chunk_id, content, document_id, document_title, headings, page_numbers, labels
+ Use this to retrieve full chunk details and metadata for citation.
+
+ ### llm(prompt) -> str
+ Call an LLM directly with the given prompt. Returns the response as a string.
+ Use this for classification, summarization, extraction, or any task where you
+ already have the content and just need LLM reasoning.
+
+ ## Pre-loaded Documents Variable
+
+ If documents were pre-loaded for this session, a `documents` variable is available:
+ ```python
+ # documents is a list of dicts with keys: id, title, uri, content
+ for doc in documents:
+ print(doc['title'], len(doc['content']))
+ ```
+ Check if it exists with: `if 'documents' in dir(): ...`
+
+ ## Available Python Features
+
+ The interpreter supports: variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, try/except, and the `json` module.
+
+ Not supported: imports (other than `json`), class definitions, generators/yield, match statements, decorators, `with` statements.
+
+ For pattern matching or text extraction, use string methods (`str.split`, `str.find`, `str.startswith`, `in` operator) or the `llm()` function.
+
+ ## Strategy Guide
+
+ 1. **Explore First**: Start by listing documents or searching to understand what's available. Document names may differ from filenames (e.g., "tbmed593.pdf" might be stored as "TB MED 593" or similar).
+ 2. **If get_document returns None**: Use `list_documents()` to see actual document titles, or `search()` to find relevant content.
+ 3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
+ 4. **Use print() Liberally**: The sandbox captures stdout - print intermediate results to see what you're working with.
+ 5. **Aggregate with Code**: For counting, averaging, or comparing across documents, write loops and data structures.
+ 6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use llm().
+ 7. **Cite Your Sources**: Use get_chunk() to retrieve chunk metadata for citations. Track which documents/chunks informed your answer.
+
+ ## Example Patterns
+
+ ### Counting documents matching a condition
+ ```python
+ docs = list_documents(limit=100)
+ count = 0
+ for doc in docs:
+ content = get_document(doc['id'])
+ if content and 'keyword' in content.lower():
+ count += 1
+ print(f"Found in: {doc['title']}")
+ print(f"Total: {count}")
+ ```
+
+ ### Extracting data with llm()
+ ```python
+ numbers = []
+ results = search("financial data", limit=20)
+ for r in results:
+ extracted = llm(f"Extract all dollar amounts from this text as a comma-separated list of numbers (no $ signs): {r['content']}")
+ for part in extracted.split(','):
+ part = part.strip().replace(',', '')
+ if part.isdigit():
+ numbers.append(int(part))
+ if numbers:
+ print(f"Average: {sum(numbers) / len(numbers)}")
+ ```
+
+ ### Using search results with get_chunk for citations
+ ```python
+ results = search("safety requirements", limit=5)
+ for r in results:
+ chunk = get_chunk(r['chunk_id'])
+ print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}")
+ ```
+
+ ### Using llm() for classification
+ ```python
+ content = get_document("Q1 Report")
+ sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
+ print(sentiment)
+ ```
+
+ ## Workflow
+
+ 1. **ALWAYS start by using execute_code** to explore the knowledge base
+ 2. Run multiple code blocks as needed to gather information
+ 3. After collecting data, provide your final answer
+
+ ## Output Format
+
+ CRITICAL: Your final response MUST be valid JSON matching this exact schema:
+ ```json
+ {"answer": "Your complete answer here as a string", "program": "Your final consolidated program here as a string"}
+ ```
+
+ - `answer`: A clear answer to the user's question with key findings and references to specific documents/chunks.
+ - `program`: A single, self-contained Python program that produces the answer. Consolidate your exploratory code executions into one clean script.
+
+ Do NOT return arbitrary JSON structures. Always use the exact format: {"answer": "...", "program": "..."}
+
+ CRITICAL: You MUST call execute_code at least once before providing your answer. Never give up without trying to execute code first.
+ role: system
+ - content: Using the pre-loaded documents variable, tell me when was the company founded and what is their mission?
+ role: user
+ - content: null
+ reasoning: Need to inspect documents variable.
+ role: assistant
+ tool_calls:
+ - function:
+ arguments: '{"code":"if ''documents'' in dir():\n for doc in documents:\n print(doc[''title''], len(doc[''content'']))\nelse:\n print(''no
+ documents'')\n"}'
+ name: execute_code
+ id: call_9n7burqq
+ type: function
+ - content: '{"code":"if ''documents'' in dir():\n for doc in documents:\n print(doc[''title''], len(doc[''content'']))\nelse:\n print(''no
+ documents'')\n","stdout":"","stderr":"NameError: name ''dir'' is not defined","success":false}'
+ role: tool
+ tool_call_id: call_9n7burqq
+ - content: null
+ reasoning: |+
+ I used dir; but dir is builtin. Wait error: name 'dir' is not defined? Actually dir is builtin but not available? Probably need to use locals() or globals()? Let's just check if 'documents' in globals().
+
+ role: assistant
+ tool_calls:
+ - function:
+ arguments: '{"code":"if ''documents'' in globals():\n for doc in documents:\n print(doc[''title''],
+ len(doc[''content'']))\nelse:\n print(''no documents'')\n"}'
+ name: execute_code
+ id: call_hiiwsnky
+ type: function
+ - content: '{"code":"if ''documents'' in globals():\n for doc in documents:\n print(doc[''title''], len(doc[''content'']))\nelse:\n print(''no
+ documents'')\n","stdout":"","stderr":"NameError: name ''globals'' is not defined","success":false}'
+ role: tool
+ tool_call_id: call_hiiwsnky
+ - content: null
+ reasoning: Globals also not defined. Probably environment only has defined functions. We might just attempt to run
+ list_documents to see if any loaded.
+ role: assistant
+ tool_calls:
+ - function:
+ arguments: '{"code":"docs = list_documents(limit=10)\nprint(''docs'',docs)\n"}'
+ name: execute_code
+ id: call_yc5ml52o
+ type: function
+ - content: '{"code":"docs = list_documents(limit=10)\nprint(''docs'',docs)\n","stdout":"docs [{''id'': ''57c6d445-39c8-4eb5-8a5e-b6c2c3096f66'',
+ ''title'': ''Company History'', ''uri'': None, ''created_at'': ''2026-02-17 15:59:48.355566''}, {''id'': ''19662d84-5066-402c-a6c1-6eb0e1e1d715'',
+ ''title'': ''Mission Statement'', ''uri'': None, ''created_at'': ''2026-02-17 15:59:50.423989''}]\n","stderr":"","success":true}'
+ role: tool
+ tool_call_id: call_yc5ml52o
+ - content: null
+ reasoning: |+
+ Now get these docs.
+
+ role: assistant
+ tool_calls:
+ - function:
+ arguments: '{"code":"content1 = get_document(''Company History'')\ncontent2 = get_document(''Mission Statement'')\nprint(''History:'',
+ content1[:200])\nprint(''Mission:'', content2[:200])\n"}'
+ name: execute_code
+ id: call_0vdkrldb
+ type: function
+ - content: '{"code":"content1 = get_document(''Company History'')\ncontent2 = get_document(''Mission Statement'')\nprint(''History:'',
+ content1[:200])\nprint(''Mission:'', content2[:200])\n","stdout":"History: The company was founded in 1985 by Jane
+ Smith.\nMission: Our mission is to make technology accessible to everyone.\n","stderr":"","success":true}'
+ role: tool
+ tool_call_id: call_0vdkrldb
+ model: gpt-oss
+ reasoning_effort: low
+ stream: false
+ tool_choice: auto
+ tools:
+ - function:
+ description: |-
+ Execute Python code in a sandboxed interpreter.
+
+ The code has access to haiku.rag functions (search, list_documents,
+ get_document, get_chunk, llm).
+
+ Use print() to output results.
+
+ Structured result with success status, stdout, and stderr.
+
+ name: execute_code
+ parameters:
+ additionalProperties: false
+ properties:
+ code:
+ description: Python code to execute.
+ type: string
+ required:
+ - code
+ type: object
+ strict: true
+ type: function
+ - function:
+ description: Result from RLM agent execution.
+ name: final_result
+ parameters:
+ additionalProperties: false
+ properties:
+ answer:
+ description: The answer to the user's question
+ type: string
+ program:
+ description: The final consolidated program
+ type: string
+ required:
+ - answer
+ - program
+ type: object
+ strict: true
+ type: function
+ uri: http://localhost:11434/v1/chat/completions
+ response:
+ headers:
+ content-length:
+ - '1089'
content-type:
- application/json
parsed_body:
@@ -1164,24 +1341,23 @@ interactions:
- finish_reason: stop
index: 0
message:
- content: '{"answer":"The company was founded in **1985**. Its mission is to **make technology accessible to everyone**.","program":"#
- Consolidated script\n# Assume pre-loaded variable ''documents''\n\n# Find founding year and mission statements\nfounded_year
- = None\nmission = None\nfor doc in documents:\n title = doc[''title''].lower()\n content = doc[''content'']\n if
- ''history'' in title:\n # Search for a year pattern\n import re\n match = re.search(r''\\b(19\\d{2})\\b'',
- content)\n if match:\n founded_year = int(match.group(1))\n if ''mission'' in title:\n mission
- = content.strip()\n\n# Output results\nprint(f\"Founded year: {founded_year}\")\nprint(f\"Mission: {mission}\")"}'
- reasoning: 'So answers: founded 1985, mission to make technology accessible to everyone. Provide answer. And program
- consolidating.'
+ content: '{"answer":"The company was founded in 1985. Its mission is to make technology accessible to everyone.","program":"#
+ Retrieve the pre‑loaded documents and extract the founding year and mission statement\n\n# Get the two relevant
+ documents by title\nhistory_text = get_document(''Company History'')\nmission_text = get_document(''Mission Statement'')\n\n#
+ Extract founding year from history document\nimport re\nfounding_year = re.search(r''\\b(\\d{4})\\b'', history_text)\nif
+ founding_year:\n founding_year = founding_year.group(1)\nelse:\n founding_year = ''unknown''\n\n# Clean
+ mission statement to plain text\nmission = mission_text.strip()\n\n# Prepare answer\nanswer = f\"The company was
+ founded in {founding_year}. Its mission is to {mission}\"\nprint(answer)"}'
role: assistant
- created: 1771336422
- id: chatcmpl-444
+ created: 1771336802
+ id: chatcmpl-19
model: gpt-oss
object: chat.completion
system_fingerprint: fp_ollama
usage:
- completion_tokens: 236
- prompt_tokens: 2225
- total_tokens: 2461
+ completion_tokens: 189
+ prompt_tokens: 2305
+ total_tokens: 2494
status:
code: 200
message: OK