by-framework 0.2.2.dev4__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 +102 -2
- 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 +40 -14
- by_framework/core/extensions/__init__.py +9 -3
- by_framework/core/extensions/plugin.py +5 -3
- by_framework/core/extensions/registry.py +12 -5
- by_framework/core/protocol/__init__.py +53 -19
- by_framework/core/protocol/byai_codec.py +7 -2
- by_framework/core/registry.py +222 -70
- by_framework/core/runtime/__init__.py +4 -2
- by_framework/core/runtime/file_manager.py +15 -12
- by_framework/core/runtime/filestore/local.py +12 -4
- by_framework/core/wakeup_controller.py +5 -3
- 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.dev4.dist-info → by_framework-0.2.2.dev5.dist-info}/METADATA +1 -1
- {by_framework-0.2.2.dev4.dist-info → by_framework-0.2.2.dev5.dist-info}/RECORD +27 -26
- {by_framework-0.2.2.dev4.dist-info → by_framework-0.2.2.dev5.dist-info}/WHEEL +0 -0
- {by_framework-0.2.2.dev4.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,20 @@ 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
|
+
)
|
|
99
115
|
self._langfuse_dispatch_fn = self._resolve_langfuse_dispatch_fn()
|
|
100
116
|
|
|
101
117
|
@staticmethod
|
|
@@ -617,6 +633,20 @@ class GatewayClient:
|
|
|
617
633
|
message_id = f"{MESSAGE_ID_PREFIX}{uuid.uuid4().hex[:8]}"
|
|
618
634
|
if not trace_id:
|
|
619
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
|
+
)
|
|
620
650
|
|
|
621
651
|
metadata = dict(params.get("metadata", {}) or {})
|
|
622
652
|
trace_parent_span_id = metadata.pop("trace_parent_span_id", "")
|
|
@@ -743,6 +773,13 @@ class GatewayClient:
|
|
|
743
773
|
output={"success": False, "error": availability.error},
|
|
744
774
|
error=availability.error,
|
|
745
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
|
+
)
|
|
746
783
|
return response
|
|
747
784
|
if availability.status == AvailabilityStatus.QUEUE_PENDING:
|
|
748
785
|
should_dispatch_control = False
|
|
@@ -759,6 +796,13 @@ class GatewayClient:
|
|
|
759
796
|
output={"success": False, "error": str(err)},
|
|
760
797
|
error=str(err),
|
|
761
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
|
+
)
|
|
762
806
|
return SendMessageResponse(
|
|
763
807
|
success=False,
|
|
764
808
|
status=ExecutionStatus.FAILED,
|
|
@@ -775,6 +819,13 @@ class GatewayClient:
|
|
|
775
819
|
output={"success": False, "error": str(err)},
|
|
776
820
|
error=str(err),
|
|
777
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
|
+
)
|
|
778
829
|
return SendMessageResponse(
|
|
779
830
|
success=False,
|
|
780
831
|
status=ExecutionStatus.FAILED,
|
|
@@ -959,3 +1010,52 @@ class GatewayClient:
|
|
|
959
1010
|
logger.warning(
|
|
960
1011
|
"Failed to record client dispatch span: %s", err, exc_info=True
|
|
961
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
|
by_framework/core/__init__.py
CHANGED
|
@@ -5,21 +5,47 @@ Provides core components including protocol definitions, worker registry,
|
|
|
5
5
|
and workspace management.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from .availability import (
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
from .availability import (
|
|
9
|
+
AvailabilityResult,
|
|
10
|
+
AvailabilityRouter,
|
|
11
|
+
AvailabilityStatus,
|
|
12
|
+
DeliveryIntent,
|
|
13
|
+
PendingDelivery,
|
|
14
|
+
RoutePolicy,
|
|
15
|
+
WakeupDecision,
|
|
16
|
+
WakeupDecisionStatus,
|
|
17
|
+
WakeupRequest,
|
|
18
|
+
)
|
|
12
19
|
from .delivery_gate import DeliveryGate
|
|
13
|
-
from .protocol import (
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
+
)
|
|
23
49
|
from .registry import WorkerRegistry
|
|
24
50
|
from .wakeup_controller import WakeupController, WakeupProvider
|
|
25
51
|
from .workspace import WorkspaceManager
|
|
@@ -8,9 +8,15 @@ managed through the form of plugins.
|
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
from .agent_config import AgentConfig, CallbackType
|
|
11
|
-
from .plugin import (
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
from .plugin import (
|
|
12
|
+
AgentConfigsSnapshot,
|
|
13
|
+
Plugin,
|
|
14
|
+
PluginBuildContext,
|
|
15
|
+
PluginManifest,
|
|
16
|
+
PluginReloadContext,
|
|
17
|
+
PluginReloadResult,
|
|
18
|
+
PromptTemplate,
|
|
19
|
+
)
|
|
14
20
|
from .registry import PluginRegistry
|
|
15
21
|
from .trace_provider import TraceProviderFactory
|
|
16
22
|
|
|
@@ -16,9 +16,11 @@ from typing import TYPE_CHECKING, Any, List, Type
|
|
|
16
16
|
from .agent_config import AgentConfig
|
|
17
17
|
|
|
18
18
|
if TYPE_CHECKING:
|
|
19
|
-
from by_framework.core.protocol.commands import (
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
from by_framework.core.protocol.commands import (
|
|
20
|
+
AskAgentCommand,
|
|
21
|
+
CancelTaskCommand,
|
|
22
|
+
ResumeCommand,
|
|
23
|
+
)
|
|
22
24
|
from by_framework.worker.context import AgentContext
|
|
23
25
|
from by_framework.worker.worker import GatewayWorker
|
|
24
26
|
|
|
@@ -19,13 +19,20 @@ import dill
|
|
|
19
19
|
from by_framework.common.logger import logger
|
|
20
20
|
|
|
21
21
|
from .agent_config import AgentConfig
|
|
22
|
-
from .plugin import (
|
|
23
|
-
|
|
22
|
+
from .plugin import (
|
|
23
|
+
AgentConfigsSnapshot,
|
|
24
|
+
Plugin,
|
|
25
|
+
PluginBuildContext,
|
|
26
|
+
PluginReloadContext,
|
|
27
|
+
PluginReloadResult,
|
|
28
|
+
)
|
|
24
29
|
|
|
25
30
|
if TYPE_CHECKING:
|
|
26
|
-
from by_framework.core.protocol.commands import (
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
from by_framework.core.protocol.commands import (
|
|
32
|
+
AskAgentCommand,
|
|
33
|
+
CancelTaskCommand,
|
|
34
|
+
ResumeCommand,
|
|
35
|
+
)
|
|
29
36
|
from by_framework.worker.context import AgentContext
|
|
30
37
|
from by_framework.worker.worker import GatewayWorker
|
|
31
38
|
|
|
@@ -14,32 +14,66 @@ where possible to ensure protocol stability.
|
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
16
|
from .action_type import ActionType, ActionTypeLiteral
|
|
17
|
-
from .agent_state import (
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
from .agent_state import (
|
|
18
|
+
TERMINAL_STATES,
|
|
19
|
+
AgentState,
|
|
20
|
+
AgentStateLiteral,
|
|
21
|
+
is_terminal_state,
|
|
22
|
+
)
|
|
23
|
+
from .byai_codec import (
|
|
24
|
+
ByaiContentCodec,
|
|
25
|
+
deserialize_byai_content,
|
|
26
|
+
serialize_byai_content,
|
|
27
|
+
)
|
|
21
28
|
from .byai_command import ByaiAskAgentCommand, ByaiResumeCommand
|
|
22
29
|
from .byai_types import ByaiContent
|
|
23
|
-
from .commands import (
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
30
|
+
from .commands import (
|
|
31
|
+
AskAgentCommand,
|
|
32
|
+
BaseCommand,
|
|
33
|
+
CancelTaskCommand,
|
|
34
|
+
GatewayCommand,
|
|
35
|
+
ReloadPluginsCommand,
|
|
36
|
+
ResumeCommand,
|
|
37
|
+
command_from_dict,
|
|
38
|
+
get_registered_command,
|
|
39
|
+
register_command,
|
|
40
|
+
unregister_command,
|
|
41
|
+
)
|
|
27
42
|
from .content_codec import ContentCodec, WireContent
|
|
28
43
|
from .content_type import SseMessageType, SseReasonMessageType
|
|
29
44
|
from .data_message import DataMessage
|
|
30
|
-
from .data_shapes import (
|
|
31
|
-
|
|
32
|
-
|
|
45
|
+
from .data_shapes import (
|
|
46
|
+
AskAgentBodyDict,
|
|
47
|
+
CancelTaskBodyDict,
|
|
48
|
+
CommandBodyDict,
|
|
49
|
+
CommandHeaderDict,
|
|
50
|
+
ExecutionDataDict,
|
|
51
|
+
RedisCommandDict,
|
|
52
|
+
ResumeBodyDict,
|
|
53
|
+
)
|
|
33
54
|
from .event_type import EventType
|
|
34
|
-
from .events import (ArtifactEvent, AskUserEvent, StateChangeEvent,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
55
|
+
from .events import (ArtifactEvent, AskUserEvent, StateChangeEvent, StreamChunkEvent)
|
|
56
|
+
from .message import (
|
|
57
|
+
BaiYingMessage,
|
|
58
|
+
BaiYingMessageRole,
|
|
59
|
+
MessageContent,
|
|
60
|
+
MessageFile,
|
|
61
|
+
Resource,
|
|
62
|
+
)
|
|
38
63
|
from .message_header import MessageHeader
|
|
39
|
-
from .responses import (
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
64
|
+
from .responses import (
|
|
65
|
+
CancelTaskResponse,
|
|
66
|
+
CancelTaskResponseDict,
|
|
67
|
+
SendMessageResponse,
|
|
68
|
+
SendMessageResponseDict,
|
|
69
|
+
)
|
|
70
|
+
from .results import (
|
|
71
|
+
AgentTaskResult,
|
|
72
|
+
JsonValue,
|
|
73
|
+
ProcessCommandResult,
|
|
74
|
+
ensure_json_serializable,
|
|
75
|
+
normalize_process_result,
|
|
76
|
+
)
|
|
43
77
|
|
|
44
78
|
__all__ = [
|
|
45
79
|
"SendMessageResponse",
|
|
@@ -5,8 +5,13 @@ from enum import Enum
|
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
7
|
from .content_codec import ContentCodec, WireContent
|
|
8
|
-
from .message import (
|
|
9
|
-
|
|
8
|
+
from .message import (
|
|
9
|
+
BaiYingMessage,
|
|
10
|
+
BaiYingMessageRole,
|
|
11
|
+
MessageContent,
|
|
12
|
+
MessageFile,
|
|
13
|
+
Resource,
|
|
14
|
+
)
|
|
10
15
|
|
|
11
16
|
|
|
12
17
|
def serialize_byai_content(content: Any) -> Any:
|