coreason-manifest 0.10.0__py3-none-any.whl → 0.13.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. coreason_manifest/__init__.py +35 -82
  2. coreason_manifest/{definitions/base.py → common.py} +18 -1
  3. coreason_manifest/governance.py +83 -0
  4. coreason_manifest/schemas/coreason-v2.schema.json +462 -0
  5. coreason_manifest/utils/logger.py +0 -45
  6. coreason_manifest/v2/__init__.py +1 -0
  7. coreason_manifest/v2/governance.py +144 -0
  8. coreason_manifest/v2/io.py +132 -0
  9. coreason_manifest/v2/resolver.py +67 -0
  10. coreason_manifest/v2/spec/__init__.py +1 -0
  11. coreason_manifest/v2/spec/contracts.py +34 -0
  12. coreason_manifest/v2/spec/definitions.py +196 -0
  13. coreason_manifest/v2/validator.py +48 -0
  14. {coreason_manifest-0.10.0.dist-info → coreason_manifest-0.13.0.dist-info}/METADATA +48 -72
  15. coreason_manifest-0.13.0.dist-info/RECORD +20 -0
  16. coreason_manifest/definitions/__init__.py +0 -60
  17. coreason_manifest/definitions/agent.py +0 -370
  18. coreason_manifest/definitions/audit.py +0 -181
  19. coreason_manifest/definitions/events.py +0 -423
  20. coreason_manifest/definitions/message.py +0 -188
  21. coreason_manifest/definitions/simulation.py +0 -79
  22. coreason_manifest/definitions/simulation_config.py +0 -46
  23. coreason_manifest/definitions/topology.py +0 -341
  24. coreason_manifest/recipes.py +0 -84
  25. coreason_manifest/schemas/agent.schema.json +0 -1051
  26. coreason_manifest/schemas/recipe.schema.json +0 -813
  27. coreason_manifest/v1/__init__.py +0 -15
  28. coreason_manifest-0.10.0.dist-info/RECORD +0 -22
  29. {coreason_manifest-0.10.0.dist-info → coreason_manifest-0.13.0.dist-info}/WHEEL +0 -0
  30. {coreason_manifest-0.10.0.dist-info → coreason_manifest-0.13.0.dist-info}/licenses/LICENSE +0 -0
  31. {coreason_manifest-0.10.0.dist-info → coreason_manifest-0.13.0.dist-info}/licenses/NOTICE +0 -0
@@ -1,188 +0,0 @@
1
- # Copyright (c) 2025 CoReason, Inc.
2
- #
3
- # This software is proprietary and dual-licensed.
4
- # Licensed under the Prosperity Public License 3.0 (the "License").
5
- # A copy of the license is available at https://prosperitylicense.com/versions/3.0.0
6
- # For details, see the LICENSE file.
7
- # Commercial use beyond a 30-day trial requires a separate license.
8
- #
9
- # Source Code: https://github.com/CoReason-AI/coreason-manifest
10
-
11
- import functools
12
- import json
13
- import warnings
14
- from enum import Enum
15
- from typing import Annotated, Any, Dict, List, Literal, Optional, Union, cast
16
-
17
- from pydantic import ConfigDict, Field, model_validator
18
-
19
- from coreason_manifest.definitions.base import CoReasonBaseModel
20
-
21
- # --- Enums ---
22
-
23
-
24
- class Role(str, Enum):
25
- SYSTEM = "system"
26
- USER = "user"
27
- ASSISTANT = "assistant"
28
- TOOL = "tool"
29
-
30
-
31
- class Modality(str, Enum):
32
- TEXT = "text"
33
- IMAGE = "image"
34
- AUDIO = "audio"
35
- VIDEO = "video"
36
-
37
-
38
- # --- Message Parts ---
39
-
40
-
41
- class TextPart(CoReasonBaseModel):
42
- """Represents text content sent to or received from the model."""
43
-
44
- model_config = ConfigDict(extra="ignore")
45
- type: Literal["text"] = "text"
46
- content: str
47
-
48
-
49
- class BlobPart(CoReasonBaseModel):
50
- """Represents blob binary data sent inline to the model."""
51
-
52
- model_config = ConfigDict(extra="ignore")
53
- type: Literal["blob"] = "blob"
54
- content: str # Base64 encoded string
55
- modality: Modality
56
- mime_type: Optional[str] = None
57
-
58
-
59
- class FilePart(CoReasonBaseModel):
60
- """Represents an external referenced file sent to the model by file id."""
61
-
62
- model_config = ConfigDict(extra="ignore")
63
- type: Literal["file"] = "file"
64
- file_id: str
65
- modality: Modality
66
- mime_type: Optional[str] = None
67
-
68
-
69
- class UriPart(CoReasonBaseModel):
70
- """Represents an external referenced file sent to the model by URI."""
71
-
72
- model_config = ConfigDict(extra="ignore")
73
- type: Literal["uri"] = "uri"
74
- uri: str
75
- modality: Modality
76
- mime_type: Optional[str] = None
77
-
78
-
79
- class ToolCallRequestPart(CoReasonBaseModel):
80
- """Represents a tool call requested by the model."""
81
-
82
- model_config = ConfigDict(extra="ignore")
83
- type: Literal["tool_call"] = "tool_call"
84
- name: str
85
- arguments: Union[Dict[str, Any], str] # Structured arguments or JSON string
86
- id: Optional[str] = None
87
-
88
- @functools.cached_property
89
- def parsed_arguments(self) -> Dict[str, Any]:
90
- """Return arguments as a dictionary, parsing JSON if necessary."""
91
- if isinstance(self.arguments, dict):
92
- return self.arguments
93
- try:
94
- result = json.loads(self.arguments)
95
- return cast(Dict[str, Any], result) if isinstance(result, dict) else {}
96
- except (json.JSONDecodeError, TypeError):
97
- return {}
98
-
99
-
100
- class ToolCallResponsePart(CoReasonBaseModel):
101
- """Represents a tool call result sent to the model."""
102
-
103
- model_config = ConfigDict(extra="ignore")
104
- type: Literal["tool_call_response"] = "tool_call_response"
105
- response: Any # The result of the tool call
106
- id: Optional[str] = None
107
-
108
-
109
- class ReasoningPart(CoReasonBaseModel):
110
- """Represents reasoning/thinking content received from the model."""
111
-
112
- model_config = ConfigDict(extra="ignore")
113
- type: Literal["reasoning"] = "reasoning"
114
- content: str
115
-
116
-
117
- # --- Union of All Parts ---
118
-
119
- Part = Annotated[
120
- Union[TextPart, BlobPart, FilePart, UriPart, ToolCallRequestPart, ToolCallResponsePart, ReasoningPart],
121
- Field(discriminator="type"),
122
- ]
123
-
124
- # --- Main Message Model ---
125
-
126
-
127
- class ChatMessage(CoReasonBaseModel):
128
- """Represents a message in a conversation with an LLM."""
129
-
130
- model_config = ConfigDict(extra="ignore")
131
-
132
- role: Role
133
- parts: List[Part] = Field(..., description="List of message parts that make up the message content.")
134
- name: Optional[str] = None
135
-
136
- @classmethod
137
- def user(cls, content: str, name: Optional[str] = None) -> "ChatMessage":
138
- """Factory method to create a user message with text content."""
139
- return cls(role=Role.USER, parts=[TextPart(content=content)], name=name)
140
-
141
- @classmethod
142
- def assistant(cls, content: str, name: Optional[str] = None) -> "ChatMessage":
143
- """Factory method to create an assistant message with text content."""
144
- return cls(role=Role.ASSISTANT, parts=[TextPart(content=content)], name=name)
145
-
146
- @classmethod
147
- def tool(cls, tool_call_id: str, content: Any) -> "ChatMessage":
148
- """Factory method to create a tool message with the result."""
149
- return cls(role=Role.TOOL, parts=[ToolCallResponsePart(id=tool_call_id, response=content)])
150
-
151
-
152
- # --- Backward Compatibility ---
153
-
154
-
155
- class FunctionCall(CoReasonBaseModel):
156
- """Deprecated: Use ToolCallRequestPart instead."""
157
-
158
- name: str
159
- arguments: str
160
-
161
- @model_validator(mode="after")
162
- def warn_deprecated(self) -> "FunctionCall":
163
- warnings.warn(
164
- "FunctionCall is deprecated. Use ToolCallRequestPart instead.",
165
- DeprecationWarning,
166
- stacklevel=2,
167
- )
168
- return self
169
-
170
-
171
- class ToolCall(CoReasonBaseModel):
172
- """Deprecated: Use ToolCallRequestPart instead."""
173
-
174
- id: str
175
- type: str = "function"
176
- function: FunctionCall
177
-
178
- @model_validator(mode="after")
179
- def warn_deprecated(self) -> "ToolCall":
180
- warnings.warn(
181
- "ToolCall is deprecated. Use ToolCallRequestPart instead.",
182
- DeprecationWarning,
183
- stacklevel=2,
184
- )
185
- return self
186
-
187
-
188
- Message = ChatMessage
@@ -1,79 +0,0 @@
1
- # Copyright (c) 2025 CoReason, Inc.
2
- #
3
- # This software is proprietary and dual-licensed.
4
- # Licensed under the Prosperity Public License 3.0 (the "License").
5
- # A copy of the license is available at https://prosperitylicense.com/versions/3.0.0
6
- # For details, see the LICENSE file.
7
- # Commercial use beyond a 30-day trial requires a separate license.
8
- #
9
- # Source Code: https://github.com/CoReason-AI/coreason-manifest
10
-
11
- from datetime import datetime
12
- from enum import Enum
13
- from typing import Any, Dict, List, Optional
14
- from uuid import UUID
15
-
16
- from pydantic import Field
17
-
18
- from coreason_manifest.definitions.base import CoReasonBaseModel
19
-
20
-
21
- class StepType(str, Enum):
22
- """Type of the simulation step."""
23
-
24
- INTERACTION = "interaction" # Normal User/Agent turn
25
- SYSTEM_EVENT = "system_event" # Chaos injection, error, or info
26
-
27
-
28
- class ValidationLogic(str, Enum):
29
- """Logic used to validate the scenario outcome."""
30
-
31
- EXACT_MATCH = "exact_match"
32
- FUZZY = "fuzzy"
33
- CODE_EVAL = "code_eval"
34
-
35
-
36
- class SimulationScenario(CoReasonBaseModel):
37
- """Definition of a simulation scenario."""
38
-
39
- id: str = Field(..., description="Unique identifier for the scenario.")
40
- name: str = Field(..., description="Name of the scenario.")
41
- objective: str = Field(..., description="The prompt/task instructions.")
42
- difficulty: int = Field(..., description="Difficulty level (1-3, aligning with GAIA levels).", ge=1, le=3)
43
- expected_outcome: Any = Field(..., description="The ground truth for validation.")
44
- validation_logic: ValidationLogic = Field(..., description="Logic used to validate the outcome.")
45
-
46
-
47
- class SimulationStep(CoReasonBaseModel):
48
- """The atomic unit of execution in a simulation."""
49
-
50
- step_id: UUID = Field(..., description="Atomic unit of execution ID.")
51
- timestamp: datetime = Field(..., description="Execution timestamp.")
52
- type: StepType = Field(default=StepType.INTERACTION, description="Type of the step.")
53
- node_id: str = Field(..., description="The graph node executed.")
54
- inputs: Dict[str, Any] = Field(..., description="Snapshot of entry state.")
55
- thought: Optional[str] = Field(None, description="The Chain-of-Thought reasoning.")
56
- action: Optional[Dict[str, Any]] = Field(None, description="Tool calls or API requests.")
57
- observation: Optional[Dict[str, Any]] = Field(None, description="Tool outputs.")
58
- snapshot: Dict[str, Any] = Field(
59
- default_factory=dict, description="Full copy of the graph state at the completion of this step."
60
- )
61
-
62
-
63
- class SimulationMetrics(CoReasonBaseModel):
64
- """Metrics gathered during simulation."""
65
-
66
- turn_count: int
67
- total_tokens: Optional[int] = None
68
- cost_usd: Optional[float] = None
69
- duration_ms: Optional[float] = None
70
-
71
-
72
- class SimulationTrace(CoReasonBaseModel):
73
- """Trace of a simulation execution."""
74
-
75
- trace_id: UUID = Field(..., description="Unique trace identifier.")
76
- agent_version: str = Field(..., description="Agent SemVer version.")
77
- steps: List[SimulationStep] = Field(..., description="List of execution steps.")
78
- outcome: Dict[str, Any] = Field(..., description="Final result.")
79
- metrics: SimulationMetrics = Field(..., description="Execution metrics (e.g., token usage, cost).")
@@ -1,46 +0,0 @@
1
- # Copyright (c) 2025 CoReason, Inc.
2
- #
3
- # This software is proprietary and dual-licensed.
4
- # Licensed under the Prosperity Public License 3.0 (the "License").
5
- # A copy of the license is available at https://prosperitylicense.com/versions/3.0.0
6
- # For details, see the LICENSE file.
7
- # Commercial use beyond a 30-day trial requires a separate license.
8
- #
9
- # Source Code: https://github.com/CoReason-AI/coreason-manifest
10
-
11
-
12
- from typing import Optional
13
-
14
- from pydantic import Field
15
-
16
- from coreason_manifest.definitions.agent import Persona
17
- from coreason_manifest.definitions.base import CoReasonBaseModel
18
- from coreason_manifest.definitions.simulation import SimulationScenario
19
-
20
-
21
- class AdversaryProfile(CoReasonBaseModel):
22
- name: str
23
- goal: str
24
- strategy_model: str # e.g., "claude-3-opus"
25
- attack_model: str # e.g., "llama-3-uncensored"
26
- persona: Optional[Persona] = Field(None, description="The full persona definition (name, description, directives).")
27
- # Potential future field: 'system_prompt_override'
28
-
29
-
30
- class ChaosConfig(CoReasonBaseModel):
31
- latency_ms: int = Field(default=0, ge=0)
32
- error_rate: float = Field(default=0.0, ge=0.0, le=1.0)
33
- noise_rate: float = Field(default=0.0, ge=0.0, le=1.0)
34
- token_throttle: bool = False
35
- exception_type: str = "RuntimeError"
36
-
37
-
38
- class SimulationRequest(CoReasonBaseModel):
39
- """
40
- Standard payload for triggering a simulation.
41
- This would replace the local 'SimulationRequest' in the Simulator.
42
- """
43
-
44
- scenario: SimulationScenario
45
- profile: AdversaryProfile
46
- chaos_config: ChaosConfig = Field(default_factory=ChaosConfig)
@@ -1,341 +0,0 @@
1
- # Copyright (c) 2025 CoReason, Inc.
2
- #
3
- # This software is proprietary and dual-licensed.
4
- # Licensed under the Prosperity Public License 3.0 (the "License").
5
- # A copy of the license is available at https://prosperitylicense.com/versions/3.0.0
6
- # For details, see the LICENSE file.
7
- # Commercial use beyond a 30-day trial requires a separate license.
8
- #
9
- # Source Code: https://github.com/CoReason-AI/coreason-manifest
10
-
11
- # Copyright (c) 2025 CoReason, Inc.
12
- #
13
- # This software is proprietary and dual-licensed.
14
- # Licensed under the Prosperity Public License 3.0 (the "License").
15
- # A copy of the license is available at https://prosperitylicense.com/versions/3.0.0
16
- # For details, see the LICENSE file.
17
- # Commercial use beyond a 30-day trial requires a separate license.
18
- #
19
- # Source Code: https://github.com/CoReason-AI/coreason_maco
20
-
21
- from enum import Enum
22
- from typing import Annotated, Any, Dict, List, Literal, Optional, Sequence, Union
23
-
24
- from pydantic import ConfigDict, Field, StringConstraints, model_validator
25
-
26
- from coreason_manifest.definitions.base import CoReasonBaseModel
27
-
28
-
29
- class StateDefinition(CoReasonBaseModel):
30
- """Defines the internal state (memory) of the Recipe.
31
-
32
- Attributes:
33
- schema: JSON Schema of the keys available in the shared memory.
34
- persistence: Configuration for state durability.
35
- """
36
-
37
- model_config = ConfigDict(extra="forbid")
38
-
39
- schema_: Dict[str, Any] = Field(
40
- ..., alias="schema", description="JSON Schema of the keys available in the shared memory."
41
- )
42
- persistence: Literal["ephemeral", "persistent"] = Field(
43
- default="ephemeral", description="Configuration for state durability."
44
- )
45
-
46
-
47
- class CouncilConfig(CoReasonBaseModel):
48
- """Configuration for 'Architectural Triangulation'.
49
-
50
- Attributes:
51
- strategy: The strategy for the council (e.g., 'consensus').
52
- voters: List of agents or models that vote.
53
- """
54
-
55
- model_config = ConfigDict(extra="forbid")
56
-
57
- strategy: str = Field(default="consensus", description="The strategy for the council, e.g., 'consensus'.")
58
- voters: List[str] = Field(..., description="List of agents or models that vote.")
59
-
60
-
61
- class VisualMetadata(CoReasonBaseModel):
62
- """Data explicitly for the UI.
63
-
64
- Attributes:
65
- label: The label to display for the node.
66
- x_y_coordinates: The X and Y coordinates for the node on the canvas.
67
- icon: The icon to represent the node.
68
- animation_style: The animation style for the node.
69
- """
70
-
71
- model_config = ConfigDict(extra="forbid")
72
-
73
- label: Optional[str] = Field(None, description="The label to display for the node.")
74
- x_y_coordinates: Optional[List[float]] = Field(
75
- None, description="The X and Y coordinates for the node on the canvas."
76
- )
77
- icon: Optional[str] = Field(None, description="The icon to represent the node.")
78
- animation_style: Optional[str] = Field(None, description="The animation style for the node.")
79
-
80
-
81
- class RuntimeVisualMetadata(VisualMetadata):
82
- """Visual metadata with runtime extensions.
83
-
84
- Attributes:
85
- color: Color override for the node (e.g., '#00FF00').
86
- progress: Progress indicator (e.g., '0.5' or '50%').
87
- animation: Animation override (e.g., 'pulse').
88
- """
89
-
90
- color: Optional[str] = Field(None, description="Color override for the node.")
91
- progress: Optional[str] = Field(None, description="Progress indicator.")
92
- animation: Optional[str] = Field(None, description="Animation override.")
93
-
94
-
95
- class BaseNode(CoReasonBaseModel):
96
- """Base model for all node types.
97
-
98
- Attributes:
99
- id: Unique identifier for the node.
100
- council_config: Optional configuration for architectural triangulation.
101
- visual: Visual metadata for the UI.
102
- metadata: Generic metadata for operational context (e.g. cost tracking, SLAs).
103
- """
104
-
105
- model_config = ConfigDict(extra="forbid")
106
-
107
- id: str = Field(..., description="Unique identifier for the node.")
108
- council_config: Optional[CouncilConfig] = Field(
109
- None, description="Optional configuration for architectural triangulation."
110
- )
111
- visual: Optional[VisualMetadata] = Field(None, description="Visual metadata for the UI.")
112
- metadata: Dict[str, Any] = Field(
113
- default_factory=dict,
114
- description="Generic metadata for operational context (e.g. cost tracking, SLAs).",
115
- )
116
-
117
-
118
- class AgentNode(BaseNode):
119
- """A node that calls a specific atomic agent.
120
-
121
- Attributes:
122
- type: The type of the node (must be 'agent').
123
- agent_name: The name of the atomic agent to call.
124
- system_prompt: Overrides the registry default prompt. Required for ad-hoc/optimized agents.
125
- config: Runtime-specific configuration (e.g., model parameters, temperature). Merged with registry defaults.
126
- overrides: Runtime overrides for the agent (e.g., temperature, prompt_template_vars).
127
- """
128
-
129
- type: Literal["agent"] = Field("agent", description="Discriminator for AgentNode.")
130
- agent_name: str = Field(..., description="The name of the atomic agent to call.")
131
- system_prompt: Optional[str] = Field(
132
- default=None,
133
- description="Overrides the registry default prompt. Required for ad-hoc/optimized agents.",
134
- )
135
- config: Optional[Dict[str, Any]] = Field(
136
- default=None,
137
- description=(
138
- "Runtime-specific configuration (e.g., model parameters, temperature). Merged with registry defaults."
139
- ),
140
- )
141
- overrides: Optional[Dict[str, Any]] = Field(
142
- default=None,
143
- description="Runtime overrides for the agent (e.g., temperature, prompt_template_vars).",
144
- )
145
-
146
-
147
- class HumanNode(BaseNode):
148
- """A node that pauses execution for user input/approval.
149
-
150
- Attributes:
151
- type: The type of the node (must be 'human').
152
- timeout_seconds: Optional timeout in seconds for the user interaction.
153
- """
154
-
155
- type: Literal["human"] = Field("human", description="Discriminator for HumanNode.")
156
- timeout_seconds: Optional[int] = Field(None, description="Optional timeout in seconds for the user interaction.")
157
-
158
-
159
- class LogicNode(BaseNode):
160
- """A node that executes pure Python logic.
161
-
162
- Attributes:
163
- type: The type of the node (must be 'logic').
164
- code: The Python logic code to execute.
165
- """
166
-
167
- type: Literal["logic"] = Field("logic", description="Discriminator for LogicNode.")
168
- code: str = Field(..., description="The Python logic code to execute.")
169
-
170
-
171
- class DataMappingStrategy(str, Enum):
172
- """Strategy for mapping data."""
173
-
174
- DIRECT = "direct"
175
- JSONPATH = "jsonpath"
176
- LITERAL = "literal"
177
-
178
-
179
- class DataMapping(CoReasonBaseModel):
180
- """Defines how to transform data between parent and child."""
181
-
182
- model_config = ConfigDict(extra="forbid")
183
-
184
- source: str = Field(..., description="The path/key source.")
185
- strategy: DataMappingStrategy = Field(default=DataMappingStrategy.DIRECT, description="The mapping strategy.")
186
-
187
-
188
- class RecipeNode(BaseNode):
189
- """A node that executes another Recipe as a sub-graph.
190
-
191
- Attributes:
192
- type: The type of the node (must be 'recipe').
193
- recipe_id: The ID of the recipe to execute.
194
- input_mapping: How parent state maps to child inputs (parent_key -> child_key).
195
- output_mapping: How child result maps back to parent state (child_key -> parent_key).
196
- """
197
-
198
- type: Literal["recipe"] = Field("recipe", description="Discriminator for RecipeNode.")
199
- recipe_id: str = Field(..., description="The ID of the recipe to execute.")
200
- input_mapping: Dict[str, Union[str, DataMapping]] = Field(
201
- ..., description="Mapping of parent state keys to child input keys."
202
- )
203
- output_mapping: Dict[str, Union[str, DataMapping]] = Field(
204
- ..., description="Mapping of child output keys to parent state keys."
205
- )
206
-
207
-
208
- class MapNode(BaseNode):
209
- """A node that spawns multiple parallel executions of a sub-branch.
210
-
211
- Attributes:
212
- type: The type of the node (must be 'map').
213
- items_path: Dot-notation path to the list in the state.
214
- processor_node_id: The node (or subgraph) to run for each item.
215
- concurrency_limit: Max parallel executions.
216
- """
217
-
218
- type: Literal["map"] = Field("map", description="Discriminator for MapNode.")
219
- items_path: str = Field(..., description="Dot-notation path to the list in the state.")
220
- processor_node_id: str = Field(..., description="The node (or subgraph) to run for each item.")
221
- concurrency_limit: int = Field(..., description="Max parallel executions.")
222
-
223
-
224
- # Discriminated Union for polymorphism
225
- Node = Annotated[
226
- Union[AgentNode, HumanNode, LogicNode, RecipeNode, MapNode],
227
- Field(discriminator="type", description="Polymorphic node definition."),
228
- ]
229
-
230
-
231
- class Edge(CoReasonBaseModel):
232
- """Represents a connection between two nodes.
233
-
234
- Attributes:
235
- source_node_id: The ID of the source node.
236
- target_node_id: The ID of the target node.
237
- condition: Optional Python expression for conditional branching.
238
- """
239
-
240
- model_config = ConfigDict(extra="forbid")
241
-
242
- source_node_id: str = Field(..., description="The ID of the source node.")
243
- target_node_id: str = Field(..., description="The ID of the target node.")
244
- condition: Optional[str] = Field(None, description="Optional Python expression for conditional branching.")
245
-
246
-
247
- RouterRef = Annotated[
248
- str,
249
- StringConstraints(
250
- pattern=r"^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*$",
251
- strip_whitespace=True,
252
- ),
253
- ]
254
-
255
-
256
- class RouterExpression(CoReasonBaseModel):
257
- """A structured expression for routing logic (e.g., CEL or JSONLogic)."""
258
-
259
- model_config = ConfigDict(extra="forbid")
260
-
261
- operator: str = Field(..., description="The operator (e.g., 'eq', 'gt').")
262
- args: List[Any] = Field(..., description="Arguments for the expression.")
263
-
264
-
265
- RouterDefinition = Annotated[
266
- Union[RouterRef, RouterExpression],
267
- Field(description="A reference to a python function or a logic expression."),
268
- ]
269
-
270
-
271
- class ConditionalEdge(CoReasonBaseModel):
272
- """Represents a dynamic routing connection from one node to multiple potential targets.
273
-
274
- Attributes:
275
- source_node_id: The ID of the source node.
276
- router_logic: A reference to a python function or a logic expression that returns the next node ID.
277
- mapping: A dictionary mapping the router's output (e.g., "approve", "reject") to target Node IDs.
278
- """
279
-
280
- model_config = ConfigDict(extra="forbid")
281
-
282
- source_node_id: str = Field(..., description="The ID of the source node.")
283
- router_logic: RouterDefinition = Field(
284
- ..., description="A reference to a python function or logic expression that determines the path."
285
- )
286
- mapping: Dict[str, str] = Field(..., description="Map of router output values to target node IDs.")
287
-
288
-
289
- def validate_edge_integrity(
290
- nodes: Sequence[Node],
291
- edges: Sequence[Union[Edge, ConditionalEdge]],
292
- ) -> None:
293
- """Ensures that all edges point to valid nodes.
294
-
295
- Args:
296
- nodes: List of nodes in the graph.
297
- edges: List of edges connecting the nodes.
298
-
299
- Raises:
300
- ValueError: If an edge points to a non-existent node.
301
- """
302
- node_ids = {node.id for node in nodes}
303
-
304
- for edge in edges:
305
- if edge.source_node_id not in node_ids:
306
- raise ValueError(f"Edge source node '{edge.source_node_id}' not found in nodes.")
307
-
308
- # ConditionalEdge mapping targets
309
- if isinstance(edge, ConditionalEdge):
310
- for target_id in edge.mapping.values():
311
- if target_id not in node_ids:
312
- raise ValueError(f"ConditionalEdge target node '{target_id}' not found in nodes.")
313
- else:
314
- # Regular Edge
315
- if edge.target_node_id not in node_ids:
316
- raise ValueError(f"Edge target node '{edge.target_node_id}' not found in nodes.")
317
-
318
-
319
- class GraphTopology(CoReasonBaseModel):
320
- """The topology definition of the recipe.
321
-
322
- Attributes:
323
- nodes: List of nodes in the graph.
324
- edges: List of edges connecting the nodes.
325
- state_schema: Optional schema definition for the graph state.
326
- """
327
-
328
- model_config = ConfigDict(extra="forbid")
329
-
330
- nodes: List[Node] = Field(..., description="List of nodes in the graph.")
331
- edges: List[Union[Edge, ConditionalEdge]] = Field(..., description="List of edges connecting the nodes.")
332
- state_schema: Optional[StateDefinition] = Field(default=None, description="Schema definition for the graph state.")
333
-
334
- @model_validator(mode="after")
335
- def validate_graph_integrity(self) -> "GraphTopology":
336
- """Ensures that all edges point to valid nodes."""
337
- validate_edge_integrity(self.nodes, self.edges)
338
- return self
339
-
340
-
341
- Topology = GraphTopology