service-forge 0.1.18__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.
Potentially problematic release.
This version of service-forge might be problematic. Click here for more details.
- service_forge/api/deprecated_websocket_api.py +86 -0
- service_forge/api/deprecated_websocket_manager.py +425 -0
- service_forge/api/http_api.py +152 -0
- service_forge/api/http_api_doc.py +455 -0
- service_forge/api/kafka_api.py +126 -0
- service_forge/api/routers/feedback/feedback_router.py +148 -0
- service_forge/api/routers/service/service_router.py +127 -0
- service_forge/api/routers/websocket/websocket_manager.py +83 -0
- service_forge/api/routers/websocket/websocket_router.py +78 -0
- service_forge/api/task_manager.py +141 -0
- service_forge/current_service.py +14 -0
- service_forge/db/__init__.py +1 -0
- service_forge/db/database.py +237 -0
- service_forge/db/migrations/feedback_migration.py +154 -0
- service_forge/db/models/__init__.py +0 -0
- service_forge/db/models/feedback.py +33 -0
- service_forge/llm/__init__.py +67 -0
- service_forge/llm/llm.py +56 -0
- service_forge/model/__init__.py +0 -0
- service_forge/model/feedback.py +30 -0
- service_forge/model/websocket.py +13 -0
- service_forge/proto/foo_input.py +5 -0
- service_forge/service.py +280 -0
- service_forge/service_config.py +44 -0
- service_forge/sft/cli.py +91 -0
- service_forge/sft/cmd/config_command.py +67 -0
- service_forge/sft/cmd/deploy_service.py +123 -0
- service_forge/sft/cmd/list_tars.py +41 -0
- service_forge/sft/cmd/service_command.py +149 -0
- service_forge/sft/cmd/upload_service.py +36 -0
- service_forge/sft/config/injector.py +129 -0
- service_forge/sft/config/injector_default_files.py +131 -0
- service_forge/sft/config/sf_metadata.py +30 -0
- service_forge/sft/config/sft_config.py +200 -0
- service_forge/sft/file/__init__.py +0 -0
- service_forge/sft/file/ignore_pattern.py +80 -0
- service_forge/sft/file/sft_file_manager.py +107 -0
- service_forge/sft/kubernetes/kubernetes_manager.py +257 -0
- service_forge/sft/util/assert_util.py +25 -0
- service_forge/sft/util/logger.py +16 -0
- service_forge/sft/util/name_util.py +8 -0
- service_forge/sft/util/yaml_utils.py +57 -0
- service_forge/storage/__init__.py +5 -0
- service_forge/storage/feedback_storage.py +245 -0
- service_forge/utils/__init__.py +0 -0
- service_forge/utils/default_type_converter.py +12 -0
- service_forge/utils/register.py +39 -0
- service_forge/utils/type_converter.py +99 -0
- service_forge/utils/workflow_clone.py +124 -0
- service_forge/workflow/__init__.py +1 -0
- service_forge/workflow/context.py +14 -0
- service_forge/workflow/edge.py +24 -0
- service_forge/workflow/node.py +184 -0
- service_forge/workflow/nodes/__init__.py +8 -0
- service_forge/workflow/nodes/control/if_node.py +29 -0
- service_forge/workflow/nodes/control/switch_node.py +28 -0
- service_forge/workflow/nodes/input/console_input_node.py +26 -0
- service_forge/workflow/nodes/llm/query_llm_node.py +41 -0
- service_forge/workflow/nodes/nested/workflow_node.py +28 -0
- service_forge/workflow/nodes/output/kafka_output_node.py +27 -0
- service_forge/workflow/nodes/output/print_node.py +29 -0
- service_forge/workflow/nodes/test/if_console_input_node.py +33 -0
- service_forge/workflow/nodes/test/time_consuming_node.py +62 -0
- service_forge/workflow/port.py +89 -0
- service_forge/workflow/trigger.py +28 -0
- service_forge/workflow/triggers/__init__.py +6 -0
- service_forge/workflow/triggers/a2a_api_trigger.py +257 -0
- service_forge/workflow/triggers/fast_api_trigger.py +201 -0
- service_forge/workflow/triggers/kafka_api_trigger.py +47 -0
- service_forge/workflow/triggers/once_trigger.py +23 -0
- service_forge/workflow/triggers/period_trigger.py +29 -0
- service_forge/workflow/triggers/websocket_api_trigger.py +189 -0
- service_forge/workflow/workflow.py +227 -0
- service_forge/workflow/workflow_callback.py +141 -0
- service_forge/workflow/workflow_config.py +66 -0
- service_forge/workflow/workflow_event.py +15 -0
- service_forge/workflow/workflow_factory.py +246 -0
- service_forge/workflow/workflow_group.py +51 -0
- service_forge/workflow/workflow_type.py +52 -0
- service_forge-0.1.18.dist-info/METADATA +98 -0
- service_forge-0.1.18.dist-info/RECORD +83 -0
- service_forge-0.1.18.dist-info/WHEEL +4 -0
- service_forge-0.1.18.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
from omegaconf import OmegaConf
|
|
3
|
+
from typing import Callable, Awaitable, AsyncIterator, Any
|
|
4
|
+
from copy import deepcopy
|
|
5
|
+
|
|
6
|
+
from service_forge.workflow.workflow_callback import BuiltinWorkflowCallback
|
|
7
|
+
from .workflow import Workflow
|
|
8
|
+
from .workflow_group import WorkflowGroup, WORKFLOW_DEFAULT_VERSION
|
|
9
|
+
from .node import Node
|
|
10
|
+
from .edge import Edge
|
|
11
|
+
from .port import Port, parse_port_name, create_workflow_input_port, create_sub_workflow_input_port
|
|
12
|
+
from .node import node_register
|
|
13
|
+
from .nodes import *
|
|
14
|
+
from .triggers import *
|
|
15
|
+
from .context import Context
|
|
16
|
+
from ..db.database import DatabaseManager
|
|
17
|
+
from .workflow_config import WorkflowConfig, WorkflowGroupConfig
|
|
18
|
+
|
|
19
|
+
# WORKFLOW_KEY_NAME = 'name'
|
|
20
|
+
# WORKFLOW_KEY_DESCRIPTION = 'description'
|
|
21
|
+
# WORKFLOW_KEY_VERSION = 'version'
|
|
22
|
+
# WORKFLOW_KEY_NODES = 'nodes'
|
|
23
|
+
# WORKFLOW_KEY_INPUTS = 'inputs'
|
|
24
|
+
# WORKFLOW_KEY_OUTPUTS = 'outputs'
|
|
25
|
+
|
|
26
|
+
# NODE_KEY_NAME = 'name'
|
|
27
|
+
# NODE_KEY_TYPE = 'type'
|
|
28
|
+
# NODE_KEY_ARGS = 'args'
|
|
29
|
+
# NODE_KEY_OUTPUTS = 'outputs'
|
|
30
|
+
# # NODE_KEY_INPUT_PORTS = 'input_ports'
|
|
31
|
+
# # NODE_KEY_OUTPUT_PORTS = 'output_ports'
|
|
32
|
+
# NODE_KEY_SUB_WORKFLOWS = 'sub_workflows'
|
|
33
|
+
# NODE_KEY_SUB_WORKFLOWS_INPUT_PORTS = 'sub_workflows_input_ports'
|
|
34
|
+
|
|
35
|
+
# PORT_KEY_NAME = 'name'
|
|
36
|
+
# PORT_KEY_PORT = 'port'
|
|
37
|
+
# PORT_KEY_VALUE = 'value'
|
|
38
|
+
|
|
39
|
+
def parse_argument(arg: Any, service_env: dict[str, Any] = None) -> Any:
|
|
40
|
+
# TODO: support import variables
|
|
41
|
+
if type(arg) == str and arg.startswith(f'<{{') and arg.endswith(f'}}>'):
|
|
42
|
+
key = arg[2:-2]
|
|
43
|
+
if key not in service_env:
|
|
44
|
+
raise ValueError(f"Key {key} not found in service env.")
|
|
45
|
+
return service_env[key]
|
|
46
|
+
return arg
|
|
47
|
+
|
|
48
|
+
def create_workflow(
|
|
49
|
+
config_path: str = None,
|
|
50
|
+
config: WorkflowConfig = None,
|
|
51
|
+
service_env: dict[str, Any] = None,
|
|
52
|
+
workflows: WorkflowGroup = None,
|
|
53
|
+
_handle_stream_output: Callable[[str, AsyncIterator[str]], Awaitable[None]] | None = None,
|
|
54
|
+
_handle_query_user: Callable[[str, str], Awaitable[str]] | None = None,
|
|
55
|
+
database_manager: DatabaseManager = None,
|
|
56
|
+
) -> Workflow:
|
|
57
|
+
if config is None:
|
|
58
|
+
if config_path is not None:
|
|
59
|
+
config = WorkflowConfig.from_yaml_file(config_path)
|
|
60
|
+
else:
|
|
61
|
+
raise ValueError("Either config_path or config must be provided")
|
|
62
|
+
|
|
63
|
+
workflow = Workflow(
|
|
64
|
+
id = uuid.uuid4(),
|
|
65
|
+
config = config,
|
|
66
|
+
nodes = [],
|
|
67
|
+
input_ports = [],
|
|
68
|
+
output_ports = [],
|
|
69
|
+
_handle_stream_output = _handle_stream_output,
|
|
70
|
+
_handle_query_user = _handle_query_user,
|
|
71
|
+
database_manager = database_manager,
|
|
72
|
+
# TODO: max_concurrent_runs
|
|
73
|
+
callbacks = [BuiltinWorkflowCallback()],
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
nodes: dict[str, Node] = {}
|
|
77
|
+
|
|
78
|
+
for node_config in config.nodes:
|
|
79
|
+
params = {
|
|
80
|
+
"name": node_config.name,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
node: Node = node_register.instance(node_config.type, ignore_keys=['type'], kwargs=params)
|
|
84
|
+
|
|
85
|
+
# Context
|
|
86
|
+
node.context = Context(variables = {})
|
|
87
|
+
|
|
88
|
+
# Input ports
|
|
89
|
+
node.input_ports = deepcopy(node.DEFAULT_INPUT_PORTS)
|
|
90
|
+
for input_port in node.input_ports:
|
|
91
|
+
input_port.node = node
|
|
92
|
+
|
|
93
|
+
# Output ports
|
|
94
|
+
node.output_ports = deepcopy(node.DEFAULT_OUTPUT_PORTS)
|
|
95
|
+
for output_port in node.output_ports:
|
|
96
|
+
output_port.node = node
|
|
97
|
+
|
|
98
|
+
# Sub workflows
|
|
99
|
+
if node_config.sub_workflows is not None:
|
|
100
|
+
sub_workflows: WorkflowGroup = WorkflowGroup(workflows=[])
|
|
101
|
+
for sub_workflow_config in node_config.sub_workflows:
|
|
102
|
+
sub_workflow = workflows.get_workflow_by_name(sub_workflow_config.name, sub_workflow_config.version)
|
|
103
|
+
sub_workflows.add_workflow(deepcopy(sub_workflow))
|
|
104
|
+
node.sub_workflows = sub_workflows
|
|
105
|
+
|
|
106
|
+
# Sub workflows input ports
|
|
107
|
+
if node_config.sub_workflows_input_ports is not None:
|
|
108
|
+
for sub_workflow_input_port_config in node_config.sub_workflows_input_ports:
|
|
109
|
+
name = sub_workflow_input_port_config.name
|
|
110
|
+
sub_workflow_name, sub_workflow_port_name = parse_port_name(sub_workflow_input_port_config.port)
|
|
111
|
+
sub_workflow = node.sub_workflows.get_workflow_by_name(sub_workflow_name, sub_workflow_config.version)
|
|
112
|
+
if sub_workflow is None:
|
|
113
|
+
raise ValueError(f"{sub_workflow_name} is not a valid sub workflow.")
|
|
114
|
+
sub_workflow_port = sub_workflow.get_input_port_by_name(sub_workflow_port_name)
|
|
115
|
+
if sub_workflow_port is None:
|
|
116
|
+
raise ValueError(f"{sub_workflow_port_name} is not a valid input port.")
|
|
117
|
+
value = sub_workflow_input_port_config.value
|
|
118
|
+
node.input_ports.append(create_sub_workflow_input_port(name=name, node=node, port=sub_workflow_port, value=value))
|
|
119
|
+
|
|
120
|
+
# Sub workflows output ports
|
|
121
|
+
...
|
|
122
|
+
|
|
123
|
+
# Hooks
|
|
124
|
+
if _handle_query_user is None:
|
|
125
|
+
node.query_user = workflow.handle_query_user
|
|
126
|
+
else:
|
|
127
|
+
node.query_user = _handle_query_user
|
|
128
|
+
|
|
129
|
+
nodes[node_config.name] = node
|
|
130
|
+
|
|
131
|
+
# Edges
|
|
132
|
+
for node_config in config.nodes:
|
|
133
|
+
start_node = nodes[node_config.name]
|
|
134
|
+
if node_config.outputs is not None:
|
|
135
|
+
for key, value in node_config.outputs.items():
|
|
136
|
+
if value is None:
|
|
137
|
+
continue
|
|
138
|
+
|
|
139
|
+
if type(value) is str:
|
|
140
|
+
value = [value]
|
|
141
|
+
|
|
142
|
+
for edge_value in value:
|
|
143
|
+
end_node_name, end_port_name = parse_port_name(edge_value)
|
|
144
|
+
end_node = nodes[end_node_name]
|
|
145
|
+
|
|
146
|
+
start_node.try_create_extended_output_port(key)
|
|
147
|
+
end_node.try_create_extended_input_port(end_port_name)
|
|
148
|
+
|
|
149
|
+
start_port = start_node.get_output_port_by_name(key)
|
|
150
|
+
end_port = end_node.get_input_port_by_name(end_port_name)
|
|
151
|
+
|
|
152
|
+
if start_port is None:
|
|
153
|
+
raise ValueError(f"{key} is not a valid output port.")
|
|
154
|
+
if end_port is None:
|
|
155
|
+
raise ValueError(f"{end_port_name} is not a valid input port.")
|
|
156
|
+
|
|
157
|
+
edge = Edge(start_node, end_node, start_port, end_port)
|
|
158
|
+
|
|
159
|
+
start_node.output_edges.append(edge)
|
|
160
|
+
end_node.input_edges.append(edge)
|
|
161
|
+
|
|
162
|
+
workflow.add_nodes(list(nodes.values()))
|
|
163
|
+
|
|
164
|
+
# Inputs
|
|
165
|
+
if config.inputs is not None:
|
|
166
|
+
for port_config in config.inputs:
|
|
167
|
+
name = port_config.name
|
|
168
|
+
node_name, node_port_name = parse_port_name(port_config.port)
|
|
169
|
+
if node_name not in nodes:
|
|
170
|
+
raise ValueError(f"{node_name} is not a valid node.")
|
|
171
|
+
node = nodes[node_name]
|
|
172
|
+
port = node.get_input_port_by_name(node_port_name)
|
|
173
|
+
if port is None:
|
|
174
|
+
raise ValueError(f"{node_port_name} is not a valid input port.")
|
|
175
|
+
value = port_config.value
|
|
176
|
+
workflow.input_ports.append(create_workflow_input_port(name=name, port=port, value=value))
|
|
177
|
+
|
|
178
|
+
# Outputs
|
|
179
|
+
if config.outputs is not None:
|
|
180
|
+
for port_config in config.outputs:
|
|
181
|
+
name = port_config.name
|
|
182
|
+
node_name, node_port_name = parse_port_name(port_config.port)
|
|
183
|
+
if node_name not in nodes:
|
|
184
|
+
raise ValueError(f"{node_name} is not a valid node.")
|
|
185
|
+
node = nodes[node_name]
|
|
186
|
+
port = node.get_output_port_by_name(node_port_name)
|
|
187
|
+
if port is None:
|
|
188
|
+
raise ValueError(f"{node_port_name} is not a valid output port.")
|
|
189
|
+
output_port = Port(name=name, type=Any, port=port)
|
|
190
|
+
workflow.output_ports.append(output_port)
|
|
191
|
+
edge = Edge(node, None, port, output_port)
|
|
192
|
+
node.output_edges.append(edge)
|
|
193
|
+
|
|
194
|
+
for node_config in config.nodes:
|
|
195
|
+
node = nodes[node_config.name]
|
|
196
|
+
# Arguments
|
|
197
|
+
if node_config.args is not None:
|
|
198
|
+
for key, value in node_config.args.items():
|
|
199
|
+
node.fill_input_by_name(key, parse_argument(value, service_env=service_env))
|
|
200
|
+
|
|
201
|
+
return workflow
|
|
202
|
+
|
|
203
|
+
def create_workflow_group(
|
|
204
|
+
config_path: str = None,
|
|
205
|
+
config: WorkflowConfig | WorkflowGroupConfig = None,
|
|
206
|
+
service_env: dict[str, Any] = None,
|
|
207
|
+
_handle_stream_output: Callable[[str, AsyncIterator[str]], Awaitable[None]] = None,
|
|
208
|
+
_handle_query_user: Callable[[str, str], Awaitable[str]] = None,
|
|
209
|
+
database_manager: DatabaseManager = None,
|
|
210
|
+
) -> WorkflowGroup:
|
|
211
|
+
|
|
212
|
+
if config is None:
|
|
213
|
+
if config_path is not None:
|
|
214
|
+
try:
|
|
215
|
+
config = WorkflowGroupConfig.from_yaml_file(config_path)
|
|
216
|
+
except:
|
|
217
|
+
config = WorkflowConfig.from_yaml_file(config_path)
|
|
218
|
+
else:
|
|
219
|
+
raise ValueError("Either config_path or config must be provided")
|
|
220
|
+
|
|
221
|
+
if type(config) == WorkflowConfig:
|
|
222
|
+
workflow = create_workflow(
|
|
223
|
+
config_path=config_path if config_path else None,
|
|
224
|
+
config=config,
|
|
225
|
+
service_env=service_env,
|
|
226
|
+
_handle_stream_output=_handle_stream_output,
|
|
227
|
+
_handle_query_user=_handle_query_user,
|
|
228
|
+
database_manager=database_manager,
|
|
229
|
+
)
|
|
230
|
+
return WorkflowGroup(workflows=[workflow], main_workflow_name=workflow.name, main_workflow_version=workflow.version)
|
|
231
|
+
elif type(config) == WorkflowGroupConfig:
|
|
232
|
+
workflows = WorkflowGroup(
|
|
233
|
+
workflows=[],
|
|
234
|
+
main_workflow_name=config.main_workflow_name,
|
|
235
|
+
main_workflow_version=config.main_workflow_version,
|
|
236
|
+
)
|
|
237
|
+
for workflow_config in config.workflows:
|
|
238
|
+
workflows.add_workflow(create_workflow(
|
|
239
|
+
config=workflow_config,
|
|
240
|
+
workflows=workflows,
|
|
241
|
+
service_env=service_env,
|
|
242
|
+
_handle_stream_output=_handle_stream_output,
|
|
243
|
+
_handle_query_user=_handle_query_user,
|
|
244
|
+
database_manager=database_manager,
|
|
245
|
+
))
|
|
246
|
+
return workflows
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
from service_forge.workflow.workflow import Workflow
|
|
3
|
+
|
|
4
|
+
WORKFLOW_DEFAULT_VERSION = "0"
|
|
5
|
+
WORKFLOW_MAIN_WORKFLOW_NAME = "main"
|
|
6
|
+
|
|
7
|
+
class WorkflowGroup:
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
workflows: list[Workflow],
|
|
11
|
+
main_workflow_name: str = WORKFLOW_MAIN_WORKFLOW_NAME,
|
|
12
|
+
main_workflow_version: str = WORKFLOW_DEFAULT_VERSION,
|
|
13
|
+
) -> None:
|
|
14
|
+
self.workflows = workflows
|
|
15
|
+
self.main_workflow_name = main_workflow_name
|
|
16
|
+
self.main_workflow_version = main_workflow_version
|
|
17
|
+
|
|
18
|
+
def add_workflow(self, workflow: Workflow) -> None:
|
|
19
|
+
self.workflows.append(workflow)
|
|
20
|
+
|
|
21
|
+
def get_workflow_by_name(self, name: str, version: str) -> Workflow | None:
|
|
22
|
+
for workflow in self.workflows:
|
|
23
|
+
if workflow.name == name and workflow.version == version:
|
|
24
|
+
return workflow
|
|
25
|
+
return None
|
|
26
|
+
|
|
27
|
+
def get_workflow_by_id(self, id: uuid.UUID) -> Workflow | None:
|
|
28
|
+
for workflow in self.workflows:
|
|
29
|
+
print(workflow.name, workflow.id, id)
|
|
30
|
+
if workflow.id == id:
|
|
31
|
+
return workflow
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
def get_main_workflow(self, allow_none: bool = True) -> Workflow | None:
|
|
35
|
+
workflow = self.get_workflow_by_name(self.main_workflow_name, self.main_workflow_version)
|
|
36
|
+
if not allow_none and workflow is None:
|
|
37
|
+
raise ValueError(f"Main workflow with name {self.main_workflow_name} and version {self.main_workflow_version} not found in workflow group.")
|
|
38
|
+
return workflow
|
|
39
|
+
|
|
40
|
+
async def run(self, name: str = None, version: str = None, id: uuid.UUID = None) -> None:
|
|
41
|
+
if name is None and id is None:
|
|
42
|
+
workflow = self.get_main_workflow()
|
|
43
|
+
elif name is not None:
|
|
44
|
+
workflow = self.get_workflow_by_name(name, version)
|
|
45
|
+
elif id is not None:
|
|
46
|
+
workflow = self.get_workflow_by_id(id)
|
|
47
|
+
else:
|
|
48
|
+
workflow = None
|
|
49
|
+
if workflow is None:
|
|
50
|
+
raise ValueError(f"Workflow with name {name} and version {version} and id {id} not found in workflow group.")
|
|
51
|
+
await workflow.run()
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import os
|
|
3
|
+
import importlib
|
|
4
|
+
import inspect
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Type, Any
|
|
7
|
+
|
|
8
|
+
from service_forge.utils.register import Register
|
|
9
|
+
|
|
10
|
+
class WorkflowType:
|
|
11
|
+
CLASS_NOT_REQUIRED_TO_REGISTER = ['WorkflowType']
|
|
12
|
+
|
|
13
|
+
def __init__(self, name: str, type: type) -> None:
|
|
14
|
+
self.name = name
|
|
15
|
+
self.type = type
|
|
16
|
+
|
|
17
|
+
def __init_subclass__(cls) -> None:
|
|
18
|
+
if cls.__name__ not in WorkflowType.CLASS_NOT_REQUIRED_TO_REGISTER:
|
|
19
|
+
workflow_type_register.register(cls.__name__, cls)
|
|
20
|
+
return super().__init_subclass__()
|
|
21
|
+
|
|
22
|
+
workflow_type_register = Register[WorkflowType]()
|
|
23
|
+
|
|
24
|
+
def _load_proto_classes():
|
|
25
|
+
# TODO: load from config
|
|
26
|
+
proto_dir = Path(__file__).parent.parent / "proto"
|
|
27
|
+
|
|
28
|
+
if not proto_dir.exists():
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
for py_file in proto_dir.glob("*.py"):
|
|
32
|
+
if py_file.name == "__init__.py":
|
|
33
|
+
continue
|
|
34
|
+
|
|
35
|
+
module_name = py_file.stem
|
|
36
|
+
module_path = f"service_forge.proto.{module_name}"
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
module = importlib.import_module(module_path)
|
|
40
|
+
|
|
41
|
+
for name, obj in inspect.getmembers(module, inspect.isclass):
|
|
42
|
+
if (not name.startswith('_') and
|
|
43
|
+
obj.__module__ == module_path and
|
|
44
|
+
hasattr(obj, '__bases__')):
|
|
45
|
+
|
|
46
|
+
workflow_type = WorkflowType(name, obj)
|
|
47
|
+
workflow_type_register.register(name, workflow_type)
|
|
48
|
+
|
|
49
|
+
except Exception as e:
|
|
50
|
+
print(f"Failed to load module {module_path}: {e}")
|
|
51
|
+
|
|
52
|
+
_load_proto_classes()
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: service-forge
|
|
3
|
+
Version: 0.1.18
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Author-email: euxcet <zcc.qwer@gmail.com>
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Requires-Dist: a2a-sdk>=0.3.22
|
|
8
|
+
Requires-Dist: aiokafka>=0.12.0
|
|
9
|
+
Requires-Dist: alembic>=1.17.0
|
|
10
|
+
Requires-Dist: asyncpg>=0.30.0
|
|
11
|
+
Requires-Dist: dotenv>=0.9.9
|
|
12
|
+
Requires-Dist: fastapi>=0.119.1
|
|
13
|
+
Requires-Dist: greenlet>=3.2.4
|
|
14
|
+
Requires-Dist: jinja2>=3.1.6
|
|
15
|
+
Requires-Dist: kubernetes>=28.0.0
|
|
16
|
+
Requires-Dist: loguru>=0.7.3
|
|
17
|
+
Requires-Dist: omegaconf>=2.3.0
|
|
18
|
+
Requires-Dist: openai>=2.3.0
|
|
19
|
+
Requires-Dist: protobuf>=6.33.1
|
|
20
|
+
Requires-Dist: psycopg2-binary>=2.9.11
|
|
21
|
+
Requires-Dist: pydantic>=2.12.0
|
|
22
|
+
Requires-Dist: pymongo>=4.15.5
|
|
23
|
+
Requires-Dist: pytest-asyncio>=1.2.0
|
|
24
|
+
Requires-Dist: pytest>=8.4.2
|
|
25
|
+
Requires-Dist: python-dotenv>=1.1.1
|
|
26
|
+
Requires-Dist: python-jose>=3.5.0
|
|
27
|
+
Requires-Dist: python-multipart>=0.0.20
|
|
28
|
+
Requires-Dist: redis>=7.1.0
|
|
29
|
+
Requires-Dist: requests>=2.32.5
|
|
30
|
+
Requires-Dist: restrictedpython>=8.0
|
|
31
|
+
Requires-Dist: rich>=13.0.0
|
|
32
|
+
Requires-Dist: sqlalchemy>=2.0.44
|
|
33
|
+
Requires-Dist: typer>=0.12.0
|
|
34
|
+
Requires-Dist: uvicorn>=0.38.0
|
|
35
|
+
Requires-Dist: websockets>=15.0.1
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
|
|
38
|
+
# Service Forge
|
|
39
|
+
|
|
40
|
+
Automated service creation and maintenance tool.
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install -e .
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## CLI Usage (sft)
|
|
49
|
+
|
|
50
|
+
Service Forge 提供了命令行工具 `sft` 用于服务管理。
|
|
51
|
+
|
|
52
|
+
### 服务上传和部署
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# 上传服务(打包并上传到服务器)
|
|
56
|
+
sft upload [project_path]
|
|
57
|
+
|
|
58
|
+
# 列出本地已打包的服务包
|
|
59
|
+
sft list
|
|
60
|
+
|
|
61
|
+
# 部署服务(只在服务器上使用)
|
|
62
|
+
sft deploy <name> <version>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 配置管理
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# 列出所有配置项
|
|
69
|
+
sft config list
|
|
70
|
+
|
|
71
|
+
# 获取指定配置项的值
|
|
72
|
+
sft config get <key>
|
|
73
|
+
|
|
74
|
+
# 设置配置项的值
|
|
75
|
+
sft config set <key> <value>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 服务管理
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# 列出所有服务
|
|
82
|
+
sft service list
|
|
83
|
+
|
|
84
|
+
# 删除服务(只在服务器上使用)
|
|
85
|
+
sft service delete <service_name> [--force, -f]
|
|
86
|
+
|
|
87
|
+
# 查看服务日志(只在服务器上使用)
|
|
88
|
+
sft service logs <service_name> [--container, -c] [--tail, -n] [--follow, -f] [--previous, -p]
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## TODO
|
|
92
|
+
|
|
93
|
+
- [x] 多次 trigger 并行执行
|
|
94
|
+
- [x] 支持 websocket 来做 trigger、输入和输出
|
|
95
|
+
- [x] 优化 websocket 客户端映射和重连支持
|
|
96
|
+
- [x] 节点和 workflow 运行情况的回调函数
|
|
97
|
+
- [ ] 支持 a2a
|
|
98
|
+
- [ ] workflow 执行异常处理
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
service_forge/current_service.py,sha256=0YKm7nQiXzUUAc1ToCcbG1QPJfOSNKcOHUpyJ4E3xrY,342
|
|
2
|
+
service_forge/service.py,sha256=rZZ7-BXTTCrc6KqnONnKpg-bRs5JCfDTvMrl4K26vbE,12633
|
|
3
|
+
service_forge/service_config.py,sha256=zsTdCZ1peMAotjGEVypPos7d-gjwrYoB9x_12g95G4g,1242
|
|
4
|
+
service_forge/api/deprecated_websocket_api.py,sha256=E36-fpUPxzMJ2YGlCPeqwRbryk2FMMbQD_pbb8k1FYI,3343
|
|
5
|
+
service_forge/api/deprecated_websocket_manager.py,sha256=Xiwg3zwXRVi63sXmVH-TgbpL2XH_djyLeo96STm4cNM,16757
|
|
6
|
+
service_forge/api/http_api.py,sha256=XnEQ45zuWQV1zrSL9vd3USUh47ymkVc4w_1SoFlNdl8,5648
|
|
7
|
+
service_forge/api/http_api_doc.py,sha256=ASlxvsIiUzDcMhVoumRjt9CfEMbh0O1U4ZLC9eobLF8,20235
|
|
8
|
+
service_forge/api/kafka_api.py,sha256=PInx2ZzKJRON7EaJFWroXkiOt_UeZY7WE6qK03gq4ak,4599
|
|
9
|
+
service_forge/api/task_manager.py,sha256=9Lk-NV4cBnuv9b8V6GVLWJJ4MCiAwCp5TVAwmYgqXbs,5269
|
|
10
|
+
service_forge/api/routers/feedback/feedback_router.py,sha256=JOJI6kaQYapg4__iA6Eo26_9su48p7R2Kpn422nbsxw,5640
|
|
11
|
+
service_forge/api/routers/service/service_router.py,sha256=hGOT-ScnXR7agHp-F9OFGWiPFjG9f3gl7NBsnayW3JI,5088
|
|
12
|
+
service_forge/api/routers/websocket/websocket_manager.py,sha256=j1AFqzXQhZZyaLQwhvZefXAS-zCOPzLcRMDEuusv6V0,3605
|
|
13
|
+
service_forge/api/routers/websocket/websocket_router.py,sha256=V0B7eQP8toO94-WbTrGraadXD3qeZ9lnKFcxwx6kLgM,3777
|
|
14
|
+
service_forge/db/__init__.py,sha256=EWLhH8bYsMOvRF_YXF6FgL3irKA6GZeLxSGvWDRM6f8,85
|
|
15
|
+
service_forge/db/database.py,sha256=IdF7RV-bOFmPPu7d4sQFtsF3e8mZKwMXUkMi7HPEPnc,9329
|
|
16
|
+
service_forge/db/migrations/feedback_migration.py,sha256=-zQ71TsOlWmQPQo1NKSIu3C1T47v3cfD6IAQ5HE_ffk,4845
|
|
17
|
+
service_forge/db/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
+
service_forge/db/models/feedback.py,sha256=gltX3y-nNhXSR9z1cd82Vg-zwjF0JhnGbOvUapkcWKQ,1253
|
|
19
|
+
service_forge/llm/__init__.py,sha256=x8--4XWqhCVR5n7U3iMvrL16mv-Altb_JgF4qdxSOGk,3170
|
|
20
|
+
service_forge/llm/llm.py,sha256=Sar99FkTPBJqkB6dwX81ww_hJp8cPYxlg7Go-zXPyg0,1865
|
|
21
|
+
service_forge/model/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
+
service_forge/model/feedback.py,sha256=Is5tkplzYkjChGb67o-Qjtbu4kSspVuaKi4Aua_QdRo,1318
|
|
23
|
+
service_forge/model/websocket.py,sha256=YIUCW32sbHIEFPHjk5FiDM_rDe2aVD6OpzBQul2R5IM,267
|
|
24
|
+
service_forge/proto/foo_input.py,sha256=-POJZSIFrGdBGz7FqZZ03r5uztpc5Apin9A0Yxbk6YI,90
|
|
25
|
+
service_forge/sft/cli.py,sha256=stB_YPhZ7gAQeOxIq03-tLyl5VfU-gnRacAT05GSMis,2904
|
|
26
|
+
service_forge/sft/cmd/config_command.py,sha256=I9t2HG28S6lCXpExHyZUc47b_1yB3i51tCFVk5J6TTU,2382
|
|
27
|
+
service_forge/sft/cmd/deploy_service.py,sha256=5IYbCVI0Nlry1KXBhm9leJmr2bzUEXrSY-2BympLR0c,4686
|
|
28
|
+
service_forge/sft/cmd/list_tars.py,sha256=Z3zvu2JLb_wNbTwi5TZXL5cZ8PxYrKks9AxkOzoUd_Q,1380
|
|
29
|
+
service_forge/sft/cmd/service_command.py,sha256=69GMMN61KtuoEFuYzFJ74ivNt8RX8q0I6rbePfJfEwQ,5538
|
|
30
|
+
service_forge/sft/cmd/upload_service.py,sha256=86PvvJSXCZKH4BU6rLytuc45grX-sRnQnOHCo9zUaPY,1232
|
|
31
|
+
service_forge/sft/config/injector.py,sha256=El8U5USveKfC0-jhhqYgaevjp4R4fwW02oMvDn7Amyk,5762
|
|
32
|
+
service_forge/sft/config/injector_default_files.py,sha256=aTMQ2Tla3wdpvdaD_5VP2X6oeZbI0X414FM9NbirnO4,2716
|
|
33
|
+
service_forge/sft/config/sf_metadata.py,sha256=Y9akhSCgOd11-oqRs3LIs8FL9pvWNw6hyy57fuFcBhc,866
|
|
34
|
+
service_forge/sft/config/sft_config.py,sha256=MgurtgbcSmyXbGlVX3NG84KD4Hst1gZWHdF9a8zi-6U,7707
|
|
35
|
+
service_forge/sft/file/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
36
|
+
service_forge/sft/file/ignore_pattern.py,sha256=UrVmR83wOx51XHFcZDTPp15dGYcvMTE5W1m07-SvHpw,2521
|
|
37
|
+
service_forge/sft/file/sft_file_manager.py,sha256=poIM77tZZg7vfwBdCsdQctBbCczVLQePdTwVINEABvE,4337
|
|
38
|
+
service_forge/sft/kubernetes/kubernetes_manager.py,sha256=IF2_X9U-k5Dx7EZuGrJ9lZ85ltbilrrZDfsl8qFyTu4,11339
|
|
39
|
+
service_forge/sft/util/assert_util.py,sha256=8HreVkOzs9_ClKiFqG4qsFn_yyDLo5uXYhYUPXlmDjM,828
|
|
40
|
+
service_forge/sft/util/logger.py,sha256=0Hi74IoxshE-wBgvBa2EZPXYj37tTrUYwlOBd9UMMMs,502
|
|
41
|
+
service_forge/sft/util/name_util.py,sha256=WSYHM6c7SZULXCFON7nmGqsvAPPs_wavd6QjCa4UbRQ,301
|
|
42
|
+
service_forge/sft/util/yaml_utils.py,sha256=9OhJNQlzj_C1NeQoUZVF8qpDovrE7RDWtNXe-H7tuNA,1703
|
|
43
|
+
service_forge/storage/__init__.py,sha256=8Jg4R9z2JHadheV1YrHtCsFxEL5aCl9n2dMQGHcJfvM,156
|
|
44
|
+
service_forge/storage/feedback_storage.py,sha256=wnuNTmEzpnS7iisiU9MrEJIgVa2G_HysqICWk_PxzfU,9124
|
|
45
|
+
service_forge/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
46
|
+
service_forge/utils/default_type_converter.py,sha256=CuUZpMATdTwgcV1M3lbK64znwmEG85Zt3y_QGXr9tYQ,625
|
|
47
|
+
service_forge/utils/register.py,sha256=nxiGQBCX238FoZZhsDoDdBMv_2QzeIZpM367HPNfaqM,874
|
|
48
|
+
service_forge/utils/type_converter.py,sha256=IRphYxyGA0ICwFvDMvqAnRnSUTpY2ZQXvTE5o99CKpo,3046
|
|
49
|
+
service_forge/utils/workflow_clone.py,sha256=I3qNmJLbhAbKb-5VITls89pMmGcWNtI64IOTxN-PUwY,4549
|
|
50
|
+
service_forge/workflow/__init__.py,sha256=9oh4qPyA33RugrUYRAlLmFtmQPUN2wxruFQE3omTJF8,49
|
|
51
|
+
service_forge/workflow/context.py,sha256=1PotSEN_l8Emd5p8_6mtXJngXGYd3NSbOs1EKHgvnlo,346
|
|
52
|
+
service_forge/workflow/edge.py,sha256=88Ex-9_dHAGD38OHgiqP0DrfxK0FrhvDAxThR3ilUi4,627
|
|
53
|
+
service_forge/workflow/node.py,sha256=jPzXuixmZBL6-_jjXikFZmierlu3SOBJOSdQxTyV0FY,7536
|
|
54
|
+
service_forge/workflow/port.py,sha256=JVj0JrnQeOWCsp7n48Cm03bfmO8r3V5oTSEsC-HTGPE,2967
|
|
55
|
+
service_forge/workflow/trigger.py,sha256=2OqiHi0dFcoC8g5GDqVpVEpHKlmqtDADb74Z7PRzHlo,879
|
|
56
|
+
service_forge/workflow/workflow.py,sha256=ykPZBN0sLrORnM83AErcLmFTdUjF5gvbNuMAnY_-p00,8332
|
|
57
|
+
service_forge/workflow/workflow_callback.py,sha256=S__F7s-7l5LgkIXcZMcG68qCyc8NgdWQX81F0hKWL1U,5135
|
|
58
|
+
service_forge/workflow/workflow_config.py,sha256=Yih10b-utKIpaR-X-nfy7fPnmBNhRvlD8Bw2_mQ5lJI,1821
|
|
59
|
+
service_forge/workflow/workflow_event.py,sha256=QG1VFJwUUF1bTKKPKvqBICnYxkBwpfYDEoAuxwQYhhE,371
|
|
60
|
+
service_forge/workflow/workflow_factory.py,sha256=AV39dK2QmG1ed9Mm3BXGI3s-w-o3PuMnwU-Y6VyAVFk,9885
|
|
61
|
+
service_forge/workflow/workflow_group.py,sha256=c-Hcfb-nVKxCVddoEQNFV6nY4LVAku0iq7tTUef9fDM,2076
|
|
62
|
+
service_forge/workflow/workflow_type.py,sha256=zRc-gL2LBE-gOgTUCU5-VDWeGUzuQahkHIg98ipEvQg,1629
|
|
63
|
+
service_forge/workflow/nodes/__init__.py,sha256=AUOoFUAMgRwfLiHNkjnDnWToMSe2AeV5vJO3NCG1eLw,381
|
|
64
|
+
service_forge/workflow/nodes/control/if_node.py,sha256=fBRnSsz0SHW5Hcf4993Y1PsUOUt2Cg9I_zcduHUnMuI,643
|
|
65
|
+
service_forge/workflow/nodes/control/switch_node.py,sha256=27mxdcQijSawNM5Fx6LdC1MB66vAoLAAI74DQyfqPqI,708
|
|
66
|
+
service_forge/workflow/nodes/input/console_input_node.py,sha256=GhQjWRgYy3aRfYUfaEql-_Xi10cca75oz-vmSZkR54w,683
|
|
67
|
+
service_forge/workflow/nodes/llm/query_llm_node.py,sha256=rFSHsPsW2V-yhHSmjx60DcokKy5fdGhNNbt8GIgEIbk,1143
|
|
68
|
+
service_forge/workflow/nodes/nested/workflow_node.py,sha256=h5NXhRCUGaoNmqMV2PXR6JNwTCM8MCRVA6ocOjoDzhs,747
|
|
69
|
+
service_forge/workflow/nodes/output/kafka_output_node.py,sha256=mC6qRMGsuwU6qXAfXA-0ZFZudrlwmgRYOJRULUrtH40,682
|
|
70
|
+
service_forge/workflow/nodes/output/print_node.py,sha256=OSgeRQOd3dq88a1plx30g9-VB793RbXnIa5X8MF9fCo,656
|
|
71
|
+
service_forge/workflow/nodes/test/if_console_input_node.py,sha256=CtKHkFqr8PN974_iGP2VSBmNpXZ-KumRHCpoRR5RyF8,956
|
|
72
|
+
service_forge/workflow/nodes/test/time_consuming_node.py,sha256=gB2qw2DdjRf82z1158u36nSnCHrheHaxscAzPRnXNyk,1813
|
|
73
|
+
service_forge/workflow/triggers/__init__.py,sha256=iQ0WEYu6JgL191Y9XslMhZ7jS7JO8bL3SZ9YqIw5LCM,269
|
|
74
|
+
service_forge/workflow/triggers/a2a_api_trigger.py,sha256=Oaw3vRLA8fWZUIQ-h33dYmojmjp4mwNF_0LHqQ_4mZQ,8583
|
|
75
|
+
service_forge/workflow/triggers/fast_api_trigger.py,sha256=8BF0A8gdcKeiP3cyF_dF0T3MH7bXnnZRCa_h5hx9kQ4,7513
|
|
76
|
+
service_forge/workflow/triggers/kafka_api_trigger.py,sha256=Zv8J75Rmg1-xqxHwpBMBhsm_TWX8p3_rqldk2RVSwVc,1561
|
|
77
|
+
service_forge/workflow/triggers/once_trigger.py,sha256=YmzSQBoKE-8liNFIoDCqi2UdqhHujizsXVDft81_8jA,572
|
|
78
|
+
service_forge/workflow/triggers/period_trigger.py,sha256=JFX3yBjKqoRP55jiulaSG_SPO-zWLMcwEb1BwcKsWUM,767
|
|
79
|
+
service_forge/workflow/triggers/websocket_api_trigger.py,sha256=DRVFVdR3NgUx8S6Rlv492_3xrmlNGDOcvUrz13jHQ7o,6902
|
|
80
|
+
service_forge-0.1.18.dist-info/METADATA,sha256=dP-GKlnX6P32jvP-1oudgM_Rj760sCCf6fayQf9FvuY,2308
|
|
81
|
+
service_forge-0.1.18.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
82
|
+
service_forge-0.1.18.dist-info/entry_points.txt,sha256=WHntHW7GAyKQUEeMcMvHDZ7_xAb0-cZeAK4iJeu9lm8,51
|
|
83
|
+
service_forge-0.1.18.dist-info/RECORD,,
|