# llmrix-rust-sdk
Official Rust SDK for [llmrix](https://github.com/llmrix/llmrix-rust-sdk) — AI Agent Platform.
- Rust 1.75+ (MSRV)
- Async/await with `tokio` runtime
- SSE streaming via `reqwest` + `futures-util`
- Fully typed with `serde` serialization
- `thiserror`-based error hierarchy
---
## Installation
Add to your `Cargo.toml`:
```toml
[dependencies]
llmrix-rust-sdk = "1.0"
tokio = { version = "1", features = ["full"] }
```
---
## Quick Start
```rust
use llmrix_sdk::{
LlmrixClient,
model::ConversationCreateRequest,
streaming::event::StreamEvent,
};
#[tokio::main]
async fn main() -> llmrix_sdk::error::Result<()> {
// 1. Build the client (cheap to clone — shares the connection pool)
let client = LlmrixClient::builder()
.base_url("https://www.llmrix.com")
.api_key("sk-xxx")
.build()?;
// 2. Create a conversation
let conv = client
.conversations()
.create(ConversationCreateRequest {
title: "My Chat".into(),
agent_id: None,
})
.await?;
// 3. Stream a chat turn
client
.chat(&conv.id)
.send("Hello, llmrix!", |event| {
if let StreamEvent::MessageChunk(e) = event {
print!("{}", e.content);
}
Ok(())
})
.await?;
Ok(())
}
```
---
## API Reference
### `LlmrixClient`
```rust
LlmrixClient::builder()
.base_url("https://www.llmrix.com") // required
.api_key("sk-xxx") // optional
.timeout(std::time::Duration::from_secs(60)) // optional, default 60 s
.build()?
```
`LlmrixClient` is `Clone` and cheap to clone — the inner `reqwest::Client`
shares its connection pool across all clones.
---
### `ConversationsResource`
Obtained via `client.conversations()`.
| `create(req).await` | `Result<Conversation>` | Create a new conversation |
| `list(last_id, size).await` | `Result<PageResult<Conversation>>` | Cursor-paginated list |
| `get(id).await` | `Result<Conversation>` | Fetch a single conversation |
| `update(id, req).await` | `Result<Conversation>` | Update title / agent |
| `delete(id).await` | `Result<()>` | Permanently delete |
| `messages(id, last_id, size).await` | `Result<PageResult<Message>>` | Paginated message history |
Cursor pagination:
```rust
let mut page = client.conversations().list(0, 20).await?;
while page.has_more {
let last = page.items.last().unwrap();
page = client.conversations().list(last.seq, 20).await?;
}
```
---
### `ChatResource`
Obtained via `client.chat(conv_id)`.
| `send(message, handler).await` | `Result<()>` | Stream a plain-text message |
| `send_request(req, handler).await` | `Result<()>` | Stream a full `ChatRequest` |
| `stop().await` | `Result<()>` | Cancel the current run |
| `decide(decisions).await` | `Result<()>` | Submit HITL decisions |
#### Stream handler
```rust
type StreamHandler = impl FnMut(StreamEvent) -> Result<()>;
```
Returning `Err(...)` from the handler aborts the stream.
---
### Stream Event Types
```rust
use llmrix_sdk::streaming::event::StreamEvent;
StreamEvent::MessageChunk(e) => print!("{}", e.content),
StreamEvent::ToolStart(e) => println!("\n[tool] {}", e.name),
StreamEvent::HitlInterrupt(e) => {
// submit decision...
}
StreamEvent::RunEnd(_) => {}
StreamEvent::Error(e) => eprintln!("error: {}", e.message),
_ => {}
}
Ok(())
}).await?;
```
| `RunStart` | `lifecycle` | Agent run started |
| `RunEnd` | `lifecycle` | Agent run completed |
| `MessageChunk` | `messages` | Incremental text chunk |
| `ToolStart` | `tools` | Tool invocation started |
| `ToolEnd` | `tools` | Tool invocation finished |
| `SubagentStart` | `tools` | Sub-agent spawned |
| `SubagentEnd` | `tools` | Sub-agent finished |
| `HitlInterrupt` | `hitl` | Human approval required |
| `Error` | `error` | Run failed with error |
| `Cancelled` | `error` | Run was cancelled |
| `Heartbeat` | `heartbeat` | Keep-alive (silently dropped) |
---
### Error Handling
```rust
use llmrix_sdk::error::{LlmrixError, Result};
match client.conversations().get("bad-id").await {
Err(LlmrixError::Api { status, body }) => {
eprintln!("HTTP {}: {}", status, body);
}
Err(LlmrixError::Auth { status, body }) => {
eprintln!("Auth failed ({}): {}", status, body);
}
Err(e) => eprintln!("SDK error: {}", e),
Ok(conv) => println!("Got: {}", conv.id),
}
```
| `LlmrixError::Api` | Non-2xx HTTP response; has `status` and `body` |
| `LlmrixError::Auth` | HTTP 401 / 403 |
| `LlmrixError::Http` | Network-level `reqwest` error |
| `LlmrixError::Sse` | Unrecoverable SSE parse error |
---
## Advanced Usage
### HITL (Human-in-the-Loop)
```rust
use llmrix_sdk::model::HitlDecision;
let chat = client.chat(&conv.id);
println!("Approve '{}'? (y/n)", e.action_name);
let mut ans = String::new();
std::io::stdin().read_line(&mut ans).unwrap();
let decision = if ans.trim() == "y" {
HitlDecision::approve()
} else {
HitlDecision::reject("user declined")
};
// Note: use tokio::task::block_in_place or restructure as needed
// for true async decide
println!("Decision recorded: {:?}", decision);
}
Ok(())
}).await?;
```
### Collecting the full response
```rust
async fn run_to_completion(
client: &LlmrixClient,
conv_id: &str,
message: &str,
) -> Result<String> {
let mut output = String::new();
client.chat(conv_id).send(message, |event| {
if let StreamEvent::MessageChunk(e) = event {
output.push_str(&e.content);
}
Ok(())
}).await?;
Ok(output)
}
```
---
## Building from Source
```bash
git clone https://github.com/llmrix/llmrix-rust-sdk.git
cd llmrix-rust-sdk
cargo build
cargo test
```
Generate documentation:
```bash
cargo doc --open
```
---
## Contributing
Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) first.
---
## License
Apache License 2.0 — see the [LICENSE](LICENSE) file.