alayaflow 0.1.0__py3-none-any.whl

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 (37) hide show
  1. alayaflow/__init__.py +5 -0
  2. alayaflow/api/__init__.py +5 -0
  3. alayaflow/api/api_singleton.py +81 -0
  4. alayaflow/clients/alayamem/base_client.py +19 -0
  5. alayaflow/clients/alayamem/http_client.py +64 -0
  6. alayaflow/common/config.py +106 -0
  7. alayaflow/component/__init__.py +0 -0
  8. alayaflow/component/chat_model.py +20 -0
  9. alayaflow/component/intent_classifier.py +94 -0
  10. alayaflow/component/langflow/__init__.py +0 -0
  11. alayaflow/component/langflow/intent_classifier.py +83 -0
  12. alayaflow/component/llm_node.py +123 -0
  13. alayaflow/component/memory.py +50 -0
  14. alayaflow/component/retrieve_node.py +17 -0
  15. alayaflow/component/web_search.py +126 -0
  16. alayaflow/execution/__init__.py +6 -0
  17. alayaflow/execution/env_manager.py +424 -0
  18. alayaflow/execution/executor_manager.py +59 -0
  19. alayaflow/execution/executors/__init__.py +9 -0
  20. alayaflow/execution/executors/base_executor.py +9 -0
  21. alayaflow/execution/executors/naive_executor.py +121 -0
  22. alayaflow/execution/executors/uv_executor.py +125 -0
  23. alayaflow/execution/executors/worker_executor.py +12 -0
  24. alayaflow/execution/langfuse_tracing.py +104 -0
  25. alayaflow/execution/workflow_runner.py +98 -0
  26. alayaflow/utils/singleton.py +14 -0
  27. alayaflow/workflow/__init__.py +6 -0
  28. alayaflow/workflow/runnable/__init__.py +7 -0
  29. alayaflow/workflow/runnable/base_runnable_workflow.py +19 -0
  30. alayaflow/workflow/runnable/state_graph_runnable_workflow.py +23 -0
  31. alayaflow/workflow/workflow_info.py +50 -0
  32. alayaflow/workflow/workflow_loader.py +168 -0
  33. alayaflow/workflow/workflow_manager.py +257 -0
  34. alayaflow-0.1.0.dist-info/METADATA +99 -0
  35. alayaflow-0.1.0.dist-info/RECORD +37 -0
  36. alayaflow-0.1.0.dist-info/WHEEL +4 -0
  37. alayaflow-0.1.0.dist-info/licenses/LICENSE +661 -0
@@ -0,0 +1,17 @@
1
+ from urllib import request
2
+ from alayaflow.clients.alayamem.http_client import HttpAlayaMemClient
3
+
4
+
5
+ class RetrieveComponent:
6
+ def __init__(self, client: HttpAlayaMemClient):
7
+ self.client = client
8
+
9
+ def __call__(self, query: str, collection_name: str, limit: int = 3) -> list[str]:
10
+ result = self.client.vdb_query([query], limit, collection_name)
11
+ return result.get('documents', [[]])[0] if result.get('documents') else []
12
+
13
+ if __name__ == "__main__":
14
+ client = HttpAlayaMemClient("http://10.16.70.46:5555")
15
+ res = client.vdb_query(messages="姓名", limit=5, collection_name="file_watcher_collection")
16
+
17
+ print(res)
@@ -0,0 +1,126 @@
1
+ from platform import java_ver
2
+ from langgraph.graph import StateGraph
3
+ from langgraph.graph.state import RunnableConfig
4
+ from pydantic import BaseModel
5
+ import requests
6
+ from typing import Annotated, Type, TypedDict, Optional
7
+ # from langchain_core.annotations import InjectedToolArg
8
+
9
+ from langchain_openai import ChatOpenAI
10
+
11
+ from alayaflow.common.config import settings
12
+
13
+ from langgraph.graph import START, END
14
+ from langchain.tools import tool
15
+
16
+ """
17
+ 结点参数
18
+ - 每个结点使用的时候有固定的参数
19
+ - 1. 工作流定义中传入
20
+ - 包含结点的工作流创建的时候有全局变量,来配置工作流
21
+ - 2. 工作流创建时输入的全局变量
22
+ - 结点运行时有输入参数
23
+ - 3. 工作流运行时输入 (来自用户)
24
+ - 4. 运行时上游结点的输出
25
+
26
+ 调用方式
27
+ - 放入langgraph中作单独结点
28
+ - 放入langflow component实现中
29
+ - ~放入LLM结点作tools~
30
+ """
31
+
32
+ # Core
33
+
34
+ def search(query, url, item_cnt, api_key):
35
+ # return requests.get(
36
+ # f"{url}/search",
37
+ # headers={"Authorization": f"Bearer {search_api_key}"},
38
+ # params={"query": query, "item": return_item_cnt}
39
+ # ).json()
40
+
41
+ # Fake run
42
+ return f"Get request from {url} with headers={{'Authorization': 'Bearer {api_key}'}} and params={{'query': {query}, 'item': {item_cnt}}}"
43
+
44
+ # @tool
45
+ # def search_as_tool(
46
+ # query: str,
47
+ # # 告诉 LangChain:这些参数不要给 LLM 看,由代码注入
48
+ # config: Annotated[dict, InjectedToolArg]
49
+ # ):
50
+ # """搜索工具,用于获取特定信息。"""
51
+ # # 从注入的 config 中提取配置
52
+ # return search(
53
+ # query=query,
54
+ # url=config["url"],
55
+ # api_key=config["api_key"],
56
+ # item_cnt=config.get("item_cnt", 3)
57
+ # )
58
+
59
+
60
+ # Workflow developer layer
61
+
62
+ class MyState(BaseModel):
63
+ query: str
64
+ search_result: Optional[str] = None
65
+
66
+ # class SearchNode:
67
+ # def __init__(self, key: str):
68
+ # self.key = key
69
+
70
+ # def __call__(self, state: MyState):
71
+ # new_state = state.model_copy()
72
+ # new_state.search_result = search(
73
+ # state.query,
74
+ # "https://xxx", # 1
75
+ # 3, # 1
76
+ # self.key, # 2
77
+ # )
78
+ # return new_state
79
+
80
+
81
+ def create_search_node(api_key: str):
82
+
83
+ def search_node(state: MyState, config: RunnableConfig):
84
+ result = search(
85
+ query=state.query,
86
+ url=config["configurable"]["search_url"],
87
+ api_key=config["configurable"]["search_api_key"],
88
+ item_cnt=3,
89
+ )
90
+ return {"search_result": result}
91
+ return search_node
92
+
93
+
94
+ def create_graph(search_api_key):
95
+ builder = StateGraph(MyState)
96
+
97
+ # 添加节点
98
+ builder.add_node("search", create_search_node(search_api_key))
99
+
100
+ # 定义边
101
+ builder.add_edge(START, "search")
102
+ builder.add_edge("search", END)
103
+
104
+ return builder.compile()
105
+
106
+ class WFConfig(TypedDict):
107
+ search_api_key: str
108
+ search_url: str
109
+
110
+ def get_config_schema() -> Type[TypedDict]:
111
+ return WFConfig
112
+
113
+
114
+ graph = create_graph(search_api_key="xxx") # 2
115
+ result = graph.invoke({
116
+ "query": "唐博", # 3
117
+ }, config={
118
+ "configurable": WFConfig(
119
+ search_api_key="abc",
120
+ search_url="https://xxxx",
121
+ )
122
+ })
123
+
124
+
125
+ print(f"result: {result}")
126
+
@@ -0,0 +1,6 @@
1
+ from .executor_manager import ExecutorManager, ExecutorType
2
+
3
+ __all__ = [
4
+ "ExecutorManager",
5
+ "ExecutorType"
6
+ ]
@@ -0,0 +1,424 @@
1
+ import os
2
+ import sys
3
+ import json
4
+ import shutil
5
+ import subprocess
6
+ import hashlib
7
+ from pathlib import Path
8
+ from typing import List, Dict, Optional, Set, Tuple
9
+
10
+ from alayaflow.common.config import settings
11
+
12
+
13
+ class EnvManager:
14
+ """
15
+ 使用 uv (Rust-based tool) 替代 venv + pip。
16
+ 提供极速的环境创建和 Python 版本管理。
17
+ 增强功能:根据requirements自动匹配现有环境
18
+ """
19
+
20
+ def __init__(self):
21
+ self.base_dir = settings.envs_dir
22
+ if not os.path.exists(self.base_dir):
23
+ os.makedirs(self.base_dir)
24
+
25
+ # 检查 uv 是否存在
26
+ if shutil.which("uv") is None:
27
+ print("[Warning] 'uv' command not found! Please install uv first.")
28
+ # 在实际桌面应用中,你应该将 uv 的二进制文件打包在你的应用里,
29
+ # 并在这里指定绝对路径,例如: self.uv_bin = "./bin/uv.exe"
30
+ self.uv_bin = "uv"
31
+ else:
32
+ self.uv_bin = "uv"
33
+
34
+ # 元数据存储
35
+ self.metadata_file = os.path.join(self.base_dir, "envs_metadata.json")
36
+ self.env_metadata = self._load_metadata()
37
+
38
+ # 初始化时扫描所有现有环境并更新包信息
39
+ self._scan_existing_envs()
40
+
41
+ def _load_metadata(self) -> Dict:
42
+ """加载环境元数据"""
43
+ if os.path.exists(self.metadata_file):
44
+ try:
45
+ with open(self.metadata_file, 'r') as f:
46
+ return json.load(f)
47
+ except json.JSONDecodeError:
48
+ return {}
49
+ return {}
50
+
51
+ def _save_metadata(self):
52
+ """保存环境元数据"""
53
+ with open(self.metadata_file, 'w') as f:
54
+ json.dump(self.env_metadata, f, indent=2)
55
+
56
+ # def get_venv_path(self, workflow_id: str, version: str) -> str:
57
+ # return os.path.join(self.base_dir, f"{workflow_id}_{version}")
58
+
59
+ def get_venv_path_by_name(self, env_name: str) -> str:
60
+ return os.path.join(self.base_dir, env_name)
61
+
62
+ def get_python_executable(self, env_path: str) -> str:
63
+ # uv 创建的 venv 结构和标准 venv 一样
64
+ if sys.platform == "win32":
65
+ return os.path.join(env_path, "Scripts", "python.exe")
66
+ return os.path.join(env_path, "bin", "python")
67
+
68
+ def _get_python_version(self, env_path: str) -> str:
69
+ """使用subprocess调用python print获取环境的Python版本"""
70
+ python_exe = self.get_python_executable(env_path)
71
+ try:
72
+ cmd = [python_exe, "-c", "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"]
73
+ result = subprocess.run(cmd, capture_output=True, text=True, check=True)
74
+ return result.stdout.strip()
75
+ except:
76
+ return "unknown"
77
+
78
+ def _scan_existing_envs(self):
79
+ """扫描现有环境(子目录存在且有python_exe)并更新包信息"""
80
+ print("[UV] Scanning existing environments...")
81
+ for env_dir in os.listdir(self.base_dir):
82
+ # 跳过元数据文件
83
+ if env_dir == "envs_metadata.json":
84
+ continue
85
+ env_path = os.path.join(self.base_dir, env_dir)
86
+ # todo: consider if the following logic is clear. Seems not good
87
+ if os.path.isdir(env_path):
88
+ # 检查是否是有效的虚拟环境
89
+ python_exe = self.get_python_executable(env_path)
90
+ if os.path.exists(python_exe):
91
+ if env_dir not in self.env_metadata:
92
+ # 新发现的环境,生成包信息
93
+ print(f"[UV] Found new environment: {env_dir}")
94
+ self._update_env_packages(env_dir)
95
+ self._save_metadata()
96
+
97
+ def _update_env_packages(self, env_name: str):
98
+ """更新指定环境的包信息"""
99
+ env_path = self.get_venv_path_by_name(env_name)
100
+
101
+ try:
102
+ # 获取已安装的包列表
103
+ cmd = [self.uv_bin, "pip", "freeze", "-p", env_path]
104
+ result = subprocess.run(cmd, capture_output=True, text=True, check=True)
105
+
106
+ packages = {}
107
+ for line in result.stdout.strip().split('\n'):
108
+ if line and '==' in line:
109
+ name, version = line.split('==', 1)
110
+ packages[name] = version
111
+
112
+ # 更新元数据
113
+ self.env_metadata[env_name] = {
114
+ "path": env_path,
115
+ "packages": packages,
116
+ "python_version": self._get_python_version(env_path),
117
+ # "requirements": [] # 初始安装的requirements(可选的)
118
+ }
119
+
120
+ print(f"[UV] Updated packages for {env_name}: {len(packages)} packages")
121
+
122
+ except subprocess.CalledProcessError as e:
123
+ print(f"[UV] Failed to scan packages for {env_name}: {e}")
124
+
125
+ def _parse_requirement(self, req_str: str) -> Tuple[str, str]:
126
+ """解析单个requirement字符串,返回包名和版本约束"""
127
+ # 移除注释和空格
128
+ req_str = req_str.split('#')[0].strip()
129
+ if not req_str:
130
+ return None, None
131
+
132
+ # 解析包名和版本
133
+ import re
134
+ # 匹配包名(允许字母、数字、点、下划线、连字符)
135
+ name_match = re.match(r'^([a-zA-Z0-9._-]+)', req_str)
136
+ if not name_match:
137
+ return None, None
138
+
139
+ name = name_match.group(1).lower()
140
+ version_spec = req_str[len(name):].strip()
141
+
142
+ # 标准化版本约束
143
+ if version_spec:
144
+ # 移除多余的空格
145
+ version_spec = version_spec.replace(' ', '')
146
+ else:
147
+ version_spec = "any" # 未指定版本
148
+
149
+ return name, version_spec
150
+
151
+ def _check_package_compatibility(self, installed_ver: str, required_spec: str) -> bool:
152
+ """检查已安装版本是否满足要求"""
153
+ if required_spec == "any":
154
+ return True
155
+
156
+ from packaging.version import parse, Version
157
+ from packaging.specifiers import SpecifierSet
158
+
159
+ try:
160
+ # 解析已安装版本
161
+ installed = parse(installed_ver)
162
+ if not isinstance(installed, Version):
163
+ return False
164
+
165
+ # 解析版本要求
166
+ specifier = SpecifierSet(required_spec)
167
+ return installed in specifier
168
+
169
+ except Exception as e:
170
+ print(f"[EM] Version check error: {e}")
171
+ return False
172
+ # 如果解析失败,进行字符串比较作为后备
173
+ # try:
174
+ # # 简单的比较逻辑
175
+ # if required_spec.startswith("=="):
176
+ # return installed_ver == required_spec[2:]
177
+ # elif required_spec.startswith(">="):
178
+ # return installed_ver >= required_spec[2:]
179
+ # elif required_spec.startswith("<="):
180
+ # return installed_ver <= required_spec[2:]
181
+ # elif required_spec.startswith(">"):
182
+ # return installed_ver > required_spec[1:]
183
+ # elif required_spec.startswith("<"):
184
+ # return installed_ver < required_spec[1:]
185
+ # elif required_spec.startswith("~="):
186
+ # # 兼容版本:近似等于
187
+ # base_version = parse(required_spec[2:])
188
+ # installed_version = parse(installed_ver)
189
+ # return (installed_version >= base_version and
190
+ # installed_version < base_version.next_minor())
191
+ # except:
192
+ # return False
193
+ #
194
+ # return True
195
+
196
+ def find_matching_env(self, requirements: List[str], python_version: str = None) -> Optional[str]:
197
+ """
198
+ 查找满足requirements的现有环境
199
+
200
+ Args:
201
+ requirements: 包要求列表
202
+ python_version: 可选的Python版本要求
203
+
204
+ Returns:
205
+ 匹配的环境名称,或None
206
+ """
207
+ if not requirements:
208
+ # 如果没有要求,返回第一个环境
209
+ return next(iter(self.env_metadata.keys()), None)
210
+
211
+ print(f"[EM] Looking for environment matching {len(requirements)} requirements...")
212
+
213
+ for env_name, metadata in self.env_metadata.items():
214
+ # 检查Python版本
215
+ if python_version and metadata.get("python_version") != python_version:
216
+ continue
217
+
218
+ env_packages = metadata.get("packages", {})
219
+ all_satisfied = True
220
+
221
+ for req in requirements:
222
+ if not req or req.startswith("#"):
223
+ continue
224
+
225
+ pkg_name, version_spec = self._parse_requirement(req)
226
+ if not pkg_name:
227
+ continue
228
+
229
+ # 检查包是否安装
230
+ if pkg_name not in env_packages:
231
+ all_satisfied = False
232
+ break
233
+
234
+ # 检查版本是否满足
235
+ installed_ver = env_packages[pkg_name]
236
+ if not self._check_package_compatibility(installed_ver, version_spec):
237
+ all_satisfied = False
238
+ break
239
+
240
+ if all_satisfied:
241
+ print(f"[EM] Found matching environment: {env_name}")
242
+ return env_name
243
+
244
+ print("[EM] No matching environment found")
245
+ return None
246
+
247
+ def create_new_env(self, env_name: str, python_version: str = "3.12") -> str:
248
+ """创建新的虚拟环境"""
249
+ env_path = self.get_venv_path_by_name(env_name)
250
+ python_exe = self.get_python_executable(env_path)
251
+
252
+ if not os.path.exists(python_exe):
253
+ print(f"[UV] Creating new VENV: {env_name}...")
254
+ try:
255
+ subprocess.check_call(
256
+ [self.uv_bin, "venv", env_path, "--python", python_version],
257
+ stdout=subprocess.DEVNULL
258
+ )
259
+ print(f"[UV] Venv created with Python {python_version}")
260
+ except subprocess.CalledProcessError as e:
261
+ print(f"[UV] Venv creation failed: {e}")
262
+ raise
263
+
264
+ return env_path
265
+
266
+ def ensure_env(self, workflow_id: str, version: str, requirements: List[str], use_venv: bool) -> str:
267
+ """
268
+ 使用 uv 准备环境
269
+ 先尝试匹配现有环境,如无匹配则创建新环境
270
+ """
271
+ if not use_venv:
272
+ return sys.executable
273
+
274
+ # 1. 首先尝试查找匹配的现有环境
275
+ env_name = self.find_matching_env(requirements)
276
+
277
+ if env_name:
278
+ # 使用现有环境
279
+ env_path = self.get_venv_path_by_name(env_name)
280
+ python_exe = self.get_python_executable(env_path)
281
+
282
+ print(f"[EM] Reusing existing environment: {env_name}")
283
+
284
+ # 检查是否安装了alayaflow
285
+ alayaflow_marker = os.path.join(env_path, "alayaflow_installed.marker")
286
+ if not os.path.exists(alayaflow_marker):
287
+ self._install_alayaflow(env_path)
288
+
289
+ return python_exe
290
+
291
+ # 2. 没有匹配的环境,创建新环境
292
+ import uuid
293
+ # todo: use function create_new_env instead?
294
+ # env_name = f"{workflow_id}_{version}"
295
+ # env_path = self.get_venv_path(workflow_id, version)
296
+ env_name=uuid.uuid4().hex
297
+ env_path = self.get_venv_path_by_name(env_name)
298
+ python_exe = self.get_python_executable(env_path)
299
+
300
+ # 1. 创建虚拟环境 (uv venv)
301
+ # 优势:如果指定了 python 版本,uv 会自动下载便携版,不需要系统预装
302
+ if not os.path.exists(python_exe):
303
+ print(f"[UV] Creating VENV for {workflow_id} v{version}...")
304
+ try:
305
+ # 假设我们想让这个环境使用 Python 3.12 (即使主程序是 3.9)
306
+ # 实际中你可以把 python_version 放在元数据里传进来
307
+ target_python = "3.12"
308
+
309
+ subprocess.check_call(
310
+ [self.uv_bin, "venv", env_path, "--python", target_python],
311
+ stdout=subprocess.DEVNULL
312
+ )
313
+ print(f"[UV] Venv created with Python {target_python}")
314
+ except subprocess.CalledProcessError as e:
315
+ print(f"[UV] Venv creation failed: {e}")
316
+ raise
317
+
318
+ # 2. 安装依赖 (uv pip install)
319
+ if requirements:
320
+ self._install_requirements(env_path, requirements)
321
+
322
+ # 安装 alayaflow
323
+ self._install_alayaflow(env_path)
324
+
325
+ # 更新环境元数据
326
+ self._update_env_packages(env_name)
327
+
328
+ return python_exe
329
+
330
+ def _install_requirements(self, env_path: str, requirements: List[str]):
331
+ """安装依赖"""
332
+ marker_file = os.path.join(env_path, "deps_installed.marker")
333
+ if not os.path.exists(marker_file):
334
+ print(f"[UV] Installing deps: {requirements}")
335
+ try:
336
+ cmd = [self.uv_bin, "pip", "install", "-p", env_path] + requirements
337
+
338
+ subprocess.check_call(
339
+ cmd,
340
+ stdout=subprocess.DEVNULL,
341
+ stderr=subprocess.PIPE
342
+ )
343
+ with open(marker_file, "w") as f:
344
+ f.write("ok")
345
+ print("[UV] Deps installed.")
346
+
347
+ except subprocess.CalledProcessError as e:
348
+ print(f"[UV] Install failed: {e}")
349
+ raise
350
+
351
+ def _install_alayaflow(self, env_path: str):
352
+ """安装alayaflow"""
353
+ # todo: Why this appear?
354
+ alayaflow_marker = os.path.join(env_path, "alayaflow_installed.marker")
355
+ if not os.path.exists(alayaflow_marker):
356
+ print("[UV] Installing alayaflow in worker env...")
357
+ try:
358
+ if settings.is_dev():
359
+ # dev模式:可编辑安装
360
+ subprocess.check_call(
361
+ [self.uv_bin, "pip", "install", "-p", env_path, "-e", str(settings.alayaflow_root)]
362
+ )
363
+ elif settings.is_uneditable_dev():
364
+ # dev-uneditable:从项目根目录安装(非可编辑模式)
365
+ print(f"[UV] Installing alayaflow from: {settings.alayaflow_root}")
366
+ subprocess.check_call(
367
+ [self.uv_bin, "pip", "install", "-p", env_path, str(settings.alayaflow_root)],
368
+ stdout=subprocess.DEVNULL,
369
+ stderr=subprocess.PIPE,
370
+ )
371
+ elif settings.is_prod():
372
+ # prod模式:从PyPI安装
373
+ subprocess.check_call(
374
+ [self.uv_bin, "pip", "install", "-p", env_path, "alayaflow"],
375
+ stdout=subprocess.DEVNULL,
376
+ stderr=subprocess.PIPE
377
+ )
378
+ else:
379
+ raise ValueError(f"Unknown dev_mode: {settings.dev_mode}")
380
+ with open(alayaflow_marker, "w") as f:
381
+ f.write("ok")
382
+ print("[UV] Alayaflow installed.")
383
+ except subprocess.CalledProcessError as e:
384
+ print(f"[UV] Alayaflow installation failed: {e}")
385
+ raise
386
+
387
+ def list_environments(self) -> List[Dict]:
388
+ """列出所有环境及其信息"""
389
+ envs = []
390
+ for env_name, metadata in self.env_metadata.items():
391
+ env_info = {
392
+ "name": env_name,
393
+ "path": metadata.get("path", ""),
394
+ "python_version": metadata.get("python_version", "unknown"),
395
+ "package_count": len(metadata.get("packages", {})),
396
+ }
397
+ envs.append(env_info)
398
+ return envs
399
+
400
+ # def clean_orphaned_envs(self):
401
+ # # 不想开放这个功能
402
+ # """清理元数据中不存在的环境"""
403
+ # env_dirs = os.listdir(self.base_dir)
404
+ #
405
+ # # 找出元数据中有但实际不存在的环境
406
+ # to_remove = []
407
+ # for env_name in list(self.env_metadata.keys()):
408
+ # env_path = self.get_venv_path_by_name(env_name)
409
+ # if not os.path.exists(env_path):
410
+ # to_remove.append(env_name)
411
+ #
412
+ # # 清理元数据
413
+ # for env_name in to_remove:
414
+ # # del self.env_metadata[env_name]
415
+ # print(f"[UV] Remove orphaned metadata for {env_name}")
416
+ #
417
+ # if to_remove:
418
+ # self._save_metadata()
419
+
420
+ def get_env_info(self, env_name: str) -> Optional[Dict]:
421
+ """获取环境的详细信息"""
422
+ if env_name in self.env_metadata:
423
+ return self.env_metadata[env_name]
424
+ return None
@@ -0,0 +1,59 @@
1
+ from typing import Generator, Dict, Optional
2
+ from enum import Enum
3
+
4
+ from alayaflow.execution.executors.base_executor import BaseExecutor
5
+ from alayaflow.execution.executors.uv_executor import UvExecutor
6
+ from alayaflow.execution.executors.naive_executor import NaiveExecutor
7
+ from alayaflow.execution.executors.worker_executor import WorkerExecutor
8
+ from alayaflow.workflow import WorkflowManager
9
+
10
+
11
+ class ExecutorType(Enum):
12
+ UV = "uv"
13
+ NAIVE = "naive"
14
+ WORKER = "worker"
15
+
16
+
17
+ class ExecutorManager:
18
+ def __init__(
19
+ self,
20
+ workflow_manager: WorkflowManager
21
+ ):
22
+ self._workflow_manager = workflow_manager
23
+ self._executor_map: Dict[ExecutorType, BaseExecutor] = {}
24
+ self._initialize_executors()
25
+
26
+ def _initialize_executors(self):
27
+ """Initialize all available executors."""
28
+ self._executor_map[ExecutorType.UV] = UvExecutor(
29
+ workflow_manager=self._workflow_manager
30
+ )
31
+ self._executor_map[ExecutorType.NAIVE] = NaiveExecutor(
32
+ workflow_manager=self._workflow_manager
33
+ )
34
+ self._executor_map[ExecutorType.WORKER] = WorkerExecutor(
35
+ workflow_manager=self._workflow_manager
36
+ )
37
+
38
+ def init_workflow(self, workflow_id: str, version: str, init_args: dict):
39
+ for executor in self._executor_map.values():
40
+ executor.init_workflow(workflow_id, version, init_args)
41
+
42
+ def exec_workflow(
43
+ self,
44
+ workflow_id: str,
45
+ version: str,
46
+ input_data: dict,
47
+ user_config: dict,
48
+ executor_type: ExecutorType | str = ExecutorType.NAIVE
49
+ ) -> Generator[Dict, None, None]:
50
+ if isinstance(executor_type, str):
51
+ executor_type = ExecutorType(executor_type)
52
+ if executor_type not in self._executor_map:
53
+ raise ValueError(
54
+ f"Unsupported executor kind: {executor_type}. "
55
+ f"Supported kinds: {list(self._executor_map.keys())}"
56
+ )
57
+ executor = self._executor_map[executor_type]
58
+ yield from executor.execute_stream(workflow_id, version, input_data, user_config)
59
+
@@ -0,0 +1,9 @@
1
+ """Executor implementations for workflow execution."""
2
+
3
+ from alayaflow.execution.executors.base_executor import BaseExecutor
4
+ from alayaflow.execution.executors.uv_executor import UvExecutor
5
+ from alayaflow.execution.executors.naive_executor import NaiveExecutor
6
+ from alayaflow.execution.executors.worker_executor import WorkerExecutor
7
+
8
+ __all__ = ["BaseExecutor", "UvExecutor", "NaiveExecutor", "WorkerExecutor"]
9
+
@@ -0,0 +1,9 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Generator, Dict
3
+
4
+
5
+ class BaseExecutor(ABC):
6
+ @abstractmethod
7
+ def execute_stream(self, workflow_id: str, version: str, input_data: dict, config: dict) -> Generator[Dict, None, None]:
8
+ pass
9
+