135 lines
4.5 KiB
Python
135 lines
4.5 KiB
Python
"""Pure invocation contract shared by configuration and runtime.
|
|
|
|
Provider configuration owns connection/authentication and adapter selection.
|
|
Model configuration owns capabilities, limits, and canonical parameters. This
|
|
module is the only layer that derives the effective wire invocation from those
|
|
inputs; the derived fields are runtime state, not administrator configuration.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
from collections.abc import Mapping
|
|
from dataclasses import dataclass
|
|
from types import MappingProxyType
|
|
from typing import Any, Literal
|
|
|
|
from ..contracts import EvoRuntimeError
|
|
from ..crypto import canonical_json_v1
|
|
|
|
ToolCallTransport = Literal["native", "disabled"]
|
|
|
|
|
|
def derive_tool_call_transport(
|
|
capabilities: Mapping[str, Any],
|
|
) -> ToolCallTransport:
|
|
"""Derive the wire transport from the authoritative tools capability."""
|
|
|
|
return "native" if bool(capabilities.get("tools", False)) else "disabled"
|
|
|
|
|
|
def derive_runtime_invocation(
|
|
api_mode: str,
|
|
capabilities: Mapping[str, Any],
|
|
) -> dict[str, str]:
|
|
"""Build the internal invocation projection for a normalized model."""
|
|
|
|
return {
|
|
"api_mode": str(api_mode),
|
|
"tool_call_transport": derive_tool_call_transport(capabilities),
|
|
}
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class InvocationPlan:
|
|
"""Complete, immutable, non-secret wire contract for one model call."""
|
|
|
|
api_mode: str
|
|
output_token_parameter: str
|
|
output_token_limit: int
|
|
tool_call_transport: ToolCallTransport
|
|
reasoning_effort: str
|
|
streaming: bool
|
|
sdk_params: Mapping[str, Any]
|
|
plan_hash: str
|
|
|
|
def model_kwargs(self) -> dict[str, Any]:
|
|
return dict(self.sdk_params)
|
|
|
|
def projection(self) -> dict[str, Any]:
|
|
return {
|
|
"api_mode": self.api_mode,
|
|
"output_token_parameter": self.output_token_parameter,
|
|
"output_token_limit": self.output_token_limit,
|
|
"tool_call_transport": self.tool_call_transport,
|
|
"reasoning_effort": self.reasoning_effort,
|
|
"streaming": self.streaming,
|
|
"sdk_params": dict(self.sdk_params),
|
|
"plan_hash": self.plan_hash,
|
|
}
|
|
|
|
|
|
def compile_invocation_plan(
|
|
*,
|
|
api_mode: str,
|
|
declared_tool_call_transport: str,
|
|
supports_tools: bool,
|
|
purpose: str,
|
|
output_token_limit: int,
|
|
reasoning_effort: str,
|
|
runtime_provider: str,
|
|
sdk_params: Mapping[str, Any],
|
|
) -> InvocationPlan:
|
|
"""Validate and freeze adapter output before constructing a provider SDK."""
|
|
|
|
params = dict(sdk_params)
|
|
streaming = True
|
|
params["streaming"] = streaming
|
|
params["disable_streaming"] = False
|
|
if runtime_provider == "openai" and streaming:
|
|
params["stream_usage"] = True
|
|
token_fields = tuple(
|
|
key
|
|
for key in ("max_output_tokens", "max_completion_tokens", "max_tokens")
|
|
if key in params
|
|
)
|
|
if len(token_fields) != 1 or params[token_fields[0]] != output_token_limit:
|
|
raise EvoRuntimeError("MODEL_ADAPTER_COMPILE_FAILED")
|
|
output_token_parameter = token_fields[0]
|
|
if api_mode == "responses" and output_token_parameter != "max_output_tokens":
|
|
raise EvoRuntimeError("MODEL_ADAPTER_COMPILE_FAILED")
|
|
if api_mode == "chat_completions" and output_token_parameter == "max_output_tokens":
|
|
raise EvoRuntimeError("MODEL_ADAPTER_COMPILE_FAILED")
|
|
if runtime_provider == "openai" and api_mode in {
|
|
"responses",
|
|
"chat_completions",
|
|
}:
|
|
if params.get("use_responses_api") is not (api_mode == "responses"):
|
|
raise EvoRuntimeError("MODEL_ADAPTER_COMPILE_FAILED")
|
|
|
|
tool_call_transport: ToolCallTransport = (
|
|
"native" if supports_tools else "disabled"
|
|
)
|
|
if declared_tool_call_transport != tool_call_transport:
|
|
raise EvoRuntimeError("MODEL_ADAPTER_COMPILE_FAILED")
|
|
|
|
plan_payload = {
|
|
"api_mode": api_mode,
|
|
"output_token_parameter": output_token_parameter,
|
|
"output_token_limit": output_token_limit,
|
|
"tool_call_transport": tool_call_transport,
|
|
"reasoning_effort": reasoning_effort,
|
|
"streaming": streaming,
|
|
"sdk_params": params,
|
|
}
|
|
return InvocationPlan(
|
|
api_mode=api_mode,
|
|
output_token_parameter=output_token_parameter,
|
|
output_token_limit=output_token_limit,
|
|
tool_call_transport=tool_call_transport,
|
|
reasoning_effort=reasoning_effort,
|
|
streaming=streaming,
|
|
sdk_params=MappingProxyType(params),
|
|
plan_hash=hashlib.sha256(canonical_json_v1(plan_payload)).hexdigest(),
|
|
)
|