Connect to multiple LLM providers through a unified interface • Stream responses • Function calling • Vision support • MCP tools support • Pydantic validation
- 🚀 Inference Gateway Python SDK
To install the SDK, use pip:
pip install inference-gatewayRequires Python 3.12+.
To create a client, instantiate InferenceGatewayClient:
from inference_gateway import InferenceGatewayClient, Message
client = InferenceGatewayClient("http://localhost:8080/v1")The client also supports authentication, custom timeouts, and an optional httpx backend:
# With authentication
client = InferenceGatewayClient(
"http://localhost:8080/v1",
token="your-api-token",
timeout=60.0,
)
# Using httpx instead of the default requests backend
client = InferenceGatewayClient(
"http://localhost:8080/v1",
use_httpx=True,
)
# Use as a context manager to ensure the underlying HTTP client is closed
with InferenceGatewayClient("http://localhost:8080/v1") as client:
models = client.list_models()To list available models, use the list_models method:
# List all models from all providers
models = client.list_models()
print("All available models:", models)
# List models for a specific provider
openai_models = client.list_models(provider="openai")
print("OpenAI models:", openai_models)MCP (Model Context Protocol) tools are listed over the MCP JSON-RPC endpoint with mcp_jsonrpc("tools/list") - see MCP JSON-RPC Endpoint below. This is only available when MCP_ENABLE and MCP_EXPOSE are set on the Inference Gateway server.
Server-Side Tool Management
Listing the connected tools is particularly useful for UI applications that need to display them to users. The key advantage is that tools are managed server-side:
- Automatic Tool Injection: Tools are automatically inferred and injected into requests by the Inference Gateway server
- Simplified Client Code: No need to manually manage or configure tools in your client application
- Transparent Tool Calls: During streaming chat completions with configured MCP servers, tool calls appear in the response stream - no special handling required except optionally displaying them to users
The gateway exposes itself as an MCP server at POST /mcp, aggregating every server in MCP_SERVERS behind one JSON-RPC 2.0 endpoint. Use mcp_jsonrpc to call it; the required params._meta block and the MCP-Protocol-Version / Mcp-Method / Mcp-Name headers are filled in for you:
# Discover the supported protocol versions and capabilities
discover = client.mcp_jsonrpc("server/discover")
print(discover.result)
# List the aggregated, namespaced tools of every healthy MCP server
tools = client.mcp_jsonrpc("tools/list")
# Call a tool - names are namespaced mcp_<server alias>_<tool name>
response = client.mcp_jsonrpc(
"tools/call",
{
"name": "mcp_deepwiki_ask_question",
"arguments": {
"repoName": "inference-gateway/inference-gateway",
"question": "How is MCP wired up?",
},
},
)
if response.error:
print(f"JSON-RPC error {response.error.code}: {response.error.message}")
else:
print(response.result)Protocol-level failures come back as JSON-RPC error envelopes on the response object rather than as exceptions. The endpoint lives at the root, not under /v1, so a /v1 suffix on the client's base URL is stripped automatically. It requires MCP_ENABLED=true and MCP_EXPOSE=true on the server.
When the gateway runs with authentication enabled, MCP clients can discover its authorization server through the RFC 9728 metadata document:
metadata = client.get_mcp_protected_resource_metadata()
print(metadata.resource, metadata.authorization_servers)To generate content using a model, use the create_chat_completion method:
Note: Some models support reasoning capabilities. You can use the
reasoning_formatparameter to control how reasoning is provided in the response. The model's reasoning will be available in thereasoningorreasoning_contentfields of the response message.
from inference_gateway import InferenceGatewayClient, Message
client = InferenceGatewayClient("http://localhost:8080/v1")
response = client.create_chat_completion(
model="ollama/llama2",
messages=[
Message(role="system", content="You are a helpful assistant."),
Message(role="user", content="What is Python?"),
],
)
print(response.choices[0].message.content.root)
# If reasoning was requested and the model supports it
if response.choices[0].message.reasoning:
print("Reasoning:", response.choices[0].message.reasoning)The SDK supports multimodal messages with images for vision-capable models like GPT-4o. You can include images via URLs or base64-encoded data URLs.
from inference_gateway import InferenceGatewayClient, Message
client = InferenceGatewayClient("http://localhost:8080/v1")
response = client.create_chat_completion(
model="openai/gpt-4o",
messages=[Message(role="user", content="What is the Python programming language?")],
)from inference_gateway import (
InferenceGatewayClient,
Message,
TextContentPart,
ImageContentPart,
ImageURL,
)
client = InferenceGatewayClient("http://localhost:8080/v1")
response = client.create_chat_completion(
model="openai/gpt-4o",
messages=[
Message(
role="user",
content=[
TextContentPart(type="text", text="What is in this image?"),
ImageContentPart(
type="image_url",
image_url=ImageURL(
url="https://example.com/image.jpg",
detail="auto",
),
),
],
)
],
)from inference_gateway import ImageContentPart, ImageURL
ImageContentPart(
type="image_url",
image_url=ImageURL(
url="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD...",
detail="high", # better quality, more expensive
),
)Message(
role="user",
content=[
TextContentPart(type="text", text="Compare these images:"),
ImageContentPart(type="image_url", image_url=ImageURL(url="https://example.com/image1.jpg")),
ImageContentPart(type="image_url", image_url=ImageURL(url="https://example.com/image2.jpg")),
],
)Image Detail Levels:
"auto": Automatic detail level (default)"low": Lower resolution, faster and cheaper"high": Higher resolution, better quality but more expensive
For a complete example, see the chat example.
You can enable reasoning capabilities by setting the reasoning_format parameter in your request:
from inference_gateway import InferenceGatewayClient, Message
client = InferenceGatewayClient("http://localhost:8080/v1")
response = client.create_chat_completion(
model="anthropic/claude-3-opus-20240229",
messages=[
Message(role="system", content="You are a helpful assistant. Please include your reasoning for complex questions."),
Message(role="user", content="What is the square root of 144 and why?"),
],
reasoning_format="parsed", # "raw" or "parsed" - defaults to "parsed"
)
print("Content:", response.choices[0].message.content.root)
if response.choices[0].message.reasoning:
print("Reasoning:", response.choices[0].message.reasoning)create_chat_completion and create_chat_completion_stream accept the full set of
OpenAI-compatible request parameters as keyword arguments. The SDK only sends the
parameters you set explicitly - spec defaults you do not specify are left off the
request so each provider applies its own.
from inference_gateway import InferenceGatewayClient, Message
client = InferenceGatewayClient("http://localhost:8080/v1")
response = client.create_chat_completion(
model="openai/gpt-4o",
messages=[Message(role="user", content="Describe Python as a JSON object.")],
temperature=0.2, # 0-2, sampling temperature
top_p=0.9, # 0-1, nucleus sampling
n=1, # 1-128, number of choices to generate
stop=["\n\n"], # a string, or up to 4 strings
frequency_penalty=0.0, # -2..2
presence_penalty=0.0, # -2..2
seed=42, # best-effort determinism
max_completion_tokens=512, # preferred over the deprecated max_tokens
response_format={"type": "json_object"},
reasoning_effort="medium", # minimal | low | medium | high
)response_format and tool_choice are oneOf unions. You can pass plain dicts (they
are validated for you) or use the exported typed models:
from inference_gateway import (
ChatCompletionNamedToolChoice,
ResponseFormatJsonObject,
)
response = client.create_chat_completion(
model="openai/gpt-4o",
messages=[Message(role="user", content="What's the weather in Paris?")],
response_format=ResponseFormatJsonObject(type="json_object"),
tool_choice=ChatCompletionNamedToolChoice(
type="function",
function={"name": "get_weather"},
),
)Note:
max_tokensis deprecated in favor ofmax_completion_tokens.
To generate content using streaming mode, use the create_chat_completion_stream method. It yields SSEvent objects:
import json
from pydantic import ValidationError
from inference_gateway import InferenceGatewayClient, Message
from inference_gateway.models import CreateChatCompletionStreamResponse
client = InferenceGatewayClient("http://localhost:8080/v1")
for chunk in client.create_chat_completion_stream(
model="ollama/llama2",
messages=[
Message(role="system", content="You are a helpful assistant."),
Message(role="user", content="Tell me a story."),
],
):
if not chunk.data:
continue
try:
data = json.loads(chunk.data)
stream_response = CreateChatCompletionStreamResponse.model_validate(data)
except (json.JSONDecodeError, ValidationError):
continue
for choice in stream_response.choices:
# Reasoning content (both reasoning and reasoning_content fields)
if choice.delta.reasoning:
print(f"💭 Reasoning: {choice.delta.reasoning}")
if choice.delta.reasoning_content:
print(f"💭 Reasoning: {choice.delta.reasoning_content}")
if choice.delta.content:
print(choice.delta.content, end="", flush=True)To use tools with the SDK, define a tool with the type-safe Pydantic models and pass it to the request:
from inference_gateway import InferenceGatewayClient, Message
from inference_gateway.models import ChatCompletionTool, FunctionObject, FunctionParameters
client = InferenceGatewayClient("http://localhost:8080/v1")
tools = [
ChatCompletionTool(
type="function",
function=FunctionObject(
name="get_current_weather",
description="Get the current weather in a given location",
parameters=FunctionParameters(
type="object",
properties={
"location": {
"type": "string",
"enum": ["san francisco", "new york", "london", "tokyo", "sydney"],
"description": "The city and state, e.g. San Francisco, CA",
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "The temperature unit to use",
},
},
required=["location"],
),
),
),
ChatCompletionTool(
type="function",
function=FunctionObject(
name="get_current_time",
description="Get the current time in a given location",
parameters=FunctionParameters(
type="object",
properties={
"location": {
"type": "string",
"enum": ["san francisco", "new york", "london", "tokyo", "sydney"],
"description": "The city and state, e.g. San Francisco, CA",
},
},
required=["location"],
),
),
),
]
response = client.create_chat_completion(
model="openai/gpt-4o",
messages=[
Message(role="system", content="You are a helpful assistant with access to weather and time information."),
Message(role="user", content="What is the weather like in New York?"),
],
tools=tools,
)
# Inspect any tool calls made by the model
if response.choices[0].message.tool_calls:
for tool_call in response.choices[0].message.tool_calls:
print(f"Tool called: {tool_call.function.name}")
print(f"Arguments: {tool_call.function.arguments}")Some providers attach opaque, per-call metadata that must be echoed back on follow-up requests. The most notable case is Google Gemini's reasoning models, which return a thought_signature on each tool call - the next request must round-trip it verbatim or the provider will reject it.
The SDK preserves this automatically as long as you append the assistant message back to the conversation as a model object (rather than reconstructing it from a dict):
response = client.create_chat_completion(
model="google/gemini-3-pro",
messages=messages,
tools=tools,
)
assistant_message = response.choices[0].message
messages.append(assistant_message) # preserves extra_content.google.thought_signature
# ... append your tool results, then send the follow-up request ...If you need to construct one explicitly:
from inference_gateway import Google, ToolCallExtraContent
extra = ToolCallExtraContent(google=Google(thought_signature="..."))The field is fully optional - providers that don't use it ignore it entirely, and model_dump(exclude_none=True) strips it from the wire when unset.
The gateway also exposes an Anthropic-compatible Messages API (POST /messages). Use create_message for standard requests - it returns a validated MessagesResponse:
from inference_gateway import InferenceGatewayClient, MessagesMessage, MessagesTextBlock
client = InferenceGatewayClient("http://localhost:8080/v1")
response = client.create_message(
model="anthropic/claude-sonnet-5",
messages=[
MessagesMessage(role="user", content="Hello! Please introduce yourself briefly."),
],
max_tokens=100,
system="You are a helpful assistant",
)
for block in response.content:
if isinstance(block.root, MessagesTextBlock):
print(block.root.text)
print(f"Stop reason: {response.stop_reason}")
print(f"Usage: {response.usage.input_tokens} in / {response.usage.output_tokens} out")create_message_stream yields typed MessagesStreamEvent objects (message_start, content_block_delta, message_stop, ...) - no manual SSE parsing needed:
for event in client.create_message_stream(
model="anthropic/claude-sonnet-5",
messages=[
MessagesMessage(role="user", content="Tell me a short story."),
],
max_tokens=200,
):
if event.type == "content_block_delta" and event.delta and event.delta.text:
print(event.delta.text, end="", flush=True)Tools use the MessagesTool shape, and tool calls come back as MessagesToolUseBlock content blocks:
from inference_gateway import MessagesTool, MessagesToolUseBlock
from inference_gateway.models import FunctionParameters
tools = [
MessagesTool(
name="get_current_weather",
description="Get the current weather in a given location",
input_schema=FunctionParameters(
type="object",
properties={
"location": {"type": "string", "description": "The city, e.g. San Francisco"},
},
required=["location"],
),
),
]
response = client.create_message(
model="anthropic/claude-sonnet-5",
messages=[MessagesMessage(role="user", content="What is the weather in New York?")],
max_tokens=200,
tools=tools,
)
for block in response.content:
if isinstance(block.root, MessagesToolUseBlock):
print(f"Tool called: {block.root.name}")
print(f"Input: {block.root.input}")Note: Not every provider implements the Messages API. Requests routed to a provider without support return a 400 error - use
create_chat_completionfor those providers.
To proxy a raw request directly to a provider's API through the gateway, use proxy_request:
response = client.proxy_request(
provider="openai",
path="/v1/models",
method="GET",
)
print("OpenAI models:", response)To check if the API is healthy:
if client.health_check():
print("API is healthy")
else:
print("API is unavailable")The SDK provides several exception types:
from inference_gateway import (
InferenceGatewayError,
InferenceGatewayAPIError,
InferenceGatewayValidationError,
)
try:
response = client.create_chat_completion(...)
except InferenceGatewayAPIError as e:
print(f"API Error: {e} (Status: {e.status_code})")
print("Response:", e.response_data)
except InferenceGatewayValidationError as e:
print(f"Validation Error: {e}")
except InferenceGatewayError as e:
print(f"General Error: {e}")For more detailed examples and use cases, check out the examples directory. The examples include:
- List Example - How to list available models
- Chat Example - Basic and advanced chat completion examples
- Tools Example - Function calling and tool usage
- Messages Example - Anthropic-compatible Messages API usage
- MCP Example - Model Context Protocol integration examples
Each example includes its own README with specific instructions and explanations.
The SDK supports the following LLM providers:
- Ollama (
"ollama") - Ollama Cloud (
"ollama_cloud") - Groq (
"groq") - OpenAI (
"openai") - DeepSeek (
"deepseek") - Cloudflare (
"cloudflare") - Cohere (
"cohere") - Anthropic (
"anthropic") - Google (
"google") - Mistral AI (
"mistral") - MiniMax (
"minimax") - Moonshot (
"moonshot") - NVIDIA (
"nvidia")
This SDK is distributed under the Apache 2.0 License, see LICENSE for more information.