model-compose 0.0.5__tar.gz → 0.0.6__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 (90) hide show
  1. {model_compose-0.0.5/src/model_compose.egg-info → model_compose-0.0.6}/PKG-INFO +1 -1
  2. {model_compose-0.0.5 → model_compose-0.0.6}/pyproject.toml +1 -1
  3. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/core/component/engine/__init__.py +1 -1
  4. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/core/component/engine/base.py +1 -1
  5. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/core/component/engine/http_client.py +55 -25
  6. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/core/component/engine/http_server.py +1 -1
  7. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/core/controller/engine/http_server.py +45 -16
  8. model_compose-0.0.6/src/mindor/core/utils/resources.py +28 -0
  9. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/core/workflow/job.py +5 -5
  10. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/core/workflow/workflow.py +5 -4
  11. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/action/impl/http_client.py +3 -3
  12. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/action/impl/http_server.py +1 -1
  13. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/action/impl/mcp_client.py +1 -1
  14. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/action/impl/mcp_server.py +2 -2
  15. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/component/component.py +1 -1
  16. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/component/impl/common.py +1 -1
  17. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/component/impl/http_client.py +2 -2
  18. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/component/impl/http_server.py +1 -1
  19. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/component/impl/mcp_client.py +2 -2
  20. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/component/impl/mcp_server.py +1 -1
  21. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/controller/controller.py +1 -1
  22. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/controller/impl/http_server.py +1 -1
  23. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/controller/impl/mcp_server.py +1 -1
  24. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/listener/impl/http_callback.py +1 -1
  25. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/listener/listener.py +1 -1
  26. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/runtime/impl/docker.py +1 -1
  27. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/runtime/runtime.py +1 -1
  28. {model_compose-0.0.5 → model_compose-0.0.6/src/model_compose.egg-info}/PKG-INFO +1 -1
  29. {model_compose-0.0.5 → model_compose-0.0.6}/src/model_compose.egg-info/SOURCES.txt +1 -0
  30. {model_compose-0.0.5 → model_compose-0.0.6}/LICENSE +0 -0
  31. {model_compose-0.0.5 → model_compose-0.0.6}/README.md +0 -0
  32. {model_compose-0.0.5 → model_compose-0.0.6}/setup.cfg +0 -0
  33. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/__init__.py +0 -0
  34. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/cli/__init__.py +0 -0
  35. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/cli/compose.py +0 -0
  36. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/core/__init__.py +0 -0
  37. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/core/component/__init__.py +0 -0
  38. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/core/component/component.py +0 -0
  39. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/core/component/engine/context.py +0 -0
  40. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/core/compose/__init__.py +0 -0
  41. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/core/compose/compose.py +0 -0
  42. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/core/compose/manager.py +0 -0
  43. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/core/controller/__init__.py +0 -0
  44. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/core/controller/controller.py +0 -0
  45. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/core/controller/engine/__init__.py +0 -0
  46. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/core/controller/engine/base.py +0 -0
  47. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/core/controller/engine/mcp_server.py +0 -0
  48. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/core/listener/__init__.py +0 -0
  49. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/core/listener/engine/__init__.py +0 -0
  50. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/core/listener/engine/base.py +0 -0
  51. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/core/listener/engine/http_callback.py +0 -0
  52. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/core/listener/listener.py +0 -0
  53. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/core/runtime/__init__.py +0 -0
  54. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/core/runtime/env.py +0 -0
  55. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/core/utils/__init__.py +0 -0
  56. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/core/utils/expiring.py +0 -0
  57. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/core/utils/http_headers.py +0 -0
  58. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/core/utils/template.py +0 -0
  59. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/core/utils/workqueue.py +0 -0
  60. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/core/workflow/__init__.py +0 -0
  61. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/core/workflow/context.py +0 -0
  62. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/__init__.py +0 -0
  63. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/loader.py +0 -0
  64. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/__init__.py +0 -0
  65. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/action/__init__.py +0 -0
  66. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/action/action.py +0 -0
  67. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/action/impl/__init__.py +0 -0
  68. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/action/impl/common.py +0 -0
  69. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/component/__init__.py +0 -0
  70. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/component/impl/__init__.py +0 -0
  71. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/component/impl/types.py +0 -0
  72. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/compose.py +0 -0
  73. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/controller/__init__.py +0 -0
  74. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/controller/impl/__init__.py +0 -0
  75. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/controller/impl/common.py +0 -0
  76. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/controller/impl/types.py +0 -0
  77. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/listener/__init__.py +0 -0
  78. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/listener/impl/__init__.py +0 -0
  79. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/listener/impl/common.py +0 -0
  80. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/listener/impl/types.py +0 -0
  81. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/runtime/__init__.py +0 -0
  82. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/runtime/impl/__init__.py +0 -0
  83. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/runtime/impl/common.py +0 -0
  84. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/runtime/impl/types.py +0 -0
  85. {model_compose-0.0.5 → model_compose-0.0.6}/src/mindor/dsl/schema/workflow.py +0 -0
  86. {model_compose-0.0.5 → model_compose-0.0.6}/src/model_compose.egg-info/dependency_links.txt +0 -0
  87. {model_compose-0.0.5 → model_compose-0.0.6}/src/model_compose.egg-info/entry_points.txt +0 -0
  88. {model_compose-0.0.5 → model_compose-0.0.6}/src/model_compose.egg-info/requires.txt +0 -0
  89. {model_compose-0.0.5 → model_compose-0.0.6}/src/model_compose.egg-info/top_level.txt +0 -0
  90. {model_compose-0.0.5 → model_compose-0.0.6}/tests/test_cli.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: model-compose
3
- Version: 0.0.5
3
+ Version: 0.0.6
4
4
  Summary: model-compose: Declarative AI Model and Workflow Orchestrator (from Mindor)
5
5
  Author-email: Hanyeol Cho <hanyeol.cho@gmail.com>
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "model-compose"
3
- version = "0.0.5"
3
+ version = "0.0.6"
4
4
  description = "model-compose: Declarative AI Model and Workflow Orchestrator (from Mindor)"
5
5
  authors = [
6
6
  { name = "Hanyeol Cho", email = "hanyeol.cho@gmail.com" }
@@ -1,4 +1,4 @@
1
1
  from .base import *
2
+ from .stream import *
2
3
  from .http_server import *
3
4
  from .http_client import *
4
-
@@ -117,7 +117,7 @@ class BaseComponent(ABC):
117
117
  pass
118
118
 
119
119
  @abstractmethod
120
- async def _run(self, action: ActionConfig, context: ComponentContext) -> Dict[str, Any]:
120
+ async def _run(self, action: ActionConfig, context: ComponentContext) -> Any:
121
121
  pass
122
122
 
123
123
  ComponentEngineMap: Dict[ComponentType, Type[BaseComponent]] = {}
@@ -1,18 +1,42 @@
1
- from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
- from mindor.dsl.schema.component import HttpClientComponentConfig, HttpClientActionConfig, HttpUrl
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, AsyncIterator, Any
2
+ from mindor.dsl.schema.component import HttpClientComponentConfig, HttpClientActionConfig
3
3
  from mindor.core.utils.http_headers import parse_options_header, get_header_value
4
+ from mindor.core.utils.resources import StreamResource
4
5
  from .base import BaseComponent, ComponentType, ComponentEngineMap, ActionConfig
5
6
  from .context import ComponentContext
6
7
 
7
8
  from urllib.parse import urlencode
8
9
  import aiohttp, json
9
10
 
11
+ class HttpStreamResource(StreamResource):
12
+ def __init__(self, session: aiohttp.ClientSession, stream: aiohttp.StreamReader, filename: Optional[str], content_type: Optional[str]):
13
+ super().__init__(content_type, filename)
14
+
15
+ self.session: aiohttp.ClientSession = session
16
+ self.stream: aiohttp.StreamReader = stream
17
+
18
+ def get_stream(self):
19
+ return self.stream
20
+
21
+ async def close(self):
22
+ await self.session.close()
23
+
24
+ async def _iterate_stream(self) -> AsyncIterator[bytes]:
25
+ _, buffer_size = self.stream.get_read_buffer_limits()
26
+ chunk_size = buffer_size or 65536
27
+
28
+ while not self.stream.at_eof():
29
+ chunk = await self.stream.read(chunk_size)
30
+ if not chunk:
31
+ break
32
+ yield chunk
33
+
10
34
  class HttpClientAction:
11
- def __init__(self, base_url: Union[str, None], config: HttpClientActionConfig):
12
- self.base_url: Union[HttpUrl, None] = base_url
35
+ def __init__(self, base_url: Optional[str], config: HttpClientActionConfig):
36
+ self.base_url: Optional[str] = base_url
13
37
  self.config: HttpClientActionConfig = config
14
38
 
15
- async def run(self, context: ComponentContext) -> Dict[str, Any]:
39
+ async def run(self, context: ComponentContext) -> Any:
16
40
  url = self._resolve_request_url(context)
17
41
  method = context.render_template(self.config.method)
18
42
  params = context.render_template(self.config.params)
@@ -21,32 +45,41 @@ class HttpClientAction:
21
45
 
22
46
  response = await self._request(url, method, params, body, headers)
23
47
 
24
- if not self.config.output:
25
- return response
26
-
27
48
  if response:
28
49
  context.register_source("response", response)
29
50
 
30
- return context.render_template(self.config.output)
51
+ return context.render_template(self.config.output) if self.config.output else response
31
52
 
32
53
  async def _request(self, url: str, method: str, params: Optional[Dict[str, Any]], body: Optional[Any], headers: Optional[Dict[str, str]]) -> Any:
33
- async with aiohttp.ClientSession() as session:
34
- async with session.request(
54
+ session = aiohttp.ClientSession()
55
+ try:
56
+ response = await session.request(
35
57
  method,
36
58
  url,
37
59
  params=params,
38
60
  data=self._serialize_request_body(body, headers) if body is not None else None,
39
61
  headers=headers
40
- ) as response:
41
- if response.status >= 400:
42
- raise ValueError(f"Request failed with status {response.status}")
62
+ )
63
+
64
+ if response.status >= 400:
65
+ raise ValueError(f"Request failed with status {response.status}")
66
+
67
+ content = await self._parse_response_content(session, response)
43
68
 
44
- return await self._deserialize_response_body(response)
69
+ if isinstance(content, HttpStreamResource):
70
+ pass
71
+ else:
72
+ await session.close()
73
+
74
+ return content
75
+ except:
76
+ await session.close()
77
+ raise
45
78
 
46
79
  def _resolve_request_url(self, context: ComponentContext) -> str:
47
80
  if self.base_url and self.config.path:
48
81
  return context.render_template(str(self.base_url)) + context.render_template(self.config.path)
49
-
82
+
50
83
  return context.render_template(str(self.config.endpoint))
51
84
 
52
85
  def _serialize_request_body(self, body: Any, headers: Optional[Dict[str, str]]) -> Any:
@@ -60,22 +93,19 @@ class HttpClientAction:
60
93
 
61
94
  return json.dumps(body)
62
95
 
63
- async def _deserialize_response_body(self, response: aiohttp.ClientResponse) -> Any:
96
+ async def _parse_response_content(self, session: aiohttp.ClientSession, response: aiohttp.ClientResponse) -> Any:
64
97
  content_type, _ = parse_options_header(response.headers.get("Content-Type", ""))
65
98
 
66
99
  if content_type == "application/json":
67
100
  return await response.json()
68
-
101
+
69
102
  if content_type.startswith("text/"):
70
103
  return await response.text()
71
104
 
72
- _, disposition = parse_options_header(response.headers.get("Content-Disposition"))
105
+ _, disposition = parse_options_header(response.headers.get("Content-Disposition", ""))
106
+ filename = disposition.get("filename")
73
107
 
74
- return {
75
- "filename": disposition.get("filename"),
76
- "content_type": content_type,
77
- "stream": response.content
78
- }
108
+ return HttpStreamResource(session, response.content, filename, content_type)
79
109
 
80
110
  class HttpClientComponent(BaseComponent):
81
111
  def __init__(self, id: str, config: HttpClientComponentConfig, env: Dict[str, str], daemon: bool):
@@ -87,7 +117,7 @@ class HttpClientComponent(BaseComponent):
87
117
  async def _shutdown(self) -> None:
88
118
  pass
89
119
 
90
- async def _run(self, action: ActionConfig, context: ComponentContext) -> Dict[str, Any]:
120
+ async def _run(self, action: ActionConfig, context: ComponentContext) -> Any:
91
121
  return await HttpClientAction(self.config.base_url, action).run(context)
92
122
 
93
123
  ComponentEngineMap[ComponentType.HTTP_CLIENT] = HttpClientComponent
@@ -13,7 +13,7 @@ class HttpServerComponent(BaseComponent):
13
13
  async def _shutdown(self) -> None:
14
14
  pass
15
15
 
16
- async def _run(self, action: ActionConfig, context: ComponentContext) -> Dict[str, Any]:
16
+ async def _run(self, action: ActionConfig, context: ComponentContext) -> Any:
17
17
  return {}
18
18
 
19
19
  ComponentEngineMap[ComponentType.HTTP_SERVER] = HttpServerComponent
@@ -5,15 +5,19 @@ from pydantic import BaseModel
5
5
  from mindor.dsl.schema.controller import HttpServerControllerConfig
6
6
  from mindor.dsl.schema.component import ComponentConfig
7
7
  from mindor.dsl.schema.workflow import WorkflowConfig
8
+ from mindor.core.utils.resources import StreamResource
8
9
  from .base import BaseController, ControllerType, ControllerEngineMap, TaskState
9
10
 
10
11
  from fastapi import FastAPI, APIRouter, Body, HTTPException
11
- import uvicorn
12
+ from fastapi.responses import JSONResponse, StreamingResponse
13
+ from starlette.background import BackgroundTask
14
+ import asyncio, uvicorn
12
15
 
13
16
  class WorkflowTaskRequestBody(BaseModel):
14
17
  workflow_id: Optional[str] = None
15
18
  input: Optional[Dict[str, Any]] = None
16
19
  wait_for_completion: bool = True
20
+ output_only: bool = False
17
21
 
18
22
  class TaskResult(BaseModel):
19
23
  task_id: str
@@ -42,35 +46,60 @@ class HttpServerController(BaseController):
42
46
  self.app.include_router(self.router, prefix=self.config.base_path)
43
47
 
44
48
  def _configure_routes(self):
45
- @self.router.post("/workflows", response_model=TaskResult, response_model_exclude_none=True)
49
+ @self.router.post("/workflows")
46
50
  async def run_workflow(
47
51
  body: WorkflowTaskRequestBody = Body(...)
48
52
  ):
49
53
  state = await self.run_workflow(body.workflow_id, body.input, body.wait_for_completion)
50
54
 
51
- return TaskResult.from_instance(state)
55
+ if body.output_only and body.wait_for_completion:
56
+ if state.status == "failed":
57
+ raise HTTPException(status_code=500, detail=str(state.error))
58
+
59
+ if isinstance(state.output, StreamResource):
60
+ return StreamingResponse(
61
+ state.output,
62
+ media_type=state.output.content_type,
63
+ headers = {
64
+ "Content-Disposition": f"attachment; filename={state.output.filename}"
65
+ } if state.output.filename else {},
66
+ #background=BackgroundTask(lambda: asyncio.create_task(state.output.close()))
67
+ )
52
68
 
53
- @self.router.get("/tasks/{task_id}", response_model=TaskResult, response_model_exclude_none=True)
69
+ return state.output
70
+
71
+ return JSONResponse(content=TaskResult.from_instance(state).model_dump(exclude_none=True))
72
+
73
+ @self.router.get("/tasks/{task_id}")
54
74
  async def get_task_state(
55
- task_id: str
75
+ task_id: str,
76
+ output_only: bool = False
56
77
  ):
57
78
  state = self.get_task_state(task_id)
58
79
 
59
80
  if not state:
60
- raise HTTPException(status_code=404, detail="Task not found")
81
+ raise HTTPException(status_code=404, detail="Task not found.")
61
82
 
62
- return TaskResult.from_instance(state)
83
+ if output_only:
84
+ if state.status in [ "pending", "processing" ]:
85
+ raise HTTPException(status_code=202, detail="Task is still in progress.")
86
+
87
+ if state.status == "failed":
88
+ raise HTTPException(status_code=500, detail=str(state.error))
63
89
 
64
- @self.router.get("/tasks/{task_id}/output")
65
- async def get_task_output(
66
- task_id: str
67
- ):
68
- state = self.get_task_state(task_id)
90
+ if isinstance(state.output, StreamResource):
91
+ return StreamingResponse(
92
+ state.output,
93
+ media_type=state.output.content_type,
94
+ headers = {
95
+ "Content-Disposition": f"attachment; filename={state.output.filename}"
96
+ } if state.output.filename else {},
97
+ # background=BackgroundTask(lambda: asyncio.create_task(state.output.close()))
98
+ )
69
99
 
70
- if not state:
71
- raise HTTPException(status_code=404, detail="Task not found")
72
-
73
- return state.output
100
+ return state.output
101
+
102
+ return JSONResponse(content=TaskResult.from_instance(state).model_dump(exclude_none=True))
74
103
 
75
104
  async def _serve(self) -> None:
76
105
  self.server = uvicorn.Server(uvicorn.Config(
@@ -0,0 +1,28 @@
1
+ from typing import Optional, AsyncIterator
2
+ from abc import ABC, abstractmethod
3
+
4
+ class StreamResource(ABC):
5
+ def __init__(self, content_type: Optional[str], filename: Optional[str]):
6
+ self.content_type = content_type or "application/octet-stream"
7
+ self.filename = filename
8
+
9
+ async def __aenter__(self):
10
+ return self
11
+
12
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
13
+ await self.close()
14
+
15
+ def __aiter__(self):
16
+ return self._iterate_stream()
17
+
18
+ @abstractmethod
19
+ def get_stream(self):
20
+ pass
21
+
22
+ @abstractmethod
23
+ async def close(self):
24
+ pass
25
+
26
+ @abstractmethod
27
+ async def _iterate_stream(self) -> AsyncIterator[bytes]:
28
+ pass
@@ -11,17 +11,17 @@ class Job:
11
11
  self.config: JobConfig = config
12
12
  self.component_provider: Callable[[Union[ComponentConfig, str]], BaseComponent] = component_provider
13
13
 
14
- async def run(self, context: WorkflowContext) -> Dict[str, Any]:
14
+ async def run(self, context: WorkflowContext) -> Any:
15
15
  component = self.component_provider(self.config.component)
16
16
 
17
17
  if not component.started:
18
18
  await component.start()
19
19
 
20
20
  call_id = ulid.ulid()
21
- input = context.render_template(self.config.input)
21
+ input = context.render_template(self.config.input) if self.config.output else context.input
22
22
  output = await component.run(self.config.action, call_id, input)
23
23
 
24
- context.register_source("output", output)
25
- output = context.render_template(self.config.output)
24
+ if output:
25
+ context.register_source("output", output)
26
26
 
27
- return output
27
+ return context.render_template(self.config.output) if self.config.output else output
@@ -80,12 +80,12 @@ class WorkflowRunner:
80
80
  self.jobs: Dict[str, JobConfig] = jobs
81
81
  self.component_provider: Callable[[Union[ComponentConfig, str]], BaseComponent] = component_provider
82
82
 
83
- async def run(self, context: WorkflowContext) -> Dict[str, Any]:
83
+ async def run(self, context: WorkflowContext) -> Any:
84
84
  pending_jobs: Dict[str, Job] = { job_id: Job(job_id, job, self.component_provider) for job_id, job in self.jobs.items() }
85
85
  running_job_ids: Set[str] = set()
86
86
  completed_job_ids: Set[str] = set()
87
87
  scheduled_job_tasks: Dict[str, asyncio.Task] = {}
88
- output: Dict[str, Any] = {}
88
+ output: Any = {}
89
89
 
90
90
  while pending_jobs:
91
91
  runnable_jobs = [ job for job in pending_jobs.values() if self._can_run_job(job, running_job_ids, completed_job_ids) ]
@@ -107,7 +107,8 @@ class WorkflowRunner:
107
107
  context.complete_job(completed_job_id, completed_job_output)
108
108
 
109
109
  if self._is_terminal_job(completed_job_id):
110
- output.update(completed_job_output)
110
+ output = completed_job_output
111
+ # output.update(completed_job_output)
111
112
 
112
113
  running_job_ids.remove(completed_job_id)
113
114
  completed_job_ids.add(completed_job_id)
@@ -129,7 +130,7 @@ class Workflow:
129
130
  self.components: Dict[str, ComponentConfig] = components
130
131
  self.env: Dict[str, str] = env
131
132
 
132
- async def run(self, task_id: str, input: Dict[str, Any]) -> Dict[str, Any]:
133
+ async def run(self, task_id: str, input: Dict[str, Any]) -> Any:
133
134
  context = WorkflowContext(task_id, input, self.env)
134
135
  runner = WorkflowRunner(self.config.jobs, self._create_component)
135
136
 
@@ -1,5 +1,5 @@
1
1
  from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
- from pydantic import BaseModel, Field, HttpUrl
2
+ from pydantic import BaseModel, Field
3
3
  from pydantic import model_validator
4
4
  from .common import CommonActionConfig
5
5
 
@@ -12,7 +12,7 @@ class CallbackCompletionConfig(CommonCompletionConfig):
12
12
 
13
13
  class PollingCompletionConfig(CommonCompletionConfig):
14
14
  type: Literal["polling"]
15
- endpoint: Optional[HttpUrl] = None
15
+ endpoint: Optional[str] = None
16
16
  path: Optional[str] = None
17
17
  method: Literal[ "GET", "POST", "PUT", "DELETE", "PATCH" ] = "GET"
18
18
  headers: Optional[Dict[str, str]] = Field(default_factory=dict)
@@ -36,7 +36,7 @@ HttpClientCompletionConfig = Annotated[
36
36
  ]
37
37
 
38
38
  class HttpClientActionConfig(CommonActionConfig):
39
- endpoint: Optional[HttpUrl] = None
39
+ endpoint: Optional[str] = None
40
40
  path: Optional[str] = None
41
41
  method: Literal[ "GET", "POST", "PUT", "DELETE", "PATCH" ] = "POST"
42
42
  headers: Optional[Dict[str, str]] = Field(default_factory=dict)
@@ -1,5 +1,5 @@
1
1
  from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
- from pydantic import BaseModel, Field, HttpUrl
2
+ from pydantic import BaseModel, Field
3
3
  from pydantic import model_validator
4
4
  from .common import CommonActionConfig
5
5
 
@@ -1,5 +1,5 @@
1
1
  from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
- from pydantic import BaseModel, Field, HttpUrl
2
+ from pydantic import BaseModel, Field
3
3
  from pydantic import model_validator
4
4
  from .common import CommonActionConfig
5
5
 
@@ -1,8 +1,8 @@
1
1
  from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
- from pydantic import BaseModel, Field, HttpUrl
2
+ from pydantic import BaseModel, Field
3
3
  from pydantic import model_validator
4
4
  from .common import CommonActionConfig
5
5
 
6
6
  class McpServerActionConfig(CommonActionConfig):
7
7
  def __init__(self):
8
- super.__init__(self)
8
+ super().__init__()
@@ -1,5 +1,5 @@
1
- from pydantic import BaseModel, Field, HttpUrl
2
1
  from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
+ from pydantic import BaseModel, Field
3
3
  from .impl import *
4
4
 
5
5
  ComponentConfig = Annotated[
@@ -1,5 +1,5 @@
1
1
  from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
- from pydantic import BaseModel, Field, HttpUrl
2
+ from pydantic import BaseModel, Field
3
3
  from .types import ComponentType
4
4
 
5
5
  class CommonComponentConfig(BaseModel):
@@ -1,12 +1,12 @@
1
1
  from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
- from pydantic import BaseModel, Field, HttpUrl
2
+ from pydantic import BaseModel, Field
3
3
  from pydantic import model_validator
4
4
  from mindor.dsl.schema.action import HttpClientActionConfig
5
5
  from .common import ComponentType, CommonComponentConfig
6
6
 
7
7
  class HttpClientComponentConfig(CommonComponentConfig):
8
8
  type: Literal[ComponentType.HTTP_CLIENT]
9
- base_url: Optional[HttpUrl] = None
9
+ base_url: Optional[str] = None
10
10
  headers: Optional[Dict[str, Any]] = Field(default_factory=dict)
11
11
  actions: Optional[Dict[str, HttpClientActionConfig]] = Field(default_factory=dict)
12
12
 
@@ -1,5 +1,5 @@
1
1
  from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
- from pydantic import BaseModel, Field, HttpUrl
2
+ from pydantic import BaseModel, Field
3
3
  from pydantic import model_validator
4
4
  from mindor.dsl.schema.action import HttpServerActionConfig
5
5
  from .common import ComponentType, CommonComponentConfig
@@ -1,10 +1,10 @@
1
1
  from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
- from pydantic import BaseModel, Field, HttpUrl
2
+ from pydantic import BaseModel, Field
3
3
  from pydantic import model_validator
4
4
  from mindor.dsl.schema.action import McpClientActionConfig
5
5
  from .common import ComponentType, CommonComponentConfig
6
6
 
7
7
  class McpClientComponentConfig(CommonComponentConfig):
8
8
  type: Literal[ComponentType.MCP_CLIENT]
9
- endpoint: HttpUrl
9
+ endpoint: Optional[str] = None
10
10
  actions: Optional[Dict[str, McpClientActionConfig]] = Field(default_factory=dict)
@@ -1,5 +1,5 @@
1
1
  from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
- from pydantic import BaseModel, Field, HttpUrl
2
+ from pydantic import BaseModel, Field
3
3
  from pydantic import model_validator
4
4
  from mindor.dsl.schema.action import McpServerActionConfig
5
5
  from .common import ComponentType, CommonComponentConfig
@@ -1,5 +1,5 @@
1
1
  from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
- from pydantic import BaseModel, Field, HttpUrl
2
+ from pydantic import BaseModel, Field
3
3
  from .impl import *
4
4
 
5
5
  ControllerConfig = Annotated[
@@ -1,5 +1,5 @@
1
1
  from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
- from pydantic import BaseModel, Field, HttpUrl
2
+ from pydantic import BaseModel, Field
3
3
  from pydantic import model_validator
4
4
  from .common import ControllerType, CommonControllerConfig
5
5
 
@@ -1,5 +1,5 @@
1
1
  from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
- from pydantic import BaseModel, Field, HttpUrl
2
+ from pydantic import BaseModel, Field
3
3
  from pydantic import model_validator
4
4
  from .common import ControllerType, CommonControllerConfig
5
5
 
@@ -1,5 +1,5 @@
1
1
  from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
- from pydantic import BaseModel, Field, HttpUrl
2
+ from pydantic import BaseModel, Field
3
3
  from pydantic import model_validator
4
4
  from .common import ListenerType, CommonListenerConfig
5
5
 
@@ -1,5 +1,5 @@
1
1
  from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
- from pydantic import BaseModel, Field, HttpUrl
2
+ from pydantic import BaseModel, Field
3
3
  from .impl import *
4
4
 
5
5
  ListenerConfig = Annotated[
@@ -1,5 +1,5 @@
1
1
  from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
- from pydantic import BaseModel, Field, HttpUrl
2
+ from pydantic import BaseModel, Field
3
3
  from pydantic import model_validator
4
4
  from .common import RuntimeType, CommonRumtimeConfig
5
5
 
@@ -1,5 +1,5 @@
1
1
  from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
- from pydantic import BaseModel, Field, HttpUrl
2
+ from pydantic import BaseModel, Field
3
3
  from .impl import *
4
4
 
5
5
  RuntimeConfig = Annotated[
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: model-compose
3
- Version: 0.0.5
3
+ Version: 0.0.6
4
4
  Summary: model-compose: Declarative AI Model and Workflow Orchestrator (from Mindor)
5
5
  Author-email: Hanyeol Cho <hanyeol.cho@gmail.com>
6
6
  License-Expression: MIT
@@ -31,6 +31,7 @@ src/mindor/core/runtime/env.py
31
31
  src/mindor/core/utils/__init__.py
32
32
  src/mindor/core/utils/expiring.py
33
33
  src/mindor/core/utils/http_headers.py
34
+ src/mindor/core/utils/resources.py
34
35
  src/mindor/core/utils/template.py
35
36
  src/mindor/core/utils/workqueue.py
36
37
  src/mindor/core/workflow/__init__.py
File without changes
File without changes
File without changes