llm-workflow-router 0.3.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.
@@ -0,0 +1,409 @@
1
+ Metadata-Version: 2.4
2
+ Name: llm-workflow-router
3
+ Version: 0.3.0
4
+ Summary: Deterministic workflow topology enforcement for LLM-powered systems.
5
+ Author: Doby Baxter
6
+ License: MIT
7
+ Keywords: llm,agents,workflow,topology,validation,determinism
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Software Development :: Libraries
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: PyYAML>=6.0.1
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
22
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
23
+ Requires-Dist: coverage>=7.0.0; extra == "dev"
24
+ Requires-Dist: ruff>=0.6.0; extra == "dev"
25
+ Requires-Dist: mypy>=1.8.0; extra == "dev"
26
+ Requires-Dist: opentelemetry-api>=1.20; extra == "dev"
27
+ Requires-Dist: opentelemetry-sdk>=1.20; extra == "dev"
28
+ Provides-Extra: otel
29
+ Requires-Dist: opentelemetry-api>=1.20; extra == "otel"
30
+ Provides-Extra: openai-agents
31
+ Requires-Dist: openai-agents>=0.18; extra == "openai-agents"
32
+ Dynamic: license-file
33
+
34
+ # LLM Workflow Router
35
+
36
+ Deterministic workflow topology enforcement for LLM-powered systems.
37
+
38
+ LLM Workflow Router is a stateless middleware engine designed to enforce explicit execution topology in AI systems that rely on large language models. It evaluates structured interaction metadata against strictly declared workflow rules and returns a terminal state.
39
+
40
+ It controls structure — not content.
41
+
42
+ ---
43
+
44
+ ## Overview
45
+
46
+ Modern LLM-driven systems frequently suffer from:
47
+
48
+ - Recursive tool invocation loops
49
+ - Circular container transitions
50
+ - Cross-container contamination
51
+ - Implicit fallback behavior
52
+ - Unbounded workflow escalation
53
+ - Inconsistent refusal logic
54
+
55
+ Most mitigation strategies limit volume (timeouts, max tool calls, retries).
56
+ LLM Workflow Router enforces topology explicitly.
57
+
58
+ The engine evaluates structured interaction metadata and returns one of three terminal states:
59
+
60
+ - PROCEED
61
+ - REFUSE
62
+ - PAUSE
63
+
64
+ No content inspection.
65
+ No moderation.
66
+ No orchestration.
67
+ No mutation of input.
68
+
69
+ Only structural enforcement.
70
+
71
+ ---
72
+
73
+ ## Core Design Principles
74
+
75
+ - Deterministic evaluation
76
+ - Stateless per evaluation
77
+ - Metadata-only inspection
78
+ - Explicit transitions only
79
+ - Static configuration (v1)
80
+ - Strict validation at load time
81
+ - No silent fallback
82
+ - Host application retains execution control
83
+
84
+ Same input → same output.
85
+
86
+ ---
87
+
88
+ ## Architecture
89
+
90
+ Application
91
+
92
+ WorkflowEngine.evaluate(metadata)
93
+
94
+ [ PROCEED | REFUSE | PAUSE ]
95
+
96
+ Application decides next action
97
+
98
+ The router does not:
99
+
100
+ - Execute tools
101
+ - Retry calls
102
+ - Modify prompts
103
+ - Orchestrate sessions
104
+
105
+ It enforces topology and returns a decision.
106
+
107
+ ---
108
+
109
+ ## Configuration
110
+
111
+ Workflow rules are defined using static YAML configuration.
112
+
113
+ ```yaml
114
+ entrypoints:
115
+ - entry
116
+
117
+ containers:
118
+ entry:
119
+ allow_transitions:
120
+ - support
121
+ - REFUSE
122
+ allow_reentry: false
123
+ max_invocations: 2
124
+
125
+ support:
126
+ allow_transitions:
127
+ - faq
128
+ - REFUSE
129
+ allow_reentry: false
130
+ max_invocations: 3
131
+
132
+ faq:
133
+ allow_transitions:
134
+ - REFUSE
135
+ allow_reentry: true
136
+ max_invocations: 5
137
+ ```
138
+
139
+ Each container defines:
140
+
141
+ - Explicit allowed transitions
142
+ - Whether re-entry is permitted
143
+ - Maximum invocation depth
144
+
145
+ Implicit transitions are not allowed.
146
+
147
+ ### Entrypoints
148
+
149
+ The optional top-level `entrypoints` list declares the authoritative root
150
+ containers. A workflow may have several roots — a support flow, a sales flow,
151
+ and an FAQ flow can each start in their own container — so `entrypoints` is a
152
+ list. When declared, any indegree-0 container that is *not* listed is treated
153
+ as an orphan and rejected. If `entrypoints` is omitted, roots are inferred from
154
+ graph shape (indegree 0) for backward compatibility, and an ambiguity warning
155
+ is raised when more than one root is inferred. See
156
+ `examples/multi_entry_config.yaml`.
157
+
158
+ ---
159
+
160
+ ## Topology Validation
161
+
162
+ At configuration load time, the router performs strict validation:
163
+
164
+ - Invalid transition targets
165
+ - Unknown containers
166
+ - Unknown declared entrypoints
167
+ - Dead-end containers
168
+ - Missing entry points
169
+ - Orphan containers (indegree 0, not a declared entrypoint)
170
+ - Unreachable containers
171
+ - Self-transition contradictions
172
+ - Cycle detection
173
+ - Re-entry safety enforcement
174
+
175
+ Configuration errors raise exceptions immediately.
176
+
177
+ Fail loudly at load time.
178
+ Never fail silently at runtime.
179
+
180
+ ---
181
+
182
+ ## Runtime Evaluation
183
+
184
+ The engine evaluates an immutable metadata structure:
185
+
186
+ ```python
187
+ InteractionMetadata:
188
+ container: str
189
+ previous_state: InteractionState
190
+ transition_history: List[str]
191
+ invocation_depth: Dict[str, int]
192
+ requested_action: str
193
+ trace_id: Optional[str]
194
+ ```
195
+
196
+ Returns:
197
+
198
+ ```python
199
+ EvaluationResult:
200
+ state: InteractionState
201
+ container: str
202
+ reason: Optional[ReasonCode]
203
+ trace_id: Optional[str]
204
+ ```
205
+
206
+ No exceptions during normal evaluation.
207
+ Only terminal states are returned.
208
+
209
+ ---
210
+
211
+ ## CLI Usage
212
+
213
+ Install:
214
+
215
+ ```bash
216
+ pip install llm-workflow-router
217
+ ```
218
+
219
+ Validate configuration:
220
+
221
+ ```bash
222
+ llm-router validate config.yaml
223
+ ```
224
+
225
+ Analyze topology:
226
+
227
+ ```bash
228
+ llm-router analyze config.yaml
229
+ ```
230
+
231
+ Evaluate metadata:
232
+
233
+ ```bash
234
+ llm-router run --config config.yaml --metadata metadata.json
235
+ ```
236
+
237
+ ---
238
+
239
+ Render the topology as a diagram:
240
+
241
+ ```bash
242
+ llm-router graph config.yaml # Mermaid (paste into any Mermaid renderer)
243
+ llm-router graph config.yaml --format dot # Graphviz DOT
244
+ ```
245
+
246
+ Like `analyze`, `graph` renders broken topologies too — unknown transition
247
+ targets are drawn and flagged, which is exactly what you want while debugging
248
+ a config.
249
+
250
+ ---
251
+
252
+ ## Session Layer (optional)
253
+
254
+ The engine is stateless by design: every `evaluate()` receives a complete
255
+ metadata snapshot. If you'd rather not do that bookkeeping yourself,
256
+ `WorkflowSession` does it for you — and only advances on `PROCEED`:
257
+
258
+ ```python
259
+ from router import WorkflowEngine, WorkflowSession
260
+
261
+ engine = WorkflowEngine(cfg)
262
+ session = WorkflowSession(engine, entry="entry", trace_id="req-42")
263
+
264
+ result = session.request("support") # entry -> support
265
+ result = session.request("faq") # support -> faq
266
+ result = session.request("REFUSE") # terminal; session closes
267
+
268
+ session.history # ("entry", "support")
269
+ session.invocation_depth # {"entry": 1, "support": 1, "faq": 1}
270
+ ```
271
+
272
+ A `REFUSE` closes the session. A `PAUSE` suspends it until `resume()`.
273
+ `session.snapshot(target)` exposes the exact metadata the next request would
274
+ evaluate, so the session is fully auditable and you can drop down to raw
275
+ `engine.evaluate()` at any time. The engine itself remains pure and stateless.
276
+
277
+ ---
278
+
279
+ ## OpenAI Agents SDK Integration
280
+
281
+ Agents are containers. Handoffs are transitions. Attach a `TopologyGuard` to
282
+ a run and every handoff is structurally evaluated **before** the next agent
283
+ executes — a refused handoff raises `TopologyViolation` and aborts the run
284
+ loudly instead of letting the agent graph wander:
285
+
286
+ ```bash
287
+ pip install "llm-workflow-router[openai-agents]"
288
+ ```
289
+
290
+ ```python
291
+ from agents import Agent, Runner
292
+ from router import WorkflowEngine
293
+ from router.integrations.openai_agents import TopologyGuard, TopologyViolation
294
+
295
+ guard = TopologyGuard(engine, entry="triage", trace_id="run-001")
296
+
297
+ try:
298
+ result = await Runner.run(triage_agent, "I was double-charged.", hooks=guard)
299
+ except TopologyViolation as violation:
300
+ print("Refused:", violation.result.reason)
301
+
302
+ # Full structural audit trail of the run:
303
+ print(guard.session.history, guard.session.invocation_depth)
304
+ ```
305
+
306
+ By default an agent's `name` is its container name; pass `container_for=` to
307
+ map differently. The guard is content-blind — it never reads prompts,
308
+ messages, or tool arguments. See
309
+ [`examples/openai_agents_example.py`](examples/openai_agents_example.py) for a
310
+ complete runnable triage → billing → refunds system.
311
+
312
+ ---
313
+
314
+ ## Why not just LangGraph (or my orchestrator's built-in graph)?
315
+
316
+ Orchestrators *describe* structure. This engine *enforces* it — as a separate,
317
+ framework-agnostic layer with properties orchestrators don't give you:
318
+
319
+ - **Independent enforcement.** The topology lives outside your agent
320
+ framework, so a prompt-induced detour, a buggy handoff, or a framework
321
+ upgrade can't silently widen what's reachable. The declared graph is a
322
+ contract, and violations fail loudly with structured reason codes.
323
+ - **Framework-agnostic.** The same YAML config governs an OpenAI Agents SDK
324
+ app today and whatever you migrate to next year. Adapters are thin; the
325
+ contract is portable.
326
+ - **Auditable determinism.** Same metadata + same config → same decision,
327
+ every time. Combined with the `wfrouter.*` OpenTelemetry conventions, you
328
+ get compliance-grade answers to "why was this transition refused?" — a
329
+ reason code, not a vibe.
330
+ - **Load-time topology analysis.** Cycles, orphans, dead ends, and
331
+ unreachable states are caught before anything runs — the kind of static
332
+ validation industrial control systems have had for decades and agent
333
+ frameworks mostly don't.
334
+
335
+ If you're happy inside one orchestrator and don't need independent structural
336
+ guarantees, its built-in graph may be enough. This tool exists for when
337
+ "probably follows the graph" isn't good enough.
338
+
339
+ ---
340
+
341
+ ## Intended Audience
342
+
343
+ - AI SaaS developers
344
+ - Internal LLM tooling teams
345
+ - Agent orchestration builders
346
+ - Platform engineering teams
347
+ - Infrastructure-focused AI developers
348
+
349
+ Not intended for content moderation or prompt filtering.
350
+
351
+ ---
352
+
353
+ ## Observability
354
+
355
+ Optional OpenTelemetry instrumentation is provided under a dedicated,
356
+ versioned namespace (`wfrouter.*`) that this project owns — it is deliberately
357
+ independent of the upstream `gen_ai.*` conventions, which assume a model at the
358
+ center of every span and do not fit a content-blind topology engine.
359
+
360
+ Install the extra:
361
+
362
+ ```bash
363
+ pip install "llm-workflow-router[otel]"
364
+ ```
365
+
366
+ Wrap evaluation:
367
+
368
+ ```python
369
+ from router.observability.otel import traced_evaluate
370
+ result = traced_evaluate(engine, metadata)
371
+ ```
372
+
373
+ If `opentelemetry-api` is not installed, instrumentation degrades to a no-op
374
+ and the engine behaves identically. A `PROCEED`, `REFUSE`, or `PAUSE` outcome
375
+ is a successful decision (span status OK); only genuine failures are errors.
376
+ See `OBSERVABILITY.md` for the full attribute and span conventions.
377
+
378
+ ---
379
+
380
+ # License
381
+
382
+ MIT. Use it, ship it, build on it. See [LICENSE](LICENSE).
383
+
384
+ If you deploy this in production, a note about your use case is always
385
+ appreciated (and helps prioritize the roadmap) — but never required.
386
+
387
+ ---
388
+
389
+ ## Version
390
+
391
+ Current version: 0.3.0
392
+
393
+ - Static configuration model
394
+ - Explicit multi-entrypoint declaration (with inferred fallback)
395
+ - Optional stateful `WorkflowSession` convenience layer
396
+ - OpenAI Agents SDK adapter (`TopologyGuard`)
397
+ - Mermaid / DOT topology export (`llm-router graph`)
398
+ - MIT licensed
399
+
400
+ No inheritance. No dynamic rule composition. See [CHANGELOG.md](CHANGELOG.md).
401
+
402
+ Future versions may extend topology modeling capabilities.
403
+
404
+ ---
405
+
406
+ ## Author
407
+
408
+ Doby Baxter
409
+ Software systems developer focused on deterministic infrastructure and human-centered tooling.
@@ -0,0 +1,24 @@
1
+ llm_workflow_router-0.3.0.dist-info/licenses/LICENSE,sha256=l2WB-wKhVb72IHjFy_GJTpm2IAxnOOQLVg13K86Tg6E,1068
2
+ router/__init__.py,sha256=ezXbO4rcNhYwmhY0_-ekV2fZsS622HxovWAW6Ogqxyo,170
3
+ router/_version.py,sha256=i0zotH1JX-8jrPYa-b2bJb3NBuV8UIOybVIvYOx8bjU,227
4
+ router/cli.py,sha256=oMM656-kFDTguyfv217Dmfs_KpSuSJmqlvc54JF8tNU,7183
5
+ router/engine.py,sha256=EfuwdR5dgwm5M1x5kAF6XNHuCUC0mFkQELqdKgqlfY0,2596
6
+ router/graphing.py,sha256=LcCsjCXFs1_nSCwxXWazy69KwUEgICOzqvxf7cFh9u0,4353
7
+ router/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ router/session.py,sha256=3i62WPzjiLv2xPkVtQ6AujM63ohcQyOMj1mkHm-gPrU,6238
9
+ router/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ router/integrations/openai_agents.py,sha256=HLiFvRfAQeekdkYNtwhZ7HBolQddy7Kx8uZXalUFIkw,6617
11
+ router/logging/schema.py,sha256=_weVBQHsPop1vkJsGfkiOgVF5qBcC10LtbHirZ8kuqI,2519
12
+ router/models/config.py,sha256=ZJyvr4eMxE2HippBzhPOzc4F4bsXGxpDZN1ght6LvU8,799
13
+ router/models/enums.py,sha256=zvf-HAwrfngmxjLP6p7pSac19crowdeysRGPw6FfuVU,398
14
+ router/models/metadata.py,sha256=0X_6ip52uIUfKw-pwxGqrdfCxnA0P5iQXCPYpkgOzuY,388
15
+ router/models/result.py,sha256=qgD-XzcJW-xf_s-x_TyYPLjR-sJhwTGov6rXO7WdXQ8,319
16
+ router/observability/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ router/observability/otel.py,sha256=ieSS2gfhzM7r7IR1MlyUhH0q5vZBGlnDVK5ALwbraVI,9316
18
+ router/validation/config_validator.py,sha256=9uV19hvQr_3K0zuXW3JDmGObsw6qj-qGaWJc4j2GhYE,1744
19
+ router/validation/transition_validator.py,sha256=qXye6AkEPz9F0vYWrFLjKPYjwnQBAiv33Ni6sHk3HgI,10441
20
+ llm_workflow_router-0.3.0.dist-info/METADATA,sha256=S8IgS2VqgYfBYgw7fay9lMO4_uYiLKRuJreYNmTW_fA,11317
21
+ llm_workflow_router-0.3.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
22
+ llm_workflow_router-0.3.0.dist-info/entry_points.txt,sha256=cMfEK5p65b63p0r8CbqUIymSXD5ogpaRWb8pK69IXtE,47
23
+ llm_workflow_router-0.3.0.dist-info/top_level.txt,sha256=e5Y_z2oozHN0yGfFuQJVJuk4MJhhfEisD1rcbh8986g,7
24
+ llm_workflow_router-0.3.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ llm-router = router.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Doby Baxter
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ router
router/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ from ._version import __version__
2
+ from .engine import WorkflowEngine
3
+ from .session import WorkflowSession
4
+
5
+ __all__ = ["WorkflowEngine", "WorkflowSession", "__version__"]
router/_version.py ADDED
@@ -0,0 +1,7 @@
1
+ """Single source of truth for the engine version.
2
+
3
+ Referenced by packaging metadata, the structured logging schema, and the
4
+ OpenTelemetry instrumentation, so the version can never drift between them.
5
+ """
6
+
7
+ __version__ = "0.3.0"