Connect to multiple LLM providers through a unified interface • Stream responses • Function calling • MCP tools support
Run cargo add inference-gateway-sdk.
Here is a full example of how to create a client and interact with the Inference Gateway API:
use inference_gateway_sdk::{
CreateChatCompletionResponse, GatewayError, InferenceGatewayAPI,
InferenceGatewayClient, ListModelsResponse, Message,
MessageContent, MessageRole, Provider,
};
use log::info;
#[tokio::main]
async fn main() -> Result<(), GatewayError> {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
// Create a client
let client = InferenceGatewayClient::new("http://localhost:8080/v1");
// List all models and all providers
let response: ListModelsResponse = client.list_models().await?;
for model in response.data {
info!("Model: {:?}", model.id);
}
// List models for a specific provider
let response: ListModelsResponse = client.list_models_by_provider(Provider::Groq).await?;
info!("Models for provider: {:?}", response.provider);
for model in response.data {
info!("Model: {:?}", model.id);
}
// Generate content - choose from available providers and models
let response: CreateChatCompletionResponse = client
.generate_content(
Provider::Deepseek,
"deepseek-v4-flash",
vec![
Message {
role: MessageRole::System,
content: MessageContent::String("You are a helpful assistant.".to_string()),
reasoning: None,
reasoning_content: None,
tool_call_id: None,
tool_calls: Vec::new(),
},
Message {
role: MessageRole::User,
content: MessageContent::String("Tell me a funny joke".to_string()),
reasoning: None,
reasoning_content: None,
tool_call_id: None,
tool_calls: Vec::new(),
},
],
)
.await?;
log::info!(
"Generated content: {:?}",
response.choices[0].message.content
);
Ok(())
}The client is configured builder-style:
let client = InferenceGatewayClient::new("http://localhost:8080/v1")
.with_token("my-token")
.with_max_tokens(Some(512))
.with_temperature(Some(0.2))
.with_timeout(std::time::Duration::from_secs(30));with_temperature applies to streaming and non-streaming chat completions;
None leaves the API default. with_timeout bounds the whole request,
response body included, so avoid a short timeout on a client used for
streaming.
To list all available models from all configured providers, use the
list_models method:
use inference_gateway_sdk::{
GatewayError,
InferenceGatewayAPI,
InferenceGatewayClient,
ListModelsResponse,
Message,
};
use log::info;
#[tokio::main]
async fn main() -> Result<(), GatewayError> {
// ...Create a client
// List models from all providers
let response: ListModelsResponse = client.list_models().await?;
for model in response.data {
info!("Model: {:?}", model.id);
}
// ...
}To list all available models from a specific provider, use the
list_models_by_provider method:
use inference_gateway_sdk::{
GatewayError,
InferenceGatewayAPI,
InferenceGatewayClient,
ListModelsResponse,
Provider,
};
use log::info;
// ...Open main function
// List models for a specific provider
let response: ListModelsResponse = client.list_models_by_provider(Provider::Groq).await?;
info!("Models for provider: {:?}", response.provider);
for model in response.data {
info!("Model: {:?}", model.id);
}
// Example with Google provider
let response: ListModelsResponse = client.list_models_by_provider(Provider::Google).await?;
info!("Google models: {:?}", response.provider);
for model in response.data {
info!("Google Model: {:?}", model.id);
}
// ...Rest of the main functionThe gateway also exposes itself as an MCP server on POST /mcp, aggregating
every configured MCP server behind one JSON-RPC endpoint. Use mcp_json_rpc
with one of the McpjsonrpcRequest constructors - the required
MCP-Protocol-Version, Mcp-Method and Mcp-Name headers are derived from the
request body:
use inference_gateway_sdk::{
GatewayError,
InferenceGatewayAPI,
InferenceGatewayClient,
McpjsonrpcRequest,
};
use log::info;
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), GatewayError> {
// ...Create a client
// The aggregated, namespaced tools of every healthy MCP server
let response = client
.mcp_json_rpc(McpjsonrpcRequest::tools_list(None))
.await?;
info!("tools/list result: {:?}", response.result);
// Call a namespaced tool: mcp_<server alias>_<tool name>
let response = client
.mcp_json_rpc(McpjsonrpcRequest::tools_call(
"mcp_deepwiki_ask_question",
json!({
"repoName": "inference-gateway/inference-gateway",
"question": "How is MCP wired up?",
}),
))
.await?;
match response.error {
Some(error) => info!("JSON-RPC error {}: {}", error.code, error.message),
None => info!("tools/call result: {:?}", response.result),
}
Ok(())
}JSON-RPC error envelopes returned with HTTP 200, 400 (-32020, -32022) or
404 (-32601) come back as response.error rather than a GatewayError. A
guardrail refusal is the exception: the gateway answers it with 403 Forbidden
and a -32001 envelope, which surfaces as GatewayError::Forbidden carrying
the policy message, so response.error never sees it. A
GatewayError::Forbidden from mcp_json_rpc therefore means either the
endpoint is not exposed (MCP_ENABLED=true and MCP_EXPOSE=true are required
server-side) or the request was blocked by guardrails.
When gateway auth is enabled, mcp_protected_resource_metadata fetches the
OAuth 2.0 Protected Resource Metadata (RFC 9728) for the endpoint, which names
the authorization servers that mint tokens for it:
let metadata = client.mcp_protected_resource_metadata().await?;
info!("Authorization servers: {:?}", metadata.authorization_servers);To generate content using a model, use the generate_content method:
use inference_gateway_sdk::{
CreateChatCompletionResponse, GatewayError, InferenceGatewayAPI, InferenceGatewayClient,
Message, MessageContent, MessageRole, Provider,
};
fn message(role: MessageRole, text: &str) -> Message {
Message {
role,
content: MessageContent::String(text.to_string()),
reasoning: None,
reasoning_content: None,
tool_call_id: None,
tool_calls: Vec::new(),
}
}
// Generate content - choose from available providers and models
let response: CreateChatCompletionResponse = client
.generate_content(
Provider::Deepseek,
"deepseek-v4-flash",
vec![
message(MessageRole::System, "You are a helpful assistant."),
message(MessageRole::User, "Tell me a funny joke"),
],
)
.await?;
log::info!(
"Generated content: {:?}",
response.choices[0].message.content
);
// Example with Google provider (Gemini models)
let response: CreateChatCompletionResponse = client
.generate_content(
Provider::Google,
"gemini-3-pro",
vec![
message(MessageRole::System, "You are a helpful AI assistant."),
message(MessageRole::User, "Explain quantum computing in simple terms"),
],
)
.await?;
log::info!(
"Google generated content: {:?}",
response.choices[0].message.content
);use futures_util::{pin_mut, StreamExt};
use inference_gateway_sdk::{
CreateChatCompletionStreamResponse, FinishReason, GatewayError, InferenceGatewayAPI,
InferenceGatewayClient, Message, MessageContent, MessageRole, Provider,
};
use log::info;
fn message(role: MessageRole, text: &str) -> Message {
Message {
role,
content: MessageContent::String(text.to_string()),
reasoning: None,
reasoning_content: None,
tool_call_id: None,
tool_calls: Vec::new(),
}
}
#[tokio::main]
async fn main() -> Result<(), GatewayError> {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
let model = "deepseek-v4-flash";
let client = InferenceGatewayClient::new("http://localhost:8080/v1");
let stream = client.generate_content_stream(
Provider::Deepseek,
model,
vec![
message(MessageRole::System, "You are a helpful assistant."),
message(MessageRole::User, "Write a poem"),
],
);
pin_mut!(stream);
// Iterate over the stream of Server Sent Events
while let Some(ssevent) = stream.next().await {
let ssevent = ssevent?;
if ssevent.data == "[DONE]" {
break;
}
// Deserialize the event response
let generate_response_stream: CreateChatCompletionStreamResponse =
serde_json::from_str(&ssevent.data)?;
let Some(choice) = generate_response_stream.choices.first() else {
continue;
};
if let Some(usage) = generate_response_stream.usage.as_ref() {
// Get the usage metrics from the response
info!("Usage Metrics: {:?}", usage);
// Probably send them over to a metrics service
break;
}
// Print the token out as it's being sent from the server
if let Some(content) = choice.delta.content.as_ref() {
print!("{}", content);
}
if let Some(reason) = choice.finish_reason.as_ref() {
if matches!(reason, FinishReason::Stop) {
info!("Finished generating content");
break;
}
}
}
Ok(())
}The gateway also exposes an Anthropic-compatible POST /messages endpoint.
Providers without Messages support return GatewayError::BadRequest; use
generate_content for those providers.
use futures_util::{pin_mut, StreamExt};
use inference_gateway_sdk::{
CreateMessagesRequest, GatewayError, InferenceGatewayAPI, InferenceGatewayClient,
MessagesMessage, MessagesMessageContent, MessagesMessageRole, MessagesResponseContentBlock,
MessagesStreamEvent, MessagesStreamEventType, Provider,
};
#[tokio::main]
async fn main() -> Result<(), GatewayError> {
let client = InferenceGatewayClient::new("http://localhost:8080/v1");
let request = CreateMessagesRequest {
max_tokens: 300,
messages: vec![MessagesMessage {
role: MessagesMessageRole::User,
content: MessagesMessageContent::String("Tell me a fun fact about Rust.".to_string()),
}],
metadata: None,
model: "claude-sonnet-5".to_string(),
output_config: None,
stop_sequences: Vec::new(),
stream: false,
system: None,
temperature: None,
thinking: None,
tool_choice: None,
tools: Vec::new(),
top_k: None,
top_p: None,
};
// Non-streaming
let response = client
.create_message(Some(Provider::Anthropic), request.clone())
.await?;
for block in &response.content {
if let MessagesResponseContentBlock::TextBlock(text) = block {
println!("{}", text.text);
}
}
// Streaming - each event's `data` is a JSON-serialized `MessagesStreamEvent`
let stream = client.create_message_stream(Some(Provider::Anthropic), request);
pin_mut!(stream);
while let Some(event) = stream.next().await {
let event: MessagesStreamEvent = serde_json::from_str(&event?.data)?;
match event.type_ {
MessagesStreamEventType::ContentBlockDelta => {
if let Some(text) = event.delta.as_ref().and_then(|d| d.text.as_deref()) {
print!("{text}");
}
}
MessagesStreamEventType::MessageStop => break,
_ => {}
}
}
Ok(())
}You can pass to the generate_content function also tools, which will be available for the LLM to use:
use inference_gateway_sdk::{
ChatCompletionTool, ChatCompletionToolType, FunctionObject,
FunctionParameters, GatewayError, InferenceGatewayAPI,
InferenceGatewayClient, Message, MessageContent, MessageRole, Provider,
};
use log::{info, warn};
use serde::{Deserialize, Serialize};
use serde_json::json;
fn message(role: MessageRole, text: &str) -> Message {
Message {
role,
content: MessageContent::String(text.to_string()),
reasoning: None,
reasoning_content: None,
tool_call_id: None,
tool_calls: Vec::new(),
}
}
#[tokio::main]
async fn main() -> Result<(), GatewayError> {
// Configure logging
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
// API endpoint - store as a variable so we can reuse it
let api_endpoint = "http://localhost:8080/v1";
// Initialize the API client
let client = InferenceGatewayClient::new(api_endpoint);
// Define the model and provider
let provider = Provider::Deepseek;
let model = "deepseek-v4-flash";
// Define the weather tool
let parameters = json!({
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city name"
}
},
"required": ["location"]
});
let tools = vec![ChatCompletionTool {
type_: ChatCompletionToolType::Function,
function: FunctionObject {
name: "get_current_weather".to_string(),
description: Some("Get the weather for a location".to_string()),
parameters: Some(FunctionParameters(
parameters.as_object().unwrap().clone(),
)),
strict: false,
},
}];
// Create initial conversation
let initial_messages = vec![
message(
MessageRole::System,
"You are a helpful assistant that can check the weather.",
),
message(MessageRole::User, "What is the current weather in Berlin?"),
];
// Make the initial API request
info!("Sending initial request to model");
let response = client
.with_tools(Some(tools.clone()))
.generate_content(provider, model, initial_messages)
.await?;
info!("Received response from model");
let Some(choice) = response.choices.first() else {
warn!("No choice returned");
return Ok(());
};
// Check for tool calls in the response
if !choice.message.tool_calls.is_empty() {
// Echo the assistant turn (with the tool_calls) before the tool replies.
let mut assistant_turn = choice.message.clone();
// Tool-call assistant messages typically have empty content; keep what
// the model sent so providers that include explanatory text round-trip.
let mut follow_up_convo = vec![
message(
MessageRole::System,
"You are a helpful assistant that can check the weather.",
),
message(MessageRole::User, "What is the current weather in Berlin?"),
];
// Take the assistant message verbatim so tool_calls are preserved.
assistant_turn.role = MessageRole::Assistant;
follow_up_convo.push(assistant_turn);
// Process each tool call
for tool_call in &choice.message.tool_calls {
info!("Tool Call Requested: {}", tool_call.function.name);
if tool_call.function.name == "get_current_weather" {
// Parse arguments into a typed value
let args: Weather = tool_call.function.parse_arguments()?;
let weather_result = get_current_weather(args);
// Add the tool response to the conversation
follow_up_convo.push(Message {
role: MessageRole::Tool,
content: MessageContent::String(weather_result),
reasoning: None,
reasoning_content: None,
tool_call_id: Some(tool_call.id.clone()),
tool_calls: Vec::new(),
});
}
}
// Send the follow-up request with the tool results
info!("Sending follow-up request with tool results");
let follow_up_client = InferenceGatewayClient::new(api_endpoint);
let follow_up_response = follow_up_client
.with_tools(Some(tools))
.generate_content(provider, model, follow_up_convo)
.await?;
if let Some(choice) = follow_up_response.choices.first() {
info!("Final response: {:?}", choice.message.content);
} else {
warn!("No response in follow-up");
}
} else {
info!("No tool calls in the response");
info!("Model response: {:?}", choice.message.content);
}
Ok(())
}
#[derive(Debug, Deserialize, Serialize)]
struct Weather {
location: String,
}
fn get_current_weather(args: Weather) -> String {
info!(
"Getting weather function was called for {}",
args.location
);
// In a real application, we would call an actual weather API here.
// For this example, we'll just return a mock response.
format!(
"The weather in {} is currently sunny with a temperature of 22°C",
args.location
)
}To check if the Inference Gateway is running, use the health_check method:
// ...rest of the imports
use log::info;
// ...main function
let is_healthy = client.health_check().await?;
info!("API is healthy: {}", is_healthy);Runnable examples live in the examples directory - see its README for the full list and instructions.
The Inference Gateway Rust SDK supports the following providers:
- Ollama (
Provider::Ollama) - Local language model server - Ollama Cloud (
Provider::OllamaCloud) - Ollama Cloud-hosted models - Groq (
Provider::Groq) - High-speed inference provider - llama.cpp (
Provider::Llamacpp) - llama.cpp OpenAI-compatiblellama-server - OpenAI (
Provider::Openai) - GPT models and other OpenAI services - Cloudflare (
Provider::Cloudflare) - Cloudflare Workers AI - Cohere (
Provider::Cohere) - Cohere language models - Anthropic (
Provider::Anthropic) - Claude models - DeepSeek (
Provider::Deepseek) - DeepSeek models - ElevenLabs (
Provider::Elevenlabs) - Audio generation viacreate_speech,create_sfxandcreate_music(no chat completions) - Google (
Provider::Google) - Google Gemini models via Generative AI API - Mistral (
Provider::Mistral) - Mistral AI models - MiniMax (
Provider::Minimax) - MiniMax models - Moonshot (
Provider::Moonshot) - Moonshot AI models - NVIDIA (
Provider::Nvidia) - NVIDIA NIM models - Z.ai (
Provider::Zai) - Z.ai GLM models
Each provider may support different models and capabilities. Use the
list_models_by_provider() method to discover available models for each
provider.
Example:
use inference_gateway_sdk::{Provider, InferenceGatewayClient, InferenceGatewayAPI};
let client = InferenceGatewayClient::new("http://localhost:8080/v1");
// List Google models
let google_models = client.list_models_by_provider(Provider::Google).await?;
for model in google_models.data {
println!("Google model: {}", model.id);
}Full API documentation is published on docs.rs.
Please refer to the CONTRIBUTING.md file for information about how to get involved. We welcome issues, questions, and pull requests.
This SDK is distributed under the Apache 2.0 License, see LICENSE for more information.