|
26 | 26 | from enum import Enum |
27 | 27 | from typing import Any, Literal, Optional, Tuple, Union |
28 | 28 |
|
29 | | -from pydantic import BaseModel, ConfigDict, Field, field_validator |
| 29 | +from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator |
30 | 30 |
|
31 | 31 | from nemoguardrails.actions.rail_outcome import TransformTarget |
32 | 32 |
|
@@ -174,6 +174,14 @@ class Binding(BaseModel): |
174 | 174 |
|
175 | 175 | model_config = ConfigDict(extra="forbid", frozen=True) |
176 | 176 |
|
| 177 | + @model_validator(mode="after") |
| 178 | + def _source_must_match_kind(self) -> "Binding": |
| 179 | + if self.kind == "literal" and self.key is not None: |
| 180 | + raise ValueError("Literal bindings cannot declare a source key.") |
| 181 | + if self.kind != "literal" and self.key is None: |
| 182 | + raise ValueError(f"{self.kind} bindings must declare a source key.") |
| 183 | + return self |
| 184 | + |
177 | 185 | @classmethod |
178 | 186 | def surface_param(cls, action_param: str, name: str, *, required: bool = True) -> "Binding": |
179 | 187 | """Bind `action_param` to a caller-supplied surface parameter. |
@@ -227,6 +235,18 @@ class RailSurface(BaseModel): |
227 | 235 |
|
228 | 236 | model_config = ConfigDict(extra="forbid", frozen=True) |
229 | 237 |
|
| 238 | + @model_validator(mode="after") |
| 239 | + def _validate_execution_contract(self) -> "RailSurface": |
| 240 | + seen = set() |
| 241 | + duplicates = set() |
| 242 | + for binding in self.bindings: |
| 243 | + if binding.action_param in seen: |
| 244 | + duplicates.add(binding.action_param) |
| 245 | + seen.add(binding.action_param) |
| 246 | + if duplicates: |
| 247 | + raise ValueError(f"Rail surfaces cannot bind action parameters more than once: {sorted(duplicates)}.") |
| 248 | + return self |
| 249 | + |
230 | 250 |
|
231 | 251 | class EnvVar(BaseModel): |
232 | 252 | """Environment variable declared by a rail requirement.""" |
|
0 commit comments