by-framework 0.2.2.dev3__py3-none-any.whl → 0.2.2.dev5__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.
- by_framework/client/client.py +117 -9
- by_framework/common/config.py +2 -0
- by_framework/common/constants.py +5 -1
- by_framework/common/logger.py +13 -1
- by_framework/core/__init__.py +95 -0
- by_framework/core/availability.py +495 -0
- by_framework/core/delivery_gate.py +60 -0
- by_framework/core/discovery.py +359 -0
- by_framework/core/extensions/__init__.py +35 -0
- by_framework/core/extensions/agent_config.py +64 -0
- by_framework/core/extensions/plugin.py +314 -0
- by_framework/core/extensions/registry.py +711 -0
- by_framework/core/extensions/trace_provider.py +20 -0
- by_framework/core/protocol/__init__.py +133 -0
- by_framework/core/protocol/action_type.py +33 -0
- by_framework/core/protocol/agent_state.py +78 -0
- by_framework/core/protocol/byai_codec.py +101 -0
- by_framework/core/protocol/byai_command.py +53 -0
- by_framework/core/protocol/byai_types.py +7 -0
- by_framework/core/protocol/commands.py +285 -0
- by_framework/core/protocol/content_codec.py +17 -0
- by_framework/core/protocol/content_type.py +38 -0
- by_framework/core/protocol/data_message.py +45 -0
- by_framework/core/protocol/data_shapes.py +83 -0
- by_framework/core/protocol/event_type.py +34 -0
- by_framework/core/protocol/events.py +69 -0
- by_framework/core/protocol/message.py +99 -0
- by_framework/core/protocol/message_header.py +78 -0
- by_framework/core/protocol/responses.py +94 -0
- by_framework/core/protocol/results.py +149 -0
- by_framework/core/registry.py +1254 -0
- by_framework/core/runtime/__init__.py +29 -0
- by_framework/core/runtime/agent_config_manager.py +283 -0
- by_framework/core/runtime/agent_runtime_state.py +75 -0
- by_framework/core/runtime/file_manager.py +437 -0
- by_framework/core/runtime/file_paths.py +76 -0
- by_framework/core/runtime/file_permissions.py +71 -0
- by_framework/core/runtime/filestore/__init__.py +15 -0
- by_framework/core/runtime/filestore/base.py +140 -0
- by_framework/core/runtime/filestore/local.py +321 -0
- by_framework/core/runtime/history/__init__.py +10 -0
- by_framework/core/runtime/history/base.py +57 -0
- by_framework/core/runtime/history/history_manager.py +55 -0
- by_framework/core/runtime/history/in_memory.py +58 -0
- by_framework/core/runtime/session_manager.py +118 -0
- by_framework/core/wakeup_controller.py +151 -0
- by_framework/core/workspace.py +126 -0
- by_framework/metrics/__init__.py +2 -0
- by_framework/metrics/collector.py +161 -0
- by_framework/trace/__init__.py +2 -0
- by_framework/trace/span_recorder.py +30 -0
- by_framework/worker/context.py +1 -65
- by_framework/worker/heartbeat.py +204 -18
- by_framework/worker/runner.py +123 -6
- by_framework/worker/worker.py +7 -0
- {by_framework-0.2.2.dev3.dist-info → by_framework-0.2.2.dev5.dist-info}/METADATA +1 -1
- by_framework-0.2.2.dev5.dist-info/RECORD +93 -0
- by_framework-0.2.2.dev3.dist-info/RECORD +0 -49
- {by_framework-0.2.2.dev3.dist-info → by_framework-0.2.2.dev5.dist-info}/WHEEL +0 -0
- {by_framework-0.2.2.dev3.dist-info → by_framework-0.2.2.dev5.dist-info}/licenses/LICENSE +0 -0
by_framework/client/client.py
CHANGED
|
@@ -43,7 +43,16 @@ from by_framework.core.protocol.responses import (
|
|
|
43
43
|
)
|
|
44
44
|
from by_framework.core.registry import WorkerRegistry
|
|
45
45
|
from by_framework.errors import WorkerRegistryNotSetError
|
|
46
|
-
from by_framework.trace.span_recorder import (
|
|
46
|
+
from by_framework.trace.span_recorder import (
|
|
47
|
+
ObservabilityConfig,
|
|
48
|
+
SpanRecorder,
|
|
49
|
+
TraceSpan,
|
|
50
|
+
build_observability_config,
|
|
51
|
+
sanitize_io_value,
|
|
52
|
+
str_to_uint64,
|
|
53
|
+
)
|
|
54
|
+
from by_framework.trace.trace_schema import TraceRecord
|
|
55
|
+
from by_framework.trace.trace_writer import TraceWriteClient
|
|
47
56
|
|
|
48
57
|
if TYPE_CHECKING:
|
|
49
58
|
pass
|
|
@@ -89,13 +98,30 @@ class GatewayClient:
|
|
|
89
98
|
redis_client: Optional[Redis] = None,
|
|
90
99
|
interceptors: Optional[List[GatewayInterceptor]] = None,
|
|
91
100
|
span_recorder: Optional[SpanRecorder] = None,
|
|
101
|
+
obs_config: Optional[ObservabilityConfig] = None,
|
|
92
102
|
):
|
|
93
103
|
self.registry = registry
|
|
94
104
|
self.redis = (
|
|
95
105
|
redis_client or (registry.redis if registry else None) or get_redis()
|
|
96
106
|
)
|
|
97
107
|
self.interceptors = interceptors or []
|
|
98
|
-
self.
|
|
108
|
+
self._obs_config = obs_config or build_observability_config()
|
|
109
|
+
self.span_recorder = span_recorder or SpanRecorder(
|
|
110
|
+
self.redis, config=self._obs_config
|
|
111
|
+
)
|
|
112
|
+
self._trace_writer = TraceWriteClient(
|
|
113
|
+
self.redis, ttl_seconds=self._obs_config.ttl_seconds
|
|
114
|
+
)
|
|
115
|
+
self._langfuse_dispatch_fn = self._resolve_langfuse_dispatch_fn()
|
|
116
|
+
|
|
117
|
+
@staticmethod
|
|
118
|
+
def _resolve_langfuse_dispatch_fn() -> Any:
|
|
119
|
+
try:
|
|
120
|
+
from by_framework_trace_langfuse import start_client_dispatch_observation
|
|
121
|
+
|
|
122
|
+
return start_client_dispatch_observation
|
|
123
|
+
except ImportError:
|
|
124
|
+
return None
|
|
99
125
|
|
|
100
126
|
def add_interceptor(self, interceptor: GatewayInterceptor):
|
|
101
127
|
self.interceptors.append(interceptor)
|
|
@@ -607,6 +633,20 @@ class GatewayClient:
|
|
|
607
633
|
message_id = f"{MESSAGE_ID_PREFIX}{uuid.uuid4().hex[:8]}"
|
|
608
634
|
if not trace_id:
|
|
609
635
|
trace_id = uuid.uuid4().hex
|
|
636
|
+
trace_start_ts = int(time.time() * 1000)
|
|
637
|
+
is_root_dispatch = not params.get("parent_message_id", "")
|
|
638
|
+
|
|
639
|
+
# Write trace root start so the trace is visible even before the worker
|
|
640
|
+
# picks up the task. Only written for root dispatches (no parent).
|
|
641
|
+
if is_root_dispatch:
|
|
642
|
+
await self._write_trace_root_start(
|
|
643
|
+
trace_id=trace_id,
|
|
644
|
+
message_id=message_id,
|
|
645
|
+
session_id=str(params.get("session_id", "")),
|
|
646
|
+
target_agent_type=str(params.get("target_agent_type", "")),
|
|
647
|
+
content=params.get("content"),
|
|
648
|
+
start_ts=trace_start_ts,
|
|
649
|
+
)
|
|
610
650
|
|
|
611
651
|
metadata = dict(params.get("metadata", {}) or {})
|
|
612
652
|
trace_parent_span_id = metadata.pop("trace_parent_span_id", "")
|
|
@@ -619,7 +659,7 @@ class GatewayClient:
|
|
|
619
659
|
)
|
|
620
660
|
|
|
621
661
|
langfuse_client_dispatch = None
|
|
622
|
-
if not
|
|
662
|
+
if not langfuse_parent_observation_id:
|
|
623
663
|
langfuse_client_dispatch = self._start_langfuse_client_dispatch_observation(
|
|
624
664
|
trace_id=trace_id,
|
|
625
665
|
message_id=message_id,
|
|
@@ -631,8 +671,7 @@ class GatewayClient:
|
|
|
631
671
|
metadata=metadata,
|
|
632
672
|
)
|
|
633
673
|
observation_id = getattr(langfuse_client_dispatch, "id", "")
|
|
634
|
-
|
|
635
|
-
langfuse_parent_observation_id = observation_id
|
|
674
|
+
langfuse_parent_observation_id = observation_id or trace_parent_span_id
|
|
636
675
|
|
|
637
676
|
header = MessageHeader(
|
|
638
677
|
message_id=message_id,
|
|
@@ -734,6 +773,13 @@ class GatewayClient:
|
|
|
734
773
|
output={"success": False, "error": availability.error},
|
|
735
774
|
error=availability.error,
|
|
736
775
|
)
|
|
776
|
+
if is_root_dispatch:
|
|
777
|
+
await self._write_trace_root_end(
|
|
778
|
+
trace_id=trace_id,
|
|
779
|
+
status="FAILED",
|
|
780
|
+
end_ts=int(time.time() * 1000),
|
|
781
|
+
error=availability.error or "",
|
|
782
|
+
)
|
|
737
783
|
return response
|
|
738
784
|
if availability.status == AvailabilityStatus.QUEUE_PENDING:
|
|
739
785
|
should_dispatch_control = False
|
|
@@ -750,6 +796,13 @@ class GatewayClient:
|
|
|
750
796
|
output={"success": False, "error": str(err)},
|
|
751
797
|
error=str(err),
|
|
752
798
|
)
|
|
799
|
+
if is_root_dispatch:
|
|
800
|
+
await self._write_trace_root_end(
|
|
801
|
+
trace_id=trace_id,
|
|
802
|
+
status="FAILED",
|
|
803
|
+
end_ts=int(time.time() * 1000),
|
|
804
|
+
error=str(err),
|
|
805
|
+
)
|
|
753
806
|
return SendMessageResponse(
|
|
754
807
|
success=False,
|
|
755
808
|
status=ExecutionStatus.FAILED,
|
|
@@ -766,6 +819,13 @@ class GatewayClient:
|
|
|
766
819
|
output={"success": False, "error": str(err)},
|
|
767
820
|
error=str(err),
|
|
768
821
|
)
|
|
822
|
+
if is_root_dispatch:
|
|
823
|
+
await self._write_trace_root_end(
|
|
824
|
+
trace_id=trace_id,
|
|
825
|
+
status="FAILED",
|
|
826
|
+
end_ts=int(time.time() * 1000),
|
|
827
|
+
error=str(err),
|
|
828
|
+
)
|
|
769
829
|
return SendMessageResponse(
|
|
770
830
|
success=False,
|
|
771
831
|
status=ExecutionStatus.FAILED,
|
|
@@ -860,10 +920,10 @@ class GatewayClient:
|
|
|
860
920
|
content: Any,
|
|
861
921
|
metadata: Dict[str, Any],
|
|
862
922
|
) -> Any:
|
|
923
|
+
if self._langfuse_dispatch_fn is None:
|
|
924
|
+
return None
|
|
863
925
|
try:
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
return start_client_dispatch_observation(
|
|
926
|
+
return self._langfuse_dispatch_fn(
|
|
867
927
|
trace_id=trace_id,
|
|
868
928
|
message_id=message_id,
|
|
869
929
|
target_agent_type=target_agent_type,
|
|
@@ -877,7 +937,6 @@ class GatewayClient:
|
|
|
877
937
|
logger.warning(
|
|
878
938
|
"Langfuse client.dispatch observation skipped: %s",
|
|
879
939
|
err,
|
|
880
|
-
exc_info=True,
|
|
881
940
|
)
|
|
882
941
|
return None
|
|
883
942
|
|
|
@@ -951,3 +1010,52 @@ class GatewayClient:
|
|
|
951
1010
|
logger.warning(
|
|
952
1011
|
"Failed to record client dispatch span: %s", err, exc_info=True
|
|
953
1012
|
)
|
|
1013
|
+
|
|
1014
|
+
async def _write_trace_root_start(
|
|
1015
|
+
self,
|
|
1016
|
+
*,
|
|
1017
|
+
trace_id: str,
|
|
1018
|
+
message_id: str,
|
|
1019
|
+
session_id: str,
|
|
1020
|
+
target_agent_type: str,
|
|
1021
|
+
content: Any,
|
|
1022
|
+
start_ts: int,
|
|
1023
|
+
) -> None:
|
|
1024
|
+
"""Write trace root start — best-effort, never propagates errors."""
|
|
1025
|
+
try:
|
|
1026
|
+
await self._trace_writer.record_trace(
|
|
1027
|
+
TraceRecord(
|
|
1028
|
+
trace_id=trace_id,
|
|
1029
|
+
name=target_agent_type,
|
|
1030
|
+
session_id=session_id,
|
|
1031
|
+
root_message_id=message_id,
|
|
1032
|
+
root_agent_type=target_agent_type,
|
|
1033
|
+
input=sanitize_io_value(content, self._obs_config),
|
|
1034
|
+
status="QUEUED",
|
|
1035
|
+
start_ts=start_ts,
|
|
1036
|
+
)
|
|
1037
|
+
)
|
|
1038
|
+
except Exception: # pylint: disable=broad-exception-caught
|
|
1039
|
+
pass
|
|
1040
|
+
|
|
1041
|
+
async def _write_trace_root_end(
|
|
1042
|
+
self,
|
|
1043
|
+
*,
|
|
1044
|
+
trace_id: str,
|
|
1045
|
+
status: str,
|
|
1046
|
+
end_ts: int,
|
|
1047
|
+
error: str = "",
|
|
1048
|
+
) -> None:
|
|
1049
|
+
"""Write trace root end on routing failure — best-effort."""
|
|
1050
|
+
try:
|
|
1051
|
+
meta: dict = {"error": error} if error else {}
|
|
1052
|
+
await self._trace_writer.record_trace(
|
|
1053
|
+
TraceRecord(
|
|
1054
|
+
trace_id=trace_id,
|
|
1055
|
+
status=status,
|
|
1056
|
+
end_ts=end_ts,
|
|
1057
|
+
metadata=meta,
|
|
1058
|
+
)
|
|
1059
|
+
)
|
|
1060
|
+
except Exception: # pylint: disable=broad-exception-caught
|
|
1061
|
+
pass
|
by_framework/common/config.py
CHANGED
|
@@ -50,6 +50,8 @@ class WorkerConfig:
|
|
|
50
50
|
)
|
|
51
51
|
heartbeat_lease_ttl_seconds: int = RedisKeys.WORKER_DEFAULT_LEASE_TTL_SECONDS
|
|
52
52
|
lock_ttl_seconds: int = 60
|
|
53
|
+
worker_id_claim_max_wait_seconds: int = 90
|
|
54
|
+
worker_id_claim_retry_interval_seconds: float = 3.0
|
|
53
55
|
stream_block_ms: int = 2000
|
|
54
56
|
|
|
55
57
|
|
by_framework/common/constants.py
CHANGED
|
@@ -136,7 +136,11 @@ class RedisKeys:
|
|
|
136
136
|
# Set of known workers used for registry enumeration.
|
|
137
137
|
KNOWN_WORKERS = "byai_gateway:registry:workers"
|
|
138
138
|
WORKER_DEFAULT_HEARTBEAT_INTERVAL_SECONDS = 5
|
|
139
|
-
|
|
139
|
+
# 6× the heartbeat interval gives enough margin even when the main event
|
|
140
|
+
# loop is briefly occupied by an LLM call. The dedicated heartbeat thread
|
|
141
|
+
# makes starvation practically impossible, but the larger TTL is a second
|
|
142
|
+
# line of defence and also gives Worker 2 time to claim after a crash.
|
|
143
|
+
WORKER_DEFAULT_LEASE_TTL_SECONDS = 30
|
|
140
144
|
|
|
141
145
|
# Default TTL (7 days) for cleaning up session-related aggregate Keys
|
|
142
146
|
DEFAULT_SESSION_TTL = 7 * 24 * 3600
|
by_framework/common/logger.py
CHANGED
|
@@ -167,7 +167,19 @@ def get_logger(name: str) -> logging.Logger:
|
|
|
167
167
|
Returns:
|
|
168
168
|
Logger instance
|
|
169
169
|
"""
|
|
170
|
-
|
|
170
|
+
base_logger = setup_logging()
|
|
171
|
+
if name == base_logger.name:
|
|
172
|
+
return base_logger
|
|
173
|
+
|
|
174
|
+
child_logger = logging.getLogger(name)
|
|
175
|
+
child_logger.setLevel(base_logger.level)
|
|
176
|
+
child_logger.propagate = False
|
|
177
|
+
|
|
178
|
+
if not child_logger.handlers:
|
|
179
|
+
for handler in base_logger.handlers:
|
|
180
|
+
child_logger.addHandler(handler)
|
|
181
|
+
|
|
182
|
+
return child_logger
|
|
171
183
|
|
|
172
184
|
|
|
173
185
|
# Expose default logger
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core module of the Gateway SDK.
|
|
3
|
+
|
|
4
|
+
Provides core components including protocol definitions, worker registry,
|
|
5
|
+
and workspace management.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .availability import (
|
|
9
|
+
AvailabilityResult,
|
|
10
|
+
AvailabilityRouter,
|
|
11
|
+
AvailabilityStatus,
|
|
12
|
+
DeliveryIntent,
|
|
13
|
+
PendingDelivery,
|
|
14
|
+
RoutePolicy,
|
|
15
|
+
WakeupDecision,
|
|
16
|
+
WakeupDecisionStatus,
|
|
17
|
+
WakeupRequest,
|
|
18
|
+
)
|
|
19
|
+
from .delivery_gate import DeliveryGate
|
|
20
|
+
from .protocol import (
|
|
21
|
+
ActionType,
|
|
22
|
+
AgentState,
|
|
23
|
+
ArtifactEvent,
|
|
24
|
+
AskAgentCommand,
|
|
25
|
+
AskUserEvent,
|
|
26
|
+
BaiYingMessage,
|
|
27
|
+
BaiYingMessageRole,
|
|
28
|
+
BaseCommand,
|
|
29
|
+
CancelTaskCommand,
|
|
30
|
+
CancelTaskResponse,
|
|
31
|
+
DataMessage,
|
|
32
|
+
EventType,
|
|
33
|
+
GatewayCommand,
|
|
34
|
+
MessageContent,
|
|
35
|
+
MessageFile,
|
|
36
|
+
MessageHeader,
|
|
37
|
+
Resource,
|
|
38
|
+
ResumeCommand,
|
|
39
|
+
SendMessageResponse,
|
|
40
|
+
SseMessageType,
|
|
41
|
+
SseReasonMessageType,
|
|
42
|
+
StateChangeEvent,
|
|
43
|
+
StreamChunkEvent,
|
|
44
|
+
command_from_dict,
|
|
45
|
+
get_registered_command,
|
|
46
|
+
register_command,
|
|
47
|
+
unregister_command,
|
|
48
|
+
)
|
|
49
|
+
from .registry import WorkerRegistry
|
|
50
|
+
from .wakeup_controller import WakeupController, WakeupProvider
|
|
51
|
+
from .workspace import WorkspaceManager
|
|
52
|
+
|
|
53
|
+
__all__ = [
|
|
54
|
+
"SendMessageResponse",
|
|
55
|
+
"CancelTaskResponse",
|
|
56
|
+
"BaiYingMessage",
|
|
57
|
+
"BaiYingMessageRole",
|
|
58
|
+
"MessageContent",
|
|
59
|
+
"MessageFile",
|
|
60
|
+
"Resource",
|
|
61
|
+
"DataMessage",
|
|
62
|
+
"ActionType",
|
|
63
|
+
"AgentState",
|
|
64
|
+
"EventType",
|
|
65
|
+
"StateChangeEvent",
|
|
66
|
+
"StreamChunkEvent",
|
|
67
|
+
"ArtifactEvent",
|
|
68
|
+
"AskUserEvent",
|
|
69
|
+
"SseMessageType",
|
|
70
|
+
"SseReasonMessageType",
|
|
71
|
+
"MessageHeader",
|
|
72
|
+
"BaseCommand",
|
|
73
|
+
"AskAgentCommand",
|
|
74
|
+
"ResumeCommand",
|
|
75
|
+
"CancelTaskCommand",
|
|
76
|
+
"GatewayCommand",
|
|
77
|
+
"command_from_dict",
|
|
78
|
+
"register_command",
|
|
79
|
+
"unregister_command",
|
|
80
|
+
"get_registered_command",
|
|
81
|
+
"WorkerRegistry",
|
|
82
|
+
"WorkspaceManager",
|
|
83
|
+
"AvailabilityRouter",
|
|
84
|
+
"AvailabilityStatus",
|
|
85
|
+
"AvailabilityResult",
|
|
86
|
+
"DeliveryIntent",
|
|
87
|
+
"RoutePolicy",
|
|
88
|
+
"PendingDelivery",
|
|
89
|
+
"WakeupRequest",
|
|
90
|
+
"WakeupDecision",
|
|
91
|
+
"WakeupDecisionStatus",
|
|
92
|
+
"WakeupController",
|
|
93
|
+
"WakeupProvider",
|
|
94
|
+
"DeliveryGate",
|
|
95
|
+
]
|