Simplify sandbox with run_monty_async, replace manual ThreadPoolExecutor start/resume loop
This commit is contained in:
parent
b9fa1a061f
commit
d3c6322481
16 changed files with 3499 additions and 2129 deletions
|
|
@ -2,32 +2,33 @@ RLM_SYSTEM_PROMPT = """You are a Recursive Language Model (RLM) agent that solve
|
||||||
|
|
||||||
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.
|
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:
|
CRITICAL: Inside execute_code, these functions are ALREADY available in the namespace. Do NOT import them - just call them with `await`:
|
||||||
- search("query") ✓ CORRECT
|
- results = await search("query") ✓ CORRECT
|
||||||
- from haiku.rag import search ✗ WRONG - will fail
|
- import search ✗ WRONG - will fail
|
||||||
|
- results = search("query") ✗ WRONG - must use await
|
||||||
|
|
||||||
You have access to a sandboxed Python interpreter with these haiku.rag functions (use them directly, no imports needed):
|
You have access to a sandboxed Python interpreter with these functions (use them directly with `await`, no imports needed):
|
||||||
|
|
||||||
## Available Functions
|
## Available Functions
|
||||||
|
|
||||||
### search(query, limit=10) -> list[dict]
|
### await search(query, limit=10) -> list[dict]
|
||||||
Search the knowledge base using hybrid search (vector + full-text).
|
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
|
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]
|
### await list_documents(limit=10, offset=0) -> list[dict]
|
||||||
List available documents in the knowledge base.
|
List available documents in the knowledge base.
|
||||||
Returns list of dicts with keys: id, title, uri, created_at
|
Returns list of dicts with keys: id, title, uri, created_at
|
||||||
|
|
||||||
### get_document(id_or_title) -> str | None
|
### await get_document(id_or_title) -> str | None
|
||||||
Get the full text content of a document by ID, title, or URI.
|
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.
|
Returns the document content as a string, or None if not found.
|
||||||
|
|
||||||
### get_chunk(chunk_id) -> dict | None
|
### await get_chunk(chunk_id) -> dict | None
|
||||||
Get a specific chunk by its ID (from search results).
|
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
|
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.
|
Use this to retrieve full chunk details and metadata for citation.
|
||||||
|
|
||||||
### llm(prompt) -> str
|
### await llm(prompt) -> str
|
||||||
Call an LLM directly with the given prompt. Returns the response as a string.
|
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
|
Use this for classification, summarization, extraction, or any task where you
|
||||||
already have the content and just need LLM reasoning.
|
already have the content and just need LLM reasoning.
|
||||||
|
|
@ -44,7 +45,7 @@ Check if it exists with: `if 'documents' in dir(): ...`
|
||||||
|
|
||||||
## Available Python Features
|
## Available Python Features
|
||||||
|
|
||||||
The interpreter supports: variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, `map()`, `sorted()`/`.sort(key=...)`, try/except, and the `json` module.
|
The interpreter supports: variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, async/await, `map()`, `sorted()`/`.sort(key=...)`, try/except, and the `json` module.
|
||||||
|
|
||||||
Not supported: imports (other than `json`), class definitions, generators/yield, match statements, decorators, `with` statements.
|
Not supported: imports (other than `json`), class definitions, generators/yield, match statements, decorators, `with` statements.
|
||||||
|
|
||||||
|
|
@ -53,21 +54,21 @@ For pattern matching or text extraction, use string methods (`str.split`, `str.f
|
||||||
## Strategy Guide
|
## 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).
|
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.
|
2. **If get_document returns None**: Use `await list_documents()` to see actual document titles, or `await search()` to find relevant content.
|
||||||
3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
|
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.
|
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.
|
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().
|
6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use `await llm()`.
|
||||||
7. **Cite Your Sources**: Use get_chunk() to retrieve chunk metadata for citations. Track which documents/chunks informed your answer.
|
7. **Cite Your Sources**: Use get_chunk() to retrieve chunk metadata for citations. Track which documents/chunks informed your answer.
|
||||||
|
|
||||||
## Example Patterns
|
## Example Patterns
|
||||||
|
|
||||||
### Counting documents matching a condition
|
### Counting documents matching a condition
|
||||||
```python
|
```python
|
||||||
docs = list_documents(limit=100)
|
docs = await list_documents(limit=100)
|
||||||
count = 0
|
count = 0
|
||||||
for doc in docs:
|
for doc in docs:
|
||||||
content = get_document(doc['id'])
|
content = await get_document(doc['id'])
|
||||||
if content and 'keyword' in content.lower():
|
if content and 'keyword' in content.lower():
|
||||||
count += 1
|
count += 1
|
||||||
print(f"Found in: {doc['title']}")
|
print(f"Found in: {doc['title']}")
|
||||||
|
|
@ -77,9 +78,9 @@ print(f"Total: {count}")
|
||||||
### Extracting data with llm()
|
### Extracting data with llm()
|
||||||
```python
|
```python
|
||||||
numbers = []
|
numbers = []
|
||||||
results = search("financial data", limit=20)
|
results = await search("financial data", limit=20)
|
||||||
for r in results:
|
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']}")
|
extracted = await 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(','):
|
for part in extracted.split(','):
|
||||||
part = part.strip().replace(',', '')
|
part = part.strip().replace(',', '')
|
||||||
if part.isdigit():
|
if part.isdigit():
|
||||||
|
|
@ -90,16 +91,16 @@ if numbers:
|
||||||
|
|
||||||
### Using search results with get_chunk for citations
|
### Using search results with get_chunk for citations
|
||||||
```python
|
```python
|
||||||
results = search("safety requirements", limit=5)
|
results = await search("safety requirements", limit=5)
|
||||||
for r in results:
|
for r in results:
|
||||||
chunk = get_chunk(r['chunk_id'])
|
chunk = await get_chunk(r['chunk_id'])
|
||||||
print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}")
|
print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}")
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using llm() for classification
|
### Using llm() for classification
|
||||||
```python
|
```python
|
||||||
content = get_document("Q1 Report")
|
content = await get_document("Q1 Report")
|
||||||
sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
|
sentiment = await llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
|
||||||
print(sentiment)
|
print(sentiment)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
import asyncio
|
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from functools import partial
|
|
||||||
from typing import TYPE_CHECKING, Any, Literal
|
from typing import TYPE_CHECKING, Any, Literal
|
||||||
|
|
||||||
import pydantic_monty
|
import pydantic_monty
|
||||||
|
|
@ -27,12 +24,10 @@ class Sandbox:
|
||||||
|
|
||||||
Uses pydantic-monty, a minimal secure Python interpreter written in Rust.
|
Uses pydantic-monty, a minimal secure Python interpreter written in Rust.
|
||||||
External functions (search, list_documents, etc.) are called by Monty code
|
External functions (search, list_documents, etc.) are called by Monty code
|
||||||
and resolved asynchronously on the host.
|
using ``await`` and resolved asynchronously on the host.
|
||||||
|
|
||||||
Use as an async context manager:
|
sandbox = Sandbox(client, config, context)
|
||||||
|
result = await sandbox.execute("print('hello')")
|
||||||
async with Sandbox(client, config, context) as sandbox:
|
|
||||||
result = await sandbox.execute("print('hello')")
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_client: "HaikuRAG"
|
_client: "HaikuRAG"
|
||||||
|
|
@ -49,14 +44,6 @@ class Sandbox:
|
||||||
self._config = config
|
self._config = config
|
||||||
self._context = context
|
self._context = context
|
||||||
|
|
||||||
async def __aenter__(self) -> "Sandbox":
|
|
||||||
return self
|
|
||||||
|
|
||||||
async def __aexit__(
|
|
||||||
self, exc_type: object, exc_val: object, exc_tb: object
|
|
||||||
) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _build_external_functions(self) -> dict[str, Any]:
|
def _build_external_functions(self) -> dict[str, Any]:
|
||||||
"""Build async external functions for the Monty interpreter."""
|
"""Build async external functions for the Monty interpreter."""
|
||||||
client = self._client
|
client = self._client
|
||||||
|
|
@ -138,12 +125,7 @@ class Sandbox:
|
||||||
}
|
}
|
||||||
|
|
||||||
async def execute(self, code: str) -> SandboxResult:
|
async def execute(self, code: str) -> SandboxResult:
|
||||||
"""Execute Python code in the Monty interpreter.
|
"""Execute Python code in the Monty interpreter."""
|
||||||
|
|
||||||
Uses a manual start/resume loop so that async external functions
|
|
||||||
are awaited on the host while Monty code calls them synchronously
|
|
||||||
(without ``await``).
|
|
||||||
"""
|
|
||||||
external_fns = self._build_external_functions()
|
external_fns = self._build_external_functions()
|
||||||
|
|
||||||
input_names: list[str] = []
|
input_names: list[str] = []
|
||||||
|
|
@ -181,45 +163,14 @@ class Sandbox:
|
||||||
"max_duration_secs": self._config.rlm.code_timeout,
|
"max_duration_secs": self._config.rlm.code_timeout,
|
||||||
}
|
}
|
||||||
|
|
||||||
loop = asyncio.get_running_loop()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with ThreadPoolExecutor() as pool:
|
output = await pydantic_monty.run_monty_async(
|
||||||
|
monty,
|
||||||
async def run_in_pool(func: Any) -> Any:
|
inputs=inputs,
|
||||||
return await loop.run_in_executor(pool, func)
|
external_functions=external_fns,
|
||||||
|
limits=limits,
|
||||||
progress = await run_in_pool(
|
print_callback=print_callback,
|
||||||
partial(
|
)
|
||||||
monty.start,
|
|
||||||
inputs=inputs,
|
|
||||||
limits=limits,
|
|
||||||
print_callback=print_callback,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
while not isinstance(progress, pydantic_monty.MontyComplete):
|
|
||||||
assert isinstance(progress, pydantic_monty.MontySnapshot)
|
|
||||||
fn = external_fns.get(progress.function_name)
|
|
||||||
if fn is None:
|
|
||||||
exc = KeyError(f"Function {progress.function_name} not found")
|
|
||||||
progress = await run_in_pool(
|
|
||||||
partial(progress.resume, exception=exc)
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
result = await fn(*progress.args, **progress.kwargs)
|
|
||||||
except Exception as exc:
|
|
||||||
progress = await run_in_pool(
|
|
||||||
partial(progress.resume, exception=exc)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
progress = await run_in_pool(
|
|
||||||
partial(progress.resume, return_value=result)
|
|
||||||
)
|
|
||||||
|
|
||||||
output = progress.output
|
|
||||||
except pydantic_monty.MontyRuntimeError as e:
|
except pydantic_monty.MontyRuntimeError as e:
|
||||||
stdout = "".join(stdout_lines)
|
stdout = "".join(stdout_lines)
|
||||||
if len(stdout) > max_chars:
|
if len(stdout) > max_chars:
|
||||||
|
|
|
||||||
|
|
@ -1406,20 +1406,20 @@ class HaikuRAG:
|
||||||
loaded_docs.append(doc)
|
loaded_docs.append(doc)
|
||||||
context.documents = loaded_docs if loaded_docs else None
|
context.documents = loaded_docs if loaded_docs else None
|
||||||
|
|
||||||
async with Sandbox(
|
sandbox = Sandbox(
|
||||||
client=self,
|
client=self,
|
||||||
config=self._config,
|
config=self._config,
|
||||||
context=context,
|
context=context,
|
||||||
) as sandbox:
|
)
|
||||||
deps = RLMDeps(
|
deps = RLMDeps(
|
||||||
sandbox=sandbox,
|
sandbox=sandbox,
|
||||||
context=context,
|
context=context,
|
||||||
)
|
)
|
||||||
|
|
||||||
agent = create_rlm_agent(self._config)
|
agent = create_rlm_agent(self._config)
|
||||||
result = await agent.run(question, deps=deps)
|
result = await agent.run(question, deps=deps)
|
||||||
|
|
||||||
return result.output
|
return result.output
|
||||||
|
|
||||||
async def visualize_chunk(self, chunk: Chunk) -> list:
|
async def visualize_chunk(self, chunk: Chunk) -> list:
|
||||||
"""Render page images with bounding box highlights for a chunk.
|
"""Render page images with bounding box highlights for a chunk.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
name: rag-rlm
|
name: rag-rlm
|
||||||
description: >
|
description: >
|
||||||
Computational analysis of the knowledge base via code execution in a Docker sandbox.
|
Computational analysis of the knowledge base via code execution in a sandboxed Python interpreter.
|
||||||
Use for questions requiring counting, aggregation, statistics, data traversal,
|
Use for questions requiring counting, aggregation, statistics, data traversal,
|
||||||
comparison across documents, or any task best answered by writing Python code.
|
comparison across documents, or any task best answered by writing Python code.
|
||||||
Examples: "how many pages?", "compare table 3 across documents",
|
Examples: "how many pages?", "compare table 3 across documents",
|
||||||
|
|
@ -10,4 +10,4 @@ description: >
|
||||||
|
|
||||||
# RLM Analysis
|
# RLM Analysis
|
||||||
|
|
||||||
Use the `analyze` tool for complex analytical questions. It writes and executes Python code against the knowledge base in an isolated Docker sandbox.
|
Use the `analyze` tool for complex analytical questions. It writes and executes Python code against the knowledge base in a sandboxed Python interpreter.
|
||||||
|
|
|
||||||
|
|
@ -62,25 +62,25 @@ def create_analysis_toolset(
|
||||||
|
|
||||||
rlm_context = RLMContext(filter=effective_filter)
|
rlm_context = RLMContext(filter=effective_filter)
|
||||||
|
|
||||||
async with Sandbox(
|
sandbox = Sandbox(
|
||||||
client=client,
|
client=client,
|
||||||
config=config,
|
config=config,
|
||||||
context=rlm_context,
|
context=rlm_context,
|
||||||
) as sandbox:
|
)
|
||||||
deps = RLMDeps(
|
deps = RLMDeps(
|
||||||
sandbox=sandbox,
|
sandbox=sandbox,
|
||||||
context=rlm_context,
|
context=rlm_context,
|
||||||
)
|
)
|
||||||
|
|
||||||
rlm_agent = create_rlm_agent(config)
|
rlm_agent = create_rlm_agent(config)
|
||||||
result = await rlm_agent.run(task, deps=deps)
|
result = await rlm_agent.run(task, deps=deps)
|
||||||
|
|
||||||
program = result.output.program
|
program = result.output.program
|
||||||
|
|
||||||
return AnalysisResult(
|
return AnalysisResult(
|
||||||
answer=result.output.answer,
|
answer=result.output.answer,
|
||||||
code_executed=bool(program),
|
code_executed=bool(program),
|
||||||
)
|
)
|
||||||
|
|
||||||
toolset: FunctionToolset[RAGDeps] = FunctionToolset()
|
toolset: FunctionToolset[RAGDeps] = FunctionToolset()
|
||||||
toolset.add_function(analyze, name=tool_name)
|
toolset.add_function(analyze, name=tool_name)
|
||||||
|
|
|
||||||
|
|
@ -18,5 +18,4 @@ async def sandbox(empty_client):
|
||||||
"""Create a Monty sandbox for testing."""
|
"""Create a Monty sandbox for testing."""
|
||||||
config = AppConfig()
|
config = AppConfig()
|
||||||
context = RLMContext()
|
context = RLMContext()
|
||||||
async with Sandbox(client=empty_client, config=config, context=context) as sandbox:
|
return Sandbox(client=empty_client, config=config, context=context)
|
||||||
yield sandbox
|
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ class TestSandboxHaikuRAG:
|
||||||
async def test_list_documents_empty(self, sandbox):
|
async def test_list_documents_empty(self, sandbox):
|
||||||
"""Test list_documents returns empty list for empty database."""
|
"""Test list_documents returns empty list for empty database."""
|
||||||
result = await sandbox.execute(
|
result = await sandbox.execute(
|
||||||
"docs = list_documents()\nprint(type(docs).__name__, len(docs))"
|
"docs = await list_documents()\nprint(type(docs).__name__, len(docs))"
|
||||||
)
|
)
|
||||||
assert result.success
|
assert result.success
|
||||||
assert "list 0" in result.stdout
|
assert "list 0" in result.stdout
|
||||||
|
|
@ -92,13 +92,15 @@ class TestSandboxHaikuRAG:
|
||||||
)
|
)
|
||||||
|
|
||||||
context = RLMContext()
|
context = RLMContext()
|
||||||
async with Sandbox(client=client, config=config, context=context) as sb:
|
sb = Sandbox(client=client, config=config, context=context)
|
||||||
result = await sb.execute(
|
result = await sb.execute(
|
||||||
"docs = list_documents()\nprint(len(docs))\nprint(docs[0]['title'])"
|
"docs = await list_documents()\n"
|
||||||
)
|
"print(len(docs))\n"
|
||||||
assert result.success
|
"print(docs[0]['title'])"
|
||||||
assert "1" in result.stdout
|
)
|
||||||
assert "Test Document" in result.stdout
|
assert result.success
|
||||||
|
assert "1" in result.stdout
|
||||||
|
assert "Test Document" in result.stdout
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.vcr()
|
@pytest.mark.vcr()
|
||||||
|
|
@ -113,15 +115,15 @@ class TestSandboxHaikuRAG:
|
||||||
)
|
)
|
||||||
|
|
||||||
context = RLMContext()
|
context = RLMContext()
|
||||||
async with Sandbox(client=client, config=config, context=context) as sb:
|
sb = Sandbox(client=client, config=config, context=context)
|
||||||
result = await sb.execute(
|
result = await sb.execute(
|
||||||
"results = search('fox', limit=5)\n"
|
"results = await search('fox', limit=5)\n"
|
||||||
"print(len(results))\n"
|
"print(len(results))\n"
|
||||||
"if results:\n"
|
"if results:\n"
|
||||||
" print('fox' in results[0]['content'].lower())"
|
" print('fox' in results[0]['content'].lower())"
|
||||||
)
|
)
|
||||||
assert result.success
|
assert result.success
|
||||||
assert "True" in result.stdout or "1" in result.stdout
|
assert "True" in result.stdout or "1" in result.stdout
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.vcr()
|
@pytest.mark.vcr()
|
||||||
|
|
@ -136,19 +138,19 @@ class TestSandboxHaikuRAG:
|
||||||
)
|
)
|
||||||
|
|
||||||
context = RLMContext()
|
context = RLMContext()
|
||||||
async with Sandbox(client=client, config=config, context=context) as sb:
|
sb = Sandbox(client=client, config=config, context=context)
|
||||||
result = await sb.execute(
|
result = await sb.execute(
|
||||||
f"content = get_document('{doc.id}')\n"
|
f"content = await get_document('{doc.id}')\n"
|
||||||
"print('foxes' in content.lower() if content else 'None')"
|
"print('foxes' in content.lower() if content else 'None')"
|
||||||
)
|
)
|
||||||
assert result.success
|
assert result.success
|
||||||
assert "True" in result.stdout
|
assert "True" in result.stdout
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_get_document_not_found(self, sandbox):
|
async def test_get_document_not_found(self, sandbox):
|
||||||
"""Test get_document returns None for missing document."""
|
"""Test get_document returns None for missing document."""
|
||||||
result = await sandbox.execute(
|
result = await sandbox.execute(
|
||||||
"content = get_document('nonexistent-id')\nprint(content is None)"
|
"content = await get_document('nonexistent-id')\nprint(content is None)"
|
||||||
)
|
)
|
||||||
assert result.success
|
assert result.success
|
||||||
assert "True" in result.stdout
|
assert "True" in result.stdout
|
||||||
|
|
@ -166,24 +168,24 @@ class TestSandboxHaikuRAG:
|
||||||
)
|
)
|
||||||
|
|
||||||
context = RLMContext()
|
context = RLMContext()
|
||||||
async with Sandbox(client=client, config=config, context=context) as sb:
|
sb = Sandbox(client=client, config=config, context=context)
|
||||||
# First search to get a chunk_id
|
# First search to get a chunk_id
|
||||||
result = await sb.execute(
|
result = await sb.execute(
|
||||||
"results = search('foxes', limit=1)\n"
|
"results = await search('foxes', limit=1)\n"
|
||||||
"chunk_id = results[0]['chunk_id']\n"
|
"chunk_id = results[0]['chunk_id']\n"
|
||||||
"chunk = get_chunk(chunk_id)\n"
|
"chunk = await get_chunk(chunk_id)\n"
|
||||||
"print(chunk['document_title'])\n"
|
"print(chunk['document_title'])\n"
|
||||||
"print('content' in chunk)"
|
"print('content' in chunk)"
|
||||||
)
|
)
|
||||||
assert result.success
|
assert result.success
|
||||||
assert "Fox Document" in result.stdout
|
assert "Fox Document" in result.stdout
|
||||||
assert "True" in result.stdout
|
assert "True" in result.stdout
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_get_chunk_not_found(self, sandbox):
|
async def test_get_chunk_not_found(self, sandbox):
|
||||||
"""Test get_chunk returns None for missing chunk."""
|
"""Test get_chunk returns None for missing chunk."""
|
||||||
result = await sandbox.execute(
|
result = await sandbox.execute(
|
||||||
"chunk = get_chunk('nonexistent-id')\nprint(chunk is None)"
|
"chunk = await get_chunk('nonexistent-id')\nprint(chunk is None)"
|
||||||
)
|
)
|
||||||
assert result.success
|
assert result.success
|
||||||
assert "True" in result.stdout
|
assert "True" in result.stdout
|
||||||
|
|
@ -205,7 +207,11 @@ class TestSandboxExternalFunctionEdgeCases:
|
||||||
sandbox._build_external_functions = patched_build
|
sandbox._build_external_functions = patched_build
|
||||||
|
|
||||||
result = await sandbox.execute(
|
result = await sandbox.execute(
|
||||||
"try:\n search('hello')\nexcept:\n print('caught')\nprint('done')"
|
"try:\n"
|
||||||
|
" await search('hello')\n"
|
||||||
|
"except:\n"
|
||||||
|
" print('caught')\n"
|
||||||
|
"print('done')"
|
||||||
)
|
)
|
||||||
assert result.success
|
assert result.success
|
||||||
assert "caught" in result.stdout
|
assert "caught" in result.stdout
|
||||||
|
|
@ -213,7 +219,12 @@ class TestSandboxExternalFunctionEdgeCases:
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_external_function_raises_exception(self, sandbox):
|
async def test_external_function_raises_exception(self, sandbox):
|
||||||
"""Test that exceptions from external functions are propagated to Monty."""
|
"""Test that exceptions from async external functions surface as errors.
|
||||||
|
|
||||||
|
With run_monty_async, exceptions from async external functions
|
||||||
|
propagate as MontyRuntimeError rather than being catchable inside
|
||||||
|
Monty's try/except.
|
||||||
|
"""
|
||||||
original_build = sandbox._build_external_functions
|
original_build = sandbox._build_external_functions
|
||||||
|
|
||||||
def patched_build():
|
def patched_build():
|
||||||
|
|
@ -227,12 +238,9 @@ class TestSandboxExternalFunctionEdgeCases:
|
||||||
|
|
||||||
sandbox._build_external_functions = patched_build
|
sandbox._build_external_functions = patched_build
|
||||||
|
|
||||||
result = await sandbox.execute(
|
result = await sandbox.execute("await search('hello')")
|
||||||
"try:\n search('hello')\nexcept:\n print('caught')\nprint('done')"
|
assert not result.success
|
||||||
)
|
assert "external error" in result.stderr
|
||||||
assert result.success
|
|
||||||
assert "caught" in result.stdout
|
|
||||||
assert "done" in result.stdout
|
|
||||||
|
|
||||||
|
|
||||||
class TestSandboxOutputTruncation:
|
class TestSandboxOutputTruncation:
|
||||||
|
|
@ -244,12 +252,12 @@ class TestSandboxOutputTruncation:
|
||||||
config = AppConfig()
|
config = AppConfig()
|
||||||
config.rlm.max_output_chars = 20
|
config.rlm.max_output_chars = 20
|
||||||
context = RLMContext()
|
context = RLMContext()
|
||||||
async with Sandbox(client=empty_client, config=config, context=context) as sb:
|
sb = Sandbox(client=empty_client, config=config, context=context)
|
||||||
result = await sb.execute("print('a' * 100)\nx = 1/0")
|
result = await sb.execute("print('a' * 100)\nx = 1/0")
|
||||||
assert not result.success
|
assert not result.success
|
||||||
assert "ZeroDivisionError" in result.stderr
|
assert "ZeroDivisionError" in result.stderr
|
||||||
assert result.stdout.endswith("... (output truncated)")
|
assert result.stdout.endswith("... (output truncated)")
|
||||||
assert len(result.stdout) < 100
|
assert len(result.stdout) < 100
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_truncate_successful_output(self, empty_client):
|
async def test_truncate_successful_output(self, empty_client):
|
||||||
|
|
@ -257,11 +265,11 @@ class TestSandboxOutputTruncation:
|
||||||
config = AppConfig()
|
config = AppConfig()
|
||||||
config.rlm.max_output_chars = 20
|
config.rlm.max_output_chars = 20
|
||||||
context = RLMContext()
|
context = RLMContext()
|
||||||
async with Sandbox(client=empty_client, config=config, context=context) as sb:
|
sb = Sandbox(client=empty_client, config=config, context=context)
|
||||||
result = await sb.execute("print('b' * 100)")
|
result = await sb.execute("print('b' * 100)")
|
||||||
assert result.success
|
assert result.success
|
||||||
assert result.stdout.endswith("... (output truncated)")
|
assert result.stdout.endswith("... (output truncated)")
|
||||||
assert len(result.stdout) < 100
|
assert len(result.stdout) < 100
|
||||||
|
|
||||||
|
|
||||||
class TestSandboxContextFilter:
|
class TestSandboxContextFilter:
|
||||||
|
|
@ -285,17 +293,17 @@ class TestSandboxContextFilter:
|
||||||
)
|
)
|
||||||
|
|
||||||
context = RLMContext(filter="uri LIKE 'public://%'")
|
context = RLMContext(filter="uri LIKE 'public://%'")
|
||||||
async with Sandbox(client=client, config=config, context=context) as sb:
|
sb = Sandbox(client=client, config=config, context=context)
|
||||||
result = await sb.execute(
|
result = await sb.execute(
|
||||||
"docs = list_documents()\n"
|
"docs = await list_documents()\n"
|
||||||
"print(len(docs))\n"
|
"print(len(docs))\n"
|
||||||
"if docs:\n"
|
"if docs:\n"
|
||||||
" print(docs[0]['title'])"
|
" print(docs[0]['title'])"
|
||||||
)
|
)
|
||||||
assert result.success
|
assert result.success
|
||||||
assert "1" in result.stdout
|
assert "1" in result.stdout
|
||||||
assert "Public Doc" in result.stdout
|
assert "Public Doc" in result.stdout
|
||||||
assert "Private Doc" not in result.stdout
|
assert "Private Doc" not in result.stdout
|
||||||
|
|
||||||
|
|
||||||
class TestSandboxPreloadedDocuments:
|
class TestSandboxPreloadedDocuments:
|
||||||
|
|
@ -317,16 +325,16 @@ class TestSandboxPreloadedDocuments:
|
||||||
Document(id="2", content="Content B", title="Doc B", uri="b://2"),
|
Document(id="2", content="Content B", title="Doc B", uri="b://2"),
|
||||||
]
|
]
|
||||||
context = RLMContext(documents=docs)
|
context = RLMContext(documents=docs)
|
||||||
async with Sandbox(client=empty_client, config=config, context=context) as sb:
|
sb = Sandbox(client=empty_client, config=config, context=context)
|
||||||
result = await sb.execute(
|
result = await sb.execute(
|
||||||
"print(len(documents))\n"
|
"print(len(documents))\n"
|
||||||
"print(documents[0]['title'])\n"
|
"print(documents[0]['title'])\n"
|
||||||
"print(documents[1]['title'])"
|
"print(documents[1]['title'])"
|
||||||
)
|
)
|
||||||
assert result.success
|
assert result.success
|
||||||
assert "2" in result.stdout
|
assert "2" in result.stdout
|
||||||
assert "Doc A" in result.stdout
|
assert "Doc A" in result.stdout
|
||||||
assert "Doc B" in result.stdout
|
assert "Doc B" in result.stdout
|
||||||
|
|
||||||
|
|
||||||
class TestSandboxLLM:
|
class TestSandboxLLM:
|
||||||
|
|
@ -338,10 +346,10 @@ class TestSandboxLLM:
|
||||||
"""Test llm() calls the model and returns a string."""
|
"""Test llm() calls the model and returns a string."""
|
||||||
config = AppConfig()
|
config = AppConfig()
|
||||||
context = RLMContext()
|
context = RLMContext()
|
||||||
async with Sandbox(client=empty_client, config=config, context=context) as sb:
|
sb = Sandbox(client=empty_client, config=config, context=context)
|
||||||
result = await sb.execute(
|
result = await sb.execute(
|
||||||
"answer = llm('What is 2 + 2? Reply with just the number.')\n"
|
"answer = await llm('What is 2 + 2? Reply with just the number.')\n"
|
||||||
"print(answer)"
|
"print(answer)"
|
||||||
)
|
)
|
||||||
assert result.success
|
assert result.success
|
||||||
assert "4" in result.stdout
|
assert "4" in result.stdout
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -128,7 +128,7 @@ interactions:
|
||||||
connection:
|
connection:
|
||||||
- keep-alive
|
- keep-alive
|
||||||
content-length:
|
content-length:
|
||||||
- '7083'
|
- '7325'
|
||||||
content-type:
|
content-type:
|
||||||
- application/json
|
- application/json
|
||||||
host:
|
host:
|
||||||
|
|
@ -141,32 +141,33 @@ interactions:
|
||||||
|
|
||||||
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.
|
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:
|
CRITICAL: Inside execute_code, these functions are ALREADY available in the namespace. Do NOT import them - just call them with `await`:
|
||||||
- search("query") ✓ CORRECT
|
- results = await search("query") ✓ CORRECT
|
||||||
- from haiku.rag import search ✗ WRONG - will fail
|
- from haiku.rag import search ✗ WRONG - will fail
|
||||||
|
- results = search("query") ✗ WRONG - must use await
|
||||||
|
|
||||||
You have access to a sandboxed Python interpreter 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 with `await`, no imports needed):
|
||||||
|
|
||||||
## Available Functions
|
## Available Functions
|
||||||
|
|
||||||
### search(query, limit=10) -> list[dict]
|
### await search(query, limit=10) -> list[dict]
|
||||||
Search the knowledge base using hybrid search (vector + full-text).
|
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
|
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]
|
### await list_documents(limit=10, offset=0) -> list[dict]
|
||||||
List available documents in the knowledge base.
|
List available documents in the knowledge base.
|
||||||
Returns list of dicts with keys: id, title, uri, created_at
|
Returns list of dicts with keys: id, title, uri, created_at
|
||||||
|
|
||||||
### get_document(id_or_title) -> str | None
|
### await get_document(id_or_title) -> str | None
|
||||||
Get the full text content of a document by ID, title, or URI.
|
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.
|
Returns the document content as a string, or None if not found.
|
||||||
|
|
||||||
### get_chunk(chunk_id) -> dict | None
|
### await get_chunk(chunk_id) -> dict | None
|
||||||
Get a specific chunk by its ID (from search results).
|
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
|
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.
|
Use this to retrieve full chunk details and metadata for citation.
|
||||||
|
|
||||||
### llm(prompt) -> str
|
### await llm(prompt) -> str
|
||||||
Call an LLM directly with the given prompt. Returns the response as a string.
|
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
|
Use this for classification, summarization, extraction, or any task where you
|
||||||
already have the content and just need LLM reasoning.
|
already have the content and just need LLM reasoning.
|
||||||
|
|
@ -183,7 +184,7 @@ interactions:
|
||||||
|
|
||||||
## Available Python Features
|
## 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.
|
The interpreter supports: variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, async/await, `map()`, `sorted()`/`.sort(key=...)`, try/except, and the `json` module.
|
||||||
|
|
||||||
Not supported: imports (other than `json`), class definitions, generators/yield, match statements, decorators, `with` statements.
|
Not supported: imports (other than `json`), class definitions, generators/yield, match statements, decorators, `with` statements.
|
||||||
|
|
||||||
|
|
@ -192,21 +193,21 @@ interactions:
|
||||||
## Strategy Guide
|
## 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).
|
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.
|
2. **If get_document returns None**: Use `await list_documents()` to see actual document titles, or `await search()` to find relevant content.
|
||||||
3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
|
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.
|
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.
|
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().
|
6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use `await llm()`.
|
||||||
7. **Cite Your Sources**: Use get_chunk() to retrieve chunk metadata for citations. Track which documents/chunks informed your answer.
|
7. **Cite Your Sources**: Use get_chunk() to retrieve chunk metadata for citations. Track which documents/chunks informed your answer.
|
||||||
|
|
||||||
## Example Patterns
|
## Example Patterns
|
||||||
|
|
||||||
### Counting documents matching a condition
|
### Counting documents matching a condition
|
||||||
```python
|
```python
|
||||||
docs = list_documents(limit=100)
|
docs = await list_documents(limit=100)
|
||||||
count = 0
|
count = 0
|
||||||
for doc in docs:
|
for doc in docs:
|
||||||
content = get_document(doc['id'])
|
content = await get_document(doc['id'])
|
||||||
if content and 'keyword' in content.lower():
|
if content and 'keyword' in content.lower():
|
||||||
count += 1
|
count += 1
|
||||||
print(f"Found in: {doc['title']}")
|
print(f"Found in: {doc['title']}")
|
||||||
|
|
@ -216,9 +217,9 @@ interactions:
|
||||||
### Extracting data with llm()
|
### Extracting data with llm()
|
||||||
```python
|
```python
|
||||||
numbers = []
|
numbers = []
|
||||||
results = search("financial data", limit=20)
|
results = await search("financial data", limit=20)
|
||||||
for r in results:
|
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']}")
|
extracted = await 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(','):
|
for part in extracted.split(','):
|
||||||
part = part.strip().replace(',', '')
|
part = part.strip().replace(',', '')
|
||||||
if part.isdigit():
|
if part.isdigit():
|
||||||
|
|
@ -229,16 +230,16 @@ interactions:
|
||||||
|
|
||||||
### Using search results with get_chunk for citations
|
### Using search results with get_chunk for citations
|
||||||
```python
|
```python
|
||||||
results = search("safety requirements", limit=5)
|
results = await search("safety requirements", limit=5)
|
||||||
for r in results:
|
for r in results:
|
||||||
chunk = get_chunk(r['chunk_id'])
|
chunk = await get_chunk(r['chunk_id'])
|
||||||
print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}")
|
print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}")
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using llm() for classification
|
### Using llm() for classification
|
||||||
```python
|
```python
|
||||||
content = get_document("Q1 Report")
|
content = await get_document("Q1 Report")
|
||||||
sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
|
sentiment = await llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
|
||||||
print(sentiment)
|
print(sentiment)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -314,7 +315,7 @@ interactions:
|
||||||
response:
|
response:
|
||||||
headers:
|
headers:
|
||||||
content-length:
|
content-length:
|
||||||
- '552'
|
- '514'
|
||||||
content-type:
|
content-type:
|
||||||
- application/json
|
- application/json
|
||||||
parsed_body:
|
parsed_body:
|
||||||
|
|
@ -323,24 +324,24 @@ interactions:
|
||||||
index: 0
|
index: 0
|
||||||
message:
|
message:
|
||||||
content: ''
|
content: ''
|
||||||
reasoning: Need to list documents.
|
reasoning: Need to list docs.
|
||||||
role: assistant
|
role: assistant
|
||||||
tool_calls:
|
tool_calls:
|
||||||
- function:
|
- function:
|
||||||
arguments: '{"code":"docs=list_documents(limit=1000)\nprint(len(docs))\nfor d in docs:\n print(d[''title''])"}'
|
arguments: '{"code":"docs = await list_documents(limit=1000)\nprint(len(docs))"}'
|
||||||
name: execute_code
|
name: execute_code
|
||||||
id: call_tumky965
|
id: call_stp0fimx
|
||||||
index: 0
|
index: 0
|
||||||
type: function
|
type: function
|
||||||
created: 1771336699
|
created: 1771924497
|
||||||
id: chatcmpl-910
|
id: chatcmpl-750
|
||||||
model: gpt-oss
|
model: gpt-oss
|
||||||
object: chat.completion
|
object: chat.completion
|
||||||
system_fingerprint: fp_ollama
|
system_fingerprint: fp_ollama
|
||||||
usage:
|
usage:
|
||||||
completion_tokens: 56
|
completion_tokens: 44
|
||||||
prompt_tokens: 1562
|
prompt_tokens: 1623
|
||||||
total_tokens: 1618
|
total_tokens: 1667
|
||||||
status:
|
status:
|
||||||
code: 200
|
code: 200
|
||||||
message: OK
|
message: OK
|
||||||
|
|
@ -353,7 +354,7 @@ interactions:
|
||||||
connection:
|
connection:
|
||||||
- keep-alive
|
- keep-alive
|
||||||
content-length:
|
content-length:
|
||||||
- '7612'
|
- '7759'
|
||||||
content-type:
|
content-type:
|
||||||
- application/json
|
- application/json
|
||||||
host:
|
host:
|
||||||
|
|
@ -366,32 +367,33 @@ interactions:
|
||||||
|
|
||||||
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.
|
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:
|
CRITICAL: Inside execute_code, these functions are ALREADY available in the namespace. Do NOT import them - just call them with `await`:
|
||||||
- search("query") ✓ CORRECT
|
- results = await search("query") ✓ CORRECT
|
||||||
- from haiku.rag import search ✗ WRONG - will fail
|
- from haiku.rag import search ✗ WRONG - will fail
|
||||||
|
- results = search("query") ✗ WRONG - must use await
|
||||||
|
|
||||||
You have access to a sandboxed Python interpreter 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 with `await`, no imports needed):
|
||||||
|
|
||||||
## Available Functions
|
## Available Functions
|
||||||
|
|
||||||
### search(query, limit=10) -> list[dict]
|
### await search(query, limit=10) -> list[dict]
|
||||||
Search the knowledge base using hybrid search (vector + full-text).
|
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
|
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]
|
### await list_documents(limit=10, offset=0) -> list[dict]
|
||||||
List available documents in the knowledge base.
|
List available documents in the knowledge base.
|
||||||
Returns list of dicts with keys: id, title, uri, created_at
|
Returns list of dicts with keys: id, title, uri, created_at
|
||||||
|
|
||||||
### get_document(id_or_title) -> str | None
|
### await get_document(id_or_title) -> str | None
|
||||||
Get the full text content of a document by ID, title, or URI.
|
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.
|
Returns the document content as a string, or None if not found.
|
||||||
|
|
||||||
### get_chunk(chunk_id) -> dict | None
|
### await get_chunk(chunk_id) -> dict | None
|
||||||
Get a specific chunk by its ID (from search results).
|
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
|
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.
|
Use this to retrieve full chunk details and metadata for citation.
|
||||||
|
|
||||||
### llm(prompt) -> str
|
### await llm(prompt) -> str
|
||||||
Call an LLM directly with the given prompt. Returns the response as a string.
|
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
|
Use this for classification, summarization, extraction, or any task where you
|
||||||
already have the content and just need LLM reasoning.
|
already have the content and just need LLM reasoning.
|
||||||
|
|
@ -408,7 +410,7 @@ interactions:
|
||||||
|
|
||||||
## Available Python Features
|
## 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.
|
The interpreter supports: variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, async/await, `map()`, `sorted()`/`.sort(key=...)`, try/except, and the `json` module.
|
||||||
|
|
||||||
Not supported: imports (other than `json`), class definitions, generators/yield, match statements, decorators, `with` statements.
|
Not supported: imports (other than `json`), class definitions, generators/yield, match statements, decorators, `with` statements.
|
||||||
|
|
||||||
|
|
@ -417,21 +419,21 @@ interactions:
|
||||||
## Strategy Guide
|
## 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).
|
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.
|
2. **If get_document returns None**: Use `await list_documents()` to see actual document titles, or `await search()` to find relevant content.
|
||||||
3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
|
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.
|
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.
|
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().
|
6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use `await llm()`.
|
||||||
7. **Cite Your Sources**: Use get_chunk() to retrieve chunk metadata for citations. Track which documents/chunks informed your answer.
|
7. **Cite Your Sources**: Use get_chunk() to retrieve chunk metadata for citations. Track which documents/chunks informed your answer.
|
||||||
|
|
||||||
## Example Patterns
|
## Example Patterns
|
||||||
|
|
||||||
### Counting documents matching a condition
|
### Counting documents matching a condition
|
||||||
```python
|
```python
|
||||||
docs = list_documents(limit=100)
|
docs = await list_documents(limit=100)
|
||||||
count = 0
|
count = 0
|
||||||
for doc in docs:
|
for doc in docs:
|
||||||
content = get_document(doc['id'])
|
content = await get_document(doc['id'])
|
||||||
if content and 'keyword' in content.lower():
|
if content and 'keyword' in content.lower():
|
||||||
count += 1
|
count += 1
|
||||||
print(f"Found in: {doc['title']}")
|
print(f"Found in: {doc['title']}")
|
||||||
|
|
@ -441,9 +443,9 @@ interactions:
|
||||||
### Extracting data with llm()
|
### Extracting data with llm()
|
||||||
```python
|
```python
|
||||||
numbers = []
|
numbers = []
|
||||||
results = search("financial data", limit=20)
|
results = await search("financial data", limit=20)
|
||||||
for r in results:
|
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']}")
|
extracted = await 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(','):
|
for part in extracted.split(','):
|
||||||
part = part.strip().replace(',', '')
|
part = part.strip().replace(',', '')
|
||||||
if part.isdigit():
|
if part.isdigit():
|
||||||
|
|
@ -454,16 +456,16 @@ interactions:
|
||||||
|
|
||||||
### Using search results with get_chunk for citations
|
### Using search results with get_chunk for citations
|
||||||
```python
|
```python
|
||||||
results = search("safety requirements", limit=5)
|
results = await search("safety requirements", limit=5)
|
||||||
for r in results:
|
for r in results:
|
||||||
chunk = get_chunk(r['chunk_id'])
|
chunk = await get_chunk(r['chunk_id'])
|
||||||
print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}")
|
print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}")
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using llm() for classification
|
### Using llm() for classification
|
||||||
```python
|
```python
|
||||||
content = get_document("Q1 Report")
|
content = await get_document("Q1 Report")
|
||||||
sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
|
sentiment = await llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
|
||||||
print(sentiment)
|
print(sentiment)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -490,18 +492,17 @@ interactions:
|
||||||
- content: How many documents are in the database?
|
- content: How many documents are in the database?
|
||||||
role: user
|
role: user
|
||||||
- content: null
|
- content: null
|
||||||
reasoning: Need to list documents.
|
reasoning: Need to list docs.
|
||||||
role: assistant
|
role: assistant
|
||||||
tool_calls:
|
tool_calls:
|
||||||
- function:
|
- function:
|
||||||
arguments: '{"code":"docs=list_documents(limit=1000)\nprint(len(docs))\nfor d in docs:\n print(d[''title''])"}'
|
arguments: '{"code":"docs = await list_documents(limit=1000)\nprint(len(docs))"}'
|
||||||
name: execute_code
|
name: execute_code
|
||||||
id: call_tumky965
|
id: call_stp0fimx
|
||||||
type: function
|
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 = await list_documents(limit=1000)\nprint(len(docs))","stdout":"3\n","stderr":"","success":true}'
|
||||||
1\nDoc 2\nDoc 3\n","stderr":"","success":true}'
|
|
||||||
role: tool
|
role: tool
|
||||||
tool_call_id: call_tumky965
|
tool_call_id: call_stp0fimx
|
||||||
model: gpt-oss
|
model: gpt-oss
|
||||||
reasoning_effort: low
|
reasoning_effort: low
|
||||||
stream: false
|
stream: false
|
||||||
|
|
@ -552,7 +553,7 @@ interactions:
|
||||||
response:
|
response:
|
||||||
headers:
|
headers:
|
||||||
content-length:
|
content-length:
|
||||||
- '517'
|
- '416'
|
||||||
content-type:
|
content-type:
|
||||||
- application/json
|
- application/json
|
||||||
parsed_body:
|
parsed_body:
|
||||||
|
|
@ -560,18 +561,17 @@ interactions:
|
||||||
- finish_reason: stop
|
- finish_reason: stop
|
||||||
index: 0
|
index: 0
|
||||||
message:
|
message:
|
||||||
content: '{"answer":"There are 3 documents in the database.","program":"docs = list_documents(limit=1000)\nprint(f\"Number
|
content: '{"answer":"There are 3 documents in the database.","program":"docs = await list_documents(limit=1000)\nprint(len(docs))"}'
|
||||||
of documents: {len(docs)}\")\nfor doc in docs:\n print(f\"- {doc[''title'']} (ID: {doc[''id'']})\")"}'
|
|
||||||
role: assistant
|
role: assistant
|
||||||
created: 1771336701
|
created: 1771924498
|
||||||
id: chatcmpl-57
|
id: chatcmpl-945
|
||||||
model: gpt-oss
|
model: gpt-oss
|
||||||
object: chat.completion
|
object: chat.completion
|
||||||
system_fingerprint: fp_ollama
|
system_fingerprint: fp_ollama
|
||||||
usage:
|
usage:
|
||||||
completion_tokens: 82
|
completion_tokens: 38
|
||||||
prompt_tokens: 1684
|
prompt_tokens: 1709
|
||||||
total_tokens: 1766
|
total_tokens: 1747
|
||||||
status:
|
status:
|
||||||
code: 200
|
code: 200
|
||||||
message: OK
|
message: OK
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -128,7 +128,7 @@ interactions:
|
||||||
connection:
|
connection:
|
||||||
- keep-alive
|
- keep-alive
|
||||||
content-length:
|
content-length:
|
||||||
- '7077'
|
- '7319'
|
||||||
content-type:
|
content-type:
|
||||||
- application/json
|
- application/json
|
||||||
host:
|
host:
|
||||||
|
|
@ -141,32 +141,33 @@ interactions:
|
||||||
|
|
||||||
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.
|
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:
|
CRITICAL: Inside execute_code, these functions are ALREADY available in the namespace. Do NOT import them - just call them with `await`:
|
||||||
- search("query") ✓ CORRECT
|
- results = await search("query") ✓ CORRECT
|
||||||
- from haiku.rag import search ✗ WRONG - will fail
|
- from haiku.rag import search ✗ WRONG - will fail
|
||||||
|
- results = search("query") ✗ WRONG - must use await
|
||||||
|
|
||||||
You have access to a sandboxed Python interpreter 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 with `await`, no imports needed):
|
||||||
|
|
||||||
## Available Functions
|
## Available Functions
|
||||||
|
|
||||||
### search(query, limit=10) -> list[dict]
|
### await search(query, limit=10) -> list[dict]
|
||||||
Search the knowledge base using hybrid search (vector + full-text).
|
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
|
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]
|
### await list_documents(limit=10, offset=0) -> list[dict]
|
||||||
List available documents in the knowledge base.
|
List available documents in the knowledge base.
|
||||||
Returns list of dicts with keys: id, title, uri, created_at
|
Returns list of dicts with keys: id, title, uri, created_at
|
||||||
|
|
||||||
### get_document(id_or_title) -> str | None
|
### await get_document(id_or_title) -> str | None
|
||||||
Get the full text content of a document by ID, title, or URI.
|
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.
|
Returns the document content as a string, or None if not found.
|
||||||
|
|
||||||
### get_chunk(chunk_id) -> dict | None
|
### await get_chunk(chunk_id) -> dict | None
|
||||||
Get a specific chunk by its ID (from search results).
|
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
|
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.
|
Use this to retrieve full chunk details and metadata for citation.
|
||||||
|
|
||||||
### llm(prompt) -> str
|
### await llm(prompt) -> str
|
||||||
Call an LLM directly with the given prompt. Returns the response as a string.
|
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
|
Use this for classification, summarization, extraction, or any task where you
|
||||||
already have the content and just need LLM reasoning.
|
already have the content and just need LLM reasoning.
|
||||||
|
|
@ -183,7 +184,7 @@ interactions:
|
||||||
|
|
||||||
## Available Python Features
|
## 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.
|
The interpreter supports: variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, async/await, `map()`, `sorted()`/`.sort(key=...)`, try/except, and the `json` module.
|
||||||
|
|
||||||
Not supported: imports (other than `json`), class definitions, generators/yield, match statements, decorators, `with` statements.
|
Not supported: imports (other than `json`), class definitions, generators/yield, match statements, decorators, `with` statements.
|
||||||
|
|
||||||
|
|
@ -192,21 +193,21 @@ interactions:
|
||||||
## Strategy Guide
|
## 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).
|
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.
|
2. **If get_document returns None**: Use `await list_documents()` to see actual document titles, or `await search()` to find relevant content.
|
||||||
3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
|
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.
|
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.
|
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().
|
6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use `await llm()`.
|
||||||
7. **Cite Your Sources**: Use get_chunk() to retrieve chunk metadata for citations. Track which documents/chunks informed your answer.
|
7. **Cite Your Sources**: Use get_chunk() to retrieve chunk metadata for citations. Track which documents/chunks informed your answer.
|
||||||
|
|
||||||
## Example Patterns
|
## Example Patterns
|
||||||
|
|
||||||
### Counting documents matching a condition
|
### Counting documents matching a condition
|
||||||
```python
|
```python
|
||||||
docs = list_documents(limit=100)
|
docs = await list_documents(limit=100)
|
||||||
count = 0
|
count = 0
|
||||||
for doc in docs:
|
for doc in docs:
|
||||||
content = get_document(doc['id'])
|
content = await get_document(doc['id'])
|
||||||
if content and 'keyword' in content.lower():
|
if content and 'keyword' in content.lower():
|
||||||
count += 1
|
count += 1
|
||||||
print(f"Found in: {doc['title']}")
|
print(f"Found in: {doc['title']}")
|
||||||
|
|
@ -216,9 +217,9 @@ interactions:
|
||||||
### Extracting data with llm()
|
### Extracting data with llm()
|
||||||
```python
|
```python
|
||||||
numbers = []
|
numbers = []
|
||||||
results = search("financial data", limit=20)
|
results = await search("financial data", limit=20)
|
||||||
for r in results:
|
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']}")
|
extracted = await 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(','):
|
for part in extracted.split(','):
|
||||||
part = part.strip().replace(',', '')
|
part = part.strip().replace(',', '')
|
||||||
if part.isdigit():
|
if part.isdigit():
|
||||||
|
|
@ -229,16 +230,16 @@ interactions:
|
||||||
|
|
||||||
### Using search results with get_chunk for citations
|
### Using search results with get_chunk for citations
|
||||||
```python
|
```python
|
||||||
results = search("safety requirements", limit=5)
|
results = await search("safety requirements", limit=5)
|
||||||
for r in results:
|
for r in results:
|
||||||
chunk = get_chunk(r['chunk_id'])
|
chunk = await get_chunk(r['chunk_id'])
|
||||||
print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}")
|
print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}")
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using llm() for classification
|
### Using llm() for classification
|
||||||
```python
|
```python
|
||||||
content = get_document("Q1 Report")
|
content = await get_document("Q1 Report")
|
||||||
sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
|
sentiment = await llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
|
||||||
print(sentiment)
|
print(sentiment)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -314,7 +315,7 @@ interactions:
|
||||||
response:
|
response:
|
||||||
headers:
|
headers:
|
||||||
content-length:
|
content-length:
|
||||||
- '526'
|
- '547'
|
||||||
content-type:
|
content-type:
|
||||||
- application/json
|
- application/json
|
||||||
parsed_body:
|
parsed_body:
|
||||||
|
|
@ -323,24 +324,24 @@ interactions:
|
||||||
index: 0
|
index: 0
|
||||||
message:
|
message:
|
||||||
content: ''
|
content: ''
|
||||||
reasoning: Need to list_documents.
|
reasoning: Need to list documents.
|
||||||
role: assistant
|
role: assistant
|
||||||
tool_calls:
|
tool_calls:
|
||||||
- function:
|
- function:
|
||||||
arguments: '{"code":"docs=list_documents(limit=1000);print(len(docs)); print(docs[:3])"}'
|
arguments: '{"code":"docs = await list_documents(limit=1000, offset=0)\nprint(len(docs))\nprint(docs[:3])"}'
|
||||||
name: execute_code
|
name: execute_code
|
||||||
id: call_rpfcy176
|
id: call_pu34e9fx
|
||||||
index: 0
|
index: 0
|
||||||
type: function
|
type: function
|
||||||
created: 1771336720
|
created: 1771924517
|
||||||
id: chatcmpl-390
|
id: chatcmpl-236
|
||||||
model: gpt-oss
|
model: gpt-oss
|
||||||
object: chat.completion
|
object: chat.completion
|
||||||
system_fingerprint: fp_ollama
|
system_fingerprint: fp_ollama
|
||||||
usage:
|
usage:
|
||||||
completion_tokens: 47
|
completion_tokens: 56
|
||||||
prompt_tokens: 1560
|
prompt_tokens: 1621
|
||||||
total_tokens: 1607
|
total_tokens: 1677
|
||||||
status:
|
status:
|
||||||
code: 200
|
code: 200
|
||||||
message: OK
|
message: OK
|
||||||
|
|
@ -353,7 +354,7 @@ interactions:
|
||||||
connection:
|
connection:
|
||||||
- keep-alive
|
- keep-alive
|
||||||
content-length:
|
content-length:
|
||||||
- '7655'
|
- '7939'
|
||||||
content-type:
|
content-type:
|
||||||
- application/json
|
- application/json
|
||||||
host:
|
host:
|
||||||
|
|
@ -366,32 +367,33 @@ interactions:
|
||||||
|
|
||||||
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.
|
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:
|
CRITICAL: Inside execute_code, these functions are ALREADY available in the namespace. Do NOT import them - just call them with `await`:
|
||||||
- search("query") ✓ CORRECT
|
- results = await search("query") ✓ CORRECT
|
||||||
- from haiku.rag import search ✗ WRONG - will fail
|
- from haiku.rag import search ✗ WRONG - will fail
|
||||||
|
- results = search("query") ✗ WRONG - must use await
|
||||||
|
|
||||||
You have access to a sandboxed Python interpreter 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 with `await`, no imports needed):
|
||||||
|
|
||||||
## Available Functions
|
## Available Functions
|
||||||
|
|
||||||
### search(query, limit=10) -> list[dict]
|
### await search(query, limit=10) -> list[dict]
|
||||||
Search the knowledge base using hybrid search (vector + full-text).
|
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
|
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]
|
### await list_documents(limit=10, offset=0) -> list[dict]
|
||||||
List available documents in the knowledge base.
|
List available documents in the knowledge base.
|
||||||
Returns list of dicts with keys: id, title, uri, created_at
|
Returns list of dicts with keys: id, title, uri, created_at
|
||||||
|
|
||||||
### get_document(id_or_title) -> str | None
|
### await get_document(id_or_title) -> str | None
|
||||||
Get the full text content of a document by ID, title, or URI.
|
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.
|
Returns the document content as a string, or None if not found.
|
||||||
|
|
||||||
### get_chunk(chunk_id) -> dict | None
|
### await get_chunk(chunk_id) -> dict | None
|
||||||
Get a specific chunk by its ID (from search results).
|
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
|
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.
|
Use this to retrieve full chunk details and metadata for citation.
|
||||||
|
|
||||||
### llm(prompt) -> str
|
### await llm(prompt) -> str
|
||||||
Call an LLM directly with the given prompt. Returns the response as a string.
|
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
|
Use this for classification, summarization, extraction, or any task where you
|
||||||
already have the content and just need LLM reasoning.
|
already have the content and just need LLM reasoning.
|
||||||
|
|
@ -408,7 +410,7 @@ interactions:
|
||||||
|
|
||||||
## Available Python Features
|
## 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.
|
The interpreter supports: variables, arithmetic, strings, f-strings, lists, dicts, tuples, sets, loops, conditionals, comprehensions, functions, async/await, `map()`, `sorted()`/`.sort(key=...)`, try/except, and the `json` module.
|
||||||
|
|
||||||
Not supported: imports (other than `json`), class definitions, generators/yield, match statements, decorators, `with` statements.
|
Not supported: imports (other than `json`), class definitions, generators/yield, match statements, decorators, `with` statements.
|
||||||
|
|
||||||
|
|
@ -417,21 +419,21 @@ interactions:
|
||||||
## Strategy Guide
|
## 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).
|
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.
|
2. **If get_document returns None**: Use `await list_documents()` to see actual document titles, or `await search()` to find relevant content.
|
||||||
3. **Iterative Refinement**: Run code, examine results, adjust your approach based on what you find.
|
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.
|
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.
|
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().
|
6. **Use llm() for Classification/Extraction**: When you need to classify, summarize, or extract structured data from content you already have, use `await llm()`.
|
||||||
7. **Cite Your Sources**: Use get_chunk() to retrieve chunk metadata for citations. Track which documents/chunks informed your answer.
|
7. **Cite Your Sources**: Use get_chunk() to retrieve chunk metadata for citations. Track which documents/chunks informed your answer.
|
||||||
|
|
||||||
## Example Patterns
|
## Example Patterns
|
||||||
|
|
||||||
### Counting documents matching a condition
|
### Counting documents matching a condition
|
||||||
```python
|
```python
|
||||||
docs = list_documents(limit=100)
|
docs = await list_documents(limit=100)
|
||||||
count = 0
|
count = 0
|
||||||
for doc in docs:
|
for doc in docs:
|
||||||
content = get_document(doc['id'])
|
content = await get_document(doc['id'])
|
||||||
if content and 'keyword' in content.lower():
|
if content and 'keyword' in content.lower():
|
||||||
count += 1
|
count += 1
|
||||||
print(f"Found in: {doc['title']}")
|
print(f"Found in: {doc['title']}")
|
||||||
|
|
@ -441,9 +443,9 @@ interactions:
|
||||||
### Extracting data with llm()
|
### Extracting data with llm()
|
||||||
```python
|
```python
|
||||||
numbers = []
|
numbers = []
|
||||||
results = search("financial data", limit=20)
|
results = await search("financial data", limit=20)
|
||||||
for r in results:
|
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']}")
|
extracted = await 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(','):
|
for part in extracted.split(','):
|
||||||
part = part.strip().replace(',', '')
|
part = part.strip().replace(',', '')
|
||||||
if part.isdigit():
|
if part.isdigit():
|
||||||
|
|
@ -454,16 +456,16 @@ interactions:
|
||||||
|
|
||||||
### Using search results with get_chunk for citations
|
### Using search results with get_chunk for citations
|
||||||
```python
|
```python
|
||||||
results = search("safety requirements", limit=5)
|
results = await search("safety requirements", limit=5)
|
||||||
for r in results:
|
for r in results:
|
||||||
chunk = get_chunk(r['chunk_id'])
|
chunk = await get_chunk(r['chunk_id'])
|
||||||
print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}")
|
print(f"From '{chunk['document_title']}', page {chunk['page_numbers']}: {chunk['content'][:100]}")
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using llm() for classification
|
### Using llm() for classification
|
||||||
```python
|
```python
|
||||||
content = get_document("Q1 Report")
|
content = await get_document("Q1 Report")
|
||||||
sentiment = llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
|
sentiment = await llm(f"Classify the sentiment as positive, negative, or mixed: {content}")
|
||||||
print(sentiment)
|
print(sentiment)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -490,18 +492,18 @@ interactions:
|
||||||
- content: How many documents are available?
|
- content: How many documents are available?
|
||||||
role: user
|
role: user
|
||||||
- content: null
|
- content: null
|
||||||
reasoning: Need to list_documents.
|
reasoning: Need to list documents.
|
||||||
role: assistant
|
role: assistant
|
||||||
tool_calls:
|
tool_calls:
|
||||||
- function:
|
- function:
|
||||||
arguments: '{"code":"docs=list_documents(limit=1000);print(len(docs)); print(docs[:3])"}'
|
arguments: '{"code":"docs = await list_documents(limit=1000, offset=0)\nprint(len(docs))\nprint(docs[:3])"}'
|
||||||
name: execute_code
|
name: execute_code
|
||||||
id: call_rpfcy176
|
id: call_pu34e9fx
|
||||||
type: function
|
type: function
|
||||||
- content: '{"code":"docs=list_documents(limit=1000);print(len(docs)); print(docs[:3])","stdout":"1\n[{''id'': ''50c659ee-70c5-4854-a418-12ae2abdcf40'',
|
- content: '{"code":"docs = await list_documents(limit=1000, offset=0)\nprint(len(docs))\nprint(docs[:3])","stdout":"1\n[{''id'':
|
||||||
''title'': ''Cats'', ''uri'': None, ''created_at'': ''2026-02-17 15:58:36.831575''}]\n","stderr":"","success":true}'
|
''90fce4e5-0d68-4736-acaf-d075e3ed4822'', ''title'': ''Cats'', ''uri'': None, ''created_at'': ''2026-02-24 11:15:14.007278''}]\n","stderr":"","success":true}'
|
||||||
role: tool
|
role: tool
|
||||||
tool_call_id: call_rpfcy176
|
tool_call_id: call_pu34e9fx
|
||||||
model: gpt-oss
|
model: gpt-oss
|
||||||
reasoning_effort: low
|
reasoning_effort: low
|
||||||
stream: false
|
stream: false
|
||||||
|
|
@ -552,7 +554,7 @@ interactions:
|
||||||
response:
|
response:
|
||||||
headers:
|
headers:
|
||||||
content-length:
|
content-length:
|
||||||
- '453'
|
- '704'
|
||||||
content-type:
|
content-type:
|
||||||
- application/json
|
- application/json
|
||||||
parsed_body:
|
parsed_body:
|
||||||
|
|
@ -560,18 +562,20 @@ interactions:
|
||||||
- finish_reason: stop
|
- finish_reason: stop
|
||||||
index: 0
|
index: 0
|
||||||
message:
|
message:
|
||||||
content: '{"answer":"There is 1 document available in the knowledge base.","program":"docs=list_documents(limit=1000)\nprint(f\"Number
|
content: '{"answer":"There is 1 document available in the knowledge base.","program":"# List all documents\nimport
|
||||||
of documents: {len(docs)}\")"}'
|
json\nasync def main():\n docs = await list_documents(limit=1000, offset=0)\n print(\"Number of documents:\",
|
||||||
|
len(docs))\n # Optional: print first few document titles for reference\n for d in docs[:5]:\n print(f\"ID:
|
||||||
|
{d[''id'']}, Title: {d[''title'']}\")\n\nawait main()"}'
|
||||||
role: assistant
|
role: assistant
|
||||||
created: 1771336721
|
created: 1771924519
|
||||||
id: chatcmpl-97
|
id: chatcmpl-487
|
||||||
model: gpt-oss
|
model: gpt-oss
|
||||||
object: chat.completion
|
object: chat.completion
|
||||||
system_fingerprint: fp_ollama
|
system_fingerprint: fp_ollama
|
||||||
usage:
|
usage:
|
||||||
completion_tokens: 46
|
completion_tokens: 117
|
||||||
prompt_tokens: 1713
|
prompt_tokens: 1793
|
||||||
total_tokens: 1759
|
total_tokens: 1910
|
||||||
status:
|
status:
|
||||||
code: 200
|
code: 200
|
||||||
message: OK
|
message: OK
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -25,7 +25,7 @@ interactions:
|
||||||
response:
|
response:
|
||||||
headers:
|
headers:
|
||||||
content-length:
|
content-length:
|
||||||
- '307'
|
- '311'
|
||||||
content-type:
|
content-type:
|
||||||
- application/json
|
- application/json
|
||||||
parsed_body:
|
parsed_body:
|
||||||
|
|
@ -34,17 +34,17 @@ interactions:
|
||||||
index: 0
|
index: 0
|
||||||
message:
|
message:
|
||||||
content: '4'
|
content: '4'
|
||||||
reasoning: Answer 4.
|
reasoning: Just reply 4.
|
||||||
role: assistant
|
role: assistant
|
||||||
created: 1771338974
|
created: 1771924616
|
||||||
id: chatcmpl-199
|
id: chatcmpl-525
|
||||||
model: gpt-oss
|
model: gpt-oss
|
||||||
object: chat.completion
|
object: chat.completion
|
||||||
system_fingerprint: fp_ollama
|
system_fingerprint: fp_ollama
|
||||||
usage:
|
usage:
|
||||||
completion_tokens: 15
|
completion_tokens: 16
|
||||||
prompt_tokens: 81
|
prompt_tokens: 81
|
||||||
total_tokens: 96
|
total_tokens: 97
|
||||||
status:
|
status:
|
||||||
code: 200
|
code: 200
|
||||||
message: OK
|
message: OK
|
||||||
|
|
|
||||||
110
uv.lock
110
uv.lock
|
|
@ -180,7 +180,7 @@ wheels = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anthropic"
|
name = "anthropic"
|
||||||
version = "0.79.0"
|
version = "0.83.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "anyio" },
|
{ name = "anyio" },
|
||||||
|
|
@ -192,9 +192,9 @@ dependencies = [
|
||||||
{ name = "sniffio" },
|
{ name = "sniffio" },
|
||||||
{ name = "typing-extensions" },
|
{ name = "typing-extensions" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/15/b1/91aea3f8fd180d01d133d931a167a78a3737b3fd39ccef2ae8d6619c24fd/anthropic-0.79.0.tar.gz", hash = "sha256:8707aafb3b1176ed6c13e2b1c9fb3efddce90d17aee5d8b83a86c70dcdcca871", size = 509825, upload-time = "2026-02-07T18:06:18.388Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/db/e5/02cd2919ec327b24234abb73082e6ab84c451182cc3cc60681af700f4c63/anthropic-0.83.0.tar.gz", hash = "sha256:a8732c68b41869266c3034541a31a29d8be0f8cd0a714f9edce3128b351eceb4", size = 534058, upload-time = "2026-02-19T19:26:38.904Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/95/b2/cc0b8e874a18d7da50b0fda8c99e4ac123f23bf47b471827c5f6f3e4a767/anthropic-0.79.0-py3-none-any.whl", hash = "sha256:04cbd473b6bbda4ca2e41dd670fe2f829a911530f01697d0a1e37321eb75f3cf", size = 405918, upload-time = "2026-02-07T18:06:20.246Z" },
|
{ url = "https://files.pythonhosted.org/packages/5f/75/b9d58e4e2a4b1fc3e75ffbab978f999baf8b7c4ba9f96e60edb918ba386b/anthropic-0.83.0-py3-none-any.whl", hash = "sha256:f069ef508c73b8f9152e8850830d92bd5ef185645dbacf234bb213344a274810", size = 456991, upload-time = "2026-02-19T19:26:40.114Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1092,7 +1092,7 @@ name = "ffmpeg-python"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "future" },
|
{ name = "future", marker = "python_full_version < '3.14'" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/dd/5e/d5f9105d59c1325759d838af4e973695081fbbc97182baf73afc78dec266/ffmpeg-python-0.2.0.tar.gz", hash = "sha256:65225db34627c578ef0e11c8b1eb528bb35e024752f6f10b78c011f6f64c4127", size = 21543, upload-time = "2019-07-06T00:19:08.989Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/dd/5e/d5f9105d59c1325759d838af4e973695081fbbc97182baf73afc78dec266/ffmpeg-python-0.2.0.tar.gz", hash = "sha256:65225db34627c578ef0e11c8b1eb528bb35e024752f6f10b78c011f6f64c4127", size = 21543, upload-time = "2019-07-06T00:19:08.989Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
|
|
@ -1306,30 +1306,6 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/c4/ab/09169d5a4612a5f92490806649ac8d41e3ec9129c636754575b3553f4ea4/googleapis_common_protos-1.72.0-py3-none-any.whl", hash = "sha256:4299c5a82d5ae1a9702ada957347726b167f9f8d1fc352477702a1e851ff4038", size = 297515, upload-time = "2025-11-06T18:29:13.14Z" },
|
{ url = "https://files.pythonhosted.org/packages/c4/ab/09169d5a4612a5f92490806649ac8d41e3ec9129c636754575b3553f4ea4/googleapis_common_protos-1.72.0-py3-none-any.whl", hash = "sha256:4299c5a82d5ae1a9702ada957347726b167f9f8d1fc352477702a1e851ff4038", size = 297515, upload-time = "2025-11-06T18:29:13.14Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "griffe"
|
|
||||||
version = "2.0.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "griffecli" },
|
|
||||||
{ name = "griffelib" },
|
|
||||||
]
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/8b/94/ee21d41e7eb4f823b94603b9d40f86d3c7fde80eacc2c3c71845476dddaa/griffe-2.0.0-py3-none-any.whl", hash = "sha256:5418081135a391c3e6e757a7f3f156f1a1a746cc7b4023868ff7d5e2f9a980aa", size = 5214, upload-time = "2026-02-09T19:09:44.105Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "griffecli"
|
|
||||||
version = "2.0.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "colorama" },
|
|
||||||
{ name = "griffelib" },
|
|
||||||
]
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e6/ed/d93f7a447bbf7a935d8868e9617cbe1cadf9ee9ee6bd275d3040fbf93d60/griffecli-2.0.0-py3-none-any.whl", hash = "sha256:9f7cd9ee9b21d55e91689358978d2385ae65c22f307a63fb3269acf3f21e643d", size = 9345, upload-time = "2026-02-09T19:09:42.554Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "griffelib"
|
name = "griffelib"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
|
|
@ -1978,14 +1954,14 @@ name = "langchain-core"
|
||||||
version = "1.2.13"
|
version = "1.2.13"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "jsonpatch" },
|
{ name = "jsonpatch", marker = "python_full_version < '3.14'" },
|
||||||
{ name = "langsmith" },
|
{ name = "langsmith", marker = "python_full_version < '3.14'" },
|
||||||
{ name = "packaging" },
|
{ name = "packaging", marker = "python_full_version < '3.14'" },
|
||||||
{ name = "pydantic" },
|
{ name = "pydantic", marker = "python_full_version < '3.14'" },
|
||||||
{ name = "pyyaml" },
|
{ name = "pyyaml", marker = "python_full_version < '3.14'" },
|
||||||
{ name = "tenacity" },
|
{ name = "tenacity", marker = "python_full_version < '3.14'" },
|
||||||
{ name = "typing-extensions" },
|
{ name = "typing-extensions", marker = "python_full_version < '3.14'" },
|
||||||
{ name = "uuid-utils" },
|
{ name = "uuid-utils", marker = "python_full_version < '3.14'" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/fb/bb/c501ca60556c11ac80d1454bdcac63cb33583ce4e64fc4535ad5a7d5c6ba/langchain_core-1.2.13.tar.gz", hash = "sha256:d2773d0d0130a356378db9a858cfeef64c3d64bc03722f1d4d6c40eb46fdf01b", size = 831612, upload-time = "2026-02-15T07:45:57.014Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/fb/bb/c501ca60556c11ac80d1454bdcac63cb33583ce4e64fc4535ad5a7d5c6ba/langchain_core-1.2.13.tar.gz", hash = "sha256:d2773d0d0130a356378db9a858cfeef64c3d64bc03722f1d4d6c40eb46fdf01b", size = 831612, upload-time = "2026-02-15T07:45:57.014Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
|
|
@ -1997,7 +1973,7 @@ name = "langchain-text-splitters"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "langchain-core" },
|
{ name = "langchain-core", marker = "python_full_version < '3.14'" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/41/42/c178dcdc157b473330eb7cc30883ea69b8ec60078c7b85e2d521054c4831/langchain_text_splitters-1.1.0.tar.gz", hash = "sha256:75e58acb7585dc9508f3cd9d9809cb14751283226c2d6e21fb3a9ae57582ca22", size = 272230, upload-time = "2025-12-14T01:15:38.659Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/41/42/c178dcdc157b473330eb7cc30883ea69b8ec60078c7b85e2d521054c4831/langchain_text_splitters-1.1.0.tar.gz", hash = "sha256:75e58acb7585dc9508f3cd9d9809cb14751283226c2d6e21fb3a9ae57582ca22", size = 272230, upload-time = "2025-12-14T01:15:38.659Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
|
|
@ -2009,15 +1985,15 @@ name = "langsmith"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "httpx" },
|
{ name = "httpx", marker = "python_full_version < '3.14'" },
|
||||||
{ name = "orjson", marker = "platform_python_implementation != 'PyPy'" },
|
{ name = "orjson", marker = "python_full_version < '3.14' and platform_python_implementation != 'PyPy'" },
|
||||||
{ name = "packaging" },
|
{ name = "packaging", marker = "python_full_version < '3.14'" },
|
||||||
{ name = "pydantic" },
|
{ name = "pydantic", marker = "python_full_version < '3.14'" },
|
||||||
{ name = "requests" },
|
{ name = "requests", marker = "python_full_version < '3.14'" },
|
||||||
{ name = "requests-toolbelt" },
|
{ name = "requests-toolbelt", marker = "python_full_version < '3.14'" },
|
||||||
{ name = "uuid-utils" },
|
{ name = "uuid-utils", marker = "python_full_version < '3.14'" },
|
||||||
{ name = "xxhash" },
|
{ name = "xxhash", marker = "python_full_version < '3.14'" },
|
||||||
{ name = "zstandard" },
|
{ name = "zstandard", marker = "python_full_version < '3.14'" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/8d/bc/8172fefad4f2da888a6d564a27d1fb7d4dbf3c640899c2b40c46235cbe98/langsmith-0.7.3.tar.gz", hash = "sha256:0223b97021af62d2cf53c8a378a27bd22e90a7327e45b353e0069ae60d5d6f9e", size = 988575, upload-time = "2026-02-13T23:25:32.916Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/8d/bc/8172fefad4f2da888a6d564a27d1fb7d4dbf3c640899c2b40c46235cbe98/langsmith-0.7.3.tar.gz", hash = "sha256:0223b97021af62d2cf53c8a378a27bd22e90a7327e45b353e0069ae60d5d6f9e", size = 988575, upload-time = "2026-02-13T23:25:32.916Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
|
|
@ -3626,20 +3602,20 @@ email = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pydantic-ai-slim"
|
name = "pydantic-ai-slim"
|
||||||
version = "1.60.0"
|
version = "1.63.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "genai-prices" },
|
{ name = "genai-prices" },
|
||||||
{ name = "griffe" },
|
{ name = "griffelib" },
|
||||||
{ name = "httpx" },
|
{ name = "httpx" },
|
||||||
{ name = "opentelemetry-api" },
|
{ name = "opentelemetry-api" },
|
||||||
{ name = "pydantic" },
|
{ name = "pydantic" },
|
||||||
{ name = "pydantic-graph" },
|
{ name = "pydantic-graph" },
|
||||||
{ name = "typing-inspection" },
|
{ name = "typing-inspection" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/16/97/f73f439f3d415d43f38250f76852121188d0ef6114ec702e84e7d69c301d/pydantic_ai_slim-1.60.0.tar.gz", hash = "sha256:12ba3e6ef933fcb9fc6a307dbdaa43ca15bbc1b8ec77521afd1b7a526d12330f", size = 418839, upload-time = "2026-02-17T00:33:29.672Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/da/6d/2b5c0c60b42e6af49830f6a09b5d38fecdb1f20d9659152691eba95613b4/pydantic_ai_slim-1.63.0.tar.gz", hash = "sha256:9377afecdfe4bc17f5c9ed72c758e460703ac5876931aa2f18ace8ac0e69312a", size = 426862, upload-time = "2026-02-23T17:56:36.215Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/e3/40/8cb494a4d2ba62b5f92ae8bc79a2abbbf8509cb692edd4bc841695187780/pydantic_ai_slim-1.60.0-py3-none-any.whl", hash = "sha256:6865188a225a2979c82bb022a299d438d805d258c6d3f9810f7fe4e3c86af80a", size = 546410, upload-time = "2026-02-17T00:33:21.901Z" },
|
{ url = "https://files.pythonhosted.org/packages/f2/ca/c4e39eec1cff5a294b64313a8a959b38d326819e0f0a41f48e61ce019a22/pydantic_ai_slim-1.63.0-py3-none-any.whl", hash = "sha256:ed393b0f871b748171f65bec5191c3025b5abb8a4fc616afee17eb9dc2dfa15d", size = 554190, upload-time = "2026-02-23T17:56:29.533Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.optional-dependencies]
|
[package.optional-dependencies]
|
||||||
|
|
@ -3683,7 +3659,7 @@ vertexai = [
|
||||||
{ name = "requests" },
|
{ name = "requests" },
|
||||||
]
|
]
|
||||||
voyageai = [
|
voyageai = [
|
||||||
{ name = "voyageai" },
|
{ name = "voyageai", marker = "python_full_version < '3.14'" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -3759,7 +3735,7 @@ wheels = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pydantic-evals"
|
name = "pydantic-evals"
|
||||||
version = "1.60.0"
|
version = "1.63.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "anyio" },
|
{ name = "anyio" },
|
||||||
|
|
@ -3769,14 +3745,14 @@ dependencies = [
|
||||||
{ name = "pyyaml" },
|
{ name = "pyyaml" },
|
||||||
{ name = "rich" },
|
{ name = "rich" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/ec/2c/bed606a726b09adc9ee414bdb919ffe499edf7f8c631ba03b0ff3aa34435/pydantic_evals-1.60.0.tar.gz", hash = "sha256:ae3edd6667075acd8ef04c0d6fffb1ebe72c37ff077295fdbd6319e59284580b", size = 54214, upload-time = "2026-02-17T00:33:31.697Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/99/43/21b6ddf65b56f7401c344f98e4e6258a02d2868c8a52a8b79c0e0e701029/pydantic_evals-1.63.0.tar.gz", hash = "sha256:eed56a7192e07c8be8cf16e53bb2ef652b4f7f7b8527650ac45fde865a4ecf9d", size = 56365, upload-time = "2026-02-23T17:56:37.71Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/fe/a2/60790b2c971f6ce78fea2db2b65800489982c341ad9aa6db750070a93dcd/pydantic_evals-1.60.0-py3-none-any.whl", hash = "sha256:7a7414535002cae63ba0d0d9b15c6e72c28252cf120290b18372c9852c91fcfe", size = 65278, upload-time = "2026-02-17T00:33:23.411Z" },
|
{ url = "https://files.pythonhosted.org/packages/9b/f2/7174ad6abca2457e35a1b902ca4fa78aa8ee72e4ec2e9cd5dc8904014ec9/pydantic_evals-1.63.0-py3-none-any.whl", hash = "sha256:2e92a3af579a5670b2babf2044081d0ef99ab5a9ef141972616d71fd7e5bfd0e", size = 67279, upload-time = "2026-02-23T17:56:31.008Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pydantic-graph"
|
name = "pydantic-graph"
|
||||||
version = "1.60.0"
|
version = "1.63.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "httpx" },
|
{ name = "httpx" },
|
||||||
|
|
@ -3784,9 +3760,9 @@ dependencies = [
|
||||||
{ name = "pydantic" },
|
{ name = "pydantic" },
|
||||||
{ name = "typing-inspection" },
|
{ name = "typing-inspection" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/f9/e6/1cae7cd39ab29f2eebc87c0a82c7ffcdfbe88492d2fb4afaaad91534e1ff/pydantic_graph-1.60.0.tar.gz", hash = "sha256:9710e457c2f8c113fd63629f05174e45bdca917d90c69ec8cf558649f995505f", size = 58492, upload-time = "2026-02-17T00:33:32.66Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/7a/c8/aa3cb56552562b799f31e9de291c8bd88306308cfc9647d220dfff2bea18/pydantic_graph-1.63.0.tar.gz", hash = "sha256:5fd98bb22fa6181f0357a6ffad38a3214af12868bd46492d6456c5db434466b4", size = 58528, upload-time = "2026-02-23T17:56:39.118Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/d8/6c/ce1c0eca77c6efbf7c7168c05a244c2756bde876a52575f750471d522024/pydantic_graph-1.60.0-py3-none-any.whl", hash = "sha256:741fa1e48424b0def86079a01100ad0652e75882f0352cd157232b75ace468a5", size = 72345, upload-time = "2026-02-17T00:33:25.077Z" },
|
{ url = "https://files.pythonhosted.org/packages/a4/1c/8dcae24c824dd2690fbe7375083b369b10ed1ad773e2b9d1122bb6c0fcdc/pydantic_graph-1.63.0-py3-none-any.whl", hash = "sha256:d9b7a387116f358d470c042b07aa08125cadfcfa8c08ef01769746a489aef0d5", size = 72353, upload-time = "2026-02-23T17:56:32.304Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -4398,7 +4374,7 @@ name = "requests-toolbelt"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "requests" },
|
{ name = "requests", marker = "python_full_version < '3.14'" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
|
|
@ -5371,16 +5347,16 @@ name = "voyageai"
|
||||||
version = "0.3.7"
|
version = "0.3.7"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "aiohttp" },
|
{ name = "aiohttp", marker = "python_full_version < '3.14'" },
|
||||||
{ name = "aiolimiter" },
|
{ name = "aiolimiter", marker = "python_full_version < '3.14'" },
|
||||||
{ name = "ffmpeg-python" },
|
{ name = "ffmpeg-python", marker = "python_full_version < '3.14'" },
|
||||||
{ name = "langchain-text-splitters" },
|
{ name = "langchain-text-splitters", marker = "python_full_version < '3.14'" },
|
||||||
{ name = "numpy", marker = "python_full_version < '3.14'" },
|
{ name = "numpy", marker = "python_full_version < '3.14'" },
|
||||||
{ name = "pillow" },
|
{ name = "pillow", marker = "python_full_version < '3.14'" },
|
||||||
{ name = "pydantic" },
|
{ name = "pydantic", marker = "python_full_version < '3.14'" },
|
||||||
{ name = "requests" },
|
{ name = "requests", marker = "python_full_version < '3.14'" },
|
||||||
{ name = "tenacity" },
|
{ name = "tenacity", marker = "python_full_version < '3.14'" },
|
||||||
{ name = "tokenizers" },
|
{ name = "tokenizers", marker = "python_full_version < '3.14'" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/94/16/1b46b3cd401e1717a68197c1fe336d7bb4e0a1833f8105e1738f5b1add05/voyageai-0.3.7.tar.gz", hash = "sha256:826cd97f97223f42b5babc5c459c9c80f3a8215ce5c0e007b0b276550f790d24", size = 26485, upload-time = "2025-12-16T18:43:05.26Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/94/16/1b46b3cd401e1717a68197c1fe336d7bb4e0a1833f8105e1738f5b1add05/voyageai-0.3.7.tar.gz", hash = "sha256:826cd97f97223f42b5babc5c459c9c80f3a8215ce5c0e007b0b276550f790d24", size = 26485, upload-time = "2025-12-16T18:43:05.26Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue