ds-api
Your Rust functions. Any LLM. Zero glue code.
cargo add ds-api
The Problem
Building an LLM agent means writing a pile of code that has nothing to do with your actual problem:
- Hand-craft JSON schemas for every tool
- Parse and validate tool arguments from raw JSON
- Detect tool calls in the response
- Implement an agent loop that re-sends results to the model
- Wire up streaming yourself
Every project. Every time.
The Solution
One macro. Your methods become AI tools.
use ;
use StreamExt;
use ;
;
async
No schema. No argument parsing. No loop. Just your function.
Key Features
#[tool] — Zero-boilerplate tool registration
Annotate any async fn. The macro reads your doc comments, infers the JSON schema from your types, and registers everything automatically.
- Doc comment → tool description. No separate description field.
param: descriptionin doc → parameter description. Inline.Option<T>→ optional parameter. The schema marks it non-required automatically.- Return any
impl Serialize.Value, structs, enums,Vec<T>— anything serde can serialize. - Compile error on unsupported parameter types. You find out at build time, not runtime.
Supported parameter types: String, bool, f32/f64, all integer primitives, Vec<T>, Option<T>.
Typed event stream — AgentEvent
chat() returns a stream of strongly-typed events. The compiler forces you to handle every case.
match event?
No if result.is_null() hacks. No optional fields you have to remember to check. Each variant carries exactly what it means.
In streaming mode, Token arrives as SSE deltas. In non-streaming mode, it arrives as one chunk. Your match arm handles both.
Automatic agent loop
The model requests a tool → ds_api executes it → feeds the result back → asks the model again. This continues until the model stops calling tools. You never write that loop.
User prompt
└─▶ API call
└─▶ ToolCall event (model wants data)
└─▶ your function runs
└─▶ ToolResult event (result fed back)
└─▶ API call (model continues)
└─▶ Token events (final answer)
Context window management — automatic summarization
Long conversations are compressed automatically. The default summarizer (LlmSummarizer) calls the model to write a concise semantic summary of older turns, replaces them with a single system message, and keeps the most recent turns verbatim. Your with_system_prompt messages are never touched.
// Default: trigger at ~60 000 estimated tokens, retain last 10 turns.
let agent = new;
// Custom thresholds:
use ;
let agent = new
.with_summarizer;
If you prefer zero extra API calls, use SlidingWindowSummarizer instead — it keeps the last N turns and silently drops everything older:
use SlidingWindowSummarizer;
let agent = new
.with_summarizer;
Your agent stays within context limits without you counting tokens.
Reusable agents — into_agent()
chat() consumes the agent to keep the borrow checker happy inside the async state machine. Get it back when the stream ends:
let mut agent = new
.with_streaming
.add_tool;
loop
Full REPL with persistent conversation history. No cloning. No Arc<Mutex<>>.
OpenAI-compatible providers
DeepseekAgent::custom(token, base_url, model) points the agent at any OpenAI-compatible endpoint. The default LlmSummarizer is automatically configured to use the same provider and model — no extra setup needed.
use ;
use StreamExt;
async
Real Example — Shell Agent
use ;
use StreamExt;
use ;
use Command;
;
async
The model decides when to call the shell. You just receive the events.
What You Never Write
| Without ds_api | With ds_api |
|---|---|
| JSON schema per tool | #[tool] |
| Argument deserialization | automatic |
| Tool call detection | automatic |
| Agent loop | automatic |
| Token counting / context trimming | automatic |
| Streaming SSE wiring | automatic |
Installation
[]
= "0.5"
= { = "1", = ["full"] }
= "0.3"
export DEEPSEEK_API_KEY=your_key_here
MCP — Model Context Protocol
Enable the mcp feature to connect any MCP server's tools directly to DeepseekAgent — no glue code required.
[]
= { = "0.5", = ["mcp"] }
use ;
async
McpTool fetches the tool list from the server at construction time (pagination handled automatically) and forwards every model tool call to the MCP server at runtime. The server's inputSchema is passed to the model as-is — no manual schema configuration needed.
Without features = ["mcp"], rmcp is never pulled in and the compiled output is identical to 0.5.5.
Roadmap
OpenAI-compatible providers- Structured output support
#[tool]parameter types: customserdestructs (return types already support anyimpl Serialize)- More examples
License
MIT OR Apache-2.0