From 459fdfca71630fb11dbb27d94127065733cfc328 Mon Sep 17 00:00:00 2001 From: Yiorgis Gozadinos Date: Tue, 24 Feb 2026 11:50:34 +0200 Subject: [PATCH] Add get_docling_document external function to sandbox --- CHANGELOG.md | 2 +- .../haiku/rag/agents/rlm/prompts.py | 26 ++++++++++++ .../haiku/rag/agents/rlm/sandbox.py | 12 ++++++ tests/agents/rlm/test_sandbox.py | 37 ++++++++++++++++ ...s_dict_for_document_with_docling_data.yaml | 42 +++++++++++++++++++ 5 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 tests/cassettes/test_sandbox/TestSandboxDoclingDocument.test_returns_dict_for_document_with_docling_data.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index 88c29a76..e6f84ce7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Changed - **RLM sandbox**: Replaced Docker-based code execution with [pydantic-monty](https://github.com/pydantic/monty), a minimal secure Python interpreter written in Rust. Eliminates Docker as a runtime dependency for RLM with sub-millisecond sandbox startup -- **RLM sandbox functions**: Replaced `get_docling_document()` with `get_chunk(chunk_id)` for retrieving chunk content and metadata from search results +- **RLM sandbox functions**: Added `get_chunk(chunk_id)` for retrieving chunk content and metadata from search results. `get_docling_document(document_id)` now returns the full document structure as a JSON dict - **`RLMConfig`**: Removed `docker_image` and `docker_memory_limit` fields ### Added diff --git a/haiku_rag_slim/haiku/rag/agents/rlm/prompts.py b/haiku_rag_slim/haiku/rag/agents/rlm/prompts.py index 92118546..d11a087d 100644 --- a/haiku_rag_slim/haiku/rag/agents/rlm/prompts.py +++ b/haiku_rag_slim/haiku/rag/agents/rlm/prompts.py @@ -28,6 +28,16 @@ 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. +### await get_docling_document(document_id) -> dict | None +Get the full document structure as a dict (DoclingDocument format). +Use `list_documents()` or search results to get document IDs first. +Top-level keys: name, texts, tables, pictures, body, pages, key_value_items, furniture, groups. +- `texts`: list of text items, each with `text`, `label` (e.g. "title", "text", "section_header", "list_item"), and `prov` (provenance with page/bounding box) +- `tables`: list of tables, each with `data` containing `grid` (list of rows, each row a list of cells with `text`), `num_rows`, `num_cols` +- `pictures`: list of figures/images with metadata +- `pages`: page dimensions and metadata +Use this for structural analysis: extracting table data, counting sections, analyzing layout. + ### await 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 @@ -97,6 +107,22 @@ for r in results: print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}") ``` +### Extracting tables from a document +```python +docs = await list_documents(limit=10) +for d in docs: + doc = await get_docling_document(d['id']) + if doc: + tables = doc.get('tables', []) + if tables: + print(f"{d['title']}: {len(tables)} table(s)") + for i, table in enumerate(tables): + grid = table.get('data', {}).get('grid', []) + for row in grid: + cells = [cell.get('text', '') for cell in row] + print(f" Table {i}: {cells}") +``` + ### Using llm() for classification ```python content = await get_document("Q1 Report") diff --git a/haiku_rag_slim/haiku/rag/agents/rlm/sandbox.py b/haiku_rag_slim/haiku/rag/agents/rlm/sandbox.py index cdd7be3f..16c6511b 100644 --- a/haiku_rag_slim/haiku/rag/agents/rlm/sandbox.py +++ b/haiku_rag_slim/haiku/rag/agents/rlm/sandbox.py @@ -1,3 +1,4 @@ +import json from dataclasses import dataclass from typing import TYPE_CHECKING, Any, Literal @@ -5,6 +6,7 @@ import pydantic_monty from haiku.rag.agents.rlm.dependencies import RLMContext from haiku.rag.config.models import AppConfig +from haiku.rag.store.compression import decompress_json if TYPE_CHECKING: from haiku.rag.client import HaikuRAG @@ -106,6 +108,15 @@ class Sandbox: "labels": meta.labels, } + async def get_docling_document( + document_id: str, + ) -> dict[str, Any] | None: + doc = await client.get_document_by_id(document_id) + if not doc or not doc.docling_document: + return None + json_str = decompress_json(doc.docling_document) + return json.loads(json_str) + async def llm(prompt: str) -> str: from pydantic_ai import Agent @@ -121,6 +132,7 @@ class Sandbox: "list_documents": list_documents, "get_document": get_document, "get_chunk": get_chunk, + "get_docling_document": get_docling_document, "llm": llm, } diff --git a/tests/agents/rlm/test_sandbox.py b/tests/agents/rlm/test_sandbox.py index 5a9fb71b..01ecfef0 100644 --- a/tests/agents/rlm/test_sandbox.py +++ b/tests/agents/rlm/test_sandbox.py @@ -337,6 +337,43 @@ class TestSandboxPreloadedDocuments: assert "Doc B" in result.stdout +class TestSandboxDoclingDocument: + """Test get_docling_document() external function.""" + + @pytest.mark.asyncio + async def test_returns_none_for_missing_document(self, sandbox): + """get_docling_document returns None for a non-existent document.""" + result = await sandbox.execute( + "doc = await get_docling_document('nonexistent-id')\nprint(doc is None)" + ) + assert result.success + assert "True" in result.stdout + + @pytest.mark.asyncio + @pytest.mark.vcr() + async def test_returns_dict_for_document_with_docling_data(self, temp_db_path): + """get_docling_document returns a dict for a document with docling data.""" + config = AppConfig() + async with HaikuRAG(temp_db_path, create=True) as client: + doc = await client.create_document( + content="Docling processed content", + uri="test://docling", + title="Docling Doc", + ) + + context = RLMContext() + sb = Sandbox(client=client, config=config, context=context) + result = await sb.execute( + f"doc = await get_docling_document('{doc.id}')\n" + "print(type(doc).__name__)\n" + "print(doc['name'])\n" + "print('texts' in doc)" + ) + assert result.success + assert "dict" in result.stdout + assert "True" in result.stdout + + class TestSandboxLLM: """Test llm() external function.""" diff --git a/tests/cassettes/test_sandbox/TestSandboxDoclingDocument.test_returns_dict_for_document_with_docling_data.yaml b/tests/cassettes/test_sandbox/TestSandboxDoclingDocument.test_returns_dict_for_document_with_docling_data.yaml new file mode 100644 index 00000000..29228880 --- /dev/null +++ b/tests/cassettes/test_sandbox/TestSandboxDoclingDocument.test_returns_dict_for_document_with_docling_data.yaml @@ -0,0 +1,42 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '95' + content-type: + - application/json + host: + - localhost:11434 + method: POST + parsed_body: + encoding_format: base64 + input: + - Docling processed content + 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: 5 + total_tokens: 5 + status: + code: 200 + message: OK +version: 1