ic-rig
A lean, modular Rust library for building LLM agent applications — designed to run anywhere, including ICP (Internet Computer Protocol) WASM canisters.
No HTTP client included. You bring your own, which means ic-rig works on native Tokio, ICP canisters, WASM runtimes, or anywhere else you can make an HTTP call.
Table of Contents
- Features
- Installation
- Quick Start
- Bringing Your Own HTTP Client
- Providers
- Building an Agent with Tools
- Embeddings
- Vector Search with LSH
- ICP Canister Example
- API Reference
Features
- Provider-agnostic — OpenAI, Anthropic, Google Gemini, and DeepSeek behind a single
CompletionModeltrait - Agentic tool-call loop — built-in multi-turn reasoning with automatic tool dispatch
- Embeddings + semantic search — batch embeddings with LSH approximate nearest-neighbor indexing and configurable distance metrics
- WASM/ICP-first — no
Send + Syncrequirements on futures, no bundled HTTP dependencies - Minimal footprint — only
serde,serde_json, andthiserrorare required
Installation
Add ic-rig to your Cargo.toml and enable the providers you need:
[]
= { = "0.1", = ["openai"] }
# or
= { = "0.1", = ["anthropic"] }
# or
= { = "0.1", = ["openai", "anthropic", "gemini", "deepseek"] }
Available feature flags:
| Flag | Enables |
|---|---|
openai |
OpenAI completion + embeddings |
anthropic |
Anthropic Claude completion |
gemini |
Google Gemini completion + embeddings |
deepseek |
DeepSeek completion |
Quick Start
use Agent;
use ;
// my_http implements ic_rig::http::HttpClient
let client = new;
let model = client.model;
let agent = builder
.preamble
.max_tokens
.build;
let reply: String = agent.prompt.await?;
println!; // "Paris."
Bringing Your Own HTTP Client
ic-rig ships no HTTP client. You implement the HttpClient trait once for your platform, then pass it to any provider client.
use ;
;
Native (reqwest) example
use ;
;
Providers
OpenAI
use ;
let client = new;
let model = client.model;
let embedder = client.embedding_model.with_dimensions;
Current generation (recommended): GPT_5_6, GPT_5_6_TERRA, GPT_5_6_LUNA, GPT_5_6_CYBER, GPT_5_3_CODEX
Previous generation (still active): GPT_5, GPT_5_MINI, GPT_5_NANO, GPT_4_1, GPT_4_1_MINI, GPT_4_1_NANO, GPT_4O, GPT_4O_MINI, O3, O3_MINI, O4_MINI
Available embedding models: TEXT_EMBEDDING_3_LARGE, TEXT_EMBEDDING_3_SMALL, TEXT_EMBEDDING_ADA_002
Note:
GPT_4_TURBOandGPT_35_TURBOare still exported but scheduled for removal by OpenAI on 2026-10-23 — migrate toGPT_5_6/GPT_5_6_TERRAandGPT_5_6_TERRA/GPT_5_6_LUNArespectively.O1andO1_MINIare exported but#[deprecated]— OpenAI already retired them (2025-07-28 and 2025-10-27); useO3/O4_MINI.
Anthropic
use ;
let client = new;
let model = client.model;
Current generation (recommended): CLAUDE_FABLE_5, CLAUDE_OPUS_5, CLAUDE_SONNET_5, CLAUDE_HAIKU_4_5
Previous generation (still active): CLAUDE_OPUS_4_8, CLAUDE_OPUS_4_7, CLAUDE_OPUS_4_6, CLAUDE_SONNET_4_6, CLAUDE_OPUS_4_5, CLAUDE_SONNET_4_5
Note:
CLAUDE_OPUS_4andCLAUDE_SONNET_4are still exported but#[deprecated]— Anthropic has deprecated them in favor of the 5-series (retirement date TBD).CLAUDE_SONNET_4_5's snapshot date was corrected from-20251101to the real-20250929on 2026-08-27; update any code that hardcoded the old string instead of the constant.Note: Anthropic requires
max_tokens. The default is 1024 if you don't call.max_tokens()on the builder.
Google Gemini
use ;
let client = new;
let model = client.model;
let embedder = client.embedding_model;
Gemini 3 (current generation, recommended): GEMINI_3_1_PRO_PREVIEW, GEMINI_3_7_FLASH, GEMINI_3_6_FLASH, GEMINI_3_5_FLASH, GEMINI_3_5_FLASH_LITE, GEMINI_3_1_FLASH_LITE
Gemini 2.5 (previous generation; GA-stable until 2026-10-16): GEMINI_2_5_PRO, GEMINI_2_5_FLASH, GEMINI_2_5_FLASH_LITE
Available embedding models: GEMINI_EMBEDDING_001, GEMINI_EMBEDDING_2_PREVIEW (multimodal)
Note:
GEMINI_2_0_FLASH,GEMINI_2_0_FLASH_LITE,GEMINI_1_5_PRO,GEMINI_1_5_FLASH,TEXT_EMBEDDING_004, andEMBEDDING_001are still exported but#[deprecated]— Google has already shut all of them down.GEMINI_2_5_PROwas repointed from the dated preview IDgemini-2.5-pro-preview-05-06(which Google now redirects) to the stablegemini-2.5-pro.
DeepSeek
DeepSeek's API is OpenAI-compatible. In thinking mode the model produces a chain-of-thought reasoning_content field; when content is empty ic-rig surfaces the reasoning trace as the reply so the agent loop always gets a usable string.
use ;
let client = new;
let flash = client.model;
let pro = client.model;
Available models: DEEPSEEK_V4_FLASH, DEEPSEEK_V4_PRO, DEEPSEEK_V4_FLASH_VISION_EXP (experimental, multimodal)
Note: DeepSeek does not provide an embeddings API; only completion is supported.
DEEPSEEK_CHATandDEEPSEEK_REASONERare still exported but#[deprecated]— DeepSeek fully retired those model IDs on 2026-07-24. Switch toDEEPSEEK_V4_FLASH(their replacement in both non-thinking and thinking mode).
Building an Agent with Tools
ic-rig has a built-in agentic loop that automatically dispatches tool calls and feeds results back to the model until it returns a final text response.
1. Define a tool
use ;
use ;
use json;
;
2. Register tools and run the agent
let agent = builder
.preamble
.tool
.max_tokens
.max_iterations
.build;
let reply = agent.prompt.await?;
// "The current weather in Tokyo is 18.5°C and sunny."
3. Multi-tool agents
let agent = builder
.preamble
.tool
.tool
.tool
.max_tokens
.build;
4. Injecting context (RAG)
let agent = builder
.preamble
.context
.context
.build;
5. Multi-turn chat
prompt() is single-turn. For a back-and-forth conversation, use chat() and maintain a Vec<Message> history yourself:
use Message;
let agent = builder.preamble.build;
let mut history: = Vecnew;
let r1 = agent.chat.await?;
let r2 = agent.chat.await?;
// r2 => "Your name is Alice."
Embeddings
Embedding a list of strings
use EmbeddingsBuilder;
use ;
let model = new.embedding_model;
let results = new
.document?
.document?
.build
.await?;
for in results
Embedding custom types
Implement the Embed trait to embed multiple fields per document:
use ;
let results = new
.documents? // Vec<Article>
.build
.await?;
// results: Vec<(Article, Vec<Embedding>)>
// Each article gets two Embeddings: one for title, one for body.
Distance metrics
Every Embedding implements VectorDistance, giving you direct access to all metrics:
use VectorDistance;
let sim = a.cosine_similarity; // false = vectors not pre-normalised
let dist = a.euclidean_distance;
let dot = a.dot_product;
When the metric is a runtime value (config, user choice), use DistanceMetric instead:
use DistanceMetric;
let metric = Cosine ;
let score = metric.score;
Available variants:
| Variant | Range | Best-first sort |
|---|---|---|
Cosine { normalized: bool } |
[-1, 1] |
descending |
Angular { normalized: bool } |
[0, 1] |
ascending |
Euclidean |
[0, ∞) |
ascending |
Manhattan |
[0, ∞) |
ascending |
Chebyshev |
[0, ∞) |
ascending |
DotProduct |
(-∞, ∞) |
descending |
Vector Search with LSH
LshIndex provides fast approximate nearest-neighbor search using locality-sensitive hashing. It is designed for use inside ICP canisters where you cannot run an external vector database.
Indexing
use LshIndex;
// new(dimensions, num_hyperplanes, num_tables, seed)
let mut index = new;
index.insert;
index.insert;
index.insert;
Searching
search() runs the LSH lookup, scores the candidates, and returns sorted (id, score) pairs in one call. Pass None for the metric to default to cosine similarity:
use DistanceMetric;
use HashMap;
// store maps each ID to its Embedding
let results: = index.search;
// or pick a metric explicitly
let results = index.search;
// results are already sorted best-first
for in &results
Low-level query
If you need the raw candidate IDs without scoring, use query() directly:
let candidates: = index.query;
// score and sort candidates yourself
Tuning
LSH trades recall for speed. The two parameters control the tradeoff:
| Parameter | Higher value | Lower value |
|---|---|---|
num_hyperplanes |
Fewer candidates, faster scoring | More candidates, slower |
num_tables |
Better recall, more RAM | Less RAM, more misses |
A good starting point for 1536-dimensional OpenAI embeddings: LshIndex::new(1536, 12, 6, 42).
ICP Canister Example
use update;
use Agent;
use ;
async
API Reference
Agent<M>
| Method | Description |
|---|---|
Agent::builder(model) |
Create an AgentBuilder |
.prompt(text) |
Single-turn prompt, returns String |
.chat(text, history) |
Multi-turn prompt, updates history in place |
AgentBuilder<M>
| Method | Description |
|---|---|
.preamble(s) |
Set the system prompt |
.tool(t) |
Register a tool |
.temperature(f) |
Sampling temperature |
.max_tokens(n) |
Maximum output tokens |
.max_iterations(n) |
Maximum tool-call rounds (default: 10) |
.context(s) |
Inject a RAG chunk into the conversation |
.build() |
Produce Agent<M> |
EmbeddingsBuilder<M, T>
| Method | Description |
|---|---|
EmbeddingsBuilder::new(model) |
Create a builder |
.document(d) |
Add a single document |
.documents(ds) |
Add many documents |
.build() |
Embed all documents, returns Vec<(T, Vec<Embedding>)> |
DistanceMetric
| Method | Description |
|---|---|
.score(a, b) |
Score two embeddings using this metric |
.higher_is_better() |
true for similarity metrics, false for distance metrics |
LshIndex
| Method | Description |
|---|---|
LshIndex::new(dim, hyperplanes, tables, seed) |
Create an index |
.insert(id, vec) |
Index a vector under a string ID |
.search(query, store, metric) |
LSH lookup + score + sort; metric is Option<DistanceMetric> |
.query(vec) |
Raw LSH lookup — returns unscored candidate IDs |
.len() / .is_empty() |
Size queries |
.clear() |
Remove all entries |
License
MIT