From a45820dbf7bcc6639af6760143f7345ab02e45d9 Mon Sep 17 00:00:00 2001 From: Yiorgis Gozadinos Date: Thu, 16 Apr 2026 15:29:53 +0300 Subject: [PATCH] add document virtual filesystem to analysis sandbox Replace get_document() and get_docling_document() with a VFS at /documents/{id}/ with metadata.json (eager), content.txt (lazy), and items.jsonl (lazy). Keep search(), list_documents() (now returns all), and llm() as external functions. Co-Authored-By: Claude Opus 4.6 (1M context) --- CHANGELOG.md | 7 +- docs/agents/analysis.md | 41 +++- .../haiku/rag/agents/analysis/agent.py | 4 +- .../haiku/rag/agents/analysis/prompts.py | 153 ++++++------ .../haiku/rag/agents/analysis/sandbox.py | 141 +++++++++-- tests/agents/analysis/test_sandbox.py | 218 +++++++++++------- ...uments.test_list_documents_with_data.yaml} | 0 ...rch_returns_doc_item_refs_and_labels.yaml} | 0 ...test_search_returns_expanded_content.yaml} | 0 ...tSandboxSearch.test_search_with_data.yaml} | 0 ...l => TestSandboxVFS.test_content_txt.yaml} | 0 ...oxVFS.test_context_filter_limits_vfs.yaml} | 0 .../TestSandboxVFS.test_items_jsonl.yaml | 82 +++++++ ...xVFS.test_iterdir_discovers_documents.yaml | 42 ++++ .../TestSandboxVFS.test_metadata_json.yaml | 42 ++++ 15 files changed, 527 insertions(+), 203 deletions(-) rename tests/cassettes/test_sandbox/{TestSandboxHaikuRAG.test_list_documents_with_data.yaml => TestSandboxListDocuments.test_list_documents_with_data.yaml} (100%) rename tests/cassettes/test_sandbox/{TestSandboxHaikuRAG.test_search_returns_doc_item_refs_and_labels.yaml => TestSandboxSearch.test_search_returns_doc_item_refs_and_labels.yaml} (100%) rename tests/cassettes/test_sandbox/{TestSandboxHaikuRAG.test_search_with_data.yaml => TestSandboxSearch.test_search_returns_expanded_content.yaml} (100%) rename tests/cassettes/test_sandbox/{TestSandboxSearchExpandsContext.test_search_returns_expanded_content.yaml => TestSandboxSearch.test_search_with_data.yaml} (100%) rename tests/cassettes/test_sandbox/{TestSandboxHaikuRAG.test_get_document.yaml => TestSandboxVFS.test_content_txt.yaml} (100%) rename tests/cassettes/test_sandbox/{TestSandboxContextFilter.test_filter_applied_to_list_documents.yaml => TestSandboxVFS.test_context_filter_limits_vfs.yaml} (100%) create mode 100644 tests/cassettes/test_sandbox/TestSandboxVFS.test_items_jsonl.yaml create mode 100644 tests/cassettes/test_sandbox/TestSandboxVFS.test_iterdir_discovers_documents.yaml create mode 100644 tests/cassettes/test_sandbox/TestSandboxVFS.test_metadata_json.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fc6327c..51c3e281 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,14 @@ # Changelog ## [Unreleased] +### Added + +- **Document virtual filesystem in analysis sandbox**: Documents are mounted at `/documents/{id}/` with `metadata.json` (eager), `content.txt` (lazy), and `items.jsonl` (lazy). The agent uses standard Python `pathlib.Path` to browse and read document content and structure. +- **`doc_item_refs` and `labels` in search results**: Search results now include document item references and labels for cross-referencing with `items.jsonl`. + ### Changed - **Analysis sandbox `search()` now returns expanded results**: Search results automatically include surrounding context (adjacent paragraphs, complete tables, section content) via the document_items table - - **BREAKING**: Rename RLM agent to analysis agent throughout: - `agents/rlm/` → `agents/analysis/`, all classes renamed (`RLMResult` → `AnalysisResult`, etc.) - `client.rlm()` → `client.analyze()` @@ -16,6 +20,7 @@ ### Removed +- **`get_document()`, `get_docling_document()`**: Removed from analysis sandbox — replaced by the document virtual filesystem - **`get_chunk()`**: Removed from analysis sandbox — search results now include expanded context automatically - **`create_analysis_toolset()`**: Removed unused `tools/analysis.py` module. diff --git a/docs/agents/analysis.md b/docs/agents/analysis.md index 333e44d0..5f04f7f7 100644 --- a/docs/agents/analysis.md +++ b/docs/agents/analysis.md @@ -11,7 +11,7 @@ The analysis agent enables complex analytical tasks by writing and executing Pyt 1. The agent receives a question 2. It writes Python code to explore the knowledge base -3. Code executes in a sandboxed Python interpreter with access to knowledge base functions +3. Code executes in a sandboxed Python interpreter with access to search, LLM, and a virtual filesystem of documents 4. The agent iterates: run code, examine results, refine approach 5. Final answer is synthesized from the gathered data @@ -54,37 +54,54 @@ async with HaikuRAG(path_to_db) as client: ## Sandbox Capabilities -The agent's code runs in a sandboxed Python interpreter ([pydantic-monty](https://github.com/pydantic/monty)) with access to these knowledge base functions: +The agent's code runs in a sandboxed Python interpreter ([pydantic-monty](https://github.com/pydantic/monty)) with: + +### Functions | Function | Description | |----------|-------------| -| `search(query, limit)` | Hybrid search (vector + full-text) with automatic context expansion | -| `list_documents(limit, offset)` | List documents in the knowledge base | -| `get_document(id_or_title)` | Get full text content of a document | -| `get_docling_document(document_id)` | Get the DoclingDocument structure as a dict (texts, tables, pictures) | +| `search(query, limit)` | Hybrid search (vector + full-text) with automatic context expansion. Returns `doc_item_refs` for cross-referencing with `items.jsonl` | +| `list_documents()` | List all documents in the knowledge base | | `llm(prompt)` | Call an LLM for classification, summarization, or extraction | -When documents are pre-loaded via the `documents` parameter, they are injected as a `documents` variable accessible in the sandbox code. +### Document Filesystem + +All documents are mounted as a virtual filesystem at `/documents/`. The agent uses standard Python `pathlib.Path` to browse and read files: + +``` +/documents/{document_id}/ + metadata.json # {id, title, uri, created_at} + content.txt # Full document text + items.jsonl # Structured items: position, self_ref, label, text, page_numbers +``` + +- **`metadata.json`** — Loaded eagerly (small). Use `Path('/documents').iterdir()` to discover documents. +- **`content.txt`** — Lazy-loaded on first read. Full document text for regex or keyword search. +- **`items.jsonl`** — Lazy-loaded on first read. One JSON object per line with structured document elements. Tables are pre-rendered as markdown. Labels include `section_header`, `text`, `table`, `list_item`, `caption`, `formula`, `picture`, `code`, `footnote`, etc. + +Search results include `doc_item_refs` (e.g. `["#/texts/5", "#/tables/0"]`) that match `self_ref` values in `items.jsonl`, enabling navigation from search hits to document structure. + +When documents are pre-loaded via the `documents` parameter, they are also injected as a `documents` variable accessible in the sandbox code. ### Python Features -The interpreter supports a subset of Python: variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, async/await, `filter()`, `getattr()`, try/except, and the `json`, `re`, `math` modules. +The interpreter supports a subset of Python: variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, async/await, `filter()`, `getattr()`, try/except, file I/O via `pathlib.Path`, and the `json`, `re`, `math` modules. -Not supported: most imports (only `json`, `re`, `math` are available), class definitions, generators/yield, match statements, decorators, `with` statements. For pattern matching, the agent can use `import re`, string methods, or the `llm()` function. +Not supported: most imports (only `json`, `re`, `math`, `pathlib` are available), class definitions, generators/yield, match statements, decorators, `with` statements. For pattern matching, the agent can use `import re`, string methods, or the `llm()` function. ### Security Code executes in an isolated interpreter with: -- **No filesystem access**: Code cannot read or write files +- **Virtual filesystem only**: The `/documents/` filesystem is sandboxed — no access to the real filesystem - **No network access**: Code cannot make HTTP requests or open sockets -- **No imports**: Only `json`, `re`, and `math` modules are available +- **No imports**: Only `json`, `re`, `math`, and `pathlib` modules are available - **Execution timeout**: Configurable limit (default 60s) - **Output truncation**: Large outputs are truncated to prevent memory issues ## Context Filter -The `filter` parameter restricts what documents the agent can access. Unlike tool parameters, the filter is applied automatically and cannot be bypassed by the LLM: +The `filter` parameter restricts what documents the agent can access. Unlike tool parameters, the filter is applied automatically and cannot be bypassed by the LLM — both the VFS and search results are scoped to the filter: ```python # Agent can only see documents with "confidential" in the URI diff --git a/haiku_rag_slim/haiku/rag/agents/analysis/agent.py b/haiku_rag_slim/haiku/rag/agents/analysis/agent.py index 2662fca5..76ef9140 100644 --- a/haiku_rag_slim/haiku/rag/agents/analysis/agent.py +++ b/haiku_rag_slim/haiku/rag/agents/analysis/agent.py @@ -34,8 +34,8 @@ def create_analysis_agent(config: AppConfig) -> Agent[AnalysisDeps, AnalysisResu async def execute_code(ctx: RunContext[AnalysisDeps], code: str) -> CodeExecution: """Execute Python code in a sandboxed interpreter. - The code has access to haiku.rag functions (search, - list_documents, get_document, get_docling_document, llm). + The code has access to search() and llm() functions, and a + virtual filesystem at /documents/ with document content and structure. Use print() to output results. diff --git a/haiku_rag_slim/haiku/rag/agents/analysis/prompts.py b/haiku_rag_slim/haiku/rag/agents/analysis/prompts.py index 00ca88cd..de4a253b 100644 --- a/haiku_rag_slim/haiku/rag/agents/analysis/prompts.py +++ b/haiku_rag_slim/haiku/rag/agents/analysis/prompts.py @@ -1,39 +1,92 @@ ANALYSIS_SYSTEM_PROMPT = """You are an analysis agent that solves complex research questions by writing and executing Python code. -You MUST use the `execute_code` tool to run Python code. The functions described below are ONLY available inside execute_code. Always execute code to answer questions; do not just describe what code would do. +You MUST use the `execute_code` tool to run Python code. The functions and filesystem described below are ONLY available inside execute_code. Always execute code to answer questions; do not just describe what code would do. + +## Available Functions Inside execute_code, these functions are ALREADY available in the namespace. Do NOT import them - just call them with `await`: - results = await search("query") ✓ CORRECT - import search ✗ WRONG - will fail - results = search("query") ✗ WRONG - must use await -## Available Functions - ### await search(query, limit=10) -> list[dict] Search the knowledge base using hybrid search (vector + full-text). Results are automatically expanded with surrounding context (adjacent paragraphs, complete tables, section content). -Returns list of dicts with keys: chunk_id, content, document_id, document_title, document_uri, score, page_numbers, headings +Returns list of dicts with keys: chunk_id, content, document_id, document_title, document_uri, score, page_numbers, headings, doc_item_refs, labels -### await list_documents(limit=10, offset=0) -> list[dict] -List available documents in the knowledge base. +### await list_documents() -> list[dict] +List all documents in the knowledge base. Returns list of dicts with keys: id, title, uri, created_at -### await 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. - -### 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. -- `texts`: list of text items, each with `text`, `label` (e.g. "title", "text", "section_header", "list_item") -- `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 - ### 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 already have the content and just need LLM reasoning. +## Document Filesystem + +All documents in the knowledge base are available as files under `/documents/`. Use `from pathlib import Path` and standard file I/O to access them. + +### Directory structure +``` +/documents/ + {document_id}/ + metadata.json # {"id", "title", "uri", "created_at"} + content.txt # Full document text + items.jsonl # Structured document items (one JSON object per line) +``` + +### metadata.json +Small file with document metadata. Use to discover and identify documents. +```python +from pathlib import Path +import json +for doc_dir in Path('/documents').iterdir(): + meta = json.loads((doc_dir / 'metadata.json').read_text()) + print(meta['title'], meta['uri']) +``` + +### content.txt +Full text content of the document. Use for regex, keyword search, or full-text analysis. +```python +content = Path(f'/documents/{doc_id}/content.txt').read_text() +``` + +### items.jsonl +Structured document items as JSONL. Each line is a JSON object with: +- `position`: sequential position in the document +- `self_ref`: item reference (e.g. "#/texts/5", "#/tables/0") +- `label`: item type — "section_header", "text", "table", "list_item", "caption", "formula", "picture", "code", "footnote", etc. +- `text`: rendered content (tables are markdown with `|` columns) +- `page_numbers`: list of page numbers where the item appears + +Use items.jsonl to find tables, section headers, or specific structural elements: +```python +import json +items_text = Path(f'/documents/{doc_id}/items.jsonl').read_text() +for line in items_text.strip().split(chr(10)): + item = json.loads(line) + if item['label'] == 'table': + print(f"Table on page {item['page_numbers']}: {item['text'][:100]}") +``` + +## Cross-referencing search results with items + +Search results include `doc_item_refs` (e.g. `["#/texts/48", "#/tables/0"]`) that correspond to `self_ref` values in items.jsonl. Use this to navigate from a search hit to the surrounding document structure: +```python +results = await search("revenue", limit=5) +r = results[0] +doc_id = r['document_id'] +refs = set(r['doc_item_refs']) + +import json +items_text = Path(f'/documents/{doc_id}/items.jsonl').read_text() +for line in items_text.strip().split(chr(10)): + item = json.loads(line) + if item['self_ref'] in refs: + print(f"Matched: {item['label']} on page {item['page_numbers']}") +``` + ## Pre-loaded Documents Variable If documents were pre-loaded for this session, a `documents` variable is available: @@ -46,68 +99,18 @@ Check if it exists with: `try: documents ... except NameError: ...` ## Available Python Features -The interpreter supports: variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, async/await, `map()`, `filter()`, `getattr()`, `sorted()`/`.sort(key=...)`, try/except, and the `json`, `re`, `math` modules. +The interpreter supports: variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, async/await, `map()`, `filter()`, `getattr()`, `sorted()`/`.sort(key=...)`, try/except, and the `json`, `re`, `math` modules. File I/O via `pathlib.Path` is supported for the `/documents/` filesystem. -Not supported: most imports (only `json`, `re`, `math` are available), class definitions, generators/yield, match statements, decorators, `with` statements. - -For pattern matching or text extraction, use `import re`, string methods (`str.split`, `str.find`, `str.startswith`, `in` operator), or the `llm()` function. +Not supported: most imports (only `json`, `re`, `math`, `pathlib` are available), class definitions, generators/yield, match statements, decorators, `with` statements. ## Strategy Guide -1. **Search First**: Start with `search()` to find relevant content. Results already include expanded context (surrounding paragraphs, complete tables, section content). -2. **Use get_document for Full Text**: When you need a document's complete text (e.g., for regex across the whole document), use `get_document(id_or_title)`. -3. **Use get_docling_document for Structure**: When you need structured data like table grids, document hierarchy, or section labels, use `get_docling_document(document_id)`. -4. **Iterate**: Run code, examine results, refine your approach. Don't try to solve everything in one execution. -5. **Use llm() for Reasoning**: When you have content and need classification, summarization, or extraction, use `llm()` rather than writing complex parsing logic. -6. **Document Titles Are Often None**: Use `uri` or `id` to identify documents. Use `list_documents()` to discover what's available. - -## Example Patterns - -### Search (results include expanded context) -```python -results = await search("revenue figures", limit=5) -for r in results: - print(f"{r['document_title']} (score={r['score']:.2f}):") - print(r['content'][:200]) -``` - -### Extracting data with regex -```python -import re -numbers = [] -results = await search("financial data", limit=20) -for r in results: - amounts = re.findall(r'\\$([\\d,]+)', r['content']) - for a in amounts: - numbers.append(int(a.replace(',', ''))) -if numbers: - print(f"Average: {sum(numbers) / len(numbers)}") -``` - -### 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}") -``` - -### Regex search across a full document -```python -import re -content = await get_document("Policy Document") -if content: - emails = re.findall(r'[\\w.+-]+@[\\w-]+\\.[\\w.]+', content) - print(f"Found {len(emails)} email addresses: {emails}") -``` +1. **Search First**: Start with `search()` to find relevant content. Results include expanded context and `doc_item_refs` for cross-referencing. +2. **Discover Documents**: Use `list_documents()` to see what's in the knowledge base. +3. **Use items.jsonl for Structure**: Find tables, section headers, or specific elements by label and page number. Tables are pre-rendered as markdown. +4. **Use content.txt for Full Text**: When you need the complete document text (e.g., for regex across the whole document). +5. **Iterate**: Run code, examine results, refine your approach. Don't try to solve everything in one execution. +6. **Use llm() for Reasoning**: When you have content and need classification, summarization, or extraction, use `llm()` rather than writing complex parsing logic. ## Output Format diff --git a/haiku_rag_slim/haiku/rag/agents/analysis/sandbox.py b/haiku_rag_slim/haiku/rag/agents/analysis/sandbox.py index 9d8cb40d..77a4dc87 100644 --- a/haiku_rag_slim/haiku/rag/agents/analysis/sandbox.py +++ b/haiku_rag_slim/haiku/rag/agents/analysis/sandbox.py @@ -1,14 +1,19 @@ +import asyncio +import concurrent.futures import json +from collections.abc import Callable from dataclasses import dataclass from typing import TYPE_CHECKING, Any, Literal import pydantic_monty +from pydantic_monty import CallbackFile, MemoryFile, OSAccess from haiku.rag.agents.analysis.dependencies import AnalysisContext from haiku.rag.config.models import AppConfig -from haiku.rag.store.compression import decompress_json if TYPE_CHECKING: + from pathlib import PurePosixPath + from haiku.rag.client import HaikuRAG @@ -21,12 +26,19 @@ class SandboxResult: success: bool +def _run_async(coro: Any) -> Any: + """Run an async coroutine from a sync context (CallbackFile read).""" + with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool: + return pool.submit(asyncio.run, coro).result() + + class Sandbox: """Execute code in a sandboxed Python interpreter. Uses pydantic-monty, a minimal secure Python interpreter written in Rust. - External functions (search, list_documents, etc.) are called by Monty code - using ``await`` and resolved asynchronously on the host. + External functions (search, llm) are called by Monty code using ``await`` + and resolved asynchronously on the host. + Documents are exposed via a virtual filesystem at ``/documents/{id}/``. sandbox = Sandbox(client, config, context) result = await sandbox.execute("print('hello')") @@ -71,12 +83,8 @@ class Sandbox: for r in expanded ] - async def list_documents( - limit: int = 10, offset: int = 0 - ) -> list[dict[str, Any]]: - docs = await client.list_documents( - limit=limit, offset=offset, filter=context.filter - ) + async def list_documents() -> list[dict[str, Any]]: + docs = await client.list_documents(filter=context.filter) return [ { "id": d.id, @@ -87,19 +95,6 @@ class Sandbox: for d in docs ] - async def get_document(id_or_title: str) -> str | None: - doc = await client.resolve_document(id_or_title) - return doc.content if doc else None - - 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 @@ -113,14 +108,111 @@ class Sandbox: return { "search": search, "list_documents": list_documents, - "get_document": get_document, - "get_docling_document": get_docling_document, "llm": llm, } + async def _build_vfs(self) -> OSAccess: + """Build the virtual filesystem with document data. + + Mounts per-document directories with: + - metadata.json: MemoryFile (eager, small) + - content.txt: CallbackFile (lazy, can be large) + - items.jsonl: CallbackFile (lazy, can be large) + """ + client = self._client + files: list[MemoryFile | CallbackFile] = [] + + docs = await client.list_documents(filter=self._context.filter) + + for doc in docs: + if not doc.id: + continue + doc_id: str = doc.id + doc_dir = f"/documents/{doc_id}" + + metadata = json.dumps( + { + "id": doc_id, + "title": doc.title, + "uri": doc.uri, + "created_at": str(doc.created_at), + }, + ensure_ascii=False, + ) + files.append(MemoryFile(f"{doc_dir}/metadata.json", metadata)) + + def _make_content_reader( + did: str, + ) -> Callable[["PurePosixPath"], str]: + def read_content(_path: "PurePosixPath") -> str: + async def _fetch() -> str: + from haiku.rag.utils import escape_sql_string + + safe_id = escape_sql_string(did) + rows = list( + client.store.documents_table.search() + .select(["content"]) + .where(f"id = '{safe_id}'") + .limit(1) + .to_list() + ) + return rows[0]["content"] if rows else "" + + return _run_async(_fetch()) + + return read_content + + def _make_items_reader( + did: str, + ) -> Callable[["PurePosixPath"], str]: + def read_items(_path: "PurePosixPath") -> str: + async def _fetch() -> str: + items = ( + await client.document_item_repository.get_items_in_range( + did, 0, 999999 + ) + ) + lines = [] + for item in items: + lines.append( + json.dumps( + { + "position": item.position, + "self_ref": item.self_ref, + "label": item.label, + "text": item.text, + "page_numbers": item.page_numbers, + }, + ensure_ascii=False, + ) + ) + return "\n".join(lines) + + return _run_async(_fetch()) + + return read_items + + files.append( + CallbackFile( + f"{doc_dir}/content.txt", + read=_make_content_reader(doc_id), + write=lambda _p, _c: None, + ) + ) + files.append( + CallbackFile( + f"{doc_dir}/items.jsonl", + read=_make_items_reader(doc_id), + write=lambda _p, _c: None, + ) + ) + + return OSAccess(files) + async def execute(self, code: str) -> SandboxResult: """Execute Python code in the Monty interpreter.""" external_fns = self._build_external_functions() + vfs = await self._build_vfs() input_names: list[str] = [] inputs: dict[str, Any] | None = None @@ -166,6 +258,7 @@ class Sandbox: external_functions=external_fns, limits=limits, print_callback=print_callback, + os=vfs, ) except pydantic_monty.MontyRuntimeError as e: stdout = "".join(stdout_lines) diff --git a/tests/agents/analysis/test_sandbox.py b/tests/agents/analysis/test_sandbox.py index 30c627de..4b846f6c 100644 --- a/tests/agents/analysis/test_sandbox.py +++ b/tests/agents/analysis/test_sandbox.py @@ -74,12 +74,12 @@ class TestSandboxErrors: assert result.stderr != "" -class TestSandboxHaikuRAG: - """Test haiku.rag functions in sandbox.""" +class TestSandboxListDocuments: + """Test list_documents function in sandbox.""" @pytest.mark.asyncio async def test_list_documents_empty(self, sandbox): - """Test list_documents returns empty list for empty database.""" + """list_documents returns empty list for empty database.""" result = await sandbox.execute( "docs = await list_documents()\nprint(type(docs).__name__, len(docs))" ) @@ -89,7 +89,7 @@ class TestSandboxHaikuRAG: @pytest.mark.asyncio @pytest.mark.vcr() async def test_list_documents_with_data(self, temp_db_path): - """Test list_documents returns documents when populated.""" + """list_documents returns documents when populated.""" config = AppConfig() async with HaikuRAG(temp_db_path, create=True) as client: await client.create_document( @@ -109,6 +109,10 @@ class TestSandboxHaikuRAG: assert "1" in result.stdout assert "Test Document" in result.stdout + +class TestSandboxSearch: + """Test search function in sandbox.""" + @pytest.mark.asyncio @pytest.mark.vcr() async def test_search_with_data(self, temp_db_path): @@ -158,46 +162,6 @@ class TestSandboxHaikuRAG: assert "True\nTrue" in result.stdout assert "list\nlist" in result.stdout - @pytest.mark.asyncio - @pytest.mark.vcr() - async def test_get_document(self, temp_db_path): - """Test get_document function.""" - config = AppConfig() - async with HaikuRAG(temp_db_path, create=True) as client: - doc = await client.create_document( - content="Content about foxes and dogs.", - uri="test://doc", - title="Fox Document", - ) - - context = AnalysisContext() - sb = Sandbox(client=client, config=config, context=context) - result = await sb.execute( - f"content = await get_document('{doc.id}')\n" - "print('foxes' in content.lower() if content else 'None')" - ) - assert result.success - assert "True" in result.stdout - - @pytest.mark.asyncio - async def test_get_document_not_found(self, sandbox): - """Test get_document returns None for missing document.""" - result = await sandbox.execute( - "content = await get_document('nonexistent-id')\nprint(content is None)" - ) - assert result.success - assert "True" in result.stdout - - -class TestSandboxSearchExpandsContext: - """Test that search() returns expanded results.""" - - @pytest.mark.asyncio - async def test_get_context_not_available(self, sandbox): - """get_context is no longer a sandbox function.""" - result = await sandbox.execute("await get_context('x')") - assert not result.success - @pytest.mark.asyncio @pytest.mark.vcr() async def test_search_returns_expanded_content(self, temp_db_path): @@ -303,13 +267,124 @@ class TestSandboxOutputTruncation: assert len(result.stdout) < 100 -class TestSandboxContextFilter: - """Test context filter is applied.""" +class TestSandboxVFS: + """Test virtual filesystem for document access.""" + + @pytest.mark.asyncio + async def test_empty_database_has_no_documents(self, sandbox): + """Empty database has no document directories.""" + result = await sandbox.execute( + "from pathlib import Path\nprint(Path('/documents').exists())" + ) + assert result.success + # /documents dir may or may not exist when empty, both are valid + # The key is it doesn't error @pytest.mark.asyncio @pytest.mark.vcr() - async def test_filter_applied_to_list_documents(self, temp_db_path): - """Test that context filter is passed to list_documents.""" + async def test_iterdir_discovers_documents(self, temp_db_path): + """Path('/documents').iterdir() lists document directories.""" + config = AppConfig() + async with HaikuRAG(temp_db_path, create=True) as client: + await client.create_document( + content="Test content", + uri="test://doc1", + title="Test Document", + ) + + context = AnalysisContext() + sb = Sandbox(client=client, config=config, context=context) + result = await sb.execute( + "from pathlib import Path\n" + "dirs = list(Path('/documents').iterdir())\n" + "print(len(dirs))\n" + "print(dirs[0].is_dir())" + ) + assert result.success + assert "1" in result.stdout + assert "True" in result.stdout + + @pytest.mark.asyncio + @pytest.mark.vcr() + async def test_metadata_json(self, temp_db_path): + """metadata.json contains document title and uri.""" + config = AppConfig() + async with HaikuRAG(temp_db_path, create=True) as client: + doc = await client.create_document( + content="Test content", + uri="test://doc1", + title="Test Document", + ) + + context = AnalysisContext() + sb = Sandbox(client=client, config=config, context=context) + result = await sb.execute( + "from pathlib import Path\n" + "import json\n" + f"meta = json.loads(Path('/documents/{doc.id}/metadata.json').read_text())\n" + "print(meta['title'])\n" + "print(meta['uri'])" + ) + assert result.success + assert "Test Document" in result.stdout + assert "test://doc1" in result.stdout + + @pytest.mark.asyncio + @pytest.mark.vcr() + async def test_content_txt(self, temp_db_path): + """content.txt returns full document text (lazy loaded).""" + config = AppConfig() + async with HaikuRAG(temp_db_path, create=True) as client: + doc = await client.create_document( + content="Content about foxes and dogs.", + uri="test://doc", + title="Fox Document", + ) + + context = AnalysisContext() + sb = Sandbox(client=client, config=config, context=context) + result = await sb.execute( + "from pathlib import Path\n" + f"content = Path('/documents/{doc.id}/content.txt').read_text()\n" + "print('foxes' in content.lower())" + ) + assert result.success + assert "True" in result.stdout + + @pytest.mark.asyncio + @pytest.mark.vcr() + async def test_items_jsonl(self, temp_db_path): + """items.jsonl returns document items as JSONL (lazy loaded).""" + config = AppConfig() + async with HaikuRAG(temp_db_path, create=True) as client: + doc = await client.create_document( + content="The quick brown fox jumps over the lazy dog.", + uri="test://animals", + title="Animals", + ) + + context = AnalysisContext() + sb = Sandbox(client=client, config=config, context=context) + result = await sb.execute( + "from pathlib import Path\n" + "import json\n" + f"text = Path('/documents/{doc.id}/items.jsonl').read_text()\n" + "lines = text.strip().split('\\n')\n" + "print(len(lines) > 0)\n" + "item = json.loads(lines[0])\n" + "print('position' in item)\n" + "print('self_ref' in item)\n" + "print('label' in item)\n" + "print('text' in item)\n" + "print('page_numbers' in item)" + ) + assert result.success + assert result.stdout.count("True") == 6 + + @pytest.mark.asyncio + @pytest.mark.vcr() + async def test_context_filter_limits_vfs(self, temp_db_path): + """Context filter restricts which documents appear in VFS.""" config = AppConfig() async with HaikuRAG(temp_db_path, create=True) as client: await client.create_document( @@ -326,10 +401,12 @@ class TestSandboxContextFilter: context = AnalysisContext(filter="uri LIKE 'public://%'") sb = Sandbox(client=client, config=config, context=context) result = await sb.execute( - "docs = await list_documents()\n" - "print(len(docs))\n" - "if docs:\n" - " print(docs[0]['title'])" + "from pathlib import Path\n" + "import json\n" + "dirs = list(Path('/documents').iterdir())\n" + "print(len(dirs))\n" + "meta = json.loads((dirs[0] / 'metadata.json').read_text())\n" + "print(meta['title'])" ) assert result.success assert "1" in result.stdout @@ -368,43 +445,6 @@ 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 = AnalysisContext() - 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/TestSandboxHaikuRAG.test_list_documents_with_data.yaml b/tests/cassettes/test_sandbox/TestSandboxListDocuments.test_list_documents_with_data.yaml similarity index 100% rename from tests/cassettes/test_sandbox/TestSandboxHaikuRAG.test_list_documents_with_data.yaml rename to tests/cassettes/test_sandbox/TestSandboxListDocuments.test_list_documents_with_data.yaml diff --git a/tests/cassettes/test_sandbox/TestSandboxHaikuRAG.test_search_returns_doc_item_refs_and_labels.yaml b/tests/cassettes/test_sandbox/TestSandboxSearch.test_search_returns_doc_item_refs_and_labels.yaml similarity index 100% rename from tests/cassettes/test_sandbox/TestSandboxHaikuRAG.test_search_returns_doc_item_refs_and_labels.yaml rename to tests/cassettes/test_sandbox/TestSandboxSearch.test_search_returns_doc_item_refs_and_labels.yaml diff --git a/tests/cassettes/test_sandbox/TestSandboxHaikuRAG.test_search_with_data.yaml b/tests/cassettes/test_sandbox/TestSandboxSearch.test_search_returns_expanded_content.yaml similarity index 100% rename from tests/cassettes/test_sandbox/TestSandboxHaikuRAG.test_search_with_data.yaml rename to tests/cassettes/test_sandbox/TestSandboxSearch.test_search_returns_expanded_content.yaml diff --git a/tests/cassettes/test_sandbox/TestSandboxSearchExpandsContext.test_search_returns_expanded_content.yaml b/tests/cassettes/test_sandbox/TestSandboxSearch.test_search_with_data.yaml similarity index 100% rename from tests/cassettes/test_sandbox/TestSandboxSearchExpandsContext.test_search_returns_expanded_content.yaml rename to tests/cassettes/test_sandbox/TestSandboxSearch.test_search_with_data.yaml diff --git a/tests/cassettes/test_sandbox/TestSandboxHaikuRAG.test_get_document.yaml b/tests/cassettes/test_sandbox/TestSandboxVFS.test_content_txt.yaml similarity index 100% rename from tests/cassettes/test_sandbox/TestSandboxHaikuRAG.test_get_document.yaml rename to tests/cassettes/test_sandbox/TestSandboxVFS.test_content_txt.yaml diff --git a/tests/cassettes/test_sandbox/TestSandboxContextFilter.test_filter_applied_to_list_documents.yaml b/tests/cassettes/test_sandbox/TestSandboxVFS.test_context_filter_limits_vfs.yaml similarity index 100% rename from tests/cassettes/test_sandbox/TestSandboxContextFilter.test_filter_applied_to_list_documents.yaml rename to tests/cassettes/test_sandbox/TestSandboxVFS.test_context_filter_limits_vfs.yaml diff --git a/tests/cassettes/test_sandbox/TestSandboxVFS.test_items_jsonl.yaml b/tests/cassettes/test_sandbox/TestSandboxVFS.test_items_jsonl.yaml new file mode 100644 index 00000000..4ca2fa50 --- /dev/null +++ b/tests/cassettes/test_sandbox/TestSandboxVFS.test_items_jsonl.yaml @@ -0,0 +1,82 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '114' + content-type: + - application/json + host: + - localhost:11434 + method: POST + parsed_body: + encoding_format: base64 + input: + - The quick brown fox jumps over the lazy dog. + 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: 11 + total_tokens: 11 + status: + code: 200 + message: OK +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '73' + content-type: + - application/json + host: + - localhost:11434 + method: POST + parsed_body: + encoding_format: base64 + input: + - fox + 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 +version: 1 diff --git a/tests/cassettes/test_sandbox/TestSandboxVFS.test_iterdir_discovers_documents.yaml b/tests/cassettes/test_sandbox/TestSandboxVFS.test_iterdir_discovers_documents.yaml new file mode 100644 index 00000000..b6252ae6 --- /dev/null +++ b/tests/cassettes/test_sandbox/TestSandboxVFS.test_iterdir_discovers_documents.yaml @@ -0,0 +1,42 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '82' + content-type: + - application/json + host: + - localhost:11434 + method: POST + parsed_body: + encoding_format: base64 + input: + - Test content + model: qwen3-embedding:4b + uri: http://localhost:11434/v1/embeddings + response: + headers: + content-type: + - application/json + transfer-encoding: + - chunked + parsed_body: + data: + - embedding: OiAUOdjp5Tx40oi8drbCO/4kaTpb50U9NOSRPQLLj7xVU3M8mVGzPLM7FjzoffM8JQiqOpsCxrz7jAU9mKdqvXmFAL0zGpi7PGA6PeW6xbtdVIW8Fx44PLFdGT0gNR294hmku03VnLwurK28PERUvcPnBT3SrA+9fKY7vA0/ar2f2Kg88IgkPKOQXjsHywK8CMvNu5byWbxDOsk8R9MVvfGgOzxaDsO6NiSQPKJf+jqjd5u89iatO0EqUzvR9Ys8MamHvMQGb7y6mME7rrpnPOA8g7zIxvy8OUzDPNd2izyMTa+8qZkmvCU/6Lv1A/+8XrLDuxz9nzsiPg+9Oi2WvPEZqLq3xF28uIk+OzOlILt657M8+6lNuzR2szydmsg80Q0hPA6cDj0xH9Y2HYzVvMD5qrk+bhA9JttgPKKWsDw8r/Y8yRXRuwadW7zJ1g89Y2KnPNMtMbszWhw8/PZVO6TiPL1BNKg6LyNQPGj6tTzoTUy7bGY7PBSopDquHFm7BLI7vE1bbLw3vZi78vNsPHq077op4Es7MxHsPBNYU7yNExW9oFVlvKQaWjpeDHE8tECCPOLhPLzvTMS7ypMEvJlrlbsCGQK9970lvG6GsrwXbI+8uhAEPT6CuTzScCA9QoX9u+d/7DwQeso7uNqwu0WYcjwnCCI60tZ6vE1sbrz3Zka7lPp+OtlLJ7wjHCC8n0NDPNJslLxYYgG9Uov9u9xguTuMidK7ZwWIvP103TzpzuC7isRmObKXRjylAZ088OpBOxUOd7rIZQu7PJ0VPQprwTu3kxC6tBDwPHzwcLyjIRO8CnksPNhO6Lf0gAs9cbBCvILJmDuUA0w8BK9EPJ9klLwlKEo8nucnvYklhTwKdrg6gStCvE9Mp7wBBtu6zlAGOf0VQrsjk2u8OPa6vPynVTzL8f27i+fAvBHWqLvc9x+9CJVmO5pOx7vvjbQ8xP0TvFK0IzuiXhg9SQ8KvMrUkTzUqZM80gEXu9ZJOTwnZyo8rjVrPKtDsLvhg2U6t5l1vEk3SjvbwY68LQnWvJTRMrwk2lc705tkPF5/2TwxfVU8YCwkuyObLD0GVAA81WlqO/OvILz66jc7fiCbvCPCfDwZ6OG8K/2JPOQnAj3ckxm87Y19vNQOJjsWryQ72+PUvAuPtrlRbHw8ow/ZPFLcBTzJ91O7hyRTvBL6djphhgq9+leePExbr7sy/K+7ce2bPJLXyLslGZA8xkr0PP7XCrxsKtC8XKtLPC8lMLwTsly8BgX0u5VZU7zqgha8BVMFOyW2b7xM14Y8kEiHPNccETta7G28/zGhPG7be7wS32i85j36vD/Sl7zNetC7oQ1XO9EyZbuiVow5m6kFPCws5DoEjRs73RvBO/kjQDvTxHa6ncugPFDcU7xnRsK6sG4AO/rXkTnZiqM7juEzPOpyibvhLOC8HLo6PaPn+Lshf8S7skIlPNYsIr1S/Iq8dZYcPFFcPzyoYoY80ys3PcIMLrtCOCk7D/uevCuxBTxhoB45I/+xuzoorztnOW48uTynvHTHwjnpgjQ8FDe/vAypjjwm5aa77Pfcu1MP/jz/Pp08ANuDPDp9KTsg72q7p/xgvDw8ibw6pI68Y/nsO8nqC7xDU447FZliO6zUgTz0TI480wKwu5m2nDq/NES8tSpjOtlaiLtu3Yq825rYvIVGoztYNi+7QcMcvdbbpbxuLRS8MpC5vLysSrtDaiC8sLyPvKMNArzykKo8VAC2upATljx5Dhq72CCOvB535zxmwwC9joS9u2hmRLzbvD68TaWZPBuNNT0S4BI9aQktPITjd7w6dbW8En/wPJkepLuu8Pi89tQhvATahDzkDxk8qtewvGd8F7vqnmK83IzvvC2+C73XdkW81WvKvEImAj20xxe9cgJNPBOVDj0iEFA7xgPyu6XEhbqTxxw8waEAPHLrAb08qzK7R7GbvF19Gz1PRbE7vC8cvYfEvjitc/872PuKPMsM3TzPA4U82QufvMqbODwDz1c893F9OlEqxjz3uPY8DPoIPLqUbrtLuSg8PP0/vCwtAj26wTe7rJmtvAswQj2wbcM8k9yfvDpOdzyTJps8lx4yPNrw8jzX4vQ8Wq9KPOzGpTxiWD09ZoDZOoej5Du9Bc08ekfFvBXycTzmfIq8PS1EvO2KXDwOXg89JuWtPMkcs7vh4Og7XKCSPMX3ATzlLA+9utVBPFECDjzULTc8RQcIvLY+E71QU0G8cVTRuyjRH7ybuUW8hVyJvPt9yLyYHnc7dwJqunUQsjzYQlc7TPPhPHVzmbvx/KI8BEUVOy+TNT0a4168vWh4PZ0G4Du4nRc6cy9zu0HvFbzesgi9jrkGvZx8CTyBdcE8b2txvKo5fbu7A688JMrgPGEqm7sFqL67SKbouSsIcLwqupe7oRCluE2ZVLyJT1o8+tdePGA77bwWcZ68jIBWvD9Irb1bi4I8mYPHPNmt0rxlHRi8rU6WvCLC5rv8BJe8bfSYOzFMEj2WLYS8mfppvAsXtbxxJ4c8KpqHvP9niDwrqp08e9lcPDYcdLs3zKY8NH5sPd173bsTldo8GnvWPBsQ/DswXUk9O6LPPA4y3zvV8ys8M3RevPhiE7x5i6Q8ieLeO34h37uOgdQ758SSvGVuiDzXaKy8eWcTPMxNhzwU/iy8zSX7PAFR5bvFs5e6pAmtvB1bGrxcXY08DeUavHGMgrxjwXk8J9M7PPvlprtYgp28R58UvUPyHzyMHrK8eGb8vO2jdbz6oYW7rZKuvLpgrbxKkNI8IsunPLQC77tWp+28U1kIPbZIhzsy7rw8Z7KZvHOx/DzMU/o7uVEQPFISND08lbc89rmCvXeQtrzYiD25KSQOPR/MHroyFQi8OGi9vHFYgbs70/I8QsLGvCLU+zsGDmO9XvoYvb3wBTzgJ2G8C/yuPNXULDzcgfu4+JjUPALphLyMPma8+Nc8PVG9jbz4m8q64rOrO+Lj5LtuTO87praKu59gAj0pUPY8FDOpOT09jrx/cGy8QyQfPEOatbwA2R+8sAk7Os8IvzzzWvq6BoKPPMBp0zs6jGu8od59PGi+s7xs/fa8Y8h1vO8Bebx2Hq08VruNvDhwzbzDoYQ8Ym8jPbakTLxmT5O8CEWfPGBICzzToWi8dq2bO8f5zTzjgt+8zUiSPApD8Ly0Vf28q1D9vLIS47yNvU48/AjEPAhL2Lz/GLe840U0PQxb67v7hxC8f7PkPCopUDwun6u8nHWIu00whjyPH2E8XQyAvMXEKDztUC28wELZvCMoILxDxDS9D1BhuxtTw7xxvwC8lUIIPBGY/Lw3oI+8j1OavPQiVL0ge768Z3BvvEY+VjzvXxC8FknQO/Csljy5fQk9vaITvT8pBjxWCbY7hAM8vcR4JT0z4548pYNVvdmgTzxTzcI755ihPDIszzwEB/Y8pLWLPLt8fL23hau8+r5+O+WRdDxV19S8yEtjPGyVDDvJRYm7+gGcPJu6Ir1481E9uSsevHQqFjxCJTM89pxsvJiYB7x7FOM85XbdO2I7y7vFX/e7zdOOvc8QybyIkNc8LfkKPRBGgDulz848MCpiutUukzyvlx497AxPvbPqVTxgXRQ8eXufPNo5qbtBFK082BifPFEX5jw7JeS8XacRPWHHVjwld268jUiiPEqGDT0i3Q09vx/yOp7JsDxTKxE9YwjpPMV8kDvZgpw6uzZeO0DXpbnCMc67PAvfumpkjTywfc87d27KPAFIqjz/HYe7e6xpPIS4pbu6vw+60jNVvD2u4jy+6BI80MyBvMg0IL1ALVe8Lq2CPFFc07zPxAy8vWkPPL3GJr0bgfw79nSsurlTqDxdzNa8vU4qvPYYXbz6/+Y8U+0RPdaCgDyGyUY8H4xePQzbBTxYj6q89VPWvCtKnjwLxQe8cfSJPBazAb1DTF+6M5v2vPS1Sbt/N/68wy1HOZiqxDvMQvE70qZUvFlMujuReae8JlCPvLJ8mjxhZXK8UiiCPIoZIz2Flnc80nVAOY33yzs6kzA9BC0APWJUED2iwQc9fiBHPAnE/zs1EVi7Mw6sPFEVmLt8zVi8geT+ujwzxjtMNDe8KKpiPAM7qjvb26e8/cVcPEQdlTxhqgi8U632PBHRoDu0zuG85LZru0WacDv2CAg7kakMu0S/xrxS5YI8APB4vMUsoryQveg7O1n6vNick7yrQVS8FliYvFEGbzuNwe87EVg5uXUlibueqvI8cPuWukwCD7x4RP+86kIcvW5DD72jqi08xvvwOktLvbxa9is9n97mPPUEOT3tz7k8dOv9u3RUHrsFYGw7PkmRPHq3Er0O6yW7LCjBPIEdIzyfv628pU2GPHHHVLz2rB+8SopQPJ8imjxrN9489ejXvIg+sbx14cq72vKOuwR8tDuexr27sFcPuuMG5ztMbe27m8OnPGUAoTyFybe8szOUvF1dhbsfMrk8fyskvVXMHrySUeK8SdmbO35Ay7zrMRU90KrrvOzo07wANS08U6KGvP3esDyxxKK8EGEYOxpy+DwaUhk998a8PFWeDTwLnmy8iCKgPJz4Mz1b1i87mdylPJ26AbxQigM7ItAWvXnRury4D607S3QQvNYuB701jEq8Flv8u8Sz9DxouFi9r85Ru995GjzbK2M7zHSoOle7MzwMPoM8YeKMvJI0aLvH94G8cnYkvNYcwLmV7+Q8xdssPXSNEzxYMw882JlYvK8YprxEkGE8erUPPEOgAjz6ZIe8H9yRPP6O7ToDnjQ8XvD2O33xKrokMRC9vexIvebmyzzeH7+8gVHkPKQoIzwtE568Mik1POmPwzo6aVy6LWUqveA5vLtJO6271Ke+OxCWjbzhKgS93H2EPIx2eDz2jdi8Os5DuzBB2LuxYHY8ln8UvRXcWryJQus7JUYFPO6BEr0P5y88spHZPLbgxbxrIBG9iCLLO2SslLwIxoK8C81zvNaSErqxY/m8m+MIPPbxDr1Zpu67oCUVvPd04zzAVcA84QITvMfTW7zX/46822WlPFy9/Dyme5O8BgHIO+sfCz1yzi87c30UPPF4gjwhwv67iAp9vAIGpLxKjfy7iMvSuxIdubzVrei7VNQ1PN9Q5Dvze0+7QrUfvNblWbxET/s7dxd0vDIZkTxafmk8lJpGu45wK7rdS1U8ynh5OyvzJ7wcaRC82wievE1hGLq6hFi89ELPO5R3bzuNniA9e8FZPZhu3DuliQS9NT0WvDVuILxb4xu9QMqPPHPgAb2VNoe8MeGlvJJ7Qjq38M+8tCLFu/K+NLz8cUO6ScXSPDHikbrjWOk8B2oTOkVZIjxYvIc8bAnZOwei+7xzN8s8EylEPGlpurxOk8a8702CvRL17TzQ5sA6SuMWPRoGnztegUG8HxLevOuRT7tZWjy8ZWW1vN0DWDzvvmA8wHyqPCbHZLthXMW6cbFXuhiKILyzKf+71dMqvZfRTbyC6IO8w6GwPKR4iTwFlNK6d0X/PAf+4Tt7MuE7i1yVu4vdhTzZ47I82CM5O84oUbx1ZSI97DLPvDd7ELxXFJo8HDNZPGyjczvlzjK83XZRPJnPlDv5NeE86MoXPJNXXTwbSao7eoOAPBJwWDw3TNk8vGKAPHpFIbzzBtU7FHGqOy9207yyEHy6FjGzPA2v+zsug+e8F1onPAyjmbzbzpc80MfMvGn/ODzVdvy7f06ou557qTuK1Je8rHbBPGTLrTxWWiQ8Tq+iPFRAIbyIKwc8llAyvKtN37x66h08tqR3vPHj4Tw3Ima7ETeRu1AD/jz9kfq8dHeOPPJqN7ygBXi7nV/nPIaoUryfPPu85t/XvLyzMruG4Us95NfZuvJ2K73FM5+7CgzyPLiMXDyD00y6CfPxu7DyLzy6oBC8bqOpvA7wY7wbBp08Fc0APYfMuju+c9U84EcYvVJFKbsjcka6HlUGPER0q7ohmfK7a9bTum2CxzxD3946qk7UvDVQfrs2W3a87WTWPFNHADujtAS8m5LBPJMazzxSHpq8Itj6uve3mDxic0C853drPPP66TsqYCe7GSYMPSHfTDyIqBq9XOF3OkZA3zyikCO7bvlJvI/Xt7rLYy28aRqjvHyt9DtbhjA9NdzlvF08vTzalcm6ZdKRO/xlr7zDs/08l4hkvCHbWDzAG5G7fqD7Ow/isTzTaUM9S2YFu9YWnjp0JZa84vlHPNtoyDxWViq8iYonvMP8Gz2LXpi7aKarvBenXbux2VG800zvu461rrzMPUM8X6blvEEuQjunB5K8n+ERPdZHbzwfGLA8ZpGOvP4MtLxB0le8FHbdu+ciwrwISiG8JROZO1PoTzvuvzc84nUdvBqVd7w/YDM91CIgvCYGkjx59Ha8GRU+PPAgcLypAE07YvEWvN+J97wr+hi9+a6PvK1mNb3hYx27C+6RO0e+wLz2IK07i9UbO63oVTl+Xas8BLwiPZSPo7sBIVy6q2+1vFb/VDzhlCq8oV0ZvIDOjDoEzPC8djKAO6g9uDwa1w67nimKPPcKO7zsc2K8PxAzvDZJrjt1gHS8pQYLPbP80zxqa+47Rt4/vNsNuTyR9mQ951jvPBy1uLyrE2084DoNvRwSbrxfym08PoIWvDn5CTwd57M7RnMsu93BljuFzv08+vgaPW9AIb25elQ7fQOBvNssbjxFKB28FDomvI950ruaTWe8BlNSvKN27jwvmSY7HpevvHDxhruYL188ilgqu3IaxjpUPhw88g5ruxSfNruWQHM7LGOePKk2z7wKWs48JomJujumLj2tsrc435wdO06ZxzwoSPW8/1eku/cEmzwFr4i8s686vKFafbwvGps7hfyWPCRZeTzSyAM9vVRVO2SVF7punga9+D6ZuzH9v7s1+yy92uVhO+XfqjxAG9q8r/lOvOY02TyMuwi9Fr0yPIwsd7xSiY679ZZavCOkyzuHagY8JJqxPFUBJjzw2Hg8SRIRvNWPujxizxE83kTBvEtk8LsQvLw7TF/nPNVMz7rYO2Q8elvtPJguijrbF9y8x2oQvUYrmjploUS8vMPxvCaz97ycNiy8e2UCO40buDpbuLa7bp+API1l+ryHfqA7sU6ANqUct7zv2uc8HoMsPKh497sRsQa7re+9vMBIuTzIPAs8z2+7PNmBsLw3Pg68vvtWvLnHYLyM95E8UgziPJWrFTtGLx+9fbRWPHeueTzGITi9P0KLPCX2HrwNYIo8vjJbPLyxZzyen9489hw4u7j+uzp+l5w7Owr+PFBepDz/vm88nUm3PEHCzDrsG4U6eWW1PNw6Oj1hRMa70JL2vIeYCT0r4kS8UebwPNK9FLsG7Gq4YN0bPcUNMDvPF3q8MgmLPL8hmrwbe8q8V94bPFjN7byPQGU7GztxvK8KwDxCMak8v2PvvCHCqDuv5g28x0tXPBK3NDxDKoa53Xn1vJWXxDxskgw8DFkXvP3u3zwDqpK70AuRuhN0nTwiLZk8LTjcPOq647zwhgQ8oBi3u2/wMb169IE8a3bgOnSHc7yVUgO9DyQaO+MPcj3t0JK83jbHvJKcT7zGjIe5DVccPNpyK7y2SbO8o015vEoj3TxzCQC8ETpyvGVpd7xZqb07SBKXvDc8Rz1EdhQ81klgu68dEL3jfY28QJ7BvDM53rygCQG8iBCxPCgcgzw5F648pYgFPMhW/Dwztj88e4mwuxMJYDwANay7DifEu3iPBDx+Vge8D5yivGgnAL0kfrm70G+HvHY6Ib06tQ485hGWvIFJmDs3Elw85rWJO+3vgDsOyhO897tbPIBXuDy3ewq8S73nPCO5ibycgDM8LFMuPVvDY7rajMM6DyTMu7BIgzx4LM+8UsskvSf4Pjsn1CY8nz5Ru6Hi7zzQ9EI8dNuKO7zOt7xP8wg65gpCvFP2lTwvh4A8mU0gPJRLCTtTHRq9PHFmPIVBxLtZ2wa9T7mhusVrGrz+2i+8W5N/vK2rpjyEEZm8v5ZPPZqMNzp09Iu8JbS8vL/5g7yMMzC8Zc/Fus1KiLwiIkQ778qWPMsu7zvZpyG92pbMOwl8XbrCJQC9YawcuXFTnTwFZc46MXLePAsktjteqf28XicMO1wsXLrV2wK8kehvu8LYvTy+wos8ZNtet5BojLvWwaK7m+evum+Embxx7nA8/dBmPEOJ5jzxS1k8OF60PPA3Dr0E8MQ806+hvHJ7qLwe8AG8ff0EvQcOCr0MgLo8N2+9uw8IArkYdB+5d8zlO2/vEDyTaNc8mGZJvIzjVTwvpxw9I4ZEvIeZyrt1S4W8IvFCvNK8GLwGBC08pRQJvS7S5DyGYDc9ij7zueCBGDtJ7g45QDP4u0YkWD0feVy8XGuMPPErUrymQ7u842V3O2cuMjxYE7u8oUneOw84r7ykwcI80sgDPHuyoTwncJu6bDGXvIgWeLv+4y+7svVSur1b4Tuw7Ti73GfivHqEqbxEFoC8ibTruwPPXLvdfrg8rgRsPP3ua7nooCQ7gMaBu4BpPzxfGLU8REpFvLR00bwbc4K8LjhJPBM5zjsv3w0968/du2yTBryBU228T5O3uhiGMD3UcC69KZSPPIdCqbyOltC85g5NvKNfs7ypdWU8hT49vKZF+Lu9gqG8NwksPJmpgDzy3+q8klnFPNs8HjzQnrA8mYgCPCFGRjwYm7W8ozMkvXQyzTsW+go92BtUvFb/JDz/8Uy8cEKEvOK6CD0EZve6QZtYPNJ6Jbz0Z7u8DycYvGflA704xZI82jIHvNmmHDyn4ZQ6oNoeu8J8xTyCiIe8DGlJO4NihTsl+rE7I1zwOzyyjTzR3IA8J1rgPCUcHLzYo0c7issRPBSPnzocUMs6+x1rPJLLojxrbRq8KBI5PJjygzuuP768Xm3iPID1ZbwS4S+9xU9uO2vg4ro5nIW8gGbnuK/87rm4Arg5F6HgvFCtvTw5LYc8sDjvuxfUMLxnh/m8a57kPLXj+DtLWtS7R23Fu0aKDb0+X0K8T2iBPB89g7shQX29vo6gvB7zLrsVpHK7e0U7u4lid7smMA09sqODu+SG8zwphVu8zfHVOzOchzzIRay8jgE/vV10lDqm3Zy8Rs49PIaFDbs9RFI7zVGtPMq6CDyfQNe8BximvI89MTgnC4s8pYsUPPAD5bv35G28NNQvvd4C1LsO4Vg8T1k5vH2UuLrnJII84/EVOyPoCj1UdkE90U0EvWSeX7ss8pG7lpgOvBYV3Dwy9PE8/RXQO4lWqjytEtG8GCMVvJwi2TzmJu+8e/C+OymYebylRk+8AvQRPCv1DLz2UzS8GqmHvP5+/zy4Kfk7Sg4CvJk6TrpTDrC7XkfQO/Lb8DxTclS8oBx0u3FJHLwoGwa7SLotvfMVF7z+lZ275//oPBgm5ru53RM8oK0RN+U/l7qm7987tqIXvcBBgrtlWTI5TJozODXVBbpNvQW9UqrUPNxZWLzaLLy7nGwDvNCEED1KLiW8JPtJPfRoZzybfSM8ud5RvOvPKT1qubq8JTIgvF3jDruH0ou8L202O9dwmjzJfig8t9zPulx0Rb2ClJE8kgq0u2juYzxUtK07PDmMu2VmmbtYTha97nyPPEsI1zxl5Xu8nCZiPBK4qbyTeLi7zzSJvOt8lbw9UcO7+/etvIIjaz2UARk9X0CXvA9r/jl+coo8QJhFO4YZ6Tzglsc8BH5DvASMMDyG2Zo8+i+yPI6nQLzv6Xo7izaWvOmJujxzhT68uuHnvOqH17zAaha8RTimvDqGAr0iRgA8w5zvO0MlHrmIsMs5T5o5vLCoqDyhGfa8TPuwvGjuljzL8sW8UfsvvJcIO73CoXq8C9iUvN0WPjyV5+671QDxvFhWWDwkvDI8lz8yPARDgjyitKs89EhlvLSA1zvmWv875RHQvKPAsLxZHPa7YFiQOsVyT7v8Z3g8+yuuOyN9FjzWEEw6AfFjvE65lbylngm8c8/QO/NfgDwejwg82DfQu7E5JjpZy5C761WNu0b54DyR+Na8RiQkvO2z6jwWpJm8g/OPPDvilzz2iyA7BRhSvBKfcjyrDYg7SkbuO+N4T7pc7Ic8dWCNPLZ5xryvuSM88Fg0PSirwTwlc7K8tnWQvHDxlTymL7a7pbojvBMoSDwwZ0q86xzGPLnCpzxZDSi8JQyNPOAOI7wcZjy9Al03vRkhqbx8Chc8rGfDPNKh/bt4d/C8sQgLvT4lFTuknLQ8rkBWPAdq+zs65/q8mV3/ux4PyzuMgPs7xKQ0PLQuODx2Md07VVwcO+lDiDw+Eo08ZejKuwdOdzywAQs9TZBZvHCxhLuyUtU7yf+tO8gNyDv6RMy6xZAJvDtbbDsK/TW8cFvAujotZTzxNkg8HCGuvPbDjLxlmA88RKoyPeeztbyPyi+6zPHRujcFvrxxFuY7u/xrvDS1tLtzfUE5XAOavDi14LzmrBA7EIj+u/wZqjtjEKU8Or+evDu+mbw/IP+7NPiWO/uVmTwU/8U5vL8rvaHBCLzJIlO7uOLJvAXO2zweqcm8syH7PBUJurtpKYA8SvHBPEWbVrwiK4q8h3kaPeEs2TyuC0k8BKuVuyBHrLsgbma9Ht01PcouFDu8BLa8roSOPBCAwDzsLxQ8gLobu4szjrwHzc28ejW2PKY2/7xJP+q8NKTqOTDC4Twt580857bFvP8QoTv7CgM90vCPPNC9ZTzXvx+6Xf19uxNxljq7hbI83kxUOyCL07qkyQm9rtejuz93/bwR9o28XeRIvEdzAb1Kfsu7NUKMu42cC718Ywa8vvu6O4pwpLwAz9S8anTUOLHlu7veDlm8GyPLu2m2BrwQm688GqpzvAV3+jwWOgO8DUD4vKZosztAL6U840pGvMrlXDtpFsc8vEq/O2gA8LxMTSy9W7emu6esN7qxADU7UCClvFHSyTrX66k7GPhsPEWqibk4eti8UILWuIQP0bxpAVu7gYzyPPhVQLu884y86ceWuqQZeDyN6ga9RkgNvfki8jx2VLo5m0dAvCYkqTyU5l+8u2mJPEzSzTyyMLu80qOgvAlcDrva1Zo8p7EBvHtEpDzcZ7i7+Yn8utLacjvWE6u7Q4M+PISkoTkYY/k712uYvJuc37yw6ty8jvajOzI2v7va3Ji8zIuLO48A67suIQy8Rub1vLnMxTzHeqa7UeBpvFskB71Tp527AbmDPKZWAT1bbbK7ImDFu+fGxjz1LKy8zUP8PCO1f7wV+gc7agVWOwWI1jyfJu27sT7WOy1ElbwtLFC8WtdNPXnuRjsO8ow8faQTPNhI/rzTGLs7UeB2PGM/5jzdQS88YsgKvXiqljx/ORW9z/oQvBCHLj1Ip1a8Ddedu3mB07yC75g6UKgfO68QWTxmh8e7Rmj4O6z05LzW0168FD04vHAdEDzuj+E8meWLO4En2Dz8z168jLxtOmgS+7pI+Jk81UxFO283ZDyWSkW8PcSrvIOYezyz+HQ8qDLtOgACnzzVnq48RAApPLQFLDzfKAe8VRMLPe9vCb3qWz+8qykOOofm/bzba+g8opDlPKeJNrzjZiq8pECrPMLBpjw2Voi7hSZZOvoN07pLMh69YEPKvIEAVrzZEs687rnKvAKcEztzxOO7QBOMPActFLvW3qM8Gy4Uuzg1S7sd5va7+9w0vHv7qLxBWBu8b++kujeDlrpbBoK7UfQevOdpm7yu+Uq8EptZO6FEhD1zfgk9i9XNOpEOxLxaAEs9U3HWvPK4oTxtNZk8/gwWPS8VK7xPA/O856dOO7cvobyuFiA9oqmHvJXM/DyczVI8zqChu/hFTD3kOAu9hO4mvAMPxbr00UO85rqUvHp5Gb2V7x49j9NSPN78Ej3wJ/u6bUafvCdttbrmzgw8VJnpPNbdhLsGYok8xt0uPF87ATwUude79oItPMGPaz0LISc9PFfdPIDzj7xb/WQ7LPzYuxYBqrwBVgE9EIkbPaN4Qbwz/Gg7X43nPEaF8jzOrmU7HDffu0769byv5Qe77AblO+QBdbysw9m7nyNmvNh7O7ydQEw8ljJQvUKU6byqbiA8JFSDPEm+QTxsR+w8XM6EvEVHZTzrDwY9+LqyvFKjVDv3Miu8Y2S/PKoC0TzoCAy8mZMnPOGrGzs6N727UMWmu7S/D71EhCm8sfdbvCDvc7wlaOs8at7sO5r1WD2y5Qg7T2qnPGOxeLnGYKK8Ml8CPRjQGTzPUtc7V2iYu8hXhrwjvG69Y2VfvIyZ2btgcTC7ax1Mu1FL07y2vuo8vThcO/I9NDyoOUw7O1Lou3zmCTskj6E8TSGPvLJKdbsUdj88XjKoOwyVJjuO4Ok7h7RKPJLu8jz32NW8hef6uhuYE73EbBA99vlZvDNi2zzs3Q087QBUvO9X0rxiKaU87GwXvGyxwzybUP87cVsCvThdnbvShgI8AVryO48LxTxPalC8yOWnvPFzwDu5hyW8ufsFvFDwIbxaegu9VWkAvE8WS7zlmsu8q7FUvD8DBD2Xbuc7ErC2u0nYODtod6g8kj0tPMnHCDwovv07+jjpPHvxG7t+umm8hWQdPESzpLzB4N+8TallO6q9L7u1YKq8PxQfOw+LorvFQGi6+vgXvI6QSTzDKS88pZEWvPvx5Duboc+8vR8uPDvk4DwkDiC7pUYmPRXjCrwxNfI6RhL1POlbiryI9iu8X1krvLFbEbxvOfQ7I+aZPAU3x7xsYTE8TrtVPAxx9Lu7jG48m9SZPHsFBD2tqp08U64jPHSTsTuMpay8EkkEvRiO+zwUPEa8DwiCvDnXMDxMRuk7eq8Pu0FYbDxCsbm6fgwDPZNuSTuuW6Q8pxisPL0jkDzykTg6PGtlPBZX/LsSm9U8hIUAu9+Iarx7ogW8ULY2vYu2dbzmQsK713JavNPbmzya5qm8ixh2vN8pQLzaV7m77yBROzMLxDxczso8fogSvW6oyrtXbpe8nFAfPGKFCLxEz+08/LUpvFdsAr0izYy8O1u8vI9EkDyThpC8ghjNO+BTALy0F2U8DIL8vPETyLxt63s730LLPIMxqLzra4u7SnhHPPruEzwDEKe88pT8u0zEIzuvn9M83Zu0POegvrxhCXG88fq/u3GqRj0e39K8h1r/u18XaLmtrTQ8XB0IPObay7w0S5c8axc5vNm++jzeR1E85m5LO5oWBbzT5Uo7TSGSPMxcRjyJMWU8x6gsvFCCfTwE+e07SfWSPGniGzyKZ6g8BuOjuyRAzbySn5K83Cruust4FDuVVGa82gyVPI3VZjwmmgO81qQ1PLKeh7yw/a07Be+OuoDBQ7xmkbK7YAnZvEhZZDtTqny8fSdIPOsneDzwtgc9K9qnPGsvYDwIvSQ7InNwu/gNhDpLWrW8SdiIPICTjrzZ/ts8KMCCvHdVbjxwtgi8C+FmvFYzajwMDP27gtrwO9PML7woL5O5CsBZPBp3WbzGRdS6wMpzvFOmDLuvIJS8+XkDPOlIBzxZXam8ZaCIPDGRETwhR0k8O9t+u2g6hzuYfmA7+xOpO9gfhbyfjAG8sYpJvPictzxD4zm9LX1SvKdpYjw1ziW9nnJ5PHxj0js0qEW801nxvIB4MbzlaYs8us2YPL57W7tlyZM8MS0ePGsIhTyIjLq7z+YHvZRN17yBG0w7e3gNvarqi7y/ml+8Pi4LO8JIwDvqClo8w2rEu7uFHL15xiI8SAWiuw== + index: 0 + object: embedding + model: qwen3-embedding:4b + object: list + usage: + prompt_tokens: 3 + total_tokens: 3 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_sandbox/TestSandboxVFS.test_metadata_json.yaml b/tests/cassettes/test_sandbox/TestSandboxVFS.test_metadata_json.yaml new file mode 100644 index 00000000..b6252ae6 --- /dev/null +++ b/tests/cassettes/test_sandbox/TestSandboxVFS.test_metadata_json.yaml @@ -0,0 +1,42 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '82' + content-type: + - application/json + host: + - localhost:11434 + method: POST + parsed_body: + encoding_format: base64 + input: + - Test 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: 3 + total_tokens: 3 + status: + code: 200 + message: OK +version: 1