allow partial configs + config refs by id + sc.

This commit is contained in:
Yamac Ay 2026-10-02 17:08:58 +02:00
parent e02d243ab5
commit 42b12bd9f9
No known key found for this signature in database
GPG key ID: D113B438819CA628
2 changed files with 177 additions and 0 deletions

View file

@ -869,3 +869,92 @@ class OrchestrationConfig(BaseModel):
class OrchestrationRequest(BaseModel):
config: OrchestrationConfig
placeholder_values: dict[str, str] | None = None
# ---------------------------------------------------------------------------
# Partial config models — used by the config_ref request variants.
# All fields are optional so callers only supply what they want to override.
# ---------------------------------------------------------------------------
class PartialPromptTemplatingModuleConfig(BaseModel):
"""Partial prompt-templating override for config_ref requests.
Both fields are optional: omit ``prompt`` to keep the referenced template,
omit ``model`` to keep the referenced model.
"""
prompt: Template | None = None
model: LLMModelDetails | None = None
class PartialModuleConfigs(BaseModel):
"""Partial module configuration for config_ref overrides.
Only specify the modules you want to override; the remaining configuration
is taken from the referenced orchestration config.
"""
prompt_templating: PartialPromptTemplatingModuleConfig | None = None
filtering: FilteringModuleConfig | None = None
masking: MaskingModuleConfig | None = None
grounding: GroundingModuleConfig | None = None
translation: TranslationModuleConfig | None = None
class PartialOrchestrationConfig(BaseModel):
"""Partial orchestration configuration for config_ref overrides.
All fields are optional. Supply only the parts that should be overridden;
the rest is taken from the referenced configuration stored in SAP AI Core.
"""
modules: PartialModuleConfigs | None = None
stream: GlobalStreamOptions | None = None
# ---------------------------------------------------------------------------
# config_ref discriminated shapes (spec: CompletionPostRequest oneOf variants)
# ---------------------------------------------------------------------------
class CompletionRequestConfigurationReferenceByIdConfigRef(BaseModel):
"""Reference an SAP AI Core orchestration configuration by its UUID."""
id: str
class CompletionRequestConfigurationReferenceById(BaseModel):
"""POST /v2/completion body variant: reference a saved config by ID.
The optional ``config`` field carries a partial override that is merged
on top of the referenced configuration. ``placeholder_values`` and
``messages_history`` work the same as in the full-config variant.
"""
config_ref: CompletionRequestConfigurationReferenceByIdConfigRef
config: PartialOrchestrationConfig | None = None
placeholder_values: dict[str, str] | None = None
messages_history: list[ChatMessage] | None = None
class CompletionRequestConfigurationReferenceByNameScenarioVersionConfigRef(BaseModel):
"""Reference an SAP AI Core orchestration configuration by name + scenario + version."""
scenario: str
name: str
version: str
class CompletionRequestConfigurationReferenceByNameScenarioVersion(BaseModel):
"""POST /v2/completion body variant: reference a saved config by name/scenario/version.
The optional ``config`` field carries a partial override that is merged
on top of the referenced configuration. ``placeholder_values`` and
``messages_history`` work the same as in the full-config variant.
"""
config_ref: CompletionRequestConfigurationReferenceByNameScenarioVersionConfigRef
config: PartialOrchestrationConfig | None = None
placeholder_values: dict[str, str] | None = None
messages_history: list[ChatMessage] | None = None

View file

@ -34,7 +34,12 @@ from .handler import (
)
from .models import (
ChatCompletionTool,
CompletionRequestConfigurationReferenceById,
CompletionRequestConfigurationReferenceByIdConfigRef,
CompletionRequestConfigurationReferenceByNameScenarioVersion,
CompletionRequestConfigurationReferenceByNameScenarioVersionConfigRef,
OrchestrationRequest,
PartialOrchestrationConfig,
ResponseFormat,
ResponseFormatJSONSchema,
SAPAssistantMessage,
@ -312,6 +317,76 @@ class GenAIHubOrchestrationConfig(OpenAIGPTConfig):
**optional_modules,
}
def _build_config_ref_body(
self,
config_ref: dict,
optional_params: dict,
messages: list[AllMessageValues],
) -> dict:
"""Build a config_ref request body (ById or ByNameScenarioVersion variant).
The discriminator is duck-typed from the keys present in ``config_ref``:
* ``{"id": "<uuid>"}`` → ById variant
* ``{"scenario": ..., "name": ..., "version": ...}`` → ByNameScenarioVersion variant
``optional_params`` may carry:
* ``placeholder_values`` (dict[str, str]) — forwarded as-is.
* ``messages_history`` (list of message dicts) — forwarded as-is.
* ``config`` (dict matching PartialOrchestrationConfig) — partial override.
Any keys not consumed here are silently ignored so that LiteLLM's standard
optional-params machinery does not break.
"""
# --- discriminate on config_ref shape -----------------------------------
if "id" in config_ref:
validated_ref = CompletionRequestConfigurationReferenceByIdConfigRef(
**config_ref
)
model_cls = CompletionRequestConfigurationReferenceById
elif "scenario" in config_ref or "name" in config_ref or "version" in config_ref:
validated_ref = CompletionRequestConfigurationReferenceByNameScenarioVersionConfigRef(
**config_ref
)
model_cls = CompletionRequestConfigurationReferenceByNameScenarioVersion
else:
raise ValueError(
"config_ref must contain either 'id' (ById) or 'scenario'/'name'/'version' "
"(ByNameScenarioVersion)."
)
# --- optional fields ----------------------------------------------------
placeholder_values: Final = optional_params.pop("placeholder_values", None)
messages_history_raw: Final = optional_params.pop("messages_history", None)
partial_config_raw: Final = optional_params.pop("config", None)
partial_config: PartialOrchestrationConfig | None = None
if partial_config_raw is not None:
partial_config = PartialOrchestrationConfig(**partial_config_raw)
messages_history = None
if messages_history_raw is not None:
messages_history = _messages_to_sap_template(messages_history_raw)
elif messages:
# When messages are provided through the standard LiteLLM path but
# no explicit messages_history override was given, treat them as the
# history so that the caller does not have to duplicate the payload.
messages_history = _messages_to_sap_template(messages)
# --- assemble and validate the full body --------------------------------
body_kwargs: dict = {
"config_ref": validated_ref,
}
if partial_config is not None:
body_kwargs["config"] = partial_config
if placeholder_values is not None:
body_kwargs["placeholder_values"] = placeholder_values
if messages_history is not None:
body_kwargs["messages_history"] = messages_history
validated = model_cls(**body_kwargs)
return validated.model_dump(by_alias=True, exclude_unset=True)
def transform_request(
self,
model: str,
@ -323,6 +398,19 @@ class GenAIHubOrchestrationConfig(OpenAIGPTConfig):
optional_params = dict(optional_params)
optional_params.pop("deployment_url", None)
# --- config_ref routing -------------------------------------------------
# When the caller supplies a `config_ref` key the request targets a
# pre-saved SAP AI Core orchestration configuration. We build the
# alternative body shape and return early, bypassing the full-config path.
config_ref: Final = optional_params.pop("config_ref", None)
if config_ref is not None:
return self._build_config_ref_body(
config_ref=dict(config_ref),
optional_params=optional_params,
messages=messages,
)
# ------------------------------------------------------------------------
template: Final = _messages_to_sap_template(messages)
placeholder_values: Final = optional_params.pop("placeholder_values", None)