state-mesh 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.
- state_mesh-0.1.0/.gitignore +206 -0
- state_mesh-0.1.0/PKG-INFO +88 -0
- state_mesh-0.1.0/README.md +50 -0
- state_mesh-0.1.0/pyproject.toml +52 -0
- state_mesh-0.1.0/requirements.txt +19 -0
- state_mesh-0.1.0/state_mesh/__init__.py +26 -0
- state_mesh-0.1.0/state_mesh/core/__init__.py +0 -0
- state_mesh-0.1.0/state_mesh/core/context.py +122 -0
- state_mesh-0.1.0/state_mesh/core/human_input.py +15 -0
- state_mesh-0.1.0/state_mesh/core/parallel.py +23 -0
- state_mesh-0.1.0/state_mesh/core/pipeline.py +178 -0
- state_mesh-0.1.0/state_mesh/core/step.py +116 -0
- state_mesh-0.1.0/state_mesh/guardrails/__init__.py +0 -0
- state_mesh-0.1.0/state_mesh/guardrails/base.py +20 -0
- state_mesh-0.1.0/state_mesh/guardrails/content.py +91 -0
- state_mesh-0.1.0/state_mesh/guardrails/runner.py +19 -0
- state_mesh-0.1.0/state_mesh/guardrails/schema.py +20 -0
- state_mesh-0.1.0/state_mesh/integrations/__init__.py +0 -0
- state_mesh-0.1.0/state_mesh/integrations/fastapi.py +111 -0
- state_mesh-0.1.0/state_mesh/mcp/__init__.py +0 -0
- state_mesh-0.1.0/state_mesh/mcp/bus.py +25 -0
- state_mesh-0.1.0/state_mesh/mcp/client.py +47 -0
- state_mesh-0.1.0/state_mesh/mcp/registry.py +26 -0
- state_mesh-0.1.0/state_mesh/mcp/server_manager.py +59 -0
- state_mesh-0.1.0/state_mesh/observability/__init__.py +0 -0
- state_mesh-0.1.0/state_mesh/observability/events.py +29 -0
- state_mesh-0.1.0/state_mesh/observability/flags.py +11 -0
- state_mesh-0.1.0/state_mesh/observability/tracer.py +21 -0
- state_mesh-0.1.0/state_mesh/output/__init__.py +0 -0
- state_mesh-0.1.0/state_mesh/output/contract.py +14 -0
- state_mesh-0.1.0/state_mesh/output/parser.py +91 -0
- state_mesh-0.1.0/state_mesh/output/retry.py +38 -0
- state_mesh-0.1.0/state_mesh/state/__init__.py +0 -0
- state_mesh-0.1.0/state_mesh/state/base.py +19 -0
- state_mesh-0.1.0/state_mesh/state/memory.py +19 -0
- state_mesh-0.1.0/state_mesh/state/redis_backend.py +31 -0
- state_mesh-0.1.0/tests/__init__.py +0 -0
- state_mesh-0.1.0/tests/unit/test_context.py +77 -0
- state_mesh-0.1.0/tests/unit/test_fastapi.py +187 -0
- state_mesh-0.1.0/tests/unit/test_guardrails.py +296 -0
- state_mesh-0.1.0/tests/unit/test_human_input.py +192 -0
- state_mesh-0.1.0/tests/unit/test_mcp.py +142 -0
- state_mesh-0.1.0/tests/unit/test_output.py +227 -0
- state_mesh-0.1.0/tests/unit/test_parallel.py +103 -0
- state_mesh-0.1.0/tests/unit/test_pipeline.py +135 -0
- state_mesh-0.1.0/tests/unit/test_resume.py +213 -0
- state_mesh-0.1.0/tests/unit/test_server_manager.py +179 -0
- state_mesh-0.1.0/tests/unit/test_sse.py +217 -0
- state_mesh-0.1.0/tests/unit/test_state.py +104 -0
- state_mesh-0.1.0/tests/unit/test_step.py +102 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# Created by https://www.toptal.com/developers/gitignore/api/python,react,rust
|
|
2
|
+
# Edit at https://www.toptal.com/developers/gitignore?templates=python,react,rust
|
|
3
|
+
|
|
4
|
+
### Python ###
|
|
5
|
+
# Byte-compiled / optimized / DLL files
|
|
6
|
+
__pycache__/
|
|
7
|
+
*.py[cod]
|
|
8
|
+
*$py.class
|
|
9
|
+
|
|
10
|
+
# C extensions
|
|
11
|
+
*.so
|
|
12
|
+
|
|
13
|
+
# Distribution / packaging
|
|
14
|
+
.Python
|
|
15
|
+
build/
|
|
16
|
+
develop-eggs/
|
|
17
|
+
dist/
|
|
18
|
+
downloads/
|
|
19
|
+
eggs/
|
|
20
|
+
.eggs/
|
|
21
|
+
lib/
|
|
22
|
+
lib64/
|
|
23
|
+
parts/
|
|
24
|
+
sdist/
|
|
25
|
+
var/
|
|
26
|
+
wheels/
|
|
27
|
+
share/python-wheels/
|
|
28
|
+
*.egg-info/
|
|
29
|
+
.installed.cfg
|
|
30
|
+
*.egg
|
|
31
|
+
MANIFEST
|
|
32
|
+
|
|
33
|
+
# PyInstaller
|
|
34
|
+
# Usually these files are written by a python script from a template
|
|
35
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
36
|
+
*.manifest
|
|
37
|
+
*.spec
|
|
38
|
+
|
|
39
|
+
# Installer logs
|
|
40
|
+
pip-log.txt
|
|
41
|
+
pip-delete-this-directory.txt
|
|
42
|
+
|
|
43
|
+
# Unit test / coverage reports
|
|
44
|
+
htmlcov/
|
|
45
|
+
.tox/
|
|
46
|
+
.nox/
|
|
47
|
+
.coverage
|
|
48
|
+
.coverage.*
|
|
49
|
+
.cache
|
|
50
|
+
nosetests.xml
|
|
51
|
+
coverage.xml
|
|
52
|
+
*.cover
|
|
53
|
+
*.py,cover
|
|
54
|
+
.hypothesis/
|
|
55
|
+
.pytest_cache/
|
|
56
|
+
cover/
|
|
57
|
+
|
|
58
|
+
# Translations
|
|
59
|
+
*.mo
|
|
60
|
+
*.pot
|
|
61
|
+
|
|
62
|
+
# Django stuff:
|
|
63
|
+
*.log
|
|
64
|
+
local_settings.py
|
|
65
|
+
db.sqlite3
|
|
66
|
+
db.sqlite3-journal
|
|
67
|
+
|
|
68
|
+
# Flask stuff:
|
|
69
|
+
instance/
|
|
70
|
+
.webassets-cache
|
|
71
|
+
|
|
72
|
+
# Scrapy stuff:
|
|
73
|
+
.scrapy
|
|
74
|
+
|
|
75
|
+
# Sphinx documentation
|
|
76
|
+
docs/_build/
|
|
77
|
+
|
|
78
|
+
# PyBuilder
|
|
79
|
+
.pybuilder/
|
|
80
|
+
target/
|
|
81
|
+
|
|
82
|
+
# Jupyter Notebook
|
|
83
|
+
.ipynb_checkpoints
|
|
84
|
+
|
|
85
|
+
# IPython
|
|
86
|
+
profile_default/
|
|
87
|
+
ipython_config.py
|
|
88
|
+
|
|
89
|
+
# pyenv
|
|
90
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
91
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
92
|
+
# .python-version
|
|
93
|
+
|
|
94
|
+
# pipenv
|
|
95
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
96
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
97
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
98
|
+
# install all needed dependencies.
|
|
99
|
+
#Pipfile.lock
|
|
100
|
+
|
|
101
|
+
# poetry
|
|
102
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
103
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
104
|
+
# commonly ignored for libraries.
|
|
105
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
106
|
+
#poetry.lock
|
|
107
|
+
|
|
108
|
+
# pdm
|
|
109
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
110
|
+
#pdm.lock
|
|
111
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
112
|
+
# in version control.
|
|
113
|
+
# https://pdm.fming.dev/#use-with-ide
|
|
114
|
+
.pdm.toml
|
|
115
|
+
|
|
116
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
117
|
+
__pypackages__/
|
|
118
|
+
|
|
119
|
+
# Celery stuff
|
|
120
|
+
celerybeat-schedule
|
|
121
|
+
celerybeat.pid
|
|
122
|
+
|
|
123
|
+
# SageMath parsed files
|
|
124
|
+
*.sage.py
|
|
125
|
+
|
|
126
|
+
# Environments
|
|
127
|
+
.env
|
|
128
|
+
.venv
|
|
129
|
+
env/
|
|
130
|
+
venv/
|
|
131
|
+
ENV/
|
|
132
|
+
env.bak/
|
|
133
|
+
venv.bak/
|
|
134
|
+
|
|
135
|
+
# Spyder project settings
|
|
136
|
+
.spyderproject
|
|
137
|
+
.spyproject
|
|
138
|
+
|
|
139
|
+
# Rope project settings
|
|
140
|
+
.ropeproject
|
|
141
|
+
|
|
142
|
+
# mkdocs documentation
|
|
143
|
+
/site
|
|
144
|
+
|
|
145
|
+
# mypy
|
|
146
|
+
.mypy_cache/
|
|
147
|
+
.dmypy.json
|
|
148
|
+
dmypy.json
|
|
149
|
+
|
|
150
|
+
# Pyre type checker
|
|
151
|
+
.pyre/
|
|
152
|
+
|
|
153
|
+
# pytype static type analyzer
|
|
154
|
+
.pytype/
|
|
155
|
+
|
|
156
|
+
# Cython debug symbols
|
|
157
|
+
cython_debug/
|
|
158
|
+
|
|
159
|
+
# PyCharm
|
|
160
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
161
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
162
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
163
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
164
|
+
#.idea/
|
|
165
|
+
|
|
166
|
+
### Python Patch ###
|
|
167
|
+
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
|
|
168
|
+
poetry.toml
|
|
169
|
+
|
|
170
|
+
# ruff
|
|
171
|
+
.ruff_cache/
|
|
172
|
+
|
|
173
|
+
# LSP config files
|
|
174
|
+
pyrightconfig.json
|
|
175
|
+
|
|
176
|
+
### react ###
|
|
177
|
+
.DS_*
|
|
178
|
+
logs
|
|
179
|
+
**/*.backup.*
|
|
180
|
+
**/*.back.*
|
|
181
|
+
|
|
182
|
+
node_modules
|
|
183
|
+
bower_components
|
|
184
|
+
|
|
185
|
+
*.sublime*
|
|
186
|
+
|
|
187
|
+
psd
|
|
188
|
+
thumb
|
|
189
|
+
sketch
|
|
190
|
+
|
|
191
|
+
### Rust ###
|
|
192
|
+
# Generated by Cargo
|
|
193
|
+
# will have compiled files and executables
|
|
194
|
+
debug/
|
|
195
|
+
|
|
196
|
+
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
|
197
|
+
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
|
198
|
+
Cargo.lock
|
|
199
|
+
|
|
200
|
+
# These are backup files generated by rustfmt
|
|
201
|
+
**/*.rs.bk
|
|
202
|
+
|
|
203
|
+
# MSVC Windows builds of rustc generate these, which store debugging information
|
|
204
|
+
*.pdb
|
|
205
|
+
|
|
206
|
+
# End of https://www.toptal.com/developers/gitignore/api/python,react,rust
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: state-mesh
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Async-native agentic pipeline runtime for Python backend engineers
|
|
5
|
+
Project-URL: Homepage, https://github.com/statemesh-123/state-mesh-agent-runtime
|
|
6
|
+
Project-URL: Repository, https://github.com/statemesh-123/state-mesh-agent-runtime
|
|
7
|
+
Project-URL: Issues, https://github.com/statemesh-123/state-mesh-agent-runtime/issues
|
|
8
|
+
Author: Meghala
|
|
9
|
+
Author-email: Jayaprabha <statemesh@gmail.com>
|
|
10
|
+
License: MIT
|
|
11
|
+
Keywords: agent,async,llm,pipeline,runtime
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Requires-Dist: anyio>=4.0
|
|
20
|
+
Requires-Dist: fastapi>=0.100
|
|
21
|
+
Requires-Dist: mcp>=1.0
|
|
22
|
+
Requires-Dist: pydantic>=2.0
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: httpx; extra == 'dev'
|
|
25
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
28
|
+
Provides-Extra: otel
|
|
29
|
+
Requires-Dist: opentelemetry-api>=1.20; extra == 'otel'
|
|
30
|
+
Requires-Dist: opentelemetry-sdk>=1.20; extra == 'otel'
|
|
31
|
+
Provides-Extra: postgres
|
|
32
|
+
Requires-Dist: asyncpg>=0.29; extra == 'postgres'
|
|
33
|
+
Provides-Extra: redis
|
|
34
|
+
Requires-Dist: redis>=5.0; extra == 'redis'
|
|
35
|
+
Provides-Extra: toxicity
|
|
36
|
+
Requires-Dist: detoxify>=0.5; extra == 'toxicity'
|
|
37
|
+
Description-Content-Type: text/markdown
|
|
38
|
+
|
|
39
|
+
# State-Mesh
|
|
40
|
+
|
|
41
|
+
Async-native agentic pipeline runtime for Python backend engineers.
|
|
42
|
+
|
|
43
|
+
## Why State-Mesh
|
|
44
|
+
|
|
45
|
+
- **Typed state** — Pydantic models flow through every step, no silent dict mutations
|
|
46
|
+
- **Built-in MCP** — register MCP servers once, every step can call any tool
|
|
47
|
+
- **Production-ready** — guardrails, structured output retry, OTel tracing, pipeline resumption out of the box
|
|
48
|
+
|
|
49
|
+
## Features
|
|
50
|
+
|
|
51
|
+
- Async-first step execution with retry, timeout, and branching
|
|
52
|
+
- Parallel step execution with `asyncio.gather`
|
|
53
|
+
- Structured output contracts with automatic LLM retry
|
|
54
|
+
- Guard chain — schema, PII, confidence, toxicity guards built in
|
|
55
|
+
- MCP tool bus (SSE + stdio transport)
|
|
56
|
+
- OpenTelemetry tracing with swappable exporters
|
|
57
|
+
- Redis state backend with pipeline resumption
|
|
58
|
+
- FastAPI integration — `mount_pipeline`, `stream_pipeline`, `pipeline_router`
|
|
59
|
+
- Human-in-the-loop with timeout and fallback
|
|
60
|
+
|
|
61
|
+
## Install
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install state-mesh
|
|
65
|
+
pip install "state-mesh[redis]" # Redis backend
|
|
66
|
+
pip install "state-mesh[otel]" # OpenTelemetry
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Quickstart
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from state_mesh import step, Pipeline, Context, OutputContract
|
|
73
|
+
from pydantic import BaseModel
|
|
74
|
+
|
|
75
|
+
class Result(BaseModel):
|
|
76
|
+
summary: str
|
|
77
|
+
|
|
78
|
+
@step(output_contract=OutputContract(target_schema=Result))
|
|
79
|
+
async def summarise(prompt: str) -> str:
|
|
80
|
+
return await my_llm(prompt)
|
|
81
|
+
|
|
82
|
+
pipeline = Pipeline(steps=[summarise], name="summarise")
|
|
83
|
+
result = await pipeline.run(Context(state={}))
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
MIT
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# State-Mesh
|
|
2
|
+
|
|
3
|
+
Async-native agentic pipeline runtime for Python backend engineers.
|
|
4
|
+
|
|
5
|
+
## Why State-Mesh
|
|
6
|
+
|
|
7
|
+
- **Typed state** — Pydantic models flow through every step, no silent dict mutations
|
|
8
|
+
- **Built-in MCP** — register MCP servers once, every step can call any tool
|
|
9
|
+
- **Production-ready** — guardrails, structured output retry, OTel tracing, pipeline resumption out of the box
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- Async-first step execution with retry, timeout, and branching
|
|
14
|
+
- Parallel step execution with `asyncio.gather`
|
|
15
|
+
- Structured output contracts with automatic LLM retry
|
|
16
|
+
- Guard chain — schema, PII, confidence, toxicity guards built in
|
|
17
|
+
- MCP tool bus (SSE + stdio transport)
|
|
18
|
+
- OpenTelemetry tracing with swappable exporters
|
|
19
|
+
- Redis state backend with pipeline resumption
|
|
20
|
+
- FastAPI integration — `mount_pipeline`, `stream_pipeline`, `pipeline_router`
|
|
21
|
+
- Human-in-the-loop with timeout and fallback
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install state-mesh
|
|
27
|
+
pip install "state-mesh[redis]" # Redis backend
|
|
28
|
+
pip install "state-mesh[otel]" # OpenTelemetry
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quickstart
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from state_mesh import step, Pipeline, Context, OutputContract
|
|
35
|
+
from pydantic import BaseModel
|
|
36
|
+
|
|
37
|
+
class Result(BaseModel):
|
|
38
|
+
summary: str
|
|
39
|
+
|
|
40
|
+
@step(output_contract=OutputContract(target_schema=Result))
|
|
41
|
+
async def summarise(prompt: str) -> str:
|
|
42
|
+
return await my_llm(prompt)
|
|
43
|
+
|
|
44
|
+
pipeline = Pipeline(steps=[summarise], name="summarise")
|
|
45
|
+
result = await pipeline.run(Context(state={}))
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## License
|
|
49
|
+
|
|
50
|
+
MIT
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "state-mesh"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Async-native agentic pipeline runtime for Python backend engineers"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.11"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Jayaprabha", email = "statemesh@gmail.com" },
|
|
14
|
+
{ name = "Meghala" },
|
|
15
|
+
]
|
|
16
|
+
keywords = ["agent", "pipeline", "llm", "async", "runtime"]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 3 - Alpha",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"License :: OSI Approved :: MIT License",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Topic :: Software Development :: Libraries",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
dependencies = [
|
|
27
|
+
"pydantic>=2.0",
|
|
28
|
+
"anyio>=4.0",
|
|
29
|
+
"fastapi>=0.100",
|
|
30
|
+
"mcp>=1.0",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://github.com/statemesh-123/state-mesh-agent-runtime"
|
|
35
|
+
Repository = "https://github.com/statemesh-123/state-mesh-agent-runtime"
|
|
36
|
+
Issues = "https://github.com/statemesh-123/state-mesh-agent-runtime/issues"
|
|
37
|
+
|
|
38
|
+
[project.optional-dependencies]
|
|
39
|
+
redis = ["redis>=5.0"]
|
|
40
|
+
postgres = ["asyncpg>=0.29"]
|
|
41
|
+
otel = ["opentelemetry-api>=1.20", "opentelemetry-sdk>=1.20"]
|
|
42
|
+
toxicity = ["detoxify>=0.5"]
|
|
43
|
+
dev = ["pytest>=8.0", "pytest-asyncio>=0.23", "ruff", "httpx"]
|
|
44
|
+
|
|
45
|
+
[tool.hatch.build.targets.wheel]
|
|
46
|
+
packages = ["state_mesh"]
|
|
47
|
+
|
|
48
|
+
[tool.pytest.ini_options]
|
|
49
|
+
asyncio_mode = "auto"
|
|
50
|
+
|
|
51
|
+
[tool.ruff]
|
|
52
|
+
line-length = 88
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
annotated-types==0.7.0
|
|
2
|
+
colorama==0.4.6
|
|
3
|
+
iniconfig==2.3.0
|
|
4
|
+
packaging==26.2
|
|
5
|
+
pluggy==1.6.0
|
|
6
|
+
pydantic==2.13.4
|
|
7
|
+
pydantic_core==2.46.4
|
|
8
|
+
Pygments==2.20.0
|
|
9
|
+
pytest==9.1.0
|
|
10
|
+
typing-inspection==0.4.2
|
|
11
|
+
typing_extensions==4.15.0
|
|
12
|
+
pytest-asyncio
|
|
13
|
+
opentelemetry-api
|
|
14
|
+
opentelemetry-sdk
|
|
15
|
+
fastapi
|
|
16
|
+
mcp
|
|
17
|
+
redis
|
|
18
|
+
detoxify
|
|
19
|
+
httpx2
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from state_mesh.core.context import Context, Flag
|
|
2
|
+
from state_mesh.core.step import Step, Branch, RetryConfig, step
|
|
3
|
+
from state_mesh.core.pipeline import Pipeline, PipelineResult
|
|
4
|
+
from state_mesh.guardrails.base import Guard, GuardResult
|
|
5
|
+
from state_mesh.guardrails.schema import SchemaGuard
|
|
6
|
+
from state_mesh.guardrails.content import PIIGuard
|
|
7
|
+
from state_mesh.output.contract import OutputContract
|
|
8
|
+
from state_mesh.mcp.server_manager import MCPServerConfig, MCPServerManager
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"step",
|
|
12
|
+
"Step",
|
|
13
|
+
"Pipeline",
|
|
14
|
+
"Context",
|
|
15
|
+
"RetryConfig",
|
|
16
|
+
"Branch",
|
|
17
|
+
"PipelineResult",
|
|
18
|
+
"Guard",
|
|
19
|
+
"GuardResult",
|
|
20
|
+
"SchemaGuard",
|
|
21
|
+
"PIIGuard",
|
|
22
|
+
"OutputContract",
|
|
23
|
+
"Flag",
|
|
24
|
+
"MCPServerConfig",
|
|
25
|
+
"MCPServerManager",
|
|
26
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import uuid
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from typing import Any, Generic, TypeVar, Literal, TYPE_CHECKING
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from bus import MCPBus
|
|
10
|
+
|
|
11
|
+
StateSchema = TypeVar("StateSchema", bound=BaseModel)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Flag(BaseModel):
|
|
15
|
+
step_name:str
|
|
16
|
+
severity: Literal["info","warn","error"]
|
|
17
|
+
timestamp:datetime=Field(default_factory=lambda:datetime.now(timezone.utc))
|
|
18
|
+
flag_type: str
|
|
19
|
+
reason:str
|
|
20
|
+
payload:dict[str,Any]=Field(default_factory=dict)
|
|
21
|
+
|
|
22
|
+
class ContextMutationError(Exception):
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
class Context(Generic[StateSchema]):
|
|
26
|
+
"""
|
|
27
|
+
Context is the main object that is passed to all steps.
|
|
28
|
+
It contains the state, run_id, trace_id, pipeline_name and flags.
|
|
29
|
+
"""
|
|
30
|
+
def __init__(self,state,run_id=None,trace_id=None,pipeline_name="unnamed",tools:MCPBus|None=None):
|
|
31
|
+
self._state = state
|
|
32
|
+
self._run_id = run_id or str(uuid.uuid4())
|
|
33
|
+
self._trace_id = trace_id or str(uuid.uuid4())
|
|
34
|
+
self._pipeline_name = pipeline_name
|
|
35
|
+
self._flags:list[Flag] = []
|
|
36
|
+
self._extras:dict[str,Any]={}
|
|
37
|
+
self._tools=tools
|
|
38
|
+
self._step_name=None
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def state(self) -> StateSchema:
|
|
42
|
+
return self._state
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def tools(self) -> MCPBus|None:
|
|
46
|
+
return self._tools
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def run_id(self) -> str:
|
|
50
|
+
return self._run_id
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def trace_id(self) -> str:
|
|
54
|
+
return self._trace_id
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def pipeline_name(self) -> str:
|
|
58
|
+
return self._pipeline_name
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def flags(self) -> list[Flag]:
|
|
62
|
+
flag=self._flags.copy() # sp the user wont be able directly modify this
|
|
63
|
+
return flag
|
|
64
|
+
|
|
65
|
+
def set(self,key:str,value:Any)->None:
|
|
66
|
+
if key in self._extras:
|
|
67
|
+
raise ContextMutationError(f"Key {key} already exists in extras")
|
|
68
|
+
self._extras[key]=value
|
|
69
|
+
|
|
70
|
+
def get(self,key:str,default:Any=None)->Any:
|
|
71
|
+
return self._extras.get(key,default)
|
|
72
|
+
|
|
73
|
+
def emit_flag(self,flag_type:str,reason:str,severity:Literal["info","warn","error"]="warn",payload:dict[str,Any]|None=None)->None:
|
|
74
|
+
self._flags.append(Flag(
|
|
75
|
+
step_name=self._step_name,
|
|
76
|
+
severity=severity,
|
|
77
|
+
flag_type=flag_type,
|
|
78
|
+
reason=reason,
|
|
79
|
+
payload=payload or {}
|
|
80
|
+
))
|
|
81
|
+
|
|
82
|
+
def _set_current_step(self, step_name: str) -> None:
|
|
83
|
+
self._step_name = step_name
|
|
84
|
+
|
|
85
|
+
def _replace_state(self, new_state: StateSchema) -> None:
|
|
86
|
+
self._state = new_state
|
|
87
|
+
|
|
88
|
+
#serialize the context for pipeline resumption
|
|
89
|
+
def to_dict(self) -> dict[str, Any]:
|
|
90
|
+
return {
|
|
91
|
+
"state": self._state.model_dump() if isinstance(self._state, BaseModel) else self._state,
|
|
92
|
+
"run_id": self._run_id,
|
|
93
|
+
"trace_id": self._trace_id,
|
|
94
|
+
"pipeline_name": self._pipeline_name,
|
|
95
|
+
"extras": self._extras,
|
|
96
|
+
"flags": [f.model_dump() for f in self._flags],
|
|
97
|
+
"last_completed_step": self._step_name,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@classmethod
|
|
101
|
+
def from_dict(cls, data: dict[str, Any], state_schema: type[BaseModel]) -> Context:
|
|
102
|
+
ctx = cls(
|
|
103
|
+
state=state_schema(**data["state"]),
|
|
104
|
+
run_id=data["run_id"],
|
|
105
|
+
trace_id=data["trace_id"],
|
|
106
|
+
pipeline_name=data["pipeline_name"],
|
|
107
|
+
)
|
|
108
|
+
ctx._extras = data["extras"]
|
|
109
|
+
ctx._flags = [Flag(**f) for f in data["flags"]]
|
|
110
|
+
ctx._step_name = data.get("last_completed_step")
|
|
111
|
+
return ctx
|
|
112
|
+
|
|
113
|
+
def __repr__(self) -> str:
|
|
114
|
+
return (
|
|
115
|
+
f"Context(run_id={self._run_id!r}, "
|
|
116
|
+
f"step={self._step_name!r}, "
|
|
117
|
+
f"flags={len(self._flags)})"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class HumanInput:
|
|
5
|
+
def __init__(
|
|
6
|
+
self,
|
|
7
|
+
prompt: str,
|
|
8
|
+
response_key: str,
|
|
9
|
+
timeout_seconds: float = 300.0,
|
|
10
|
+
fallback_step: str | None = None,
|
|
11
|
+
):
|
|
12
|
+
self.prompt = prompt
|
|
13
|
+
self.response_key = response_key
|
|
14
|
+
self.timeout_seconds = timeout_seconds
|
|
15
|
+
self.fallback_step = fallback_step
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
5
|
+
from state_mesh.core.context import Context
|
|
6
|
+
from state_mesh.core.step import Step, StepResult
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Parallel:
|
|
10
|
+
def __init__(self, steps: list[Step], name: str = "parallel"):
|
|
11
|
+
self.name = name
|
|
12
|
+
self.steps = steps
|
|
13
|
+
|
|
14
|
+
async def execute(self, ctx: Context) -> list[StepResult]:
|
|
15
|
+
results: list[StepResult] = list(
|
|
16
|
+
await asyncio.gather(*[step.execute(ctx) for step in self.steps], return_exceptions=True)
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
for step, result in zip(self.steps, results):
|
|
20
|
+
if not isinstance(result, BaseException) and result.status == "success":
|
|
21
|
+
ctx.set(step.name, result.output)
|
|
22
|
+
|
|
23
|
+
return results
|