model-compose 0.1.0__tar.gz → 0.1.2__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.
- {model_compose-0.1.0 → model_compose-0.1.2}/PKG-INFO +20 -28
- {model_compose-0.1.0 → model_compose-0.1.2}/README.md +19 -27
- {model_compose-0.1.0 → model_compose-0.1.2}/pyproject.toml +1 -1
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/component/engine/http_client.py +11 -13
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/controller/engine/http_server.py +2 -1
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/controller/webui/gradio.py +53 -19
- model_compose-0.1.2/src/mindor/core/controller/webui/schema.py +198 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/controller/webui/webui.py +2 -1
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/listener/engine/http_callback.py +4 -3
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/utils/http_client.py +3 -3
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/utils/template.py +3 -3
- model_compose-0.1.2/src/mindor/core/workflow/job.py +37 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/workflow/workflow.py +2 -2
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/workflow.py +18 -6
- {model_compose-0.1.0 → model_compose-0.1.2}/src/model_compose.egg-info/PKG-INFO +20 -28
- model_compose-0.1.0/src/mindor/core/controller/webui/schema.py +0 -135
- model_compose-0.1.0/src/mindor/core/workflow/job.py +0 -27
- {model_compose-0.1.0 → model_compose-0.1.2}/LICENSE +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/setup.cfg +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/__init__.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/cli/__init__.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/cli/compose.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/__init__.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/component/__init__.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/component/component.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/component/engine/__init__.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/component/engine/base.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/component/engine/context.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/component/engine/http_server.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/compose/__init__.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/compose/compose.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/compose/manager.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/controller/__init__.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/controller/controller.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/controller/engine/__init__.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/controller/engine/base.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/controller/engine/mcp_server.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/controller/runner/__init__.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/controller/runner/client.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/controller/runner/http_client.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/controller/runner/mcp_client.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/controller/runner/runner.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/controller/webui/__init__.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/gateway/__init__.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/gateway/engine/__init__.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/gateway/engine/base.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/gateway/engine/http_tunnel.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/gateway/gateway.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/listener/__init__.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/listener/engine/__init__.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/listener/engine/base.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/listener/listener.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/runtime/__init__.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/runtime/env.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/services/__init__.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/services/async_service.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/utils/__init__.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/utils/expiring.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/utils/http_request.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/utils/image.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/utils/streaming.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/utils/time.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/utils/workqueue.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/workflow/__init__.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/workflow/context.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/__init__.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/loader.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/__init__.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/action/__init__.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/action/action.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/action/impl/__init__.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/action/impl/common.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/action/impl/http_client.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/action/impl/http_server.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/action/impl/mcp_client.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/action/impl/mcp_server.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/component/__init__.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/component/component.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/component/impl/__init__.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/component/impl/common.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/component/impl/http_client.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/component/impl/http_server.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/component/impl/mcp_client.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/component/impl/mcp_server.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/component/impl/types.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/compose.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/controller/__init__.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/controller/controller.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/controller/impl/__init__.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/controller/impl/common.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/controller/impl/http_server.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/controller/impl/mcp_server.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/controller/impl/types.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/controller/impl/webui.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/gateway/__init__.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/gateway/gateway.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/gateway/impl/__init__.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/gateway/impl/common.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/gateway/impl/http_tunnel.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/gateway/impl/types.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/listener/__init__.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/listener/impl/__init__.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/listener/impl/common.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/listener/impl/http_callback.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/listener/impl/types.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/listener/listener.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/runtime/__init__.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/runtime/impl/__init__.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/runtime/impl/common.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/runtime/impl/docker.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/runtime/impl/types.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/dsl/schema/runtime/runtime.py +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/model_compose.egg-info/SOURCES.txt +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/model_compose.egg-info/dependency_links.txt +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/model_compose.egg-info/entry_points.txt +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/model_compose.egg-info/requires.txt +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/src/model_compose.egg-info/top_level.txt +0 -0
- {model_compose-0.1.0 → model_compose-0.1.2}/tests/test_cli.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: model-compose
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: model-compose: Declarative AI Model and Workflow Orchestrator (part of the Mindor project)
|
|
5
5
|
Author-email: Hanyeol Cho <hanyeol.cho@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -21,41 +21,28 @@ Requires-Dist: gradio
|
|
|
21
21
|
Requires-Dist: Pillow
|
|
22
22
|
Dynamic: license-file
|
|
23
23
|
|
|
24
|
-
# model-compose: Declarative AI Model and Workflow Orchestrator
|
|
24
|
+
# model-compose: Declarative AI Model and Workflow Orchestrator
|
|
25
25
|
|
|
26
26
|
[](https://python.org)
|
|
27
27
|
[](https://opensource.org/licenses/MIT)
|
|
28
28
|
[](http://makeapullrequest.com)
|
|
29
29
|
|
|
30
|
+

|
|
31
|
+
|
|
30
32
|
**model-compose** is an open-source, declarative workflow orchestrator inspired by `docker-compose`. It lets you define and run AI model pipelines using simple YAML files — no custom code required. Effortlessly connect external AI services or run local AI models, all within powerful, composable workflows.
|
|
31
33
|
|
|
32
34
|
---
|
|
33
35
|
|
|
34
36
|
## ✨ Features
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
#### 🛠️ Multi-Workflow Support
|
|
46
|
-
Define multiple named workflows in one project. Run them by name or set a default for quick execution.
|
|
47
|
-
|
|
48
|
-
#### 🧩 Modular Components
|
|
49
|
-
Break down logic into reusable components and jobs. Easily plug, swap, and extend them across workflows.
|
|
50
|
-
|
|
51
|
-
#### ⚙️ Flexible I/O Routing
|
|
52
|
-
Connect inputs and outputs between jobs using clean, scoped variables - no glue code needed.
|
|
53
|
-
|
|
54
|
-
#### 🧪 Run Locally, Serve Remotely
|
|
55
|
-
Execute workflows from the CLI or expose them as HTTP or MCP endpoints with an optional Web UI.
|
|
56
|
-
|
|
57
|
-
#### 📦 Works with Environment Variables
|
|
58
|
-
Easily inject secrets and configuration via `.env` files or environment variables to keep your YAML clean and secure.
|
|
38
|
+
- **Declarative by Design:** Define complete AI workflows using simple YAML files—no complex scripting required.
|
|
39
|
+
- **Compose Anything:** Combine multiple AI models, APIs, and tools into a single, unified pipeline.
|
|
40
|
+
- **Built for Orchestration:** Orchestrate multi-step model interactions with ease. Transform individual API calls into maintainable, end-to-end systems.
|
|
41
|
+
- **Multi-Workflow Support:** Define multiple named workflows in one project. Run them by name or set a default for quick execution.
|
|
42
|
+
- **Modular Components:** Break down logic into reusable components and jobs. Easily plug, swap, and extend them across workflows.
|
|
43
|
+
- **Flexible I/O Routing:** Connect inputs and outputs between jobs using clean, scoped variables—no glue code needed.
|
|
44
|
+
- **Run Locally, Serve Remotely:** Execute workflows from the CLI or expose them as HTTP or MCP endpoints with an optional Web UI.
|
|
45
|
+
- **Environment Variable Support:** Easily inject secrets and configuration via `.env` files or environment variables to keep your YAML clean and secure.
|
|
59
46
|
|
|
60
47
|
---
|
|
61
48
|
|
|
@@ -214,14 +201,14 @@ listener:
|
|
|
214
201
|
port: 8090
|
|
215
202
|
base_path: /callbacks
|
|
216
203
|
callbacks:
|
|
217
|
-
- path: /
|
|
204
|
+
- path: /chat-ai
|
|
218
205
|
method: POST
|
|
219
206
|
item: ${body.data}
|
|
220
207
|
identify_by: ${item.task_id}
|
|
221
208
|
result: ${item.choices[0].message.content}
|
|
222
209
|
```
|
|
223
210
|
|
|
224
|
-
This listener sets up an HTTP callback endpoint at
|
|
211
|
+
This listener sets up an HTTP callback endpoint at http://localhost:8090/callbacks/chat-ai to handle asynchronous responses from an external service that behaves like ChatGPT but supports delayed or push-based results. This is useful when integrating with services that notify results via webhook-style callbacks.
|
|
225
212
|
|
|
226
213
|
#### 🌐 Gateway Example
|
|
227
214
|
|
|
@@ -234,7 +221,12 @@ gateway:
|
|
|
234
221
|
|
|
235
222
|
This gateway configuration exposes the local listener defined above to the public internet using an HTTP tunnel powered by ngrok. It forwards incoming traffic from a secure, public URL (e.g., https://abc123.ngrok.io) directly to your local callback endpoint at http://localhost:8090. This is essential when integrating with third-party services that need to push data back to your workflow via webhooks or asynchronous callbacks.
|
|
236
223
|
|
|
237
|
-
> 📁 For more example model-compose.yml configurations, check the
|
|
224
|
+
> 📁 For more example model-compose.yml configurations, check the [examples directory](examples) in the source code.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
## 🏗 Architecture
|
|
228
|
+
|
|
229
|
+

|
|
238
230
|
|
|
239
231
|
---
|
|
240
232
|
|
|
@@ -1,38 +1,25 @@
|
|
|
1
|
-
# model-compose: Declarative AI Model and Workflow Orchestrator
|
|
1
|
+
# model-compose: Declarative AI Model and Workflow Orchestrator
|
|
2
2
|
|
|
3
3
|
[](https://python.org)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
[](http://makeapullrequest.com)
|
|
6
6
|
|
|
7
|
+

|
|
8
|
+
|
|
7
9
|
**model-compose** is an open-source, declarative workflow orchestrator inspired by `docker-compose`. It lets you define and run AI model pipelines using simple YAML files — no custom code required. Effortlessly connect external AI services or run local AI models, all within powerful, composable workflows.
|
|
8
10
|
|
|
9
11
|
---
|
|
10
12
|
|
|
11
13
|
## ✨ Features
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
#### 🛠️ Multi-Workflow Support
|
|
23
|
-
Define multiple named workflows in one project. Run them by name or set a default for quick execution.
|
|
24
|
-
|
|
25
|
-
#### 🧩 Modular Components
|
|
26
|
-
Break down logic into reusable components and jobs. Easily plug, swap, and extend them across workflows.
|
|
27
|
-
|
|
28
|
-
#### ⚙️ Flexible I/O Routing
|
|
29
|
-
Connect inputs and outputs between jobs using clean, scoped variables - no glue code needed.
|
|
30
|
-
|
|
31
|
-
#### 🧪 Run Locally, Serve Remotely
|
|
32
|
-
Execute workflows from the CLI or expose them as HTTP or MCP endpoints with an optional Web UI.
|
|
33
|
-
|
|
34
|
-
#### 📦 Works with Environment Variables
|
|
35
|
-
Easily inject secrets and configuration via `.env` files or environment variables to keep your YAML clean and secure.
|
|
15
|
+
- **Declarative by Design:** Define complete AI workflows using simple YAML files—no complex scripting required.
|
|
16
|
+
- **Compose Anything:** Combine multiple AI models, APIs, and tools into a single, unified pipeline.
|
|
17
|
+
- **Built for Orchestration:** Orchestrate multi-step model interactions with ease. Transform individual API calls into maintainable, end-to-end systems.
|
|
18
|
+
- **Multi-Workflow Support:** Define multiple named workflows in one project. Run them by name or set a default for quick execution.
|
|
19
|
+
- **Modular Components:** Break down logic into reusable components and jobs. Easily plug, swap, and extend them across workflows.
|
|
20
|
+
- **Flexible I/O Routing:** Connect inputs and outputs between jobs using clean, scoped variables—no glue code needed.
|
|
21
|
+
- **Run Locally, Serve Remotely:** Execute workflows from the CLI or expose them as HTTP or MCP endpoints with an optional Web UI.
|
|
22
|
+
- **Environment Variable Support:** Easily inject secrets and configuration via `.env` files or environment variables to keep your YAML clean and secure.
|
|
36
23
|
|
|
37
24
|
---
|
|
38
25
|
|
|
@@ -191,14 +178,14 @@ listener:
|
|
|
191
178
|
port: 8090
|
|
192
179
|
base_path: /callbacks
|
|
193
180
|
callbacks:
|
|
194
|
-
- path: /
|
|
181
|
+
- path: /chat-ai
|
|
195
182
|
method: POST
|
|
196
183
|
item: ${body.data}
|
|
197
184
|
identify_by: ${item.task_id}
|
|
198
185
|
result: ${item.choices[0].message.content}
|
|
199
186
|
```
|
|
200
187
|
|
|
201
|
-
This listener sets up an HTTP callback endpoint at
|
|
188
|
+
This listener sets up an HTTP callback endpoint at http://localhost:8090/callbacks/chat-ai to handle asynchronous responses from an external service that behaves like ChatGPT but supports delayed or push-based results. This is useful when integrating with services that notify results via webhook-style callbacks.
|
|
202
189
|
|
|
203
190
|
#### 🌐 Gateway Example
|
|
204
191
|
|
|
@@ -211,7 +198,12 @@ gateway:
|
|
|
211
198
|
|
|
212
199
|
This gateway configuration exposes the local listener defined above to the public internet using an HTTP tunnel powered by ngrok. It forwards incoming traffic from a secure, public URL (e.g., https://abc123.ngrok.io) directly to your local callback endpoint at http://localhost:8090. This is essential when integrating with third-party services that need to push data back to your workflow via webhooks or asynchronous callbacks.
|
|
213
200
|
|
|
214
|
-
> 📁 For more example model-compose.yml configurations, check the
|
|
201
|
+
> 📁 For more example model-compose.yml configurations, check the [examples directory](examples) in the source code.
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
## 🏗 Architecture
|
|
205
|
+
|
|
206
|
+

|
|
215
207
|
|
|
216
208
|
---
|
|
217
209
|
|
|
@@ -10,8 +10,9 @@ from datetime import datetime, timezone
|
|
|
10
10
|
import asyncio
|
|
11
11
|
|
|
12
12
|
class HttpClientPollingCompletion:
|
|
13
|
-
def __init__(self, base_url: Optional[str], config: HttpClientCompletionConfig):
|
|
13
|
+
def __init__(self, base_url: Optional[str], headers: Optional[Dict[str, str]], config: HttpClientCompletionConfig):
|
|
14
14
|
self.base_url: Optional[str] = base_url
|
|
15
|
+
self.headers: Optional[Dict[str, str]] = headers
|
|
15
16
|
self.config: HttpClientCompletionConfig = config
|
|
16
17
|
self.client: HttpClient = HttpClient()
|
|
17
18
|
|
|
@@ -20,7 +21,7 @@ class HttpClientPollingCompletion:
|
|
|
20
21
|
method = await context.render_template(self.config.method)
|
|
21
22
|
params = await context.render_template(self.config.params)
|
|
22
23
|
body = await context.render_template(self.config.body)
|
|
23
|
-
headers = await context.render_template(self.config.headers)
|
|
24
|
+
headers = await context.render_template({ **self.headers, **self.config.headers })
|
|
24
25
|
|
|
25
26
|
interval = parse_duration(self.config.interval) if self.config.interval else 5.0
|
|
26
27
|
timeout = parse_duration(self.config.timeout) if self.config.timeout else 300
|
|
@@ -30,8 +31,8 @@ class HttpClientPollingCompletion:
|
|
|
30
31
|
|
|
31
32
|
while datetime.now(timezone.utc) < deadline:
|
|
32
33
|
response = await self.client.request(url, method, params, body, headers)
|
|
33
|
-
|
|
34
34
|
context.register_source("result", response)
|
|
35
|
+
|
|
35
36
|
status = (await context.render_template(self.config.status)) if self.config.status else response["status"]
|
|
36
37
|
|
|
37
38
|
if not status:
|
|
@@ -67,8 +68,9 @@ class HttpClientCallbackCompletion:
|
|
|
67
68
|
return await future
|
|
68
69
|
|
|
69
70
|
class HttpClientAction:
|
|
70
|
-
def __init__(self, base_url: Optional[str], config: HttpClientActionConfig):
|
|
71
|
+
def __init__(self, base_url: Optional[str], headers: Optional[Dict[str, str]], config: HttpClientActionConfig):
|
|
71
72
|
self.base_url: Optional[str] = base_url
|
|
73
|
+
self.headers: Optional[Dict[str, str]] = headers
|
|
72
74
|
self.config: HttpClientActionConfig = config
|
|
73
75
|
self.client: HttpClient = HttpClient()
|
|
74
76
|
|
|
@@ -77,18 +79,14 @@ class HttpClientAction:
|
|
|
77
79
|
method = await context.render_template(self.config.method)
|
|
78
80
|
params = await context.render_template(self.config.params)
|
|
79
81
|
body = await context.render_template(self.config.body)
|
|
80
|
-
headers = await context.render_template(self.config.headers)
|
|
82
|
+
headers = await context.render_template({ **self.headers, **self.config.headers })
|
|
81
83
|
|
|
82
84
|
response, result = await self.client.request(url, method, params, body, headers), None
|
|
83
|
-
|
|
84
|
-
if response:
|
|
85
|
-
context.register_source("response", response)
|
|
85
|
+
context.register_source("response", response)
|
|
86
86
|
|
|
87
87
|
if self.config.completion:
|
|
88
88
|
result = await self._handle_completion(self.config.completion, context)
|
|
89
|
-
|
|
90
|
-
if result:
|
|
91
|
-
context.register_source("result", result)
|
|
89
|
+
context.register_source("result", result)
|
|
92
90
|
|
|
93
91
|
return (await context.render_template(self.config.output)) if self.config.output else (result or response)
|
|
94
92
|
|
|
@@ -100,7 +98,7 @@ class HttpClientAction:
|
|
|
100
98
|
|
|
101
99
|
async def _handle_completion(self, completion: HttpClientCompletionConfig, context: ComponentContext) -> Any:
|
|
102
100
|
if completion.type == "polling":
|
|
103
|
-
return await HttpClientPollingCompletion(self.base_url, completion).run(context)
|
|
101
|
+
return await HttpClientPollingCompletion(self.base_url, self.headers, completion).run(context)
|
|
104
102
|
|
|
105
103
|
if completion.type == "callback":
|
|
106
104
|
return await HttpClientCallbackCompletion(completion).run(context)
|
|
@@ -118,6 +116,6 @@ class HttpClientComponent(ComponentEngine):
|
|
|
118
116
|
pass
|
|
119
117
|
|
|
120
118
|
async def _run(self, action: ActionConfig, context: ComponentContext) -> Any:
|
|
121
|
-
return await HttpClientAction(self.config.base_url, action).run(context)
|
|
119
|
+
return await HttpClientAction(self.config.base_url, self.config.headers, action).run(context)
|
|
122
120
|
|
|
123
121
|
ComponentEngineMap[ComponentType.HTTP_CLIENT] = HttpClientComponent
|
{model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/controller/engine/http_server.py
RENAMED
|
@@ -109,7 +109,8 @@ class HttpServerController(ControllerEngine):
|
|
|
109
109
|
await self.server.serve()
|
|
110
110
|
|
|
111
111
|
async def _shutdown(self) -> None:
|
|
112
|
-
self.server
|
|
112
|
+
if self.server:
|
|
113
|
+
self.server.should_exit = True
|
|
113
114
|
|
|
114
115
|
async def _parse_workflow_body(self, request: Request) -> WorkflowTaskRequestBody:
|
|
115
116
|
content_type, _ = parse_options_header(request.headers, "Content-Type")
|
|
@@ -1,23 +1,29 @@
|
|
|
1
1
|
from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Callable, Awaitable, Any
|
|
2
|
-
from mindor.dsl.schema.workflow import WorkflowVariableConfig
|
|
2
|
+
from mindor.dsl.schema.workflow import WorkflowVariableConfig, WorkflowVariableGroupConfig
|
|
3
3
|
from mindor.core.utils.http_client import HttpStreamResource
|
|
4
4
|
from mindor.core.utils.image import load_image_from_stream
|
|
5
5
|
from mindor.core.utils.streaming import Base64StreamResource, save_stream_to_temporary_file
|
|
6
6
|
from .schema import WorkflowSchema
|
|
7
7
|
import gradio as gr
|
|
8
|
+
import json
|
|
9
|
+
|
|
10
|
+
class ComponentGroup:
|
|
11
|
+
def __init__(self, group: gr.Component, components: List[gr.Component]):
|
|
12
|
+
self.group: gr.Component = group
|
|
13
|
+
self.components: List[gr.Component] = components
|
|
8
14
|
|
|
9
15
|
class GradioWebUIBuilder:
|
|
10
16
|
def build(self, schema: Dict[str, WorkflowSchema], runner: Callable[[Optional[str], Any], Awaitable[Any]]) -> gr.Blocks:
|
|
11
17
|
with gr.Blocks() as blocks:
|
|
12
18
|
for workflow_id, workflow in schema.items():
|
|
13
|
-
async def
|
|
19
|
+
async def _run_workflow(input: Any, workflow_id=workflow_id) -> Any:
|
|
14
20
|
return await runner(workflow_id, input)
|
|
15
21
|
|
|
16
22
|
if len(schema) > 1:
|
|
17
23
|
with gr.Tab(label=workflow.name or workflow_id):
|
|
18
|
-
self._build_workflow_section(workflow,
|
|
24
|
+
self._build_workflow_section(workflow, _run_workflow)
|
|
19
25
|
else:
|
|
20
|
-
self._build_workflow_section(workflow,
|
|
26
|
+
self._build_workflow_section(workflow, _run_workflow)
|
|
21
27
|
|
|
22
28
|
return blocks
|
|
23
29
|
|
|
@@ -38,24 +44,16 @@ class GradioWebUIBuilder:
|
|
|
38
44
|
if not output_components:
|
|
39
45
|
output_components = gr.Textbox(label="", lines=8, interactive=False, show_copy_button=True)
|
|
40
46
|
|
|
41
|
-
async def
|
|
42
|
-
input = { variable.name: value for variable, value in zip(workflow.input, args) }
|
|
47
|
+
async def _run_workflow(*args):
|
|
48
|
+
input = { variable.name: value if value != "" else None for variable, value in zip(workflow.input, args) }
|
|
43
49
|
output = await runner(input)
|
|
44
|
-
|
|
45
|
-
if
|
|
46
|
-
if isinstance(output, dict):
|
|
47
|
-
output = [ await self._convert_type(output[variable.name], variable.type, variable.subtype, variable.format) for variable in workflow.output ]
|
|
48
|
-
output = output[0] if len(output) == 1 else output
|
|
49
|
-
else:
|
|
50
|
-
variable = workflow.output[0]
|
|
51
|
-
output = await self._convert_type(output, variable.type, variable.subtype, variable.format)
|
|
52
|
-
|
|
53
|
-
return output
|
|
50
|
+
output = await self._flatten_output(output, workflow.output)
|
|
51
|
+
return output[0] if len(output) == 1 else output
|
|
54
52
|
|
|
55
53
|
run_button.click(
|
|
56
|
-
fn=
|
|
54
|
+
fn=_run_workflow,
|
|
57
55
|
inputs=input_components,
|
|
58
|
-
outputs=output_components
|
|
56
|
+
outputs=self._flatten_output_components(output_components)
|
|
59
57
|
)
|
|
60
58
|
|
|
61
59
|
return section
|
|
@@ -85,7 +83,16 @@ class GradioWebUIBuilder:
|
|
|
85
83
|
|
|
86
84
|
return gr.Textbox(label=label, value=default, info=f"Unsupported type: {variable.type}")
|
|
87
85
|
|
|
88
|
-
def _build_output_component(self, variable: WorkflowVariableConfig) -> gr.Component:
|
|
86
|
+
def _build_output_component(self, variable: Union[WorkflowVariableConfig, WorkflowVariableGroupConfig]) -> Union[gr.Component, List[ComponentGroup]]:
|
|
87
|
+
if isinstance(variable, WorkflowVariableGroupConfig):
|
|
88
|
+
groups: List[ComponentGroup] = []
|
|
89
|
+
for index in range(variable.repeat_count if variable.repeat_count != 0 else 100):
|
|
90
|
+
visible = True if variable.repeat_count != 0 or index == 0 else False
|
|
91
|
+
with gr.Column(visible=visible) as group:
|
|
92
|
+
components = [ self._build_output_component(v) for v in variable.variables ]
|
|
93
|
+
groups.append(ComponentGroup(group, components))
|
|
94
|
+
return groups
|
|
95
|
+
|
|
89
96
|
label = variable.name or ""
|
|
90
97
|
info = variable.description or ""
|
|
91
98
|
|
|
@@ -100,7 +107,34 @@ class GradioWebUIBuilder:
|
|
|
100
107
|
|
|
101
108
|
return gr.Textbox(label=label, info=f"Unsupported type: {variable.type}")
|
|
102
109
|
|
|
110
|
+
def _flatten_output_components(self, components: List[Union[gr.Component, List[ComponentGroup]]]) -> List[gr.Component]:
|
|
111
|
+
flattened = []
|
|
112
|
+
for item in components:
|
|
113
|
+
if isinstance(item, list):
|
|
114
|
+
for group in item:
|
|
115
|
+
flattened.extend(group.components)
|
|
116
|
+
else:
|
|
117
|
+
flattened.append(item)
|
|
118
|
+
return flattened
|
|
119
|
+
|
|
120
|
+
async def _flatten_output(self, output: Any, variables: List[Union[WorkflowVariableConfig, WorkflowVariableGroupConfig]]) -> Any:
|
|
121
|
+
flattened = []
|
|
122
|
+
for variable in variables:
|
|
123
|
+
if isinstance(variable, WorkflowVariableGroupConfig):
|
|
124
|
+
group = output[variable.name] if variable.name else output
|
|
125
|
+
for value in group:
|
|
126
|
+
flattened.extend(await self._flatten_output(value, variable.variables))
|
|
127
|
+
else:
|
|
128
|
+
value = output[variable.name] if variable.name else output
|
|
129
|
+
flattened.append(await self._convert_type(value, variable.type, variable.subtype, variable.format))
|
|
130
|
+
return flattened
|
|
131
|
+
|
|
103
132
|
async def _convert_type(self, value: Any, type: Optional[str], subtype: Optional[str], format: Optional[str]) -> Any:
|
|
133
|
+
if type == "string":
|
|
134
|
+
if isinstance(value, (dict, list)):
|
|
135
|
+
return json.dumps(value)
|
|
136
|
+
return str(value)
|
|
137
|
+
|
|
104
138
|
if type == "image":
|
|
105
139
|
if format == "base64" and isinstance(value, str):
|
|
106
140
|
return await load_image_from_stream(Base64StreamResource(value), subtype)
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
from mindor.dsl.schema.workflow import WorkflowConfig, WorkflowVariableConfig, WorkflowVariableGroupConfig
|
|
4
|
+
from mindor.dsl.schema.component import ComponentConfig
|
|
5
|
+
import re, json
|
|
6
|
+
|
|
7
|
+
class WorkflowVariable:
|
|
8
|
+
def __init__(self, name: Optional[str], type: str, subtype: Optional[str], format: Optional[str], default: Optional[Any]):
|
|
9
|
+
self.name: Optional[str] = name
|
|
10
|
+
self.type: str = type
|
|
11
|
+
self.subtype: Optional[str] = subtype
|
|
12
|
+
self.format: Optional[str] = format
|
|
13
|
+
self.default: Optional[Any] = default
|
|
14
|
+
|
|
15
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
16
|
+
return {
|
|
17
|
+
"name": self.name,
|
|
18
|
+
"type": self.type,
|
|
19
|
+
"subtype": self.subtype,
|
|
20
|
+
"format": self.format,
|
|
21
|
+
"default": self.default
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
def __eq__(self, other):
|
|
25
|
+
if not isinstance(other, WorkflowVariable):
|
|
26
|
+
return False
|
|
27
|
+
if self.name is None or other.name is None:
|
|
28
|
+
return False
|
|
29
|
+
return self.name == other.name
|
|
30
|
+
|
|
31
|
+
def __hash__(self):
|
|
32
|
+
return hash(self.name) if self.name is not None else id(self)
|
|
33
|
+
|
|
34
|
+
class WorkflowVariableGroup:
|
|
35
|
+
def __init__(self, name: Optional[str], variables: List[WorkflowVariable], repeat_count: int):
|
|
36
|
+
self.name: Optional[str] = name
|
|
37
|
+
self.variables: List[WorkflowVariable] = variables
|
|
38
|
+
self.repeat_count: int = repeat_count
|
|
39
|
+
|
|
40
|
+
class WorkflowVariableResolver:
|
|
41
|
+
def __init__(self):
|
|
42
|
+
self.patterns: Dict[str, re.Pattern] = {
|
|
43
|
+
"variable": re.compile(
|
|
44
|
+
r"""\$\{ # ${
|
|
45
|
+
([a-zA-Z_][^.\s]*) # key: input, env, etc.
|
|
46
|
+
(?:\.([^\s\|\}]+))? # path: key, key.path[0], etc.
|
|
47
|
+
(?:\s*as\s*([^\s\|\}/;]+)(?:/([^\s\|\};]+))?(?:;([^\s\|\}]+))?)? # type/subtype;format
|
|
48
|
+
(?:\s*\|\s*([^\}]+))? # default value after `|`
|
|
49
|
+
\}""", # }
|
|
50
|
+
re.VERBOSE,
|
|
51
|
+
),
|
|
52
|
+
"keypath": re.compile(r"[-_\w]+|\[\d+\]"),
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
def _enumerate_input_variables(self, value: Any, wanted_key: str) -> List[WorkflowVariable]:
|
|
56
|
+
if isinstance(value, str):
|
|
57
|
+
variables: List[WorkflowVariable] = []
|
|
58
|
+
|
|
59
|
+
for match in self.patterns["variable"].finditer(value):
|
|
60
|
+
key, path, type, subtype, format, default = match.group(1, 2, 3, 4, 5, 6)
|
|
61
|
+
|
|
62
|
+
if type and default:
|
|
63
|
+
default = self._parse_as_type(default, type)
|
|
64
|
+
|
|
65
|
+
if key == wanted_key:
|
|
66
|
+
variables.append(WorkflowVariable(path, type or "string", subtype, format, default))
|
|
67
|
+
|
|
68
|
+
return variables
|
|
69
|
+
|
|
70
|
+
if isinstance(value, BaseModel):
|
|
71
|
+
return self._enumerate_input_variables(value.model_dump(exclude_none=True), wanted_key)
|
|
72
|
+
|
|
73
|
+
if isinstance(value, dict):
|
|
74
|
+
return sum([ self._enumerate_input_variables(v, wanted_key) for v in value.values() ], [])
|
|
75
|
+
|
|
76
|
+
if isinstance(value, list):
|
|
77
|
+
return sum([ self._enumerate_input_variables(v, wanted_key) for v in value ], [])
|
|
78
|
+
|
|
79
|
+
return []
|
|
80
|
+
|
|
81
|
+
def _enumerate_output_variables(self, name: Optional[str], value: Any) -> List[WorkflowVariable]:
|
|
82
|
+
variables: List[WorkflowVariable] = []
|
|
83
|
+
|
|
84
|
+
if isinstance(value, str):
|
|
85
|
+
for match in self.patterns["variable"].finditer(value):
|
|
86
|
+
key, path, type, subtype, format, default = match.group(1, 2, 3, 4, 5, 6)
|
|
87
|
+
|
|
88
|
+
if type and default:
|
|
89
|
+
default = self._parse_as_type(default, type)
|
|
90
|
+
|
|
91
|
+
variables.append(WorkflowVariable(name, type or "string", subtype, format, default))
|
|
92
|
+
|
|
93
|
+
return variables
|
|
94
|
+
|
|
95
|
+
if isinstance(value, BaseModel):
|
|
96
|
+
return self._enumerate_output_variables(name, value.model_dump(exclude_none=True))
|
|
97
|
+
|
|
98
|
+
if isinstance(value, dict):
|
|
99
|
+
return sum([ self._enumerate_output_variables(f"{name}.{k}" if name else f"{k}", v) for k, v in value.items() ], [])
|
|
100
|
+
|
|
101
|
+
if isinstance(value, list):
|
|
102
|
+
return sum([ self._enumerate_output_variables(f"{name}[{i}]" if name else f"[{i}]", v) for i, v in enumerate(value) ], [])
|
|
103
|
+
|
|
104
|
+
return []
|
|
105
|
+
|
|
106
|
+
def _to_variable_config_list(self, variables: List[Union[WorkflowVariable, WorkflowVariableGroup]]) -> List[Union[WorkflowVariableConfig, WorkflowVariableGroupConfig]]:
|
|
107
|
+
configs: List[Union[WorkflowVariableConfig, WorkflowVariableGroupConfig]] = []
|
|
108
|
+
seen_single: Set[WorkflowVariable] = set()
|
|
109
|
+
|
|
110
|
+
for item in variables:
|
|
111
|
+
if isinstance(item, WorkflowVariableGroup):
|
|
112
|
+
group: List[WorkflowVariableConfig] = []
|
|
113
|
+
seen_in_group: Set[WorkflowVariable] = set()
|
|
114
|
+
for v in item.variables:
|
|
115
|
+
if v not in seen_in_group:
|
|
116
|
+
seen_in_group.add(v)
|
|
117
|
+
group.append(WorkflowVariableConfig(**v.to_dict()))
|
|
118
|
+
configs.append(WorkflowVariableGroupConfig(name=item.name, variables=group, repeat_count=item.repeat_count))
|
|
119
|
+
else:
|
|
120
|
+
if item not in seen_single:
|
|
121
|
+
seen_single.add(item)
|
|
122
|
+
configs.append(WorkflowVariableConfig(**item.to_dict()))
|
|
123
|
+
|
|
124
|
+
return configs
|
|
125
|
+
|
|
126
|
+
def _parse_as_type(self, value: Any, type: str) -> Any:
|
|
127
|
+
if type == "number":
|
|
128
|
+
return float(value)
|
|
129
|
+
|
|
130
|
+
if type == "integer":
|
|
131
|
+
return int(value)
|
|
132
|
+
|
|
133
|
+
if type == "boolean":
|
|
134
|
+
return str(value).lower() in [ "true", "1" ]
|
|
135
|
+
|
|
136
|
+
if type == "json":
|
|
137
|
+
return json.loads(value)
|
|
138
|
+
|
|
139
|
+
return value
|
|
140
|
+
|
|
141
|
+
class WorkflowInputResolver(WorkflowVariableResolver):
|
|
142
|
+
def resolve(self, workflow: WorkflowConfig, components: Dict[str, ComponentConfig]) -> List[WorkflowVariableConfig]:
|
|
143
|
+
variables: List[WorkflowVariable] = []
|
|
144
|
+
|
|
145
|
+
for job in workflow.jobs.values():
|
|
146
|
+
if not job.input or job.input == "${input}":
|
|
147
|
+
action_id = job.action or "__default__"
|
|
148
|
+
if isinstance(job.component, str):
|
|
149
|
+
component: Optional[ComponentConfig] = components[job.component] if job.component in components else None
|
|
150
|
+
if component:
|
|
151
|
+
variables.extend(self._enumerate_input_variables(component.actions[action_id], "input"))
|
|
152
|
+
else:
|
|
153
|
+
variables.extend(self._enumerate_input_variables(job.component.actions[action_id], "input"))
|
|
154
|
+
else:
|
|
155
|
+
variables.extend(self._enumerate_input_variables(job.input, "input"))
|
|
156
|
+
|
|
157
|
+
for value in [ job.repeat_count ]:
|
|
158
|
+
variables.extend(self._enumerate_input_variables(value, "input"))
|
|
159
|
+
|
|
160
|
+
return self._to_variable_config_list(variables)
|
|
161
|
+
|
|
162
|
+
class WorkflowOutputResolver(WorkflowVariableResolver):
|
|
163
|
+
def resolve(self, workflow: WorkflowConfig, components: Dict[str, ComponentConfig]) -> List[Union[WorkflowVariableConfig, WorkflowVariableGroupConfig]]:
|
|
164
|
+
variables: List[Union[WorkflowVariable, WorkflowVariableGroup]] = []
|
|
165
|
+
|
|
166
|
+
for job_id, job in workflow.jobs.items():
|
|
167
|
+
if not self._is_terminal_job(workflow, job_id):
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
job_variables: List[WorkflowVariable] = variables
|
|
171
|
+
repeat_count: int = job.repeat_count if isinstance(job.repeat_count, int) else 0
|
|
172
|
+
|
|
173
|
+
if repeat_count != 1:
|
|
174
|
+
variables.append(WorkflowVariableGroup(None, job_variables := [], repeat_count))
|
|
175
|
+
|
|
176
|
+
if not job.output or job.output == "${output}":
|
|
177
|
+
action_id = job.action or "__default__"
|
|
178
|
+
if isinstance(job.component, str):
|
|
179
|
+
component: Optional[ComponentConfig] = components[job.component] if job.component in components else None
|
|
180
|
+
if component:
|
|
181
|
+
job_variables.extend(self._enumerate_output_variables(None, component.actions[action_id].output))
|
|
182
|
+
else:
|
|
183
|
+
job_variables.extend(self._enumerate_output_variables(None, job.component.actions[action_id].output))
|
|
184
|
+
else:
|
|
185
|
+
job_variables.extend(self._enumerate_output_variables(None, job.output))
|
|
186
|
+
|
|
187
|
+
return self._to_variable_config_list(variables)
|
|
188
|
+
|
|
189
|
+
def _is_terminal_job(self, workflow: WorkflowConfig, job_id: str) -> bool:
|
|
190
|
+
return all(job_id not in job.depends_on for other_id, job in workflow.jobs.items() if other_id != job_id)
|
|
191
|
+
|
|
192
|
+
class WorkflowSchema:
|
|
193
|
+
def __init__(self, name: str, title: str, description: Optional[str], input: List[WorkflowVariableConfig], output: List[Union[WorkflowVariableConfig, WorkflowVariableGroupConfig]]):
|
|
194
|
+
self.name: str = name
|
|
195
|
+
self.title: str = title
|
|
196
|
+
self.description: Optional[str] = description
|
|
197
|
+
self.input: List[WorkflowVariableConfig] = input
|
|
198
|
+
self.output: List[Union[WorkflowVariableConfig, WorkflowVariableGroupConfig]] = output
|
|
@@ -54,7 +54,8 @@ class ControllerWebUI(AsyncService):
|
|
|
54
54
|
await self.server.serve()
|
|
55
55
|
|
|
56
56
|
async def _shutdown(self) -> None:
|
|
57
|
-
self.server
|
|
57
|
+
if self.server:
|
|
58
|
+
self.server.should_exit = True
|
|
58
59
|
|
|
59
60
|
async def _run_workflow(self, workflow_id: Optional[str], input: Any) -> Any:
|
|
60
61
|
return await self.runner.run_workflow(workflow_id, input)
|
{model_compose-0.1.0 → model_compose-0.1.2}/src/mindor/core/listener/engine/http_callback.py
RENAMED
|
@@ -66,7 +66,7 @@ class HttpCallbackListener(ListenerEngine):
|
|
|
66
66
|
)
|
|
67
67
|
|
|
68
68
|
def _make_callback_handler(self, callback: HttpCallbackConfig) -> Callable:
|
|
69
|
-
async def
|
|
69
|
+
async def _handler(request: Request) -> Response:
|
|
70
70
|
content_type, _ = parse_options_header(request.headers, "Content-Type")
|
|
71
71
|
body, query = await parse_request_body(request, content_type), request.query_params
|
|
72
72
|
context: HttpCallbackContext = HttpCallbackContext(body, query, callback.bulk, callback.item)
|
|
@@ -85,7 +85,7 @@ class HttpCallbackListener(ListenerEngine):
|
|
|
85
85
|
|
|
86
86
|
return Response()
|
|
87
87
|
|
|
88
|
-
return
|
|
88
|
+
return _handler
|
|
89
89
|
|
|
90
90
|
async def _is_callback_succeeded(self, callback: HttpCallbackConfig, context: HttpCallbackContext) -> bool:
|
|
91
91
|
status = (await context.render_template(callback.status)) if callback.status else None
|
|
@@ -109,7 +109,8 @@ class HttpCallbackListener(ListenerEngine):
|
|
|
109
109
|
await self.server.serve()
|
|
110
110
|
|
|
111
111
|
async def _shutdown(self) -> None:
|
|
112
|
-
self.server
|
|
112
|
+
if self.server:
|
|
113
|
+
self.server.should_exit = True
|
|
113
114
|
|
|
114
115
|
def _get_pending_future(self, id: str) -> Optional[asyncio.Future]:
|
|
115
116
|
with _pending_futures_lock:
|
|
@@ -39,11 +39,11 @@ class HttpClient:
|
|
|
39
39
|
headers=headers
|
|
40
40
|
)
|
|
41
41
|
|
|
42
|
-
if response.status >= 400:
|
|
43
|
-
raise ValueError(f"Request failed with status {response.status}")
|
|
44
|
-
|
|
45
42
|
content = await self._parse_response_content(session, response)
|
|
46
43
|
|
|
44
|
+
if response.status >= 400:
|
|
45
|
+
raise ValueError(f"Request failed with status {response.status}: {content}")
|
|
46
|
+
|
|
47
47
|
if not isinstance(content, HttpStreamResource):
|
|
48
48
|
await session.close()
|
|
49
49
|
|