service-forge 0.1.18__py3-none-any.whl → 0.1.39__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/__init__.py +0 -0
- service_forge/api/deprecated_websocket_api.py +91 -33
- service_forge/api/deprecated_websocket_manager.py +70 -53
- service_forge/api/http_api.py +205 -55
- service_forge/api/kafka_api.py +113 -25
- service_forge/api/routers/meta_api/meta_api_router.py +57 -0
- service_forge/api/routers/service/service_router.py +42 -6
- service_forge/api/routers/trace/trace_router.py +326 -0
- service_forge/api/routers/websocket/websocket_router.py +69 -1
- service_forge/api/service_studio.py +9 -0
- service_forge/db/database.py +17 -0
- service_forge/execution_context.py +106 -0
- service_forge/frontend/static/assets/CreateNewNodeDialog-DkrEMxSH.js +1 -0
- service_forge/frontend/static/assets/CreateNewNodeDialog-DwFcBiGp.css +1 -0
- service_forge/frontend/static/assets/EditorSidePanel-BNVms9Fq.css +1 -0
- service_forge/frontend/static/assets/EditorSidePanel-DZbB3ILL.js +1 -0
- service_forge/frontend/static/assets/FeedbackPanel-CC8HX7Yo.js +1 -0
- service_forge/frontend/static/assets/FeedbackPanel-ClgniIVk.css +1 -0
- service_forge/frontend/static/assets/FormattedCodeViewer.vue_vue_type_script_setup_true_lang-BNuI1NCs.js +1 -0
- service_forge/frontend/static/assets/NodeDetailWrapper-BqFFM7-r.js +1 -0
- service_forge/frontend/static/assets/NodeDetailWrapper-pZBxv3J0.css +1 -0
- service_forge/frontend/static/assets/TestRunningDialog-D0GrCoYs.js +1 -0
- service_forge/frontend/static/assets/TestRunningDialog-dhXOsPgH.css +1 -0
- service_forge/frontend/static/assets/TracePanelWrapper-B9zvDSc_.js +1 -0
- service_forge/frontend/static/assets/TracePanelWrapper-BiednCrq.css +1 -0
- service_forge/frontend/static/assets/WorkflowEditor-CcaGGbko.js +3 -0
- service_forge/frontend/static/assets/WorkflowEditor-CmasOOYK.css +1 -0
- service_forge/frontend/static/assets/WorkflowList-Copuwi-a.css +1 -0
- service_forge/frontend/static/assets/WorkflowList-LrRJ7B7h.js +1 -0
- service_forge/frontend/static/assets/WorkflowStudio-CthjgII2.css +1 -0
- service_forge/frontend/static/assets/WorkflowStudio-FCyhGD4y.js +2 -0
- service_forge/frontend/static/assets/api-BDer3rj7.css +1 -0
- service_forge/frontend/static/assets/api-DyiqpKJK.js +1 -0
- service_forge/frontend/static/assets/code-editor-DBSql_sc.js +12 -0
- service_forge/frontend/static/assets/el-collapse-item-D4LG0FJ0.css +1 -0
- service_forge/frontend/static/assets/el-empty-D4ZqTl4F.css +1 -0
- service_forge/frontend/static/assets/el-form-item-BWkJzdQ_.css +1 -0
- service_forge/frontend/static/assets/el-input-D6B3r8CH.css +1 -0
- service_forge/frontend/static/assets/el-select-B0XIb2QK.css +1 -0
- service_forge/frontend/static/assets/el-tag-DljBBxJR.css +1 -0
- service_forge/frontend/static/assets/element-ui-D3x2y3TA.js +12 -0
- service_forge/frontend/static/assets/elkjs-Dm5QV7uy.js +24 -0
- service_forge/frontend/static/assets/highlightjs-D4ATuRwX.js +3 -0
- service_forge/frontend/static/assets/index-BMvodlwc.js +2 -0
- service_forge/frontend/static/assets/index-CjSe8i2q.css +1 -0
- service_forge/frontend/static/assets/js-yaml-yTPt38rv.js +32 -0
- service_forge/frontend/static/assets/time-DKCKV6Ug.js +1 -0
- service_forge/frontend/static/assets/ui-components-DQ7-U3pr.js +1 -0
- service_forge/frontend/static/assets/vue-core-DL-LgTX0.js +1 -0
- service_forge/frontend/static/assets/vue-flow-Dn7R8GPr.js +39 -0
- service_forge/frontend/static/index.html +16 -0
- service_forge/frontend/static/vite.svg +1 -0
- service_forge/model/meta_api/__init__.py +0 -0
- service_forge/model/meta_api/schema.py +29 -0
- service_forge/model/trace.py +82 -0
- service_forge/service.py +39 -11
- service_forge/service_config.py +14 -0
- service_forge/sft/cli.py +39 -0
- service_forge/sft/cmd/remote_deploy.py +160 -0
- service_forge/sft/cmd/remote_list_tars.py +111 -0
- service_forge/sft/config/injector.py +54 -7
- service_forge/sft/config/injector_default_files.py +13 -1
- service_forge/sft/config/sf_metadata.py +31 -27
- service_forge/sft/config/sft_config.py +18 -0
- service_forge/sft/util/assert_util.py +0 -1
- service_forge/telemetry.py +66 -0
- service_forge/utils/default_type_converter.py +1 -1
- service_forge/utils/type_converter.py +5 -0
- service_forge/utils/workflow_clone.py +1 -0
- service_forge/workflow/node.py +274 -27
- service_forge/workflow/triggers/fast_api_trigger.py +64 -28
- service_forge/workflow/triggers/websocket_api_trigger.py +66 -38
- service_forge/workflow/workflow.py +140 -37
- service_forge/workflow/workflow_callback.py +27 -4
- service_forge/workflow/workflow_factory.py +14 -0
- {service_forge-0.1.18.dist-info → service_forge-0.1.39.dist-info}/METADATA +4 -1
- service_forge-0.1.39.dist-info/RECORD +134 -0
- service_forge-0.1.18.dist-info/RECORD +0 -83
- {service_forge-0.1.18.dist-info → service_forge-0.1.39.dist-info}/WHEEL +0 -0
- {service_forge-0.1.18.dist-info → service_forge-0.1.39.dist-info}/entry_points.txt +0 -0
|
@@ -2,9 +2,9 @@ import yaml
|
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from service_forge.sft.util.logger import log_info, log_error
|
|
4
4
|
from service_forge.sft.config.injector_default_files import *
|
|
5
|
-
from service_forge.sft.config.sf_metadata import load_metadata
|
|
5
|
+
from service_forge.sft.config.sf_metadata import load_metadata, save_metadata
|
|
6
6
|
from service_forge.sft.config.sft_config import sft_config
|
|
7
|
-
from service_forge.service_config import ServiceConfig, ServiceFeedbackConfig
|
|
7
|
+
from service_forge.service_config import ServiceConfig, ServiceFeedbackConfig, SignozConfig, TraceConfig
|
|
8
8
|
from service_forge.sft.util.name_util import get_service_name
|
|
9
9
|
from service_forge.sft.util.yaml_utils import load_sf_metadata_as_string
|
|
10
10
|
|
|
@@ -16,7 +16,9 @@ class Injector:
|
|
|
16
16
|
self.ingress_yaml_path = project_dir / "ingress.yaml"
|
|
17
17
|
self.dockerfile_path = project_dir / "Dockerfile"
|
|
18
18
|
self.pyproject_toml_path = project_dir / "pyproject.toml"
|
|
19
|
+
self.start_sh_path = project_dir / "start.sh"
|
|
19
20
|
self.metadata = load_metadata(self.metadata_path)
|
|
21
|
+
self.metadata.mode = "release"
|
|
20
22
|
self.name = self.metadata.name
|
|
21
23
|
self.version = self.metadata.version
|
|
22
24
|
self.namespace = sft_config.k8s_namespace
|
|
@@ -93,6 +95,32 @@ class Injector:
|
|
|
93
95
|
api_timeout=sft_config.inject_feedback_api_timeout,
|
|
94
96
|
)
|
|
95
97
|
|
|
98
|
+
if config.signoz is not None:
|
|
99
|
+
config.signoz.api_url = sft_config.inject_signoz_api_url
|
|
100
|
+
config.signoz.api_key = sft_config.inject_signoz_api_key
|
|
101
|
+
else:
|
|
102
|
+
config.signoz = SignozConfig(
|
|
103
|
+
api_url=sft_config.inject_signoz_api_url,
|
|
104
|
+
api_key=sft_config.inject_signoz_api_key,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
if config.trace is not None:
|
|
108
|
+
config.trace.enable = True
|
|
109
|
+
config.trace.url = sft_config.inject_trace_url
|
|
110
|
+
config.trace.headers = sft_config.inject_trace_headers
|
|
111
|
+
config.trace.arg = sft_config.inject_trace_arg
|
|
112
|
+
config.trace.namespace = sft_config.inject_trace_namespace
|
|
113
|
+
config.trace.hostname = sft_config.inject_trace_hostname
|
|
114
|
+
else:
|
|
115
|
+
config.trace = TraceConfig(
|
|
116
|
+
enable=True,
|
|
117
|
+
url=sft_config.inject_trace_url,
|
|
118
|
+
headers=sft_config.inject_trace_headers,
|
|
119
|
+
arg=sft_config.inject_trace_arg,
|
|
120
|
+
namespace=sft_config.inject_trace_namespace,
|
|
121
|
+
hostname=sft_config.inject_trace_hostname,
|
|
122
|
+
)
|
|
123
|
+
|
|
96
124
|
with open(service_config_path, "w", encoding="utf-8") as f:
|
|
97
125
|
yaml.dump(config.model_dump(), f, allow_unicode=True, indent=2)
|
|
98
126
|
|
|
@@ -121,9 +149,28 @@ class Injector:
|
|
|
121
149
|
f.write(pyproject_toml)
|
|
122
150
|
print("pyproject_toml_path: ", self.pyproject_toml_path)
|
|
123
151
|
|
|
152
|
+
def clear_start_sh(self) -> None:
|
|
153
|
+
if Path(self.start_sh_path).exists():
|
|
154
|
+
with open(self.start_sh_path, "rb") as f:
|
|
155
|
+
content = f.read()
|
|
156
|
+
content_str = content.decode("utf-8")
|
|
157
|
+
lines = content_str.splitlines()
|
|
158
|
+
new_content = "\n".join(lines) + ("\n" if content_str.endswith(('\n', '\r')) else "")
|
|
159
|
+
with open(self.start_sh_path, "w", encoding="utf-8", newline="\n") as f:
|
|
160
|
+
f.write(new_content)
|
|
161
|
+
|
|
124
162
|
def inject(self) -> None:
|
|
125
|
-
self.
|
|
126
|
-
|
|
127
|
-
self.
|
|
128
|
-
|
|
129
|
-
self.
|
|
163
|
+
if self.metadata.inject.pyproject_toml:
|
|
164
|
+
self.inject_pyproject_toml()
|
|
165
|
+
if self.metadata.inject.deployment:
|
|
166
|
+
self.inject_deployment()
|
|
167
|
+
if self.metadata.inject.service_config:
|
|
168
|
+
self.inject_service_config()
|
|
169
|
+
if self.metadata.inject.ingress:
|
|
170
|
+
self.inject_ingress()
|
|
171
|
+
if self.metadata.inject.dockerfile:
|
|
172
|
+
self.inject_dockerfile()
|
|
173
|
+
if self.metadata.inject.pyproject_toml:
|
|
174
|
+
self.inject_pyproject_toml()
|
|
175
|
+
self.clear_start_sh()
|
|
176
|
+
save_metadata(self.metadata, self.metadata_path)
|
|
@@ -65,6 +65,18 @@ spec:
|
|
|
65
65
|
entryPoints:
|
|
66
66
|
- web
|
|
67
67
|
routes:
|
|
68
|
+
- match: PathPrefix(`/api/v1/{name}-{version}/sdk`)
|
|
69
|
+
kind: Rule
|
|
70
|
+
services:
|
|
71
|
+
- name: sf-{name}-{version}v
|
|
72
|
+
namespace: {namespace}
|
|
73
|
+
port: 80
|
|
74
|
+
middlewares:
|
|
75
|
+
- name: strip-prefix-sf-{name}-{version}v
|
|
76
|
+
namespace: {namespace}
|
|
77
|
+
- name: cors
|
|
78
|
+
namespace: {namespace}
|
|
79
|
+
|
|
68
80
|
- match: PathPrefix(`/api/v1/{name}-{version}/openapi.json`)
|
|
69
81
|
kind: Rule
|
|
70
82
|
services:
|
|
@@ -128,4 +140,4 @@ DEFAULT_PYPROJECT_TOML = """
|
|
|
128
140
|
|
|
129
141
|
[tool.uv.sources]
|
|
130
142
|
service-forge = { workspace = true }
|
|
131
|
-
"""
|
|
143
|
+
"""
|
|
@@ -1,30 +1,34 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import yaml
|
|
3
|
+
from pydantic import BaseModel
|
|
2
4
|
|
|
3
|
-
class
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
5
|
+
class SfMetadataInject(BaseModel):
|
|
6
|
+
deployment: bool = True
|
|
7
|
+
service_config: bool = True
|
|
8
|
+
ingress: bool = True
|
|
9
|
+
dockerfile: bool = True
|
|
10
|
+
pyproject_toml: bool = True
|
|
11
|
+
|
|
12
|
+
class SfMetadata(BaseModel):
|
|
13
|
+
name: str
|
|
14
|
+
version: str
|
|
15
|
+
description: str
|
|
16
|
+
service_config: str
|
|
17
|
+
config_only: bool
|
|
18
|
+
env: list[dict]
|
|
19
|
+
inject: SfMetadataInject = SfMetadataInject()
|
|
20
|
+
enable_auth_middleware: bool = True
|
|
21
|
+
mode: str = "debug"
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def from_yaml_file(cls, filepath: str) -> SfMetadata:
|
|
25
|
+
with open(filepath, 'r', encoding='utf-8') as f:
|
|
26
|
+
data = yaml.safe_load(f)
|
|
27
|
+
return cls(**data)
|
|
19
28
|
|
|
20
29
|
def load_metadata(path: str) -> SfMetadata:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
description=data.get('description'),
|
|
27
|
-
service_config=data.get('service_config'),
|
|
28
|
-
config_only=data.get('config_only'),
|
|
29
|
-
env=data.get('env', []),
|
|
30
|
-
)
|
|
30
|
+
return SfMetadata.from_yaml_file(path)
|
|
31
|
+
|
|
32
|
+
def save_metadata(meta: SfMetadata, path: str) -> None:
|
|
33
|
+
with open(path, 'w', encoding='utf-8') as f:
|
|
34
|
+
yaml.safe_dump(meta.model_dump(), f, allow_unicode=True)
|
|
@@ -57,6 +57,15 @@ class SftConfig:
|
|
|
57
57
|
inject_feedback_api_url: str = "http://vps.shiweinan.com:37919/api/v1/feedback",
|
|
58
58
|
inject_feedback_api_timeout: int = 5,
|
|
59
59
|
|
|
60
|
+
inject_signoz_api_url: str = "http://signoz.vps.shiweinan.com:37919",
|
|
61
|
+
inject_signoz_api_key: str = "JlxvqRtNFu5yc4o1bRcJyzeolA96iWzAyQnBePRRJd0=",
|
|
62
|
+
|
|
63
|
+
inject_trace_url: str = "http://traces.vps.shiweinan.com:37919/v1/traces",
|
|
64
|
+
inject_trace_headers: str = "",
|
|
65
|
+
inject_trace_arg: float = 1.0,
|
|
66
|
+
inject_trace_namespace: str = "secondbrain",
|
|
67
|
+
inject_trace_hostname: str = "",
|
|
68
|
+
|
|
60
69
|
deepseek_api_key: str = "82c9df22-f6ed-411e-90d7-c5255376b7ca",
|
|
61
70
|
deepseek_base_url: str = "https://ark.cn-beijing.volces.com/api/v3",
|
|
62
71
|
):
|
|
@@ -88,6 +97,15 @@ class SftConfig:
|
|
|
88
97
|
self.inject_feedback_api_url = inject_feedback_api_url
|
|
89
98
|
self.inject_feedback_api_timeout = inject_feedback_api_timeout
|
|
90
99
|
|
|
100
|
+
self.inject_signoz_api_url = inject_signoz_api_url
|
|
101
|
+
self.inject_signoz_api_key = inject_signoz_api_key
|
|
102
|
+
|
|
103
|
+
self.inject_trace_url = inject_trace_url
|
|
104
|
+
self.inject_trace_headers = inject_trace_headers
|
|
105
|
+
self.inject_trace_arg = inject_trace_arg
|
|
106
|
+
self.inject_trace_namespace = inject_trace_namespace
|
|
107
|
+
self.inject_trace_hostname = inject_trace_hostname
|
|
108
|
+
|
|
91
109
|
self.deepseek_api_key = deepseek_api_key
|
|
92
110
|
self.deepseek_base_url = deepseek_base_url
|
|
93
111
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from loguru import logger
|
|
6
|
+
from opentelemetry import trace
|
|
7
|
+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
|
|
8
|
+
from opentelemetry.sdk.resources import Resource
|
|
9
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
10
|
+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
11
|
+
from opentelemetry.sdk.trace.sampling import ParentBased, TraceIdRatioBased
|
|
12
|
+
from service_forge.service_config import TraceConfig
|
|
13
|
+
|
|
14
|
+
_initialized = False
|
|
15
|
+
|
|
16
|
+
def _parse_headers(raw: Optional[str]) -> dict[str, str]:
|
|
17
|
+
if not raw:
|
|
18
|
+
return {}
|
|
19
|
+
headers: dict[str, str] = {}
|
|
20
|
+
for part in raw.split(","):
|
|
21
|
+
if "=" in part:
|
|
22
|
+
key, value = part.split("=", 1)
|
|
23
|
+
headers[key.strip()] = value.strip()
|
|
24
|
+
return headers
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def setup_tracing(service_name: Optional[str] = None, config: TraceConfig = None) -> None:
|
|
28
|
+
"""Initialize a global tracer provider with OTLP exporter if not already configured."""
|
|
29
|
+
if config is None or not config.enable:
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
global _initialized
|
|
33
|
+
if _initialized:
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
service_name = service_name or "service_forge_service"
|
|
37
|
+
endpoint = config.url
|
|
38
|
+
headers = _parse_headers(config.headers)
|
|
39
|
+
|
|
40
|
+
sampler_arg = config.arg or 1.0
|
|
41
|
+
try:
|
|
42
|
+
ratio = float(sampler_arg)
|
|
43
|
+
except ValueError:
|
|
44
|
+
ratio = 1.0
|
|
45
|
+
sampler = ParentBased(TraceIdRatioBased(ratio))
|
|
46
|
+
|
|
47
|
+
resource = Resource.create(
|
|
48
|
+
{
|
|
49
|
+
"service.name": service_name,
|
|
50
|
+
"service.namespace": config.namespace or "secondbrain",
|
|
51
|
+
"service.instance.id": config.hostname or "",
|
|
52
|
+
}
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
provider = TracerProvider(resource=resource, sampler=sampler)
|
|
57
|
+
exporter = OTLPSpanExporter(endpoint=endpoint, headers=headers)
|
|
58
|
+
processor = BatchSpanProcessor(exporter)
|
|
59
|
+
provider.add_span_processor(processor)
|
|
60
|
+
trace.set_tracer_provider(provider)
|
|
61
|
+
_initialized = True
|
|
62
|
+
logger.info(
|
|
63
|
+
f"Tracing initialized: endpoint={endpoint}, service={service_name}, ratio={ratio}"
|
|
64
|
+
)
|
|
65
|
+
except Exception as exc:
|
|
66
|
+
logger.warning(f"Tracing initialization failed: {exc}")
|
|
@@ -2,8 +2,8 @@ from ..utils.type_converter import TypeConverter
|
|
|
2
2
|
from ..workflow.workflow import Workflow
|
|
3
3
|
from ..api.http_api import fastapi_app
|
|
4
4
|
from ..api.kafka_api import KafkaApp, kafka_app
|
|
5
|
-
from fastapi import FastAPI
|
|
6
5
|
from ..workflow.workflow_type import WorkflowType, workflow_type_register
|
|
6
|
+
from fastapi import FastAPI
|
|
7
7
|
|
|
8
8
|
type_converter = TypeConverter()
|
|
9
9
|
type_converter.register(str, Workflow, lambda s, node: node.sub_workflows.get_workflow(s))
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from typing import Any, Callable, Type, Dict, Tuple, Set, List
|
|
2
2
|
from collections import deque
|
|
3
3
|
import inspect
|
|
4
|
+
import traceback
|
|
5
|
+
from pydantic import BaseModel
|
|
4
6
|
from typing_extensions import get_origin, get_args
|
|
5
7
|
|
|
6
8
|
def is_type(value, dst_type):
|
|
@@ -57,6 +59,9 @@ class TypeConverter:
|
|
|
57
59
|
except Exception:
|
|
58
60
|
pass
|
|
59
61
|
|
|
62
|
+
if issubclass(dst_type, BaseModel) and isinstance(value, dict):
|
|
63
|
+
return dst_type(**value)
|
|
64
|
+
|
|
60
65
|
path = self._find_path(src_type, dst_type)
|
|
61
66
|
if not path:
|
|
62
67
|
raise TypeError(f"No conversion path found from {src_type.__name__} to {dst_type.__name__}.")
|