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.

Files changed (80) hide show
  1. service_forge/__init__.py +0 -0
  2. service_forge/api/deprecated_websocket_api.py +91 -33
  3. service_forge/api/deprecated_websocket_manager.py +70 -53
  4. service_forge/api/http_api.py +205 -55
  5. service_forge/api/kafka_api.py +113 -25
  6. service_forge/api/routers/meta_api/meta_api_router.py +57 -0
  7. service_forge/api/routers/service/service_router.py +42 -6
  8. service_forge/api/routers/trace/trace_router.py +326 -0
  9. service_forge/api/routers/websocket/websocket_router.py +69 -1
  10. service_forge/api/service_studio.py +9 -0
  11. service_forge/db/database.py +17 -0
  12. service_forge/execution_context.py +106 -0
  13. service_forge/frontend/static/assets/CreateNewNodeDialog-DkrEMxSH.js +1 -0
  14. service_forge/frontend/static/assets/CreateNewNodeDialog-DwFcBiGp.css +1 -0
  15. service_forge/frontend/static/assets/EditorSidePanel-BNVms9Fq.css +1 -0
  16. service_forge/frontend/static/assets/EditorSidePanel-DZbB3ILL.js +1 -0
  17. service_forge/frontend/static/assets/FeedbackPanel-CC8HX7Yo.js +1 -0
  18. service_forge/frontend/static/assets/FeedbackPanel-ClgniIVk.css +1 -0
  19. service_forge/frontend/static/assets/FormattedCodeViewer.vue_vue_type_script_setup_true_lang-BNuI1NCs.js +1 -0
  20. service_forge/frontend/static/assets/NodeDetailWrapper-BqFFM7-r.js +1 -0
  21. service_forge/frontend/static/assets/NodeDetailWrapper-pZBxv3J0.css +1 -0
  22. service_forge/frontend/static/assets/TestRunningDialog-D0GrCoYs.js +1 -0
  23. service_forge/frontend/static/assets/TestRunningDialog-dhXOsPgH.css +1 -0
  24. service_forge/frontend/static/assets/TracePanelWrapper-B9zvDSc_.js +1 -0
  25. service_forge/frontend/static/assets/TracePanelWrapper-BiednCrq.css +1 -0
  26. service_forge/frontend/static/assets/WorkflowEditor-CcaGGbko.js +3 -0
  27. service_forge/frontend/static/assets/WorkflowEditor-CmasOOYK.css +1 -0
  28. service_forge/frontend/static/assets/WorkflowList-Copuwi-a.css +1 -0
  29. service_forge/frontend/static/assets/WorkflowList-LrRJ7B7h.js +1 -0
  30. service_forge/frontend/static/assets/WorkflowStudio-CthjgII2.css +1 -0
  31. service_forge/frontend/static/assets/WorkflowStudio-FCyhGD4y.js +2 -0
  32. service_forge/frontend/static/assets/api-BDer3rj7.css +1 -0
  33. service_forge/frontend/static/assets/api-DyiqpKJK.js +1 -0
  34. service_forge/frontend/static/assets/code-editor-DBSql_sc.js +12 -0
  35. service_forge/frontend/static/assets/el-collapse-item-D4LG0FJ0.css +1 -0
  36. service_forge/frontend/static/assets/el-empty-D4ZqTl4F.css +1 -0
  37. service_forge/frontend/static/assets/el-form-item-BWkJzdQ_.css +1 -0
  38. service_forge/frontend/static/assets/el-input-D6B3r8CH.css +1 -0
  39. service_forge/frontend/static/assets/el-select-B0XIb2QK.css +1 -0
  40. service_forge/frontend/static/assets/el-tag-DljBBxJR.css +1 -0
  41. service_forge/frontend/static/assets/element-ui-D3x2y3TA.js +12 -0
  42. service_forge/frontend/static/assets/elkjs-Dm5QV7uy.js +24 -0
  43. service_forge/frontend/static/assets/highlightjs-D4ATuRwX.js +3 -0
  44. service_forge/frontend/static/assets/index-BMvodlwc.js +2 -0
  45. service_forge/frontend/static/assets/index-CjSe8i2q.css +1 -0
  46. service_forge/frontend/static/assets/js-yaml-yTPt38rv.js +32 -0
  47. service_forge/frontend/static/assets/time-DKCKV6Ug.js +1 -0
  48. service_forge/frontend/static/assets/ui-components-DQ7-U3pr.js +1 -0
  49. service_forge/frontend/static/assets/vue-core-DL-LgTX0.js +1 -0
  50. service_forge/frontend/static/assets/vue-flow-Dn7R8GPr.js +39 -0
  51. service_forge/frontend/static/index.html +16 -0
  52. service_forge/frontend/static/vite.svg +1 -0
  53. service_forge/model/meta_api/__init__.py +0 -0
  54. service_forge/model/meta_api/schema.py +29 -0
  55. service_forge/model/trace.py +82 -0
  56. service_forge/service.py +39 -11
  57. service_forge/service_config.py +14 -0
  58. service_forge/sft/cli.py +39 -0
  59. service_forge/sft/cmd/remote_deploy.py +160 -0
  60. service_forge/sft/cmd/remote_list_tars.py +111 -0
  61. service_forge/sft/config/injector.py +54 -7
  62. service_forge/sft/config/injector_default_files.py +13 -1
  63. service_forge/sft/config/sf_metadata.py +31 -27
  64. service_forge/sft/config/sft_config.py +18 -0
  65. service_forge/sft/util/assert_util.py +0 -1
  66. service_forge/telemetry.py +66 -0
  67. service_forge/utils/default_type_converter.py +1 -1
  68. service_forge/utils/type_converter.py +5 -0
  69. service_forge/utils/workflow_clone.py +1 -0
  70. service_forge/workflow/node.py +274 -27
  71. service_forge/workflow/triggers/fast_api_trigger.py +64 -28
  72. service_forge/workflow/triggers/websocket_api_trigger.py +66 -38
  73. service_forge/workflow/workflow.py +140 -37
  74. service_forge/workflow/workflow_callback.py +27 -4
  75. service_forge/workflow/workflow_factory.py +14 -0
  76. {service_forge-0.1.18.dist-info → service_forge-0.1.39.dist-info}/METADATA +4 -1
  77. service_forge-0.1.39.dist-info/RECORD +134 -0
  78. service_forge-0.1.18.dist-info/RECORD +0 -83
  79. {service_forge-0.1.18.dist-info → service_forge-0.1.39.dist-info}/WHEEL +0 -0
  80. {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.inject_deployment()
126
- self.inject_service_config()
127
- self.inject_ingress()
128
- self.inject_dockerfile()
129
- self.inject_pyproject_toml()
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 omegaconf import OmegaConf
1
+ from __future__ import annotations
2
+ import yaml
3
+ from pydantic import BaseModel
2
4
 
3
- class SfMetadata:
4
- def __init__(
5
- self,
6
- name: str,
7
- version: str,
8
- description: str,
9
- service_config: str,
10
- config_only: bool,
11
- env: list[dict],
12
- ) -> None:
13
- self.name = name
14
- self.version = version
15
- self.description = description
16
- self.service_config = service_config
17
- self.config_only = config_only
18
- self.env = env
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
- with open(path, 'r') as file:
22
- data = OmegaConf.load(file)
23
- return SfMetadata(
24
- name=data.get('name'),
25
- version=data.get('version'),
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
 
@@ -2,7 +2,6 @@ import typer
2
2
  from pathlib import Path
3
3
  from typing import Callable, TypeVar, Any
4
4
  from service_forge.sft.util.logger import log_error, log_info
5
- from service_forge.sft.config.sf_metadata import load_metadata, SfMetadata
6
5
 
7
6
  T = TypeVar('T')
8
7
 
@@ -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__}.")
@@ -69,6 +69,7 @@ def workflow_clone(self: Workflow, task_id: uuid.UUID, trigger_node: Trigger) ->
69
69
  callbacks=self.callbacks,
70
70
  task_id=task_id,
71
71
  real_trigger_node=trigger_node,
72
+ global_context=self.global_context,
72
73
  )
73
74
 
74
75
  for node in workflow.nodes: