model-compose 0.1.0__tar.gz → 0.1.1__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.
Files changed (116) hide show
  1. {model_compose-0.1.0 → model_compose-0.1.1}/PKG-INFO +20 -28
  2. {model_compose-0.1.0 → model_compose-0.1.1}/README.md +19 -27
  3. {model_compose-0.1.0 → model_compose-0.1.1}/pyproject.toml +1 -1
  4. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/component/engine/http_client.py +8 -6
  5. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/controller/engine/http_server.py +2 -1
  6. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/controller/webui/gradio.py +11 -2
  7. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/controller/webui/schema.py +5 -5
  8. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/controller/webui/webui.py +2 -1
  9. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/listener/engine/http_callback.py +2 -1
  10. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/utils/template.py +1 -1
  11. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/workflow/job.py +12 -4
  12. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/workflow/workflow.py +2 -2
  13. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/workflow.py +6 -5
  14. {model_compose-0.1.0 → model_compose-0.1.1}/src/model_compose.egg-info/PKG-INFO +20 -28
  15. {model_compose-0.1.0 → model_compose-0.1.1}/LICENSE +0 -0
  16. {model_compose-0.1.0 → model_compose-0.1.1}/setup.cfg +0 -0
  17. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/__init__.py +0 -0
  18. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/cli/__init__.py +0 -0
  19. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/cli/compose.py +0 -0
  20. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/__init__.py +0 -0
  21. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/component/__init__.py +0 -0
  22. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/component/component.py +0 -0
  23. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/component/engine/__init__.py +0 -0
  24. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/component/engine/base.py +0 -0
  25. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/component/engine/context.py +0 -0
  26. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/component/engine/http_server.py +0 -0
  27. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/compose/__init__.py +0 -0
  28. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/compose/compose.py +0 -0
  29. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/compose/manager.py +0 -0
  30. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/controller/__init__.py +0 -0
  31. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/controller/controller.py +0 -0
  32. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/controller/engine/__init__.py +0 -0
  33. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/controller/engine/base.py +0 -0
  34. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/controller/engine/mcp_server.py +0 -0
  35. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/controller/runner/__init__.py +0 -0
  36. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/controller/runner/client.py +0 -0
  37. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/controller/runner/http_client.py +0 -0
  38. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/controller/runner/mcp_client.py +0 -0
  39. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/controller/runner/runner.py +0 -0
  40. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/controller/webui/__init__.py +0 -0
  41. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/gateway/__init__.py +0 -0
  42. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/gateway/engine/__init__.py +0 -0
  43. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/gateway/engine/base.py +0 -0
  44. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/gateway/engine/http_tunnel.py +0 -0
  45. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/gateway/gateway.py +0 -0
  46. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/listener/__init__.py +0 -0
  47. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/listener/engine/__init__.py +0 -0
  48. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/listener/engine/base.py +0 -0
  49. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/listener/listener.py +0 -0
  50. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/runtime/__init__.py +0 -0
  51. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/runtime/env.py +0 -0
  52. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/services/__init__.py +0 -0
  53. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/services/async_service.py +0 -0
  54. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/utils/__init__.py +0 -0
  55. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/utils/expiring.py +0 -0
  56. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/utils/http_client.py +0 -0
  57. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/utils/http_request.py +0 -0
  58. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/utils/image.py +0 -0
  59. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/utils/streaming.py +0 -0
  60. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/utils/time.py +0 -0
  61. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/utils/workqueue.py +0 -0
  62. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/workflow/__init__.py +0 -0
  63. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/core/workflow/context.py +0 -0
  64. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/__init__.py +0 -0
  65. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/loader.py +0 -0
  66. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/__init__.py +0 -0
  67. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/action/__init__.py +0 -0
  68. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/action/action.py +0 -0
  69. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/action/impl/__init__.py +0 -0
  70. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/action/impl/common.py +0 -0
  71. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/action/impl/http_client.py +0 -0
  72. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/action/impl/http_server.py +0 -0
  73. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/action/impl/mcp_client.py +0 -0
  74. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/action/impl/mcp_server.py +0 -0
  75. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/component/__init__.py +0 -0
  76. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/component/component.py +0 -0
  77. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/component/impl/__init__.py +0 -0
  78. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/component/impl/common.py +0 -0
  79. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/component/impl/http_client.py +0 -0
  80. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/component/impl/http_server.py +0 -0
  81. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/component/impl/mcp_client.py +0 -0
  82. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/component/impl/mcp_server.py +0 -0
  83. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/component/impl/types.py +0 -0
  84. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/compose.py +0 -0
  85. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/controller/__init__.py +0 -0
  86. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/controller/controller.py +0 -0
  87. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/controller/impl/__init__.py +0 -0
  88. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/controller/impl/common.py +0 -0
  89. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/controller/impl/http_server.py +0 -0
  90. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/controller/impl/mcp_server.py +0 -0
  91. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/controller/impl/types.py +0 -0
  92. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/controller/impl/webui.py +0 -0
  93. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/gateway/__init__.py +0 -0
  94. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/gateway/gateway.py +0 -0
  95. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/gateway/impl/__init__.py +0 -0
  96. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/gateway/impl/common.py +0 -0
  97. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/gateway/impl/http_tunnel.py +0 -0
  98. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/gateway/impl/types.py +0 -0
  99. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/listener/__init__.py +0 -0
  100. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/listener/impl/__init__.py +0 -0
  101. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/listener/impl/common.py +0 -0
  102. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/listener/impl/http_callback.py +0 -0
  103. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/listener/impl/types.py +0 -0
  104. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/listener/listener.py +0 -0
  105. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/runtime/__init__.py +0 -0
  106. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/runtime/impl/__init__.py +0 -0
  107. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/runtime/impl/common.py +0 -0
  108. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/runtime/impl/docker.py +0 -0
  109. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/runtime/impl/types.py +0 -0
  110. {model_compose-0.1.0 → model_compose-0.1.1}/src/mindor/dsl/schema/runtime/runtime.py +0 -0
  111. {model_compose-0.1.0 → model_compose-0.1.1}/src/model_compose.egg-info/SOURCES.txt +0 -0
  112. {model_compose-0.1.0 → model_compose-0.1.1}/src/model_compose.egg-info/dependency_links.txt +0 -0
  113. {model_compose-0.1.0 → model_compose-0.1.1}/src/model_compose.egg-info/entry_points.txt +0 -0
  114. {model_compose-0.1.0 → model_compose-0.1.1}/src/model_compose.egg-info/requires.txt +0 -0
  115. {model_compose-0.1.0 → model_compose-0.1.1}/src/model_compose.egg-info/top_level.txt +0 -0
  116. {model_compose-0.1.0 → model_compose-0.1.1}/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.0
3
+ Version: 0.1.1
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 (_part of the Mindor project_)
24
+ # model-compose: Declarative AI Model and Workflow Orchestrator
25
25
 
26
26
  [![Python Version](https://img.shields.io/badge/python-3.9+-blue.svg)](https://python.org)
27
27
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
28
28
  [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](http://makeapullrequest.com)
29
29
 
30
+ ![Screenshots](docs/images/screenshots.png)
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
- #### 🔧 Declarative by Design
37
- Define complete AI workflows using simple YAML files no complex scripting required.
38
-
39
- #### 🔗 Compose Anything
40
- Combine multiple AI models, APIs, and tools into a single, unified pipeline.
41
-
42
- #### 🧠 Built for Orchestration
43
- Orchestrate multi-step model interactions with ease. Turn individual API calls into maintainable, end-to-end systems using **model-compose**.
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: /chatgpt
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 `http://localhost:8090/callbacks/chatgpt` to receive asynchronous responses from a remote ChatGPT-compatible service. It extracts the incoming data from the request body, identifies the task using `task_id`, and stores the final message content from the first choice in the response.
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 samples directory in the source code.
224
+ > 📁 For more example model-compose.yml configurations, check the [examples directory](examples) in the source code.
225
+
226
+ ---
227
+ ## 🏗 Architecture
228
+
229
+ ![Archtecture Diagram](docs/images/architecture-diagram.png)
238
230
 
239
231
  ---
240
232
 
@@ -1,38 +1,25 @@
1
- # model-compose: Declarative AI Model and Workflow Orchestrator (_part of the Mindor project_)
1
+ # model-compose: Declarative AI Model and Workflow Orchestrator
2
2
 
3
3
  [![Python Version](https://img.shields.io/badge/python-3.9+-blue.svg)](https://python.org)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
  [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](http://makeapullrequest.com)
6
6
 
7
+ ![Screenshots](docs/images/screenshots.png)
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
- #### 🔧 Declarative by Design
14
- Define complete AI workflows using simple YAML files no complex scripting required.
15
-
16
- #### 🔗 Compose Anything
17
- Combine multiple AI models, APIs, and tools into a single, unified pipeline.
18
-
19
- #### 🧠 Built for Orchestration
20
- Orchestrate multi-step model interactions with ease. Turn individual API calls into maintainable, end-to-end systems using **model-compose**.
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: /chatgpt
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 `http://localhost:8090/callbacks/chatgpt` to receive asynchronous responses from a remote ChatGPT-compatible service. It extracts the incoming data from the request body, identifies the task using `task_id`, and stores the final message content from the first choice in the response.
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 samples directory in the source code.
201
+ > 📁 For more example model-compose.yml configurations, check the [examples directory](examples) in the source code.
202
+
203
+ ---
204
+ ## 🏗 Architecture
205
+
206
+ ![Archtecture Diagram](docs/images/architecture-diagram.png)
215
207
 
216
208
  ---
217
209
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "model-compose"
3
- version = "0.1.0"
3
+ version = "0.1.1"
4
4
  description = "model-compose: Declarative AI Model and Workflow Orchestrator (part of the Mindor project)"
5
5
  authors = [
6
6
  { name = "Hanyeol Cho", email = "hanyeol.cho@gmail.com" }
@@ -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
@@ -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,7 +79,7 @@ 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
85
 
@@ -100,7 +102,7 @@ class HttpClientAction:
100
102
 
101
103
  async def _handle_completion(self, completion: HttpClientCompletionConfig, context: ComponentContext) -> Any:
102
104
  if completion.type == "polling":
103
- return await HttpClientPollingCompletion(self.base_url, completion).run(context)
105
+ return await HttpClientPollingCompletion(self.base_url, self.headers, completion).run(context)
104
106
 
105
107
  if completion.type == "callback":
106
108
  return await HttpClientCallbackCompletion(completion).run(context)
@@ -118,6 +120,6 @@ class HttpClientComponent(ComponentEngine):
118
120
  pass
119
121
 
120
122
  async def _run(self, action: ActionConfig, context: ComponentContext) -> Any:
121
- return await HttpClientAction(self.config.base_url, action).run(context)
123
+ return await HttpClientAction(self.config.base_url, self.config.headers, action).run(context)
122
124
 
123
125
  ComponentEngineMap[ComponentType.HTTP_CLIENT] = HttpClientComponent
@@ -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.should_exit = True
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")
@@ -5,6 +5,7 @@ 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
8
9
 
9
10
  class GradioWebUIBuilder:
10
11
  def build(self, schema: Dict[str, WorkflowSchema], runner: Callable[[Optional[str], Any], Awaitable[Any]]) -> gr.Blocks:
@@ -44,8 +45,11 @@ class GradioWebUIBuilder:
44
45
 
45
46
  if workflow.output:
46
47
  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
48
+ outputs = []
49
+ for variable in workflow.output:
50
+ value = output[variable.name] if variable.name else output
51
+ outputs.append(await self._convert_type(value, variable.type, variable.subtype, variable.format))
52
+ output = outputs[0] if len(outputs) == 1 else outputs
49
53
  else:
50
54
  variable = workflow.output[0]
51
55
  output = await self._convert_type(output, variable.type, variable.subtype, variable.format)
@@ -115,4 +119,9 @@ class GradioWebUIBuilder:
115
119
  return await save_stream_to_temporary_file(value, subtype)
116
120
  return None
117
121
 
122
+ if type == "string":
123
+ if isinstance(value, (dict, list)):
124
+ return json.dumps(value)
125
+ return str(value)
126
+
118
127
  return value
@@ -26,7 +26,7 @@ class WorkflowVariableResolver:
26
26
  key, path, type, subtype, format, default = match.group(1, 2, 3, 4, 5, 6)
27
27
 
28
28
  if type and default:
29
- default = self._convert_type(default, type, subtype, format)
29
+ default = self._parse_as_type(default, type)
30
30
 
31
31
  if key == wanted_key:
32
32
  variables.append((path, type or "string", subtype, format, default))
@@ -52,7 +52,7 @@ class WorkflowVariableResolver:
52
52
  type, subtype, format, default = match.group(3, 4, 5, 6)
53
53
 
54
54
  if type and default:
55
- default = self._convert_type(default, type, subtype, format)
55
+ default = self._parse_as_type(default, type)
56
56
 
57
57
  variables.append((key, type or "string", subtype, format, default))
58
58
 
@@ -67,12 +67,12 @@ class WorkflowVariableResolver:
67
67
  if isinstance(value, list):
68
68
  return sum([ self._enumerate_output_variables(f"{key}[{i}]" if key else f"[{i}]", v) for i, v in enumerate(value) ], [])
69
69
 
70
- return []
70
+ return []
71
71
 
72
- def _convert_type(self, value: Any, type: str, subtype: str, format: Optional[str]) -> Any:
72
+ def _parse_as_type(self, value: Any, type: str) -> Any:
73
73
  if type == "number":
74
74
  return float(value)
75
-
75
+
76
76
  if type == "integer":
77
77
  return int(value)
78
78
 
@@ -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.should_exit = True
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)
@@ -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.should_exit = True
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:
@@ -46,7 +46,7 @@ class TemplateRenderer:
46
46
  except Exception:
47
47
  value = None
48
48
 
49
- value = default if not value else value
49
+ value = default if value is None else value
50
50
 
51
51
  if type and value:
52
52
  value = await self._convert_type(value, type, subtype, format)
@@ -6,10 +6,10 @@ from .context import WorkflowContext
6
6
  import ulid
7
7
 
8
8
  class Job:
9
- def __init__(self, id: str, config: JobConfig, component_provider: Callable[[Union[ComponentConfig, str]], ComponentEngine]):
9
+ def __init__(self, id: str, config: JobConfig, component_provider: Callable[[str, Union[ComponentConfig, str]], ComponentEngine]):
10
10
  self.id: str = id
11
11
  self.config: JobConfig = config
12
- self.component_provider: Callable[[Union[ComponentConfig, str]], ComponentEngine] = component_provider
12
+ self.component_provider: Callable[[str, Union[ComponentConfig, str]], ComponentEngine] = component_provider
13
13
 
14
14
  async def run(self, context: WorkflowContext) -> Any:
15
15
  component: ComponentEngine = self.component_provider(self.id, self.config.component)
@@ -17,9 +17,17 @@ class Job:
17
17
  if not component.started:
18
18
  await component.start()
19
19
 
20
- call_id = ulid.ulid()
21
20
  input = (await context.render_template(self.config.input)) if self.config.input else context.input
22
- output = await component.run(self.config.action, call_id, input)
21
+ outputs = []
22
+
23
+ for _ in range(self.config.repeats):
24
+ call_id = ulid.ulid()
25
+ output = await component.run(self.config.action, call_id, input)
26
+
27
+ if output:
28
+ outputs.append(output)
29
+
30
+ output = outputs[0] if len(outputs) == 1 else outputs or None
23
31
 
24
32
  if output:
25
33
  context.register_source("output", output)
@@ -74,9 +74,9 @@ class WorkflowResolver:
74
74
  return default_ids[0] if default_ids else "__default__"
75
75
 
76
76
  class WorkflowRunner:
77
- def __init__(self, jobs: Dict[str, JobConfig], component_provider: Callable[[Union[str, ComponentConfig, str]], ComponentEngine]):
77
+ def __init__(self, jobs: Dict[str, JobConfig], component_provider: Callable[[str, Union[ComponentConfig, str]], ComponentEngine]):
78
78
  self.jobs: Dict[str, JobConfig] = jobs
79
- self.component_provider: Callable[[Union[str, ComponentConfig, str]], ComponentEngine] = component_provider
79
+ self.component_provider: Callable[[str, Union[ComponentConfig, str]], ComponentEngine] = component_provider
80
80
 
81
81
  async def run(self, context: WorkflowContext) -> Any:
82
82
  pending_jobs: Dict[str, Job] = { job_id: Job(job_id, job, self.component_provider) for job_id, job in self.jobs.items() }
@@ -5,11 +5,12 @@ from pydantic import model_validator
5
5
  from .component import ComponentConfig
6
6
 
7
7
  class JobConfig(BaseModel):
8
- component: Optional[Union[ str, ComponentConfig ]] = "__default__"
9
- action: Optional[str] = "__default__"
10
- input: Optional[Any] = None
11
- output: Optional[Any] = None
12
- depends_on: Optional[List[str]] = Field(default_factory=list)
8
+ component: Optional[Union[str, 'ComponentConfig']] = Field(default="__default__", description="The component to execute. Can be a string identifier or a ComponentConfig object.")
9
+ action: Optional[str] = Field(default="__default__", description="The action to invoke within the component. Defaults to '__default__'.")
10
+ repeats: Optional[int] = Field(default=1, ge=1, description="Number of times to repeat the component execution. Must be at least 1.")
11
+ input: Optional[Any] = Field(default=None, description="The input data passed to the component. Can be of any type.")
12
+ output: Optional[Any] = Field(default=None, description="The expected output data from the component. Can be of any type.")
13
+ depends_on: Optional[List[str]] = Field(default_factory=list, description="List of job names that this job depends on. Ensures execution order.")
13
14
 
14
15
  class WorkflowVariableType(str, Enum):
15
16
  # Primitive data types
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: model-compose
3
- Version: 0.1.0
3
+ Version: 0.1.1
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 (_part of the Mindor project_)
24
+ # model-compose: Declarative AI Model and Workflow Orchestrator
25
25
 
26
26
  [![Python Version](https://img.shields.io/badge/python-3.9+-blue.svg)](https://python.org)
27
27
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
28
28
  [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](http://makeapullrequest.com)
29
29
 
30
+ ![Screenshots](docs/images/screenshots.png)
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
- #### 🔧 Declarative by Design
37
- Define complete AI workflows using simple YAML files no complex scripting required.
38
-
39
- #### 🔗 Compose Anything
40
- Combine multiple AI models, APIs, and tools into a single, unified pipeline.
41
-
42
- #### 🧠 Built for Orchestration
43
- Orchestrate multi-step model interactions with ease. Turn individual API calls into maintainable, end-to-end systems using **model-compose**.
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: /chatgpt
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 `http://localhost:8090/callbacks/chatgpt` to receive asynchronous responses from a remote ChatGPT-compatible service. It extracts the incoming data from the request body, identifies the task using `task_id`, and stores the final message content from the first choice in the response.
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 samples directory in the source code.
224
+ > 📁 For more example model-compose.yml configurations, check the [examples directory](examples) in the source code.
225
+
226
+ ---
227
+ ## 🏗 Architecture
228
+
229
+ ![Archtecture Diagram](docs/images/architecture-diagram.png)
238
230
 
239
231
  ---
240
232
 
File without changes
File without changes