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: NJvFuNTcdTwqa4g8Ux4bPWmPr7naNE49b1GFPVeNlDy4qHQ8U6yVPHxaPzySRVA6T4UEO3yByLwG0Zg8uh8rveBr3DsE9Ms7m2nIPPlR2LvozyC8H81zPTQb2jwk4za9txzLvLWq6rwKbtW8jKClvSa7vbzfYT08Q6/9vI6fi7zhr0I9hfrwuz6UizkreDC7Y/L2O0vEx7viA3682DbxPGNSDD3Wxai80FyUPLFQmzuUrfm84k+NPItMNDyuAq28gv3lvExsn7wfP6U7DP6jOxlFKL0mGey8nhXAO2m8EbyU+wk9pxJ/u1eYG70DJvq8731suu5P5jtf6xi9A++kuyW9z7vUUOS8G3rQvOlYD70mlSs8LZXjOtVIxrogO9g8Y6jHuztSabzv57o8xDawvA5mbLz9jgs9fNvmOURCBDxlMe88Wx+qu8tp3jr53Sy9cRqgPBK0pLyYJ4q6VnMcu0f//7wvPSe5IuGaPAoBozy3crg7WAqEuwBobzwvfb27ValEvGsRwLwfqM47rhfHOxoCVrw8biG8PfePvPOSrbvVt8S8yiYIvQ2QWrwZlEy70uP0O6pE9js4ASa8f80yvO69OroyTCe81Yh7u/XPArxZIuY8273MPJOAIzw+frG8Z+IevC2yrzynOf877ThOO/u6RjsDjJ48cOeYu+hSjjyiCR67QR8RPVNenzqIhpK7sJCBPJoQkrwwofO8IbZIPJbGq7zttW66AIWGvJ+TWjzWtzS8bWAaugJ3+DvJ/9K8wG7FvMPDJ7x29lo8/NsMPIOxHDy9GZG7A4rJPGCJ07viRk88YBVLPBYfTDwgsuE8aSLrOjYoFzytQjA5AwXhPCGuiLukNxg8iykXOF9aATx3tQs86PlzPKPGGry6m7Y8WibPvB1RU7zyFZk7l5wOuyYjabyicT+80yNivLa3cTw2t7e8JYhaPCvT9bvj44I8Ib4QvAgbfryIzW+6q94VO9NFbTzdXjs8VnAJvGVPZDzCF1q6ghnUOkFLyLwwsbw7NVSYPJ/eljw9aLO7trAkvDEt/btDK0O8KGPHutpYhTwfwu47yG2OO9FXSTvAcEa8HIE8vMQVETzXW5a8PMxGu5s1Hjxyyqu8hJ0DPc6zero0ALC80+GkvJyjnjxE6KA70SiPvO9CRLzKwbY8oNQ5PXqwnzw4sly8pNKEvLc/qzwAeR696JbhOxhmPrw78WW7KnGAPPfEfzvrgDg9i0ORPPuiJbzDwGm8YkA4O3h9nTyKHom8y5UAvAViHTx9krm7Qh1QPG5S0bsgwCC8KLIRPL2567oE9je8GOofu1D+g7u8Orm8978pvDsU+bsy5588c7EqPBHJOLy0DDm8TcwwPJVknrs8iIW8fb0/OyXICzzU2sS7h4JYPAHMrrxmz587saawu2c7vryhpI48mATQus5ncTwGLCK8aiMhPbCQHLx5qbe7Cmyhu78vOjtuw3W8zn2EusQ3hDwrzOQ7S/MLPf05F73GK8E8bgr4u0mbyrwLHsC7dHOtPKGCKzy5Yow8/hvyvCzm1bvdcG+7bitsvCv8FjyZlp461U4BPP0vObx8CDQ8M7YgvEHGnzvAFKC8/CCaPK8nijtlkJW6BTQMPE0QnTveumU61N5su7xJkTw82q23tsvTvAk577swG4a8QPxOPO6tI7sG+QK8374pvXehKLyGMh48UffjvBuWwrxhg5s7r71fvbMk07yZqG27oaqTPBsrI7uxPYo8VACAPMCE1DxwwsQ7IJLcvCC88jyIuiq9OiwiPPXZdztV7F68hpVIvEKP/zyKQYA8a+AGPFSNFzv/eqw67h+ZPOPX3Ly2vRO9skc7vDlnEDyAPtU72NKRvA2Gf7wp3NK8En8NvZFQ9Tnnps685zOGvOxNCj2IsSe8EbhzPGtxLDwWhJC8/Ek3u5vKczwRQj28MC90vLxiNzmkELS7RYwJvHjMEj2OVoa8NrVXvP3hHTw2FGa8Jk6CO6eK87x1jRy82s5RO6vSSTsfJyS8B1X5ublmsDuYuBY9gNvYPKVybbynYhc8QIu5vD6alDuCX9q8kxM+vMwahTyhASQ969QRvIc5Ors+MXQ8WuhFvEqMTrvXZfw8WmCAuznDGzy8ae27UbyYvBbTnryhe028xUP6vFTd27xDC9e8zTDRvITqDbzoyio9ioKhuteHpztDKaS8QXvDPMxAUTubTJS4bQi1vDKUyDyLPrQ8fMRLu3eRFLwyb7s82M74u1Xo7rsEXJM7i/5WOaxGKbvLcVU8aboiu/pGEzz4HwW98dSGuxHWJbqI/ug7g7bcO+GAbzygfyg8y8gJPaAPOTzafm68ELQVvPzB+7xk2i07ureEuy1vkTzH5HI8QFAKvW5yMDt8D4U7rNeyvBeNjj104Ia8XqNEvKcwPbxoGHg8CB8/uzLDBr2AHGU78btCPHDVy7vPwgC9HgvDury4nL1gsBA9R3IuPDT8lzoXeZG8QVC6vNkh07wiA9g7R9J1Ox7EDT327Bi9gEq3vKhPBLxxRKo7AUaPPM5gyzw2bgU80kbTu39Wpjx49Bs9Q7ILPRHPN7uvqS88KgybPIrMqTrA4eU6Z3ivPAE9jjwbIBo9bIP6u7bj/TuwFsA8O3cEvfUDq7vRbSQ8UXzGO2iqNzziwy69qgKLPFi2ATwpAlI8xgMJu532n7wSlKe8c1jWPMrB8jxFcBc6+pebvF9Opry3Ftg7agDtPIJydzvtICq8V5javLmLKDzi6DW8V+SavFDXjbpN3Z48pYgvvb2AJLxSTS+7VCp7O+2Sj7yeNMG8CHEYPQbHWLsU+Ew649PIO7M2NzxU1Yw7soggvFJ2D7yTQdM8OSlcvVfZ/rtZYew7i3qyPPWzo7vFRg28xQAWubGCBDyXCfY7IAa4u8UZLz0I8CY9l/McO4cYEL1LIXi9rHmiPPMf9zrdFWq8+F8EvAK8trxvGZi6ZnpKPavNc7o9UW88RvBjOpe0TLwRpgM9iFH4vIokXTxJQ9o8ixk/PA7X37yGO9Q8stC6u3Cyqjw/yEm81nvAOu7Emjzzrlu8eefXu5EurjwJACK9eBG1PJnZy7qhI328bnFYvEzZ1rsFLzG8EzHdtlxMh7yyZZQ6E66nvPrxi7t7XrQ8S+FxPAvpvjrbbLa8nO5jPGGeAj3G6U08qUzoPBMtWLwlnps8Wzu8u8dHfbxM5PK7sKkNPS9c07z17Aa8FOPXujcQtrzCfxk6SqxYPNh8JD39sXE8oNBgO0fuwTuU3D08suxUvO/hsjxaD8w8J448u8xbMrv8FgW9vroUvDOSEbwxMI28MEoDvbzUXLyscJY8ZO0du4MO87y4i4K8mwS+O5Ga1DmaZKa7BU6PvNxinLuHQjk9xJ0zO3VNXTzLaSC8hRoEvbgNhjzGpJw8pGeUuxucTjxy3OE8QauFOnszvzxkWzU8GroWvaqdEr0yUng6BPwbvT7R3jze1qi73rPdO6ENbbx5kbW8S9FUPHAp5ryHKRU8FqunudDUtDyYlBo8VJ2nu6ju/rzaHGu6NmMNPZajhL2KB4m8UVuavMA5Mb16Wkc9x8B1vGUsOjyYyt27wrCavKJHkLzijeU8rirZvDR3Irw8A4Q7pgY6u0ePwLp1iiS7nvInvL8pXrulD9M73J7rOugvdLuHh5k7cWgTPM/rlrt4FNw8iE96vJGu07xsPIk8jCZfPDUjvjsoccE8+24gu9DCfTx3HCi9Jw3KvC5fRLxEwHU81LctOvQROjwRjwS85CdoOouMNzwmt1U8/rTJPLRW5zzZrtW8IAlovVFQGrvLO+a7Ahq4OxtM17yTeDq982BoPClpn7uvJ4470EijPHDrY7vAfd+8Y+2fO+uNdzy7N0Q8v2olO5Z1tbtNEYU7b1EyPR6dxjvyPzg78SzeO4aKzDzejJ88lg3gPICX+ryjFNc8iQ9sOh0Mc7z1aO07W2fsuxVtrjvsCgY8fyEyvPfZwjxZN7m8XED3vFWFMrvwKHi7L2e/vOkyhDvWJ6k7BIKavK223LyPHLY8rD//Oj+egDvbPrU8q5i+PO4HmDyh1Dm74/CpvDvOuzym4sW8DIe9PAe8WTwCvwS9KCMGvelfpDt67mi7i2zOPKjEbLz1coe7FrgWPG9DzDz13x69eRNbu7lzujwqaag7n+YROaYWwzuLOG+6YXUtPJhRlbsV2Uo7BGsdvdNhbjuXzoe8rjr8vH6OxTvyahY8ScuxvNbWQ7p/9zQ9KnBCOpRI6LvjZ4u81oIKvV5pm7yxYye7AvGUu1rlnzw5tz09NdxSPNDOxTwAHoi7LHUAPAs1bLwIwLM72U+0PDrdOr109/67lmBLO0O8abxkbOC8YdbRPCaJOjuxB9k7k1gRPPKYbTxku1g8uIfIOwry2LtxOAc9tO/0vAp9jjt5aAO9dzyCO3Kcijp3NwI8G30avCINvrxDZz48NTUcvIKqpLz7w5s75AZVvZpEh7whydm8BpIevFXRxLslAR09WGyQvMKFm7y5C2s8CUa3PNpTtLt4PJ68NZQRPXoTgTxGK4M9w7E8O7h8Gj2Y3Ou7xO+LPBkXRz0bjIg86E5CvMrHNLvhuHQ46GxEvBX1XbwO4B68ipGXPEYm3rupNmo87JmQvDVDSbstemK9CcI4PX7mpDw7wTM9pFoKPEazgzxP3yk82F87vJI25LwL1Ca74Do6PE727DyXOm68pjUrPbXY0DxZceW8jdU8PfPUHjzeFuM7n2onvHD8Grzrrr+8zPTRPNKz3LoNjzc7ZljavMvxlzw8H5q8/LhKvTsP0jyhko+7jXdOvJFl/TxWCkq8LV/PPOLrZj3YGvq7hemRvOKz4juO+hW7LYZqvOrssrvnXPq8A7ZVvJdyATwwKem7SXHzuzXd1ztyJau8Dly9vJSfmrzYkK08VXSMvLJV37z61GQ8xS1NPf+Fjrz17bm8iN/VO4kkkbylDPM87Q0LvZ0Ib7us/pu7UxBhvMow5btO7GM7hLsBPaWK/zyum2I8EhM5vF3CDLyCYh2895nNu/lWMTx/WA+8xR7lPJJJFD2mbIc8y3XEu6BJ4TnuAnu7VHUbvRTD5zsCHxw8DIWHuzBEfbybAw48d17QvPCpNLz5zqU8mWz2vExq5zvITSa8qnLrPGEugjzj9vC84cozPBVwvTttS8e8FHy2vFJ2IrrIssW8Jy+quvair7qUH5o7kYt/ui/lSjw2pe08LdrgPOhjJj3ZcA29w0rUu7etc7xC1x+8ypRovFixSL3dx1A8juG5vAndVzzmzRm9Q8k3O69LOzzKIj08nsUHPSPzVLw2Jwc9SftkvA3XujzF2IQ7puSzu/8Hm7tU97U8bKKIu3Xw9bzTK5W8QuB7vClEFT0DfEU8cr0qPTX4jLyGcxu81Nt8vGjU0js7LZi8HvyFO1WN8btQdeu7vhUhPbOqDr3NLqo8+5rSOTFqQ7p9rUc7iimAPKi5jjyrYTy9iTCdu3/GCzzbtlU8lJKXu5NPnbphSTU8GTo2vJ9dTzxmgsQ79/LqO2SQebhq3gQ9ov0xPLmSULyWc4e7Q7dwu3XcirtzQlE7GQOqO0+UjTxZ5Fw8PKvQu9W0yLyXmNg8dbScul2a2zzkSYy8mofHPFmUl7yCniQ8zccBPMXjtDvHsVs8iQfkPFjqgDp6SJy8IUSPvLWwmDzPlBe6wL0GvYLe0btN1Ho8O3UKvSghYDw/ZRm9l3doPIewjrw/re47j6KgO3R+rrrpWOM6jGs8O5KVL73mOji911VHvQcJrryBJZy8yh6SvOhriTzN4xS9yrKHPNNdrzt1riS6lRw6PWjmOjzghTq8vTzPvAHktTxNvyk9oviNOoih57zpcVE8FjVkvBTnijxPjkk80asIPS9Lzzn4V7Y8mjOUvGEsubzwE8u8Kotku/+pL7y1/yY8CKFuuzzI2ztbwW+7G53hPIoZgzx30QC833yDvDnqsLwSppm8Ocr5vMJ/wzx0QiU8gtGUvJouezuNE+S8pKL2O3gnMj2h9zE9naFHu7tiuTznzqk6mfYCPcf/WTyRAiK7F0aQPR9iBj26EhW9ydyyvHVfoDy2RwK9lmT9uyuvNrzRQrI8oEfJO/O/SjwNSiQ9hqUtvRp7XDwUVpw8ohfbvCmOF72tCk08NhC/vA/PmzwN0yE8kGtovJvRkzu67XE8Kv5PO/+1XzyciE28s0x1O+vugLzWpc48aKfFu+JLEj1XvHC8tx2Uu10Fw7x335q8fCbjO74nr7xkrwM8uvrLvJSrcbzVrcc7ue4DPdEBgTYhG5M733QlvMNy6jzQl4C71AGJPKsH97zUSac7QFPVOor67jyAsGw7LB9jPAxgJzyNIsI8MuSNvBdtRbyM01+8iChDPN+Nprxmc3Q8CcsBvW77ar2m8C+9twjNvGIEAbxf/dw36D2bO4FrjLypoBU91oEUPFNAPTzKiZK8JHhjPUTZozwH6wU9sJgUPPxjdLuBRyC9Oo5NPPoErjxYVne7nMq5PAU5kbyNia68npxrPJiuPruPt028uJS2vGRQz7xUsTm8nzDROrD8RTxiNkK82I0WPY2ZizwnQzk99jijPJOOnLzfD7879uo4vLU6mbwM3WE8FvGxPHBHGjx40Zo83kddvPe4oTs8wDg9qTBtPMPaxbzNllQ86IurPBlQobzCkc271ofzu7Ygl7s8nms8Y/JDvBygnjuR+K88+TKfutothrx9iJI8MxcbvXHKJb2+WQS9tUzxvJFI9jw37ta58zmYt7o2gzr62Na8reE6PZMS5zumHhI9TRu9vPMCFj2NWhY7pKTvO18qy7yE1WW7FabPu7L0sDq1lu08lAAyO1PxwroCS7S7Ka5CuuUSrLw5ei69/LxvPYzTkjqKLAm9CSoqPJ9KBzxqENu7h2T4vF9mSjy2cmE77WKWPL1t27zA4KY8nHcpu0L2oLw8gs07ksQkO8E3zrwJEyE6FRcwvbeFMD2krZs8+OygOXjeuDsD6AY8F5O0vCOwvbyNt5g8A9/0O2WO2LnKSHC8s9TTvNTHCjzwBJ67ovYTPJxqELyeg/A8vRMZPSXrjzxQdXA8nhsJvOFAh7vzLv+7AzapPF/EQLxxXqQ8d3aqvLkhyjydqbA7DOGcPEttQzx3wmg8shccPcS3nbxawMW72tVsvBypE715R327G1LjPAdh9DtBXua8bT/YOwmEgbzLMk+8ZSP9O4Ip17wspgI8P/8YPRX/QLy04mA8LwzJPPmfUjuCl9A7zrYCPMh+yTz84Sa9hBbmPNp7KDw1Yg69taqwO6LSqTw3fwm8czVzvKy65Dv6/rQ8qZsSPUeFHbyBZVy8wjcRPdngTrts2gG5O6Cau2Hyu7x6gW+8rge/PHs2V7xXqjI8DznevL0TNT3JUXU8SeXpO1NTxjyiM6k8tRkCPQqm1Lzebys8QA2WvMNdCzy9Q3U8z5Ghu42tBrw6QD+8j6vVOo0/y7x6V988J0OVu0HBwbxkLq87jvlLO3sjHbxx6Ba7+TgGvEUXEr2nAxW8ll+ivLbwKT0D0eC8r+bFvLtVwTu+1/e8O3rXPPqTxryb6nY755UevL9pBT3WmpG8KnMqPHtASLxaAxK8cS/yu7OEwzwIb3+8CJObvAWM2DvQkzi7oi/WvPd4R7wTsWS8yaT8O40h9zvAN4A8QyqMuqxLEj2MWc27bdyGOhe8rjuRX6k84sIbvCCfubzU/xY8M3ftOlmr3LxHiPY8S3cKPM6+T7wunkM8Xrj6PD7zw7z5sVi7JkXXvLE11Dxm5ug7h1iXPGsPD7xMgJ27jPcRPMc0xrxXbII8lCJXPdhFxTuAFtu864Jqu2WZ3LvejS+9TaiIvd4QEj2arDa8xS4QPC3B3rsVLsc82Yt6OmOnT72iJcA8DZbGu/wwTLxkZNs7HvrXOw5GKjwwpD287x/QPMGwqDwSmg28y+acPDmHHjw5qp27VNwmvF2EljzFX5K6zkzOPEXy0zzseQ29XXq6vG5y5rn3x++7iqZpO8ZQjzz5mbg85KmJuzygXTygmOC7qUZOPCpgMrtIyi876dKbu4JhWjwi3bO8GL/gOmK66bkt0VG8/H7nOzVsyjpLejS8hAojPPtoAD2y/Q66/JulvAFYKrx0Vs28XX8RPN+5A70/zLE8drNlPKXeQzz23GQ8/RkNvcSVTrzKHtU8CFVUvKuZDL1FXYk8KdWPvPJ1hDyoJKc8sd2XuZaLfzz1ixO896LRO9HN8Tz/qW88uprfvJg2EjzMr4M8bSYbvDZJ5zriWWQ7n2MrvCFwHzwtuJE8dI0qvA6INjpQkpw8x4Jtuz/BN7u3f9Y7IHf0PED7Dz1yFA47SoGyvBPTurw7GOo5MZFvPIMZ5DzYEg88ICOPPFj9lbyEV7M8++0hPDIFDzx0rae6IISQu8teZLvYxc48NHGJPLI+i7tmU4O8LBCEvJT3T7whzaK8c7sRt51hpLyGbo88MtwvPXlRvLwPjLa8jkLUOgglUTtOn3E8vWysOxY/JbzF1+28ElgZvK8HR7yR6lU9g2oJPCrOrzuDuWi8aeQRPODWjjz/i5q8sbfPu0fo2jtBXXi7Fyjcu033ILteDf48rtVOvL/qwTxlbxU8KHEdPKXqCTvitFc7qkkQPTWtNrzKf7S7XJVsPDe+O7uiEzq8gZb7u48rzTykZ3O8W6GBPNWJAru43TA87uRBvUHlHD0GmXe8m9zNO3QjxrtPqx+9/z3OO7kiM7sD/js9JUmGPNMkULwNbYm8Hf6fvDcemDy4nAe9tiqNvMSmkjuH4bg8rJMLO3WTxjzq5SK9gcXwPLvAWTx3dZK8JzdqPJG0LbwcHko8rZ81PLGcljuld5W8FpzavAaQ3LuKCZe6zhA0Pb/HQDxBhsy8D6WFPATOiruxzxO7SHMmuwcUtrxPa8m7BgnQvO/FRToK2Q89R6W9vGFLXTgmYLY7K+6GPEY02ry/njU8zDBxu2Q8v7z6a0W8MBDAO9fCJLv/QXO8av6uuzeEZTxSKkG8mcmdO9FNjrx8BBw9fRBevL6D+TsWWx+87VWIO7yBWryXvlU7lzcYvMO/qztEfTO9migbOzGUUbkg5Au9VnkMPB4njjnUSjG9ZR53vDug8Lw4UYI73zouPI6qhzzcTLS7gmsFvAKknDxEwHw7xvVLPAO3tzxMSiQ8FWeyPEMRDDtpVTM9fuMLPATOw7s3wzY8hoioOzOxvTzwFhM8xBKZOvGdmTwhqww9O3qRuvXYPD2rP2e9vzO3OyEd1rwK1m46FFCoPF4dkbx3/8i8Q854OpPeRjyfghU7ckzgPDd+/bt0G588EFVDvMlIqzzFfUs8odCFvP4ifztTyoE75fElPAOecLx/Kxy9aCoMPSPgAzwYxZU89MzivE9WmzsB1Ii74pKnvLqrI73UmhK7HrR3vIXC1rw8A3O8yjyoPKJZaLzBXPC8YqqFvJ0WVD1s84c8Tr+UPCAFATyJ2J28cfOkOuQF/Tx18+K7C0jovKhH7DzoVKs8hF74vNooA7xcQwA8El9dPCfBLDx4FQO8ejJ5u0pOQruAYBe8DBQ+PbdSZTpEuy69G1yWPMlvvDyOjZc7TxY3vOyPmju39+A7p19tPOlKJL3LS5c8ZqoAPC0eSTvfyMk8QUWUPBZLqDuUe/A8lGQ4PJTDMD1TLDs97qTjuqFDFjzIcT87mjWgu8CL9rsfXoU6Hhkyu3YhJrw+xsu8esCQvDD677pGR6e8lD3xvEnHubzNGww9JM+uvNvA2ryCeTI6+riUu/Lw5jtTeie9Nrz9vFzlAT0R9Nq8FjW9PMXYxrwK5/A8bdlEOyq7GjwonXQ8SstmvL5xrDxqAMG8JLQQO66+e7yV2iE8Q0UDvScLvzzSnnA8aZu6u/0/PbxK2w47oSC2vA2fYbsT3wa7WZ6EPMarpzxUBUi8DQ2qvKON4Lz54V+8ows6PdQOAz1lgLg7NoeFvIQ16rwhpni8T0PKuQwOAbzv0YW8AXYjvJIfQbzw2vA7kcKHPOFenzyijok7gWvCOy58Q7ySkKk8Dq37PLtYczzDqL88QBq8PGshsrsr9xk8nis7PWyfcbxFfKW8lKL0O2rc6TyxjyQ8WR6PPBBqpTu8lPW7lH6vuN1m1zzPNxm8kc/mOv/dUrx5BQO8TEqMO5wrCjzmh2e8EMhWvBJpzLsEASK7T/GTvGKmc7q5Lgm9uCyCO4ir8jvoHqi6tdgEPCRwczxvgRC8X8ouPF40qLxZr1a8bZtPPFfVqzznXPc8DqLru8Hc8DxX9FQ8jOe0PDUO3ryt5/M6elIDO74/xTwiJgG90InqPN/1ELxFXx68ejUNPObDPDzJGK+7KlbUPJf3HrzzlAC6CBA1PaUktry9vs08snmuOyqXqrww57i8MSyZucHulDkZZi88IO7JvGRTwLqQ5128z7l1O5dA+Lsjxqo6Q1rfO5ru1bxCYOC5DEx3PC9yvDt65jQ83o0IvQg0z7xbo7e77a7gu0TOBj3HLUm8nbLpPPzZMbx6mx08qNoau+FcBbzoqhi8QHCXPI4JejwCF+O7/oZ0PKGXzLt+RZG8uTnPPPWT4zsn1wI7TZmmPCOrAzw4nwM9rZE2PAVyortOz7w8SHsBPQyXK7swhYa8jWXyu62g7jx+DJc8QEdgOrAFrDy2LQA90EKzPJRIKDx/rDY7L5EBPBrXTDyS+ZU729ZovNCtD73zFWu7VRItPCJhLbzkAQU72t6QvJ7nPL3yeta7pYuSvOPjzzr1DdY7vsUIPatg2TyS/Ei8wU+avGpXVzzhMlw85pf0OypQBb2y8Be8j6cevIRIVzyaWjK6o9j0uok84Lwob7M64lMGvDQjTbyz5ZY8RHfOPANnZbzux3m9+K5DvIH95Tzl9Cs6QupYu2L4dTp8B6g8gM36uqDn7rv+VPm8zS5BO65jALxMLvS6fj2OPDA2C7xegGY8J+m+uwi5RbyH+dy8+1gVvYnoBD3cdIa8f8Awu+hHtjsAGJi8OUwkPY+1jTw7rh+9QL3NPKQ2pbwm4y28u+SOPHF66Dy5Sxm6kgWvvC/kDbkKjQS9qNU1u4GPyrwTjKU7G+ODO9R3KD3LYC+9L24mvGFnv7vyaGq7/NGdPIecxDwDA9u7/MuivAMR4LxcahO8bUdIO+nPBr2ZGpa8VgMkPC63LDx91/E6hhTWOzGcczxCBwu81AYaPKjVkTyBvgO8LMpgvMIQmzt6K4I8JE+QPCKHBDzeAJU5uX8HPXJ9NjxGrP67JHu4PDfTwrs6Aci8xqUePFpxcztf1V68P0qXPLZVrTvzpvm8cVnhukmQiDwGcWm7530bOom7Br3836w7x4cjvPGsqryoKis8HG6wu6umDbtY9jC8l+0pPC5JMzzCVJo8W5W6ujwY77ue8Ui8ERBMPE/UAjynJg68PXLhPL+qnDtQMO08yE94OR0mPzySD6G8FCmwu1DoIz0m7no8lup/u1K7OTxG9NC8fOzAOzfdt7vIJpm8+BLjPJsRC71hMyA9w76EPKXSCr0B0LK8C+pWu9xotzsDo3y8RVwrvf8cSDu9/pG8XfekvC+VYbvsptg8xGgju4D3pDyxbwm9TEecOgE/5Dxx3dQ80+LuvLf1lbx0KRm8MlICPHV2AzzRxDi8A/4ivEDKKb3oZDI8VZehvKnumjwY4S883q+6vJGbmzxI1dc8kByYPG/GrzvvWz49Yr+VvLvwCL1SZqW5VoOVPL2uMDyaSwK9R/9uPP5y+rw2QS083kcvvGsmuDyAxw+9J2EHvZxNkztZRQy9s8RKvDnHO7zAMYy8ubaIvIKpPb3iS6881nquvDWcxbvKzgO8M/wIva/CnLyu9ga8cOn0PJC57jzUs/g7VnOjPDyADTzDnRG8cXrGPDFDSzxxZvu6+G7FuoBw4Ltmok088mNiPD4XAL34WkE72zGaPJZg8LvmPIC8+K/Ou5jU1Dy3WnW8ElTEvB7Z7LmxeKi8JaHJvNdqpzo59zi7TsP2vBxsMrzzB767xQB9vY173LxS0Ea8EN+jPMQb6rpjz6K7PuMZu3pAAzzNMFQ803krvAkgMz0M9c07SDjvOSsyZD3F6R48i97MO3ixSjzjbCu9tw25vFjfAr0setG8cLzguyxIiLwg+JW8QwaNPFnLBTsbHrG8W5YovEuTArsmeGu8SjSIu+SEkTxW9rc8DUWYvD8PlLyhfiO9JoarvL8parwA8ao8a7fCPO3U8LvXUfc8l1cfPaYfaTs4LQw80dVEvAz+yzw2BmY7OkvjvJV8Ervjmi08XjNOOgmIg7tg17U7LjZFPJpLtzxQCe+7cOGUPCAlRr3Mlpk8yb38vKiB07tdUCS80NX2vA73krzH81U8Y63IPJJJVzxaDFA8inMfvQu/Hb0VVKc8E2AcubhubzxfEOS77mO1uMKhZTyzKfI7T3BTvLOzX7vZGd47pCp5vFhmP7xWDTu8JwI5vIAtT7tP4qc7kXwYvATxiDy077e70GaFvDcfjLzACPO81CYPPYkOfrylURw8E13tvAyXgzw2jMi7SZaTvOkMiTqJc3G8pK4IPJelDbzqT4+8LSiGvGx5mjw5/js6wspAvEALubw1TcS7STzBPHfhLT2EHqA8LJA+O1YVhLvIv7C8Lk23OpK/LDxCpjG8ATAkvch0j7w82o28xTGwPM7tx7zPVQC8oJe4O1OtMjz/m208SnjqO2I99jyeTJg85jaZOsZ0qTxscxk8TC1vO9KQgjxFzZ486VBKPLS1FzyFOCI8JrZ6uxY84bwxKBM9U+B7PDxat7zEZ8w7m7hjvMTABjsIZj28q28TPfZ847yxQLM8hzQSOvzA/buxjnQ8JbAKumIrL7yhHYW6/9gJvOs7BDxHjuW7j9GqvHrauzz/ZxG893o9OzHw0zxijPY8aRe2vK41Gb3qYta8BqHpu4atH7uDD6I8T/PQvHsQ/ryi5cU7oy6lvDx+3btD5mo7wYgEu6nrqTsbNrk8uVLwvGQT2DzIMcK8bcFIO77vxbwkNya87JTGPPMjdrnX98+8BlhAPIhnJjpqZrY8O+kWPX8O9zvJjPs7CsVWPOUtODxoRLm7jReuPBkiQbwZdLQ7qQeYOzvwWDre54y8CaAvPI1lijwwimc8ld4Wu2gcg7zB2p08x5FjPJlzrDzMsrQ8kOegPLiquLze84+8YfeTPIK5vjzi6Qw8x8GPvOt6Vzsy+UU8ctHwO2e8YD2DPrG8PfGBvPlO2rw1T2o7avbQPPGyKDu3PhU7fD6VO41GcboPLxO7F6uevEkLs7rGTae8hr4xPKVtXrzgzBI9NUi8OxCWSjyDjJG7aJGtu+NW17vPNi68Ci7IPHsJIrxkUE48mKqRu+pZDLx+gJG8XwEtvDWo6buB3XS85B1wuurp5rvAwIK7szp0OzLn5bz13qA848vnvHankTxAZ0+82ms1vPVnLLzrG0a8rKoQvGDT/bzBNdI7aVneu8gNO7hTF4W8lpnjvNt7wbu36eY7BVK3PH7U8rzibKw8c+OnvPEGb7sH5/y7T9t6PG5xhztQPTm8T0mAPHZZizxBbOc8u2FPvLhuGbyXfIm8C19MvF5V5boUwd28pvkQvF2M/ryqNb88w0YkPA98m7xq7SG6WzEUvBq5vzz6Cjs8zTz8O1wyk7zhW407QXznuw== 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