by-framework 0.2.2.dev3__py3-none-any.whl → 0.2.2.dev4__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 +15 -7
- by_framework/core/__init__.py +69 -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 +29 -0
- by_framework/core/extensions/agent_config.py +64 -0
- by_framework/core/extensions/plugin.py +312 -0
- by_framework/core/extensions/registry.py +704 -0
- by_framework/core/extensions/trace_provider.py +20 -0
- by_framework/core/protocol/__init__.py +99 -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 +96 -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 +1102 -0
- by_framework/core/runtime/__init__.py +27 -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 +434 -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 +313 -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 +149 -0
- by_framework/core/workspace.py +126 -0
- {by_framework-0.2.2.dev3.dist-info → by_framework-0.2.2.dev4.dist-info}/METADATA +1 -1
- by_framework-0.2.2.dev4.dist-info/RECORD +92 -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.dev4.dist-info}/WHEEL +0 -0
- {by_framework-0.2.2.dev3.dist-info → by_framework-0.2.2.dev4.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
"""Service discovery module with Redis-based registry and local cache."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import json
|
|
5
|
+
import random
|
|
6
|
+
import socket
|
|
7
|
+
import time
|
|
8
|
+
import uuid
|
|
9
|
+
from dataclasses import asdict, dataclass, field
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
from by_framework.common.constants import RedisKeys
|
|
13
|
+
from by_framework.common.redis_client import Redis, get_redis
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_local_ip(target_host: str = "8.8.8.8", target_port: int = 80) -> str:
|
|
17
|
+
"""Get the local machine's outbound IP address.
|
|
18
|
+
|
|
19
|
+
Detects the local network interface IP by attempting to connect to the target
|
|
20
|
+
host (without sending data). If target_host is localhost or 127.0.0.1, attempts
|
|
21
|
+
to connect to a public address for detection. If that fails or the environment
|
|
22
|
+
is restricted, attempts to return a non-loopback address.
|
|
23
|
+
"""
|
|
24
|
+
# If the target is a loopback address, detection may return 127.0.0.1, which is
|
|
25
|
+
# meaningless for cross-machine communication. In this case, try connecting to a
|
|
26
|
+
# public or external address to detect the actual local outbound IP
|
|
27
|
+
is_loopback = target_host in ("127.0.0.1", "localhost", "::1")
|
|
28
|
+
actual_target = ("8.8.8.8", 80) if is_loopback else (target_host, target_port)
|
|
29
|
+
|
|
30
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
31
|
+
try:
|
|
32
|
+
s.connect(actual_target)
|
|
33
|
+
ip = s.getsockname()[0]
|
|
34
|
+
except Exception: # pylint: disable=broad-exception-caught
|
|
35
|
+
# Fallback: get IP for the hostname
|
|
36
|
+
try:
|
|
37
|
+
ip = socket.gethostbyname(socket.gethostname())
|
|
38
|
+
except Exception: # pylint: disable=broad-exception-caught
|
|
39
|
+
ip = "127.0.0.1"
|
|
40
|
+
finally:
|
|
41
|
+
s.close()
|
|
42
|
+
|
|
43
|
+
# If the acquired address is a loopback but other network interfaces exist,
|
|
44
|
+
# more complex scanning could be expanded here
|
|
45
|
+
return ip
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class ServiceInstance:
|
|
50
|
+
"""Service instance data structure."""
|
|
51
|
+
|
|
52
|
+
id: str
|
|
53
|
+
host: str
|
|
54
|
+
port: int
|
|
55
|
+
protocol: str = "http"
|
|
56
|
+
path_prefix: Optional[str] = None
|
|
57
|
+
weight: int = 1
|
|
58
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
59
|
+
last_heartbeat: int = field(default=0, repr=False)
|
|
60
|
+
|
|
61
|
+
def to_json(self) -> str:
|
|
62
|
+
payload = asdict(self)
|
|
63
|
+
payload.pop("last_heartbeat", None)
|
|
64
|
+
return json.dumps(payload, ensure_ascii=False)
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
def from_json(cls, data: str) -> "ServiceInstance":
|
|
68
|
+
return cls(**json.loads(data))
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class ServiceRegistry:
|
|
72
|
+
"""Redis-based service registry SDK.
|
|
73
|
+
|
|
74
|
+
Used by server-side for service registration, automatic heartbeat
|
|
75
|
+
maintenance, and deregistration.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
def __init__(self, redis_client: Optional[Redis] = None):
|
|
79
|
+
self.redis = redis_client or get_redis()
|
|
80
|
+
self._heartbeat_task: Optional[asyncio.Task] = None
|
|
81
|
+
self._current_instance: Optional[ServiceInstance] = None
|
|
82
|
+
self._current_service_name: Optional[str] = None
|
|
83
|
+
|
|
84
|
+
async def register(
|
|
85
|
+
self,
|
|
86
|
+
service_name: str,
|
|
87
|
+
host: Optional[str] = None,
|
|
88
|
+
port: int = 0,
|
|
89
|
+
weight: int = 1,
|
|
90
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
91
|
+
heartbeat_interval: int = RedisKeys.SD_DEFAULT_HEARTBEAT_INTERVAL_SECONDS,
|
|
92
|
+
protocol: str = "http",
|
|
93
|
+
path_prefix: Optional[str] = None,
|
|
94
|
+
) -> None:
|
|
95
|
+
"""Register the current service instance and start background heartbeat.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
service_name: Service name.
|
|
99
|
+
host: Instance listening address. If None, auto-detected from Redis
|
|
100
|
+
connection address.
|
|
101
|
+
port: Instance listening port. Defaults to 0 (meaning no listening port).
|
|
102
|
+
weight: Load balancing weight.
|
|
103
|
+
metadata: Metadata.
|
|
104
|
+
heartbeat_interval: Heartbeat interval.
|
|
105
|
+
protocol: Service protocol used by discovery-aware clients.
|
|
106
|
+
path_prefix: Optional URL prefix advertised by this instance.
|
|
107
|
+
"""
|
|
108
|
+
if host is None:
|
|
109
|
+
# Attempt to get target address from Redis connection config
|
|
110
|
+
redis_host = "8.8.8.8"
|
|
111
|
+
redis_port = 80
|
|
112
|
+
try:
|
|
113
|
+
pool = getattr(self.redis, "connection_pool", None)
|
|
114
|
+
if pool:
|
|
115
|
+
redis_host = pool.connection_kwargs.get("host", "8.8.8.8")
|
|
116
|
+
redis_port = pool.connection_kwargs.get("port", 6379)
|
|
117
|
+
except Exception: # pylint: disable=broad-exception-caught
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
host = get_local_ip(target_host=redis_host, target_port=redis_port)
|
|
121
|
+
|
|
122
|
+
instance_id = f"{service_name}:{uuid.uuid4().hex[:8]}"
|
|
123
|
+
self._current_instance = ServiceInstance(
|
|
124
|
+
id=instance_id,
|
|
125
|
+
protocol=protocol or "http",
|
|
126
|
+
host=host,
|
|
127
|
+
port=port,
|
|
128
|
+
path_prefix=path_prefix,
|
|
129
|
+
weight=weight,
|
|
130
|
+
metadata=metadata or {},
|
|
131
|
+
)
|
|
132
|
+
self._current_service_name = service_name
|
|
133
|
+
|
|
134
|
+
now_ms = int(time.time() * 1000)
|
|
135
|
+
|
|
136
|
+
# 1. Write instance details
|
|
137
|
+
await self.redis.hset(
|
|
138
|
+
RedisKeys.sd_instance_details(service_name),
|
|
139
|
+
instance_id,
|
|
140
|
+
self._current_instance.to_json(),
|
|
141
|
+
)
|
|
142
|
+
# 2. Make the instance immediately discoverable.
|
|
143
|
+
await self.redis.zadd(
|
|
144
|
+
RedisKeys.sd_active_instances(service_name), {instance_id: now_ms}
|
|
145
|
+
)
|
|
146
|
+
self._current_instance.last_heartbeat = now_ms
|
|
147
|
+
# 3. Add service name to global index
|
|
148
|
+
await self.redis.sadd(RedisKeys.SD_SERVICES, service_name)
|
|
149
|
+
|
|
150
|
+
# 4. Start recurring heartbeats only when requested.
|
|
151
|
+
if heartbeat_interval > RedisKeys.SD_NO_HEARTBEAT:
|
|
152
|
+
self._heartbeat_task = asyncio.create_task(
|
|
153
|
+
self._heartbeat_loop(service_name, instance_id, heartbeat_interval)
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
async def register_only(
|
|
157
|
+
self,
|
|
158
|
+
service_name: str,
|
|
159
|
+
host: Optional[str] = None,
|
|
160
|
+
port: int = 0,
|
|
161
|
+
weight: int = 1,
|
|
162
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
163
|
+
protocol: str = "http",
|
|
164
|
+
path_prefix: Optional[str] = None,
|
|
165
|
+
) -> None:
|
|
166
|
+
"""Register an instance without starting recurring heartbeats."""
|
|
167
|
+
await self.register(
|
|
168
|
+
service_name=service_name,
|
|
169
|
+
host=host,
|
|
170
|
+
port=port,
|
|
171
|
+
weight=weight,
|
|
172
|
+
metadata=metadata,
|
|
173
|
+
heartbeat_interval=RedisKeys.SD_NO_HEARTBEAT,
|
|
174
|
+
protocol=protocol,
|
|
175
|
+
path_prefix=path_prefix,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
async def unregister(self):
|
|
179
|
+
"""Deregister the current service instance and stop heartbeat."""
|
|
180
|
+
if self._heartbeat_task:
|
|
181
|
+
self._heartbeat_task.cancel()
|
|
182
|
+
try:
|
|
183
|
+
await self._heartbeat_task
|
|
184
|
+
except asyncio.CancelledError:
|
|
185
|
+
pass
|
|
186
|
+
self._heartbeat_task = None
|
|
187
|
+
|
|
188
|
+
if self._current_instance and self._current_service_name:
|
|
189
|
+
pipe = self.redis.pipeline()
|
|
190
|
+
pipe.hdel(
|
|
191
|
+
RedisKeys.sd_instance_details(self._current_service_name),
|
|
192
|
+
self._current_instance.id,
|
|
193
|
+
)
|
|
194
|
+
pipe.zrem(
|
|
195
|
+
RedisKeys.sd_active_instances(self._current_service_name),
|
|
196
|
+
self._current_instance.id,
|
|
197
|
+
)
|
|
198
|
+
await pipe.execute()
|
|
199
|
+
|
|
200
|
+
async def _send_heartbeat(self):
|
|
201
|
+
if self._current_instance and self._current_service_name:
|
|
202
|
+
now = int(time.time() * 1000)
|
|
203
|
+
self._current_instance.last_heartbeat = now
|
|
204
|
+
await self.redis.zadd(
|
|
205
|
+
RedisKeys.sd_active_instances(self._current_service_name),
|
|
206
|
+
{self._current_instance.id: now},
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# pylint: disable=unused-argument
|
|
210
|
+
async def _heartbeat_loop(self, service_name: str, instance_id: str, interval: int):
|
|
211
|
+
"""Background heartbeat coroutine."""
|
|
212
|
+
while True:
|
|
213
|
+
try:
|
|
214
|
+
await asyncio.sleep(interval)
|
|
215
|
+
await self._send_heartbeat()
|
|
216
|
+
except asyncio.CancelledError:
|
|
217
|
+
break
|
|
218
|
+
except Exception: # pylint: disable=broad-exception-caught
|
|
219
|
+
await asyncio.sleep(1)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
class DiscoveryClient:
|
|
223
|
+
"""Efficient service discovery client with local cache.
|
|
224
|
+
|
|
225
|
+
Used by consumers. Reduces Redis access frequency through in-memory cache
|
|
226
|
+
and background refresh mechanisms.
|
|
227
|
+
"""
|
|
228
|
+
|
|
229
|
+
def __init__(
|
|
230
|
+
self,
|
|
231
|
+
redis_client: Optional[Redis] = None,
|
|
232
|
+
cache_interval: int = 5,
|
|
233
|
+
):
|
|
234
|
+
self.redis = redis_client or get_redis()
|
|
235
|
+
self.cache_interval = cache_interval
|
|
236
|
+
self._cache: Dict[str, List[ServiceInstance]] = {}
|
|
237
|
+
self._last_refresh: Dict[str, float] = {}
|
|
238
|
+
self._watched_services: set[str] = set()
|
|
239
|
+
self._refresh_task: Optional[asyncio.Task] = None
|
|
240
|
+
self._rr_counters: Dict[str, int] = {}
|
|
241
|
+
|
|
242
|
+
async def get_instances(
|
|
243
|
+
self,
|
|
244
|
+
service_name: str,
|
|
245
|
+
force_refresh: bool = False,
|
|
246
|
+
health_threshold_ms: int = RedisKeys.SD_DEFAULT_HEALTH_THRESHOLD_MS,
|
|
247
|
+
) -> List[ServiceInstance]:
|
|
248
|
+
"""Get service instances. Prefers using cache."""
|
|
249
|
+
now = time.time()
|
|
250
|
+
|
|
251
|
+
# Determine if cache is valid
|
|
252
|
+
is_stale = now - self._last_refresh.get(service_name, 0) > self.cache_interval
|
|
253
|
+
|
|
254
|
+
if force_refresh or is_stale or service_name not in self._cache:
|
|
255
|
+
await self._refresh_service(service_name, health_threshold_ms)
|
|
256
|
+
|
|
257
|
+
instances = self._cache.get(service_name, [])
|
|
258
|
+
if health_threshold_ms == RedisKeys.SD_NO_HEALTH_CHECK:
|
|
259
|
+
return instances
|
|
260
|
+
|
|
261
|
+
min_score = int(time.time() * 1000) - health_threshold_ms
|
|
262
|
+
return [
|
|
263
|
+
instance for instance in instances if instance.last_heartbeat >= min_score
|
|
264
|
+
]
|
|
265
|
+
|
|
266
|
+
async def _refresh_service(self, service_name: str, health_threshold_ms: int):
|
|
267
|
+
"""Sync instance list from Redis and update cache."""
|
|
268
|
+
del health_threshold_ms
|
|
269
|
+
|
|
270
|
+
# 1. Get active IDs together with their latest heartbeat timestamp.
|
|
271
|
+
raw_instances = await self.redis.zrange(
|
|
272
|
+
RedisKeys.sd_active_instances(service_name), 0, -1, withscores=True
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
if not raw_instances:
|
|
276
|
+
self._cache[service_name] = []
|
|
277
|
+
self._last_refresh[service_name] = time.time()
|
|
278
|
+
return
|
|
279
|
+
|
|
280
|
+
instance_ids: list[str] = []
|
|
281
|
+
heartbeat_by_id: dict[str, int] = {}
|
|
282
|
+
for instance_id, score in raw_instances:
|
|
283
|
+
normalized_id = (
|
|
284
|
+
instance_id.decode("utf-8")
|
|
285
|
+
if isinstance(instance_id, bytes)
|
|
286
|
+
else instance_id
|
|
287
|
+
)
|
|
288
|
+
instance_ids.append(normalized_id)
|
|
289
|
+
heartbeat_by_id[normalized_id] = int(score)
|
|
290
|
+
|
|
291
|
+
# 2. Get details
|
|
292
|
+
details_raw = await self.redis.hmget(
|
|
293
|
+
RedisKeys.sd_instance_details(service_name), instance_ids
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
instances = []
|
|
297
|
+
for raw in details_raw:
|
|
298
|
+
if raw:
|
|
299
|
+
data = raw.decode("utf-8") if isinstance(raw, bytes) else raw
|
|
300
|
+
instance = ServiceInstance.from_json(data)
|
|
301
|
+
instance.last_heartbeat = heartbeat_by_id.get(instance.id, 0)
|
|
302
|
+
instances.append(instance)
|
|
303
|
+
|
|
304
|
+
self._cache[service_name] = instances
|
|
305
|
+
self._last_refresh[service_name] = time.time()
|
|
306
|
+
|
|
307
|
+
def watch(self, service_name: str):
|
|
308
|
+
"""Add service to background auto-refresh list."""
|
|
309
|
+
self._watched_services.add(service_name)
|
|
310
|
+
if not self._refresh_task:
|
|
311
|
+
self._refresh_task = asyncio.create_task(self._refresh_loop())
|
|
312
|
+
|
|
313
|
+
async def _refresh_loop(self):
|
|
314
|
+
"""Background periodic cache refresh coroutine."""
|
|
315
|
+
while True:
|
|
316
|
+
try:
|
|
317
|
+
await asyncio.sleep(self.cache_interval)
|
|
318
|
+
for service_name in list(self._watched_services):
|
|
319
|
+
await self._refresh_service(
|
|
320
|
+
service_name, RedisKeys.SD_NO_HEALTH_CHECK
|
|
321
|
+
)
|
|
322
|
+
except asyncio.CancelledError:
|
|
323
|
+
break
|
|
324
|
+
except Exception: # pylint: disable=broad-exception-caught
|
|
325
|
+
await asyncio.sleep(1)
|
|
326
|
+
|
|
327
|
+
async def discover(
|
|
328
|
+
self,
|
|
329
|
+
service_name: str,
|
|
330
|
+
strategy: str = "random",
|
|
331
|
+
health_threshold_ms: int = RedisKeys.SD_DEFAULT_HEALTH_THRESHOLD_MS,
|
|
332
|
+
) -> Optional[ServiceInstance]:
|
|
333
|
+
"""Perform load-balanced discovery."""
|
|
334
|
+
instances = await self.get_instances(
|
|
335
|
+
service_name, health_threshold_ms=health_threshold_ms
|
|
336
|
+
)
|
|
337
|
+
if not instances:
|
|
338
|
+
return None
|
|
339
|
+
|
|
340
|
+
if strategy == "random":
|
|
341
|
+
return random.choice(instances)
|
|
342
|
+
|
|
343
|
+
if strategy == "round-robin":
|
|
344
|
+
counter = self._rr_counters.get(service_name, 0)
|
|
345
|
+
instance = instances[counter % len(instances)]
|
|
346
|
+
self._rr_counters[service_name] = counter + 1
|
|
347
|
+
return instance
|
|
348
|
+
|
|
349
|
+
return random.choice(instances)
|
|
350
|
+
|
|
351
|
+
async def close(self):
|
|
352
|
+
"""Close the client, stop background tasks."""
|
|
353
|
+
if self._refresh_task:
|
|
354
|
+
self._refresh_task.cancel()
|
|
355
|
+
try:
|
|
356
|
+
await self._refresh_task
|
|
357
|
+
except asyncio.CancelledError:
|
|
358
|
+
pass
|
|
359
|
+
self._refresh_task = None
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Plugin system module - Provides pluggable Worker extension mechanism.
|
|
3
|
+
|
|
4
|
+
This module implements a standardized plugin registration and management
|
|
5
|
+
system, allowing business logic (such as tools, prompts, skills, callbacks)
|
|
6
|
+
to be decoupled from Worker infrastructure, and dynamically injected and
|
|
7
|
+
managed through the form of plugins.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from .agent_config import AgentConfig, CallbackType
|
|
11
|
+
from .plugin import (AgentConfigsSnapshot, Plugin, PluginBuildContext,
|
|
12
|
+
PluginManifest, PluginReloadContext, PluginReloadResult,
|
|
13
|
+
PromptTemplate)
|
|
14
|
+
from .registry import PluginRegistry
|
|
15
|
+
from .trace_provider import TraceProviderFactory
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"AgentConfig",
|
|
19
|
+
"AgentConfigsSnapshot",
|
|
20
|
+
"CallbackType",
|
|
21
|
+
"PluginManifest",
|
|
22
|
+
"Plugin",
|
|
23
|
+
"PluginBuildContext",
|
|
24
|
+
"PluginReloadContext",
|
|
25
|
+
"PluginReloadResult",
|
|
26
|
+
"PromptTemplate",
|
|
27
|
+
"PluginRegistry",
|
|
28
|
+
"TraceProviderFactory",
|
|
29
|
+
]
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# pylint: disable=C0103
|
|
2
|
+
"""
|
|
3
|
+
Agent configuration definitions for the plugin system.
|
|
4
|
+
|
|
5
|
+
Contains AgentConfig dataclass and CallbackType enum used to configure
|
|
6
|
+
agent types, tools, prompts, and callbacks.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from enum import Enum
|
|
11
|
+
from typing import Any, Callable, Literal
|
|
12
|
+
|
|
13
|
+
ConflictStrategy = Literal["error", "overwrite", "skip"]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CallbackType(Enum):
|
|
17
|
+
"""Callback type enum, defining callback points during agent execution.
|
|
18
|
+
|
|
19
|
+
Attributes:
|
|
20
|
+
before_model_callback: Triggered before model call
|
|
21
|
+
after_model_callback: Triggered after model call
|
|
22
|
+
before_tool_callback: Triggered before tool call
|
|
23
|
+
after_tool_callback: Triggered after tool call
|
|
24
|
+
before_agent_callback: Triggered before agent call
|
|
25
|
+
after_agent_callback: Triggered after agent call
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
before_model_callback = "before_model_callback"
|
|
29
|
+
after_model_callback = "after_model_callback"
|
|
30
|
+
before_tool_callback = "before_tool_callback"
|
|
31
|
+
after_tool_callback = "after_tool_callback"
|
|
32
|
+
before_agent_callback = "before_agent_callback"
|
|
33
|
+
after_agent_callback = "after_agent_callback"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class AgentConfig:
|
|
38
|
+
"""Single agent capability configuration.
|
|
39
|
+
|
|
40
|
+
Attributes:
|
|
41
|
+
agent_id: Agent unique identifier
|
|
42
|
+
name: Agent name
|
|
43
|
+
description: Agent description
|
|
44
|
+
prompts: Prompt template dictionary
|
|
45
|
+
tools: Tool configuration dictionary
|
|
46
|
+
skills: Skill configuration dictionary
|
|
47
|
+
callbacks: Callback function dictionary
|
|
48
|
+
knowledge_bases: Knowledge base configuration dictionary
|
|
49
|
+
sub_agents: Sub-agent ID list
|
|
50
|
+
on_conflict: Conflict strategy: error, overwrite, or skip
|
|
51
|
+
extra: Extension information
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
agent_id: str
|
|
55
|
+
name: str = ""
|
|
56
|
+
description: str = ""
|
|
57
|
+
prompts: dict[str, Any] = field(default_factory=dict)
|
|
58
|
+
tools: dict[str, Any] = field(default_factory=dict)
|
|
59
|
+
skills: dict[str, Any] = field(default_factory=dict)
|
|
60
|
+
callbacks: dict[CallbackType, list[Callable]] = field(default_factory=dict)
|
|
61
|
+
knowledge_bases: dict[str, Any] = field(default_factory=dict)
|
|
62
|
+
sub_agents: list[str] = field(default_factory=list)
|
|
63
|
+
extra: dict[str, Any] = field(default_factory=dict)
|
|
64
|
+
on_conflict: ConflictStrategy = "error"
|