nemoguardrails.guardrails.model_engine
nemoguardrails.guardrails.model_engine
Model engine for IORails.
Wraps a single Model config and makes raw HTTP calls to its OpenAI-compatible /v1/chat/completions endpoint via aiohttp. Retries are handled by aiohttp-retry (ExponentialRetry).
Module Contents
Classes
Functions
Data
API
Bases: BaseEngine
Wraps a single Model config and makes HTTP calls to its endpoint.
Each ModelEngine owns its own RetryClient with per-model timeout, retry, and connection pool settings.
Raise if the engine has not been started.
Return the value stored in environment variable variable_name.
Build the client, URL, headers, and body common to every request.
Raise ModelEngineError if the HTTP status indicates an error.
Resolve the API key from model config or environment.
Resolve the base URL from model parameters or engine type.
Strips an optional trailing “/v1” so users can follow the OpenAI / LLMRails convention of including “/v1” in base_url without producing a doubled “/v1/v1/chat/completions” path when _CHAT_COMPLETIONS_ENDPOINT is appended.
Wrap an unexpected exception in a ModelEngineError.
Make a POST request to the /v1/chat/completions endpoint.
Retries on transient failures (429, 5xx, connection errors) are handled automatically by the RetryClient with exponential backoff.
Parameters:
List of message dicts in OpenAI format.
Additional parameters for the request body (temperature, max_tokens, etc.)
Returns: dict
The parsed JSON response dict from the API.
Raises:
ModelEngineError: If the request fails after all retries.
Generate a chat completion and return a structured LLMResponse.
Calls the /v1/chat/completions endpoint and parses the OpenAI-format
response into an LLMResponse carrying content, reasoning (when the
provider exposes reasoning_content), usage, finish reason, and
request id.
Raises:
ModelEngineError: If the request fails or the response format is unexpected.
Group messages into per-turn (tool_calls, tool_results) exchanges.
Each exchange pairs one assistant turn’s tool calls with the tool results that
answer it, so RailsManager.are_tool_results_safe can validate call_id
linkage turn-locally rather than across the whole flattened history (the latter
falsely flags ids reused across turns, which the OpenAI spec permits). Keyed on
the model’s engine (_TOOL_EXCHANGE_EXTRACTORS); OpenAI and NIM share the Chat
Completions shape and an engine with no registered extractor falls back to it.
Extract incoming tool results from messages into ToolResult objects.
Pulls the provider’s tool-result messages out of the conversation and
normalizes them into the internal ToolResult shape the ToolResultRail
consumes, keyed on the model’s engine (_RESULT_EXTRACTORS). OpenAI and
NIM share the Chat Completions shape (role:"tool" messages); an engine
with no registered extractor falls back to it. Returns an empty list when
there are no tool results.
Parse the provider tool block in llm_params into a Toolset.
Reads the opaque tools block forwarded via
GenerationOptions.llm_params and normalizes it into the internal
Toolset the tool rails validate against, keyed on the model’s engine
(_TOOL_PARSERS). OpenAI and NIM share the Chat Completions shape; an
engine with no registered parser falls back to it. Returns an empty
Toolset when no tools are declared.
Make a streaming POST request to the /v1/chat/completions endpoint.
Sends stream=True and yields one LLMResponseChunk per SSE
event that carries a content delta, reasoning delta, OR a
usage payload. Role-only, finish-only, and empty-choices
events without usage are skipped. Retries are handled by the
RetryClient (same as call()).
Note: when the upstream payload includes
stream_options.include_usage=true (default for the
OpenAI-compatible client), the provider sends a final
usage-only chunk with empty choices after the last content
chunk. That terminal chunk is yielded as
LLMResponseChunk(usage=...) with both delta_content
and delta_reasoning unset — callers that only care about
content should gate on chunk.delta_content rather than
assuming every yielded chunk carries one.
Tool calls (when the request declared tools) are accumulated
from streamed delta.tool_calls fragments and surfaced as a
single LLMResponseChunk whose delta_tool_calls carries the
COMPLETE finalized list exactly once — on the first chunk with a
finish_reason ("tool_calls" for a free choice, "stop"
for a forced tool_choice), or via a post-loop safety net if the
provider omits a parseable finish frame. No other chunk carries
delta_tool_calls, so consumers may treat it as last-write-wins.
Parameters:
List of message dicts in OpenAI format.
Additional parameters for the request body (temperature, max_tokens, etc.)
Raises:
ModelEngineError: If the request fails after all retries.
Stream a chat completion and yield LLMResponseChunk objects.
Thin pass-through over stream_call — see that method’s
docstring for the contract, including the terminal usage-only
chunk emitted when stream_options.include_usage is on.
Raises:
ModelEngineError: If the request fails after all retries.
Bases: Exception
Raised when a model engine call fails.
Bases: NamedTuple
Pre-built parameters for an HTTP request to the completions endpoint.
Update the tool-call accumulator with any tool_call deltas from a raw SSE chunk.
OpenAI streams argument JSON as fragments across many chunks; NIM delivers
complete arguments in one delta. Both are handled uniformly: tool_calls
is keyed by the OpenAI index field and mutated in place on every call.
Finalize with _finalize_tool_calls once finish_reason=="tool_calls".
Extract NIM tool exchanges. NIM uses the OpenAI Chat Completions shape.
Group an OpenAI Chat Completions conversation into per-turn ToolExchangees.
Extract NIM tool results. NIM uses the OpenAI Chat Completions shape.
Extract OpenAI Chat Completions tool results into ToolResult objects.
Chat Completions carries each tool result as a top-level {"role": "tool", "tool_call_id", "content"} message (optionally name).
Assemble accumulated tool-call fragments into ToolCall objects.
Called once when the stream emits finish_reason=‘tool_calls’. An empty buffer (no
argument fragments streamed) is a no-argument call and becomes {}; a non-empty
buffer that is not a valid JSON object (e.g. arguments truncated mid-stream) raises
ValueError so the malformed call fails closed rather than silently degrading to
empty arguments that could pass the tool-call rail. This mirrors the non-streaming
parser (ChatMessage.from_dict), which raises on the same bytes; stream_call
wraps the error into ModelEngineError exactly as the non-streaming path does.
Convert a /v1/chat/completions response dict into an LLMResponse.
Reasoning is read from message.reasoning_content when the provider
exposes it (NIM, DeepSeek-style). Tool calls are parsed from
message.tool_calls (OpenAI shape) into LLMResponse.tool_calls via
ChatMessage.from_dict, which normalizes JSON-string arguments into a
dict. content is None on a tool-call-only response and is
normalized to an empty string; a None content with no tool calls is
treated as a malformed response.
Build an LLMResponseChunk from an SSE chunk dict.
Returns None for chunks without one of: content delta, reasoning delta, a usage payload, or a finish_reason. Role-only first events map to None.
Finish-only frames are preserved: a delta with no content/reasoning
(OpenAI sends delta: {}, NIM sends delta: {"content": ""}) and no
usage, carrying only a finish_reason. Dropping them would strip
gen_ai.response.finish_reasons from the LLM span. (Some providers
instead attach finish_reason to the final content chunk — that case is
already captured, since content keeps the chunk alive.) When
stream_options.include_usage=true the usage payload arrives in a
separate later frame with empty choices — so finish_reason and usage
do not share a frame.
Last chunk from OpenAI-compatible providers has a usage field when
stream_options.include_usage=true. This is passed through to capture
the token usage metadata.
Parse NIM tool definitions. NIM uses the OpenAI Chat Completions tool shape.
Parse OpenAI Chat Completions tool definitions into Tool objects.
Each entry has the nested shape {"type": "function", "function": {"name", "description", "parameters", "strict"}}; function.parameters (the JSON
Schema) maps to Tool.arguments_schema. Entries that are not a dict, lack a
function block, or whose function has no non-empty name are skipped.
Build UsageInfo from an OpenAI-format usage dict.
Picks up reasoning_tokens from completion_tokens_details (OpenAI reasoning models) and cached_tokens from prompt_tokens_details when present.
Extract the tool calls from one assistant message into ToolCall objects.
Malformed tool calls fall back to just id, type, function
Normalize one OpenAI Chat Completions role:"tool" message into a ToolResult.
This shape has no error flag, so is_error is always False.