ratel-ai retrieves the tools and skills relevant to each agent turn instead of sending the full catalog to the model. It bundles Ratel's Rust engine in-process: BM25 by default, with configurable semantic and hybrid retrieval available when needed. The default and local-model paths require no API key, vector database, or service. Installing a published package on a supported prebuilt target also requires no Rust toolchain.
Use ToolCatalog for ranked tools with sync or async handlers and SkillCatalog for ranked Markdown playbooks loaded on demand. Expose search_capabilities_tool, invoke_tool_tool, and get_skill_content_tool so an agent can discover tools and skills, invoke tools, and load full skill instructions. Tools from existing MCP servers can be ingested into the tool catalog with the mcp extra. Experimental — facts: the opt-in ratel_ai.experimental namespace adds FactCatalog for constant grounding content (a shop's address, a brand's voice). See Facts below. This API may change or be removed without a major version bump.
Semantic and hybrid retrieval use a configurable embedding model (ADR 0012), set per catalog via the embedding argument: the built-in default, a HuggingFace repo or local directory (in-process), or an OpenAI-compatible endpoint (OpenAI, Ollama, TEI, vLLM).
For semantic or hybrid retrieval, register() folds embedding in: it accepts one tool or a whole batch and embeds on a worker thread, so model loading, HTTP, and inference never block the asyncio loop or hold the GIL — and embedding errors surface right at register():
async def retrieve(tools):
catalog = ToolCatalog(method="semantic", embedding={"ollama": "nomic-embed-text"})
await catalog.register(tools) # embeds the batch here
return await catalog.search_async("deploy the service", 5)
register() is async for every method (BM25 too); search() stays synchronous for BM25 only, and search_async() covers all three. To change the endpoint's model or vector dimension, construct a new catalog and re-register.
A SkillCatalog also takes a whole reloaded catalog at once with replace_all(), for a source that fetches the full set rather than individual changes (ADR 0015). The batch is the catalog: ids missing from it are removed, including ones registered in-process, so a host that mixes local and remote skills composes the batch itself. It mutates in place, so every holder of the catalog sees the reload without being rebuilt.
outcome = await catalog.replace_all([*local_skills, *await fetch_remote_skills()])
print(f"reload: +{outcome.added} -{outcome.removed} ~{outcome.updated}")
The corpus swap is the synchronous half of that call, so the counts are already final when it returns — read them without awaiting and a reload whose embedding pass fails still reports what it changed:
reload = catalog.replace_all(batch) # corpus is live; counts are final
try:
await reload # drives the embedding pass
except EmbedderError:
log.warning("applied +%d -%d, embeddings pending", reload.added, reload.removed)
Only new and re-worded skills are embedded — reloading an unchanged catalog costs no embedding calls — and a reload that races an in-flight operation — dense work, but also an ordinary BM25 search_async holding the read lock — raises rather than applying half of itself.
Build-time embedding artifacts (ADR 0018, experimental) avoid corpus/document embedding inference for covered entries on cold start: experimental_build_embedding_artifact writes a mixed Tool+Skill RAT1 (halves merged internally; no public merge API), and catalogs accept experimental_embedding_artifact (path or bytes; default on_miss="error") to warm the dense cache on register / replace_all — each call re-resolves the artifact source and re-warms the whole current corpus. ToolRegistry / SkillRegistry also expose experimental_build_embedding_artifact and experimental_warm_embeddings_from_artifact. With default on_miss="error", every id in each non-empty registering corpus must be covered; a tool-only artifact is valid while Skill stays empty (and vice versa); when both sides register, use a mixed artifact or on_miss="embed". Semantic/hybrid search still requires query embedding through the configured backend; Local/HF paths may still initialize/load the model, and endpoint performs its normal remote query embedding. ArtifactWarmError covers warm failures (.code, .missing); ArtifactError covers non-embedder artifact construction failures (EmbedderError remains the embedding/backend failure); IncompatibleMergeError may surface from the high-level mixed builder's internal Tool+Skill composition; writing the output file may raise OSError.
Install
pip install ratel-ai
# MCP ingestion: pip install 'ratel-ai[mcp]'
Quickstart
Save as quickstart.py, then run python quickstart.py:
import asyncio
from ratel_ai import ExecutableTool, ToolCatalog
async def main():
catalog = ToolCatalog()
await catalog.register(
ExecutableTool(
id="get_weather",
name="get_weather",
description="Get the current weather for a city.",
input_schema={"properties": {"city": {"type": "string"}}},
output_schema={"type": "object"},
execute=lambda args: {"forecast": f"Sunny in {args['city']}"},
)
)
hit = catalog.search("What is the weather in Rome?", 1)[0]
print(await catalog.invoke(hit.tool_id, {"city": "Rome"}))
asyncio.run(main())
Continue with the Python guide, capability tools, API reference, or the Pydantic AI example.
Runtime events and catalog snapshots
RuntimeEvents merges tool and skill facts into one bounded push stream. Give the paired
RuntimeCatalog the stream's source_id so envelopes and full snapshots identify the same
deployment source:
from ratel_ai import RuntimeCatalog, RuntimeEvents, SkillCatalog, ToolCatalog
tools = ToolCatalog()
skills = SkillCatalog()
events = RuntimeEvents(
[tools, skills],
session_id="agent-session",
source_id="checkout-agent",
)
catalog = RuntimeCatalog(tools, skills, source_id=events.source_id)
async def publish(batch):
await send_runtime_facts(batch)
subscription = events.subscribe(publish) # call from the target asyncio event loop
# Register, search, and invoke through tools / skills as usual.
await subscription.flush()
snapshot = catalog.snapshot()
subscription.unsubscribe()
Async handlers are marshaled onto the subscribing event loop; synchronous handlers run on the
native callback thread. Both are observational and fail open. flush() waits for work already
accepted by the bounded native queues and for async handlers to settle. Snapshots contain sorted
public definitions only — never tool executors or skill bodies. Python exposes no Cloud transport;
applications may publish these events and snapshots through their own adapter.
Facts (experimental)
Tools and skills are pulled — a query ranks them and only the winners reach the model. Facts are the opposite: constant content the agent should always work from (a shop's address, hours, a brand's voice), pushed into the context and deduplicated so it is injected once rather than every turn.
Facts live in the opt-in ratel_ai.experimental namespace and may change without a major version bump. Registering one is like a skill, plus a pin tier:
from ratel_ai.experimental import Fact, FactCatalog, Pin
facts = FactCatalog()
await facts.register([
Fact(
id="shop-address",
name="shop address & hours",
description="where the shop is and when it's open",
body="Fade & Blade — 12 Baker Street, London. Open Mon–Sat 9am–7pm.",
pin=Pin.ALWAYS, # every turn, regardless of the query
),
Fact(
id="cancellation",
name="cancellation policy",
description="cancelling or rescheduling a booking, and refunds",
body="Cancel at least 24h ahead for a full refund; same-day is a 50% fee.",
pin=Pin.RETRIEVED, # only when the turn's query ranks it in (default)
),
])
Then pick one of two injection modes per turn.
ground() — persist into your stored history. Returns only the facts not already present; render each body verbatim and keep it in the messages you save. It takes a list of per-message strings — flatten multi-part content yourself, and note that a bare str is rejected (it is itself a Sequence[str], so it would be iterated character by character):
def text_of(message: dict) -> str:
content = message["content"]
if isinstance(content, str):
return content
return "\n".join(part.get("text", "") for part in content) # multi-part content
result = await facts.ground(user_text, [text_of(m) for m in messages])
for item in result.inject:
messages.append({"role": "system", "content": item.body}) # verbatim — presence is the dedupe
Turn 1 injects the address; turn 2 sees it in the transcript and injects nothing. It re-injects only when the body is gone (compaction) or was edited — item.reason is "never" / "evicted" / "mutated".
ground_snapshot() — per call, nothing stored. Returns the full applicable set every time; put it in the request you're about to send and discard it:
snapshot = await facts.ground_snapshot(user_text)
payload = [{"role": "system", "content": f.body} for f in snapshot] + messages
Use ground() for a long-lived agent whose messages you persist; ground_snapshot() for one-shot or stateless calls, or to keep injected content out of your stored history.
Facts are host-driven: the model-facing search_capabilities tool is unchanged and never returns facts — you decide what is true and inject it, rather than letting the model discover it. Every decision is traced (fact_inject with its reason, fact_inject_skip, fact_snapshot), so the skip rate — the tokens you saved — is measurable. See ADR-0017.
Telemetry export is optional. With the otlp extra installed, configure_telemetry() reads RATEL_OTLP_ENDPOINT (falling back to the superseded RATEL_URL, which warns) and RATEL_API_KEY, wires trace and Logs exporters, and returns a shutdown handle. It exports only gen_ai.*/ratel.* signal spans and EventRecords by default — export_all_spans=True widens spans only. Message/tool content stays off by default; opt in with capture_content/include_span_and_events (see the telemetry guide for the capture modes and their privacy implications). Hosts that already own OpenTelemetry providers add both ratel_span_processor and ratel_log_record_processor instead.
Package layout: ratel_ai/ is the Python surface (including embedding_artifact.py for build/warm helpers), native/ contains the PyO3 binding, and tests/ exercises both. For local development, create .venv with uv, install maturin, pytest, pytest-asyncio, ruff, and mypy, then run .venv/bin/maturin develop and .venv/bin/pytest.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ratel_ai-0.11.0.tar.gz.
File metadata
- Download URL: ratel_ai-0.11.0.tar.gz
- Upload date:
- Size: 308.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d264ea1bfad139262596be2613b3b3115a823d7e821ff71e41ee01f7cd13eb89
|
|
| MD5 |
5f2e2321223013b093bbd6c8bd09a618
|
|
| BLAKE2b-256 |
d203a76cd48c5f33b275fce0409ffad2470f969c98ea38f18777981e4a6714df
|
Provenance
The following attestation bundles were made for ratel_ai-0.11.0.tar.gz:
Publisher:
release.yml on ratel-ai/ratel
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ratel_ai-0.11.0.tar.gz -
Subject digest:
d264ea1bfad139262596be2613b3b3115a823d7e821ff71e41ee01f7cd13eb89 - Sigstore transparency entry: 2498128026
- Sigstore integration time:
-
Permalink:
ratel-ai/ratel@eaf8d405431976c619798aec2a3f874d6b271cab -
Branch / Tag:
refs/tags/sdk-py-v0.11.0 - Owner: https://github.com/ratel-ai
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@eaf8d405431976c619798aec2a3f874d6b271cab -
Trigger Event:
push
-
Statement type:
File details
Details for the file ratel_ai-0.11.0-cp39-abi3-win_amd64.whl.
File metadata
- Download URL: ratel_ai-0.11.0-cp39-abi3-win_amd64.whl
- Upload date:
- Size: 4.2 MB
- Tags: CPython 3.9+, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9f1c2a707f05f2a53c423c98a80f3209440ab737f629eb42801c047837741dae
|
|
| MD5 |
8abf7c2fe69058a4f3c92f56a7472a41
|
|
| BLAKE2b-256 |
ce081b2e7e55d9143d632237e4dbf49f29d3fc016395627737867c90f81eec2e
|
Provenance
The following attestation bundles were made for ratel_ai-0.11.0-cp39-abi3-win_amd64.whl:
Publisher:
release.yml on ratel-ai/ratel
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ratel_ai-0.11.0-cp39-abi3-win_amd64.whl -
Subject digest:
9f1c2a707f05f2a53c423c98a80f3209440ab737f629eb42801c047837741dae - Sigstore transparency entry: 2498128112
- Sigstore integration time:
-
Permalink:
ratel-ai/ratel@eaf8d405431976c619798aec2a3f874d6b271cab -
Branch / Tag:
refs/tags/sdk-py-v0.11.0 - Owner: https://github.com/ratel-ai
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@eaf8d405431976c619798aec2a3f874d6b271cab -
Trigger Event:
push
-
Statement type:
File details
Details for the file ratel_ai-0.11.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: ratel_ai-0.11.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 4.3 MB
- Tags: CPython 3.9+, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9db83d5574476b4b977fbfc2fac1fa6718d673e0ea78cf6a25d9bcb738f59478
|
|
| MD5 |
1d21dc7e354a5c375de6b5627e83bdfd
|
|
| BLAKE2b-256 |
fdb296aab2e16df67a7ef3d0e772342d024a6c23887220ca034316679f917d87
|
Provenance
The following attestation bundles were made for ratel_ai-0.11.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:
Publisher:
release.yml on ratel-ai/ratel
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ratel_ai-0.11.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -
Subject digest:
9db83d5574476b4b977fbfc2fac1fa6718d673e0ea78cf6a25d9bcb738f59478 - Sigstore transparency entry: 2498128055
- Sigstore integration time:
-
Permalink:
ratel-ai/ratel@eaf8d405431976c619798aec2a3f874d6b271cab -
Branch / Tag:
refs/tags/sdk-py-v0.11.0 - Owner: https://github.com/ratel-ai
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@eaf8d405431976c619798aec2a3f874d6b271cab -
Trigger Event:
push
-
Statement type:
File details
Details for the file ratel_ai-0.11.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: ratel_ai-0.11.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 4.0 MB
- Tags: CPython 3.9+, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f1035b75391fa3d4f25454fd62dc4e666a8b95920ee53914dd7bc49eee06b8e2
|
|
| MD5 |
aa6ddf98f26e4587eaa53c97bab44135
|
|
| BLAKE2b-256 |
328b7598aba9049db1816b3ca34f36c584e6a98ba89c1a56f499270d74808171
|
Provenance
The following attestation bundles were made for ratel_ai-0.11.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:
Publisher:
release.yml on ratel-ai/ratel
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ratel_ai-0.11.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl -
Subject digest:
f1035b75391fa3d4f25454fd62dc4e666a8b95920ee53914dd7bc49eee06b8e2 - Sigstore transparency entry: 2498128094
- Sigstore integration time:
-
Permalink:
ratel-ai/ratel@eaf8d405431976c619798aec2a3f874d6b271cab -
Branch / Tag:
refs/tags/sdk-py-v0.11.0 - Owner: https://github.com/ratel-ai
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@eaf8d405431976c619798aec2a3f874d6b271cab -
Trigger Event:
push
-
Statement type:
File details
Details for the file ratel_ai-0.11.0-cp39-abi3-macosx_11_0_arm64.whl.
File metadata
- Download URL: ratel_ai-0.11.0-cp39-abi3-macosx_11_0_arm64.whl
- Upload date:
- Size: 3.8 MB
- Tags: CPython 3.9+, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7685a8021edf892dca6a7dece3bde7c2dbcc4e10778b2dd2a62a38ed8e34f580
|
|
| MD5 |
f5be5859f08a9903993e4f7c5cd765ce
|
|
| BLAKE2b-256 |
9e2dc10984a2f9b7a2fee348793862eb1af246450a71f0848ec7be7d9e5ac89c
|
Provenance
The following attestation bundles were made for ratel_ai-0.11.0-cp39-abi3-macosx_11_0_arm64.whl:
Publisher:
release.yml on ratel-ai/ratel
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ratel_ai-0.11.0-cp39-abi3-macosx_11_0_arm64.whl -
Subject digest:
7685a8021edf892dca6a7dece3bde7c2dbcc4e10778b2dd2a62a38ed8e34f580 - Sigstore transparency entry: 2498128071
- Sigstore integration time:
-
Permalink:
ratel-ai/ratel@eaf8d405431976c619798aec2a3f874d6b271cab -
Branch / Tag:
refs/tags/sdk-py-v0.11.0 - Owner: https://github.com/ratel-ai
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@eaf8d405431976c619798aec2a3f874d6b271cab -
Trigger Event:
push
-
Statement type:
File details
Details for the file ratel_ai-0.11.0-cp39-abi3-macosx_10_12_x86_64.whl.
File metadata
- Download URL: ratel_ai-0.11.0-cp39-abi3-macosx_10_12_x86_64.whl
- Upload date:
- Size: 4.1 MB
- Tags: CPython 3.9+, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
762a33c3951694186801d7666001ad7ccc9961991507c39c62870654b1a85b3d
|
|
| MD5 |
81cf79a977df4ab14aa3288508de735e
|
|
| BLAKE2b-256 |
e83dc992fe6dea8a80dd383cec88556bdb8926c373a4737e99e567c4b552e3f4
|
Provenance
The following attestation bundles were made for ratel_ai-0.11.0-cp39-abi3-macosx_10_12_x86_64.whl:
Publisher:
release.yml on ratel-ai/ratel
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ratel_ai-0.11.0-cp39-abi3-macosx_10_12_x86_64.whl -
Subject digest:
762a33c3951694186801d7666001ad7ccc9961991507c39c62870654b1a85b3d - Sigstore transparency entry: 2498128035
- Sigstore integration time:
-
Permalink:
ratel-ai/ratel@eaf8d405431976c619798aec2a3f874d6b271cab -
Branch / Tag:
refs/tags/sdk-py-v0.11.0 - Owner: https://github.com/ratel-ai
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@eaf8d405431976c619798aec2a3f874d6b271cab -
Trigger Event:
push
-
Statement type: