nemoir-runtime 0.1.0__tar.gz
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.
- nemoir_runtime-0.1.0/.gitignore +5 -0
- nemoir_runtime-0.1.0/LICENSE +21 -0
- nemoir_runtime-0.1.0/PKG-INFO +99 -0
- nemoir_runtime-0.1.0/README.md +51 -0
- nemoir_runtime-0.1.0/pyproject.toml +75 -0
- nemoir_runtime-0.1.0/src/nemoir_runtime/__init__.py +136 -0
- nemoir_runtime-0.1.0/src/nemoir_runtime/capabilities.py +70 -0
- nemoir_runtime-0.1.0/src/nemoir_runtime/errors.py +57 -0
- nemoir_runtime-0.1.0/src/nemoir_runtime/events.py +105 -0
- nemoir_runtime-0.1.0/src/nemoir_runtime/models.py +791 -0
- nemoir_runtime-0.1.0/src/nemoir_runtime/runtime.py +992 -0
- nemoir_runtime-0.1.0/src/nemoir_runtime/tools.py +199 -0
- nemoir_runtime-0.1.0/tests/__init__.py +0 -0
- nemoir_runtime-0.1.0/tests/conftest.py +61 -0
- nemoir_runtime-0.1.0/tests/test_capabilities.py +87 -0
- nemoir_runtime-0.1.0/tests/test_events.py +894 -0
- nemoir_runtime-0.1.0/tests/test_generated_package.py +804 -0
- nemoir_runtime-0.1.0/tests/test_integration_runtime.py +251 -0
- nemoir_runtime-0.1.0/tests/test_model_stage_executor.py +531 -0
- nemoir_runtime-0.1.0/tests/test_model_streaming.py +322 -0
- nemoir_runtime-0.1.0/tests/test_models.py +1049 -0
- nemoir_runtime-0.1.0/tests/test_policy_runtime.py +813 -0
- nemoir_runtime-0.1.0/tests/test_runtime.py +856 -0
- nemoir_runtime-0.1.0/tests/test_tools.py +357 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 NemoIR
|
|
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,99 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nemoir-runtime
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python runtime core for NemoIR — execute compiled agent workflows as structured state machines with tool orchestration, policy enforcement, model-backed stage execution, and live event streaming.
|
|
5
|
+
Project-URL: Repository, https://github.com/nemoir
|
|
6
|
+
Author: NemoIR Contributors
|
|
7
|
+
License: MIT License
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2025 NemoIR
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Keywords: agent,ai,compiler,llm,nemo,runtime,workflow
|
|
30
|
+
Classifier: Development Status :: 3 - Alpha
|
|
31
|
+
Classifier: Intended Audience :: Developers
|
|
32
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
33
|
+
Classifier: Programming Language :: Python :: 3
|
|
34
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
38
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
39
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
40
|
+
Requires-Python: >=3.11
|
|
41
|
+
Requires-Dist: litellm>=1.0.0
|
|
42
|
+
Provides-Extra: dev
|
|
43
|
+
Requires-Dist: pyright>=1.1.0; extra == 'dev'
|
|
44
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
|
|
45
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
46
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
47
|
+
Description-Content-Type: text/markdown
|
|
48
|
+
|
|
49
|
+
# NemoIR Runtime
|
|
50
|
+
|
|
51
|
+
Python runtime core for [NemoIR](https://github.com/nemoir) — an LLVM-inspired compiler stack for agentic workflows.
|
|
52
|
+
|
|
53
|
+
Executes compiled agent workflows as structured state machines with tool orchestration, policy enforcement, model-backed stage execution (via [LiteLLM](https://github.com/BerriAI/litellm)), and live event streaming.
|
|
54
|
+
|
|
55
|
+
## Features
|
|
56
|
+
|
|
57
|
+
- **Workflow runtime** — state-machine execution with stage ordering, read/write resolution, transition selection, and run limits.
|
|
58
|
+
- **Tool framework** — capability-based tool registration, catalog-driven parameter validation, and policy-gated invocation (`fs.read`, `fs.write`, `user.confirm`, `os.shell`, `user.elicit`).
|
|
59
|
+
- **Policy engine** — deny and before-policies with expression evaluation (e.g., path containment guards).
|
|
60
|
+
- **Model integration** — `ModelStageExecutor` with LiteLLM adapter, structured output enforcement, tool-call loop, `ModelRouter` for per-stage model routing, and optional streaming via `ModelStreamingAdapter`.
|
|
61
|
+
- **Live event streaming** — `WorkflowRuntime.stream()` / generated `Agent.stream()` async iterator emitting `WorkflowEvent` values (run lifecycle, model deltas, tool calls, policy decisions) for UIs, debugging, and observability.
|
|
62
|
+
- **Compiler backend target** — generated workflow-specific Python packages consume this runtime; see `nemoir-backend-python` in the main NemoIR repo.
|
|
63
|
+
|
|
64
|
+
## Install
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install nemoir-runtime
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Quick start
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
import asyncio
|
|
74
|
+
from pathlib import Path
|
|
75
|
+
from nemoir_runtime import WorkflowRuntime, WorkflowManifest, Tool, ToolContext, ToolRegistry
|
|
76
|
+
|
|
77
|
+
# Define tools
|
|
78
|
+
async def read_file(*, path: Path, ctx: ToolContext) -> str:
|
|
79
|
+
return Path(path).read_text()
|
|
80
|
+
|
|
81
|
+
tools = ToolRegistry([
|
|
82
|
+
Tool(name="read_file", capability="fs.read", description="Read a file",
|
|
83
|
+
input_schema={"path": Path}, handler=read_file),
|
|
84
|
+
])
|
|
85
|
+
|
|
86
|
+
# Load a manifest (typically generated by the NemoIR compiler)
|
|
87
|
+
manifest = WorkflowManifest(...)
|
|
88
|
+
|
|
89
|
+
runtime = WorkflowRuntime(manifest=manifest, tools=tools, stage_executor=my_executor)
|
|
90
|
+
result = await runtime.run({"task": "analyze code"})
|
|
91
|
+
print(result.output)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
See the [NemoIR project](https://github.com/nemoir) for the full compiler workflow (DSL → IR → generated package).
|
|
95
|
+
|
|
96
|
+
## Requirements
|
|
97
|
+
|
|
98
|
+
- Python ≥ 3.11
|
|
99
|
+
- LiteLLM ≥ 1.0.0 (for `LiteLLMModelAdapter`; custom `ModelAdapter` implementations can avoid this dependency)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# NemoIR Runtime
|
|
2
|
+
|
|
3
|
+
Python runtime core for [NemoIR](https://github.com/nemoir) — an LLVM-inspired compiler stack for agentic workflows.
|
|
4
|
+
|
|
5
|
+
Executes compiled agent workflows as structured state machines with tool orchestration, policy enforcement, model-backed stage execution (via [LiteLLM](https://github.com/BerriAI/litellm)), and live event streaming.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Workflow runtime** — state-machine execution with stage ordering, read/write resolution, transition selection, and run limits.
|
|
10
|
+
- **Tool framework** — capability-based tool registration, catalog-driven parameter validation, and policy-gated invocation (`fs.read`, `fs.write`, `user.confirm`, `os.shell`, `user.elicit`).
|
|
11
|
+
- **Policy engine** — deny and before-policies with expression evaluation (e.g., path containment guards).
|
|
12
|
+
- **Model integration** — `ModelStageExecutor` with LiteLLM adapter, structured output enforcement, tool-call loop, `ModelRouter` for per-stage model routing, and optional streaming via `ModelStreamingAdapter`.
|
|
13
|
+
- **Live event streaming** — `WorkflowRuntime.stream()` / generated `Agent.stream()` async iterator emitting `WorkflowEvent` values (run lifecycle, model deltas, tool calls, policy decisions) for UIs, debugging, and observability.
|
|
14
|
+
- **Compiler backend target** — generated workflow-specific Python packages consume this runtime; see `nemoir-backend-python` in the main NemoIR repo.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install nemoir-runtime
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick start
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
import asyncio
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from nemoir_runtime import WorkflowRuntime, WorkflowManifest, Tool, ToolContext, ToolRegistry
|
|
28
|
+
|
|
29
|
+
# Define tools
|
|
30
|
+
async def read_file(*, path: Path, ctx: ToolContext) -> str:
|
|
31
|
+
return Path(path).read_text()
|
|
32
|
+
|
|
33
|
+
tools = ToolRegistry([
|
|
34
|
+
Tool(name="read_file", capability="fs.read", description="Read a file",
|
|
35
|
+
input_schema={"path": Path}, handler=read_file),
|
|
36
|
+
])
|
|
37
|
+
|
|
38
|
+
# Load a manifest (typically generated by the NemoIR compiler)
|
|
39
|
+
manifest = WorkflowManifest(...)
|
|
40
|
+
|
|
41
|
+
runtime = WorkflowRuntime(manifest=manifest, tools=tools, stage_executor=my_executor)
|
|
42
|
+
result = await runtime.run({"task": "analyze code"})
|
|
43
|
+
print(result.output)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
See the [NemoIR project](https://github.com/nemoir) for the full compiler workflow (DSL → IR → generated package).
|
|
47
|
+
|
|
48
|
+
## Requirements
|
|
49
|
+
|
|
50
|
+
- Python ≥ 3.11
|
|
51
|
+
- LiteLLM ≥ 1.0.0 (for `LiteLLMModelAdapter`; custom `ModelAdapter` implementations can avoid this dependency)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "nemoir-runtime"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python runtime core for NemoIR — execute compiled agent workflows as structured state machines with tool orchestration, policy enforcement, model-backed stage execution, and live event streaming."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { file = "LICENSE" }
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "NemoIR Contributors" },
|
|
13
|
+
]
|
|
14
|
+
keywords = ["nemo", "agent", "workflow", "llm", "compiler", "runtime", "ai"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Programming Language :: Python :: 3.13",
|
|
23
|
+
"Programming Language :: Python :: 3.14",
|
|
24
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
25
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
26
|
+
]
|
|
27
|
+
urls = { Repository = "https://github.com/nemoir" }
|
|
28
|
+
requires-python = ">=3.11"
|
|
29
|
+
dependencies = [
|
|
30
|
+
"litellm>=1.0.0",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.optional-dependencies]
|
|
34
|
+
dev = [
|
|
35
|
+
"pyright>=1.1.0",
|
|
36
|
+
"ruff>=0.1.0",
|
|
37
|
+
"pytest>=7.0.0",
|
|
38
|
+
"pytest-asyncio>=0.21.0",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[tool.pytest.ini_options]
|
|
42
|
+
asyncio_mode = "auto"
|
|
43
|
+
|
|
44
|
+
[tool.pyright]
|
|
45
|
+
typeCheckingMode = "strict"
|
|
46
|
+
venv = ".venv"
|
|
47
|
+
venvPath = "."
|
|
48
|
+
|
|
49
|
+
[tool.ruff]
|
|
50
|
+
line-length = 100
|
|
51
|
+
|
|
52
|
+
[tool.ruff.lint]
|
|
53
|
+
select = ["ALL"]
|
|
54
|
+
ignore = [
|
|
55
|
+
"D",
|
|
56
|
+
"COM812",
|
|
57
|
+
"TRY003",
|
|
58
|
+
"ANN401",
|
|
59
|
+
"PLR0913",
|
|
60
|
+
"BLE001",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
[tool.ruff.lint.per-file-ignores]
|
|
64
|
+
"tests/**" = [
|
|
65
|
+
"S101",
|
|
66
|
+
"ARG001",
|
|
67
|
+
"S108",
|
|
68
|
+
"PLR2004",
|
|
69
|
+
"PT011",
|
|
70
|
+
"B017",
|
|
71
|
+
"ANN001",
|
|
72
|
+
]
|
|
73
|
+
"tests/test_events.py" = ["PERF401"]
|
|
74
|
+
"tests/test_models.py" = ["PERF401"]
|
|
75
|
+
"tests/test_generated_package.py" = ["PERF401"]
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
from nemoir_runtime.capabilities import (
|
|
2
|
+
CAPABILITY_CATALOG,
|
|
3
|
+
CapabilityParam,
|
|
4
|
+
CapabilityParamType,
|
|
5
|
+
CapabilitySpec,
|
|
6
|
+
get_capability,
|
|
7
|
+
required_param_names,
|
|
8
|
+
)
|
|
9
|
+
from nemoir_runtime.errors import (
|
|
10
|
+
DataUnavailableError,
|
|
11
|
+
MaxStepsExceededError,
|
|
12
|
+
MissingCapabilityError,
|
|
13
|
+
ModelOutputValidationError,
|
|
14
|
+
ModelProviderError,
|
|
15
|
+
NemoIRRuntimeError,
|
|
16
|
+
NoTransitionMatchedError,
|
|
17
|
+
PolicyDeniedError,
|
|
18
|
+
PolicyEvaluationError,
|
|
19
|
+
StageOutputValidationError,
|
|
20
|
+
ToolInvocationError,
|
|
21
|
+
ToolValidationError,
|
|
22
|
+
WorkflowTimeoutError,
|
|
23
|
+
WorkflowValidationError,
|
|
24
|
+
)
|
|
25
|
+
from nemoir_runtime.events import (
|
|
26
|
+
WorkflowEvent,
|
|
27
|
+
WorkflowEventChannel,
|
|
28
|
+
WorkflowEventEmitter,
|
|
29
|
+
WorkflowEventKind,
|
|
30
|
+
WorkflowEventSink,
|
|
31
|
+
)
|
|
32
|
+
from nemoir_runtime.models import (
|
|
33
|
+
LiteLLMModelAdapter,
|
|
34
|
+
ModelAdapter,
|
|
35
|
+
ModelRequest,
|
|
36
|
+
ModelResponse,
|
|
37
|
+
ModelRouter,
|
|
38
|
+
ModelSpec,
|
|
39
|
+
ModelStageExecutor,
|
|
40
|
+
ModelStreamChunk,
|
|
41
|
+
ModelStreamingAdapter,
|
|
42
|
+
ModelToolCall,
|
|
43
|
+
supports_streaming,
|
|
44
|
+
)
|
|
45
|
+
from nemoir_runtime.runtime import (
|
|
46
|
+
ExprSpec,
|
|
47
|
+
GuardSpec,
|
|
48
|
+
InputSpec,
|
|
49
|
+
PolicySpec,
|
|
50
|
+
ReadSpec,
|
|
51
|
+
RefSpec,
|
|
52
|
+
RequiredCapabilitySpec,
|
|
53
|
+
RunOptions,
|
|
54
|
+
StageContext,
|
|
55
|
+
StageExecutor,
|
|
56
|
+
StageSpec,
|
|
57
|
+
TransitionSpec,
|
|
58
|
+
TriggerSpec,
|
|
59
|
+
WorkflowManifest,
|
|
60
|
+
WorkflowResult,
|
|
61
|
+
WorkflowRuntime,
|
|
62
|
+
WorkflowState,
|
|
63
|
+
WriteSpec,
|
|
64
|
+
)
|
|
65
|
+
from nemoir_runtime.tools import (
|
|
66
|
+
Tool,
|
|
67
|
+
ToolContext,
|
|
68
|
+
ToolRegistry,
|
|
69
|
+
tool,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
__all__ = [
|
|
73
|
+
"CAPABILITY_CATALOG",
|
|
74
|
+
"CapabilityParam",
|
|
75
|
+
"CapabilityParamType",
|
|
76
|
+
"CapabilitySpec",
|
|
77
|
+
"DataUnavailableError",
|
|
78
|
+
"ExprSpec",
|
|
79
|
+
"GuardSpec",
|
|
80
|
+
"InputSpec",
|
|
81
|
+
"LiteLLMModelAdapter",
|
|
82
|
+
"MaxStepsExceededError",
|
|
83
|
+
"MissingCapabilityError",
|
|
84
|
+
"ModelAdapter",
|
|
85
|
+
"ModelOutputValidationError",
|
|
86
|
+
"ModelProviderError",
|
|
87
|
+
"ModelRequest",
|
|
88
|
+
"ModelResponse",
|
|
89
|
+
"ModelRouter",
|
|
90
|
+
"ModelSpec",
|
|
91
|
+
"ModelStageExecutor",
|
|
92
|
+
"ModelStreamChunk",
|
|
93
|
+
"ModelStreamingAdapter",
|
|
94
|
+
"ModelToolCall",
|
|
95
|
+
"NemoIRRuntimeError",
|
|
96
|
+
"NoTransitionMatchedError",
|
|
97
|
+
"PolicyDeniedError",
|
|
98
|
+
"PolicyEvaluationError",
|
|
99
|
+
"PolicySpec",
|
|
100
|
+
"ReadSpec",
|
|
101
|
+
"RefSpec",
|
|
102
|
+
"RequiredCapabilitySpec",
|
|
103
|
+
"RunOptions",
|
|
104
|
+
"StageContext",
|
|
105
|
+
"StageExecutor",
|
|
106
|
+
"StageOutputValidationError",
|
|
107
|
+
"StageSpec",
|
|
108
|
+
"Tool",
|
|
109
|
+
"ToolContext",
|
|
110
|
+
"ToolInvocationError",
|
|
111
|
+
"ToolRegistry",
|
|
112
|
+
"ToolValidationError",
|
|
113
|
+
"TransitionSpec",
|
|
114
|
+
"TriggerSpec",
|
|
115
|
+
"WorkflowManifest",
|
|
116
|
+
"WorkflowResult",
|
|
117
|
+
"WorkflowRuntime",
|
|
118
|
+
"WorkflowState",
|
|
119
|
+
"WorkflowTimeoutError",
|
|
120
|
+
"WorkflowValidationError",
|
|
121
|
+
"WriteSpec",
|
|
122
|
+
"get_capability",
|
|
123
|
+
"required_param_names",
|
|
124
|
+
"supports_streaming",
|
|
125
|
+
"tool",
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
# Extended with Phase 5 event primitives (re-exported for convenience).
|
|
129
|
+
# Advanced users may import directly from nemoir_runtime.events.
|
|
130
|
+
__all__ += [
|
|
131
|
+
"WorkflowEvent",
|
|
132
|
+
"WorkflowEventChannel",
|
|
133
|
+
"WorkflowEventEmitter",
|
|
134
|
+
"WorkflowEventKind",
|
|
135
|
+
"WorkflowEventSink",
|
|
136
|
+
]
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from types import MappingProxyType
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from collections.abc import Mapping
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CapabilityParamType(Enum):
|
|
13
|
+
STRING = "string"
|
|
14
|
+
PATH = "path"
|
|
15
|
+
BOOL = "bool"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class CapabilityParam:
|
|
20
|
+
name: str
|
|
21
|
+
type: CapabilityParamType
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class CapabilitySpec:
|
|
26
|
+
name: str
|
|
27
|
+
required_params: tuple[CapabilityParam, ...]
|
|
28
|
+
|
|
29
|
+
def has_required_param(self, name: str) -> bool:
|
|
30
|
+
return any(param.name == name for param in self.required_params)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
CAPABILITY_CATALOG: Mapping[str, CapabilitySpec] = MappingProxyType(
|
|
34
|
+
{
|
|
35
|
+
"fs.read": CapabilitySpec(
|
|
36
|
+
name="fs.read",
|
|
37
|
+
required_params=(CapabilityParam("path", CapabilityParamType.PATH),),
|
|
38
|
+
),
|
|
39
|
+
"fs.write": CapabilitySpec(
|
|
40
|
+
name="fs.write",
|
|
41
|
+
required_params=(
|
|
42
|
+
CapabilityParam("path", CapabilityParamType.PATH),
|
|
43
|
+
CapabilityParam("content", CapabilityParamType.STRING),
|
|
44
|
+
),
|
|
45
|
+
),
|
|
46
|
+
"os.shell": CapabilitySpec(
|
|
47
|
+
name="os.shell",
|
|
48
|
+
required_params=(CapabilityParam("command", CapabilityParamType.STRING),),
|
|
49
|
+
),
|
|
50
|
+
"user.elicit": CapabilitySpec(
|
|
51
|
+
name="user.elicit",
|
|
52
|
+
required_params=(CapabilityParam("question", CapabilityParamType.STRING),),
|
|
53
|
+
),
|
|
54
|
+
"user.confirm": CapabilitySpec(
|
|
55
|
+
name="user.confirm",
|
|
56
|
+
required_params=(CapabilityParam("message", CapabilityParamType.STRING),),
|
|
57
|
+
),
|
|
58
|
+
},
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def get_capability(name: str) -> CapabilitySpec | None:
|
|
63
|
+
return CAPABILITY_CATALOG.get(name)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def required_param_names(name: str) -> frozenset[str]:
|
|
67
|
+
spec = get_capability(name)
|
|
68
|
+
if spec is None:
|
|
69
|
+
return frozenset()
|
|
70
|
+
return frozenset(param.name for param in spec.required_params)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class NemoIRRuntimeError(Exception):
|
|
5
|
+
pass
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ToolValidationError(NemoIRRuntimeError):
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MissingCapabilityError(NemoIRRuntimeError):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ToolInvocationError(NemoIRRuntimeError):
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class PolicyDeniedError(NemoIRRuntimeError):
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class PolicyEvaluationError(NemoIRRuntimeError):
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class StageOutputValidationError(NemoIRRuntimeError):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class DataUnavailableError(NemoIRRuntimeError):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class NoTransitionMatchedError(NemoIRRuntimeError):
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class MaxStepsExceededError(NemoIRRuntimeError):
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class WorkflowTimeoutError(NemoIRRuntimeError):
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class WorkflowValidationError(NemoIRRuntimeError):
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ModelProviderError(NemoIRRuntimeError):
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class ModelOutputValidationError(NemoIRRuntimeError):
|
|
57
|
+
pass
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Workflow event primitives for live streaming, UI observation, and debugging.
|
|
2
|
+
|
|
3
|
+
Defines the event types, channels, emitter, and sink that the runtime uses
|
|
4
|
+
to expose stage lifecycle, model deltas, tool calls, policy decisions, and
|
|
5
|
+
run-level outcomes as an observable async stream.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from collections.abc import Awaitable, Callable, Mapping
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from datetime import UTC, datetime
|
|
13
|
+
from typing import Any, Literal
|
|
14
|
+
|
|
15
|
+
WorkflowEventKind = Literal[
|
|
16
|
+
"run_started",
|
|
17
|
+
"stage_started",
|
|
18
|
+
"model_delta",
|
|
19
|
+
"model_completed",
|
|
20
|
+
"tool_call_started",
|
|
21
|
+
"tool_call_completed",
|
|
22
|
+
"tool_call_failed",
|
|
23
|
+
"policy_checked",
|
|
24
|
+
"policy_denied",
|
|
25
|
+
"transition_selected",
|
|
26
|
+
"stage_completed",
|
|
27
|
+
"run_completed",
|
|
28
|
+
"run_failed",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
WorkflowEventChannel = Literal[
|
|
32
|
+
"assistant",
|
|
33
|
+
"progress",
|
|
34
|
+
"reasoning_summary",
|
|
35
|
+
"debug",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=True)
|
|
40
|
+
class WorkflowEvent:
|
|
41
|
+
kind: WorkflowEventKind
|
|
42
|
+
run_id: str
|
|
43
|
+
sequence: int
|
|
44
|
+
timestamp: datetime
|
|
45
|
+
stage_id: str | None = None
|
|
46
|
+
channel: WorkflowEventChannel | None = None
|
|
47
|
+
text: str | None = None
|
|
48
|
+
capability: str | None = None
|
|
49
|
+
tool_name: str | None = None
|
|
50
|
+
args: Mapping[str, Any] | None = None
|
|
51
|
+
output: Mapping[str, Any] | None = None
|
|
52
|
+
result: Any = None
|
|
53
|
+
error: str | None = None
|
|
54
|
+
transition_to: str | None = None
|
|
55
|
+
metadata: Mapping[str, Any] = field(default_factory=dict) # type: ignore[reportUnknownVariableType]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
WorkflowEventSink = Callable[[WorkflowEvent], Awaitable[None]]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class WorkflowEventEmitter:
|
|
62
|
+
"""Per-run event emitter with monotonic sequencing.
|
|
63
|
+
|
|
64
|
+
The emitter is cheap when the sink is ``None``: ``emit()`` still
|
|
65
|
+
constructs and returns the event (for tests), but no I/O happens.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
def __init__(self, *, run_id: str, sink: WorkflowEventSink | None = None) -> None:
|
|
69
|
+
self._run_id = run_id
|
|
70
|
+
self._sink = sink
|
|
71
|
+
self._seq = 0
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def run_id(self) -> str:
|
|
75
|
+
return self._run_id
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def sequence(self) -> int:
|
|
79
|
+
return self._seq
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def has_sink(self) -> bool:
|
|
83
|
+
"""True when an actual consumer is attached.
|
|
84
|
+
|
|
85
|
+
Use this to decide whether to activate provider-side streaming:
|
|
86
|
+
only stream when someone is listening.
|
|
87
|
+
"""
|
|
88
|
+
return self._sink is not None
|
|
89
|
+
|
|
90
|
+
async def emit(
|
|
91
|
+
self,
|
|
92
|
+
kind: WorkflowEventKind,
|
|
93
|
+
**kwargs: Any,
|
|
94
|
+
) -> WorkflowEvent:
|
|
95
|
+
self._seq += 1
|
|
96
|
+
event = WorkflowEvent(
|
|
97
|
+
kind=kind,
|
|
98
|
+
run_id=self._run_id,
|
|
99
|
+
sequence=self._seq,
|
|
100
|
+
timestamp=datetime.now(tz=UTC),
|
|
101
|
+
**kwargs,
|
|
102
|
+
)
|
|
103
|
+
if self._sink is not None:
|
|
104
|
+
await self._sink(event)
|
|
105
|
+
return event
|