emergent-translator 1.1.0__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.
- emergent_translator/__init__.py +126 -0
- emergent_translator/adaptive_codebook.py +342 -0
- emergent_translator/api_server.py +4988 -0
- emergent_translator/batch_encoder.py +555 -0
- emergent_translator/chunk_collector.py +978 -0
- emergent_translator/chunk_coordinator.py +738 -0
- emergent_translator/claude_compression.py +375 -0
- emergent_translator/cli.py +413 -0
- emergent_translator/client_sdk.py +903 -0
- emergent_translator/code_skeleton.py +448 -0
- emergent_translator/core.py +1081 -0
- emergent_translator/emergent_symbols.py +690 -0
- emergent_translator/format_handlers.py +901 -0
- emergent_translator/gpu_batch_encoder.py +848 -0
- emergent_translator/intelligent_router.py +509 -0
- emergent_translator/metrics.py +436 -0
- emergent_translator/py.typed +0 -0
- emergent_translator-1.1.0.dist-info/METADATA +568 -0
- emergent_translator-1.1.0.dist-info/RECORD +23 -0
- emergent_translator-1.1.0.dist-info/WHEEL +5 -0
- emergent_translator-1.1.0.dist-info/entry_points.txt +2 -0
- emergent_translator-1.1.0.dist-info/licenses/LICENSE +82 -0
- emergent_translator-1.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Emergent Language Translator - Intelligent Router with Agent Lightning
|
|
3
|
+
|
|
4
|
+
Uses Microsoft Agent Lightning for reinforcement learning-based routing
|
|
5
|
+
that learns from failures and optimizes request distribution.
|
|
6
|
+
|
|
7
|
+
Architecture:
|
|
8
|
+
StressTestClient → IntelligentRouter (Agent Lightning) → Fly.io Instances
|
|
9
|
+
↓
|
|
10
|
+
LightningStore (learns from success/failure)
|
|
11
|
+
↓
|
|
12
|
+
Optimized Routing Policy
|
|
13
|
+
|
|
14
|
+
Key Features:
|
|
15
|
+
- Learns which instances perform best for different payload types
|
|
16
|
+
- Adapts retry strategies based on failure patterns
|
|
17
|
+
- Credit assignment identifies root causes of failures
|
|
18
|
+
- Continuous improvement without code changes
|
|
19
|
+
|
|
20
|
+
Installation:
|
|
21
|
+
pip install agentlightning
|
|
22
|
+
|
|
23
|
+
Usage:
|
|
24
|
+
from intelligent_router import IntelligentRouter
|
|
25
|
+
|
|
26
|
+
router = IntelligentRouter(endpoints=[
|
|
27
|
+
"https://emergent-language.fly.dev",
|
|
28
|
+
"https://eudaimonia.fly.dev"
|
|
29
|
+
])
|
|
30
|
+
|
|
31
|
+
# Route request with learning
|
|
32
|
+
result = await router.route_request(payload)
|
|
33
|
+
|
|
34
|
+
# Router learns from outcome automatically
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
import asyncio
|
|
38
|
+
import random
|
|
39
|
+
import time
|
|
40
|
+
from dataclasses import dataclass, field
|
|
41
|
+
from typing import Dict, List, Optional, Any, Tuple
|
|
42
|
+
from enum import Enum
|
|
43
|
+
import logging
|
|
44
|
+
import os
|
|
45
|
+
|
|
46
|
+
import httpx
|
|
47
|
+
|
|
48
|
+
# Agent Lightning imports (optional - graceful fallback)
|
|
49
|
+
try:
|
|
50
|
+
import agentlightning as agl
|
|
51
|
+
AGENT_LIGHTNING_AVAILABLE = True
|
|
52
|
+
except ImportError:
|
|
53
|
+
AGENT_LIGHTNING_AVAILABLE = False
|
|
54
|
+
agl = None
|
|
55
|
+
|
|
56
|
+
logger = logging.getLogger(__name__)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class RoutingStrategy(Enum):
|
|
60
|
+
"""Available routing strategies."""
|
|
61
|
+
ROUND_ROBIN = "round_robin"
|
|
62
|
+
RANDOM = "random"
|
|
63
|
+
LEAST_LATENCY = "least_latency"
|
|
64
|
+
LEAST_ERRORS = "least_errors"
|
|
65
|
+
LEARNED = "learned" # Agent Lightning RL-based
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class EndpointStats:
|
|
70
|
+
"""Statistics for a single endpoint."""
|
|
71
|
+
url: str
|
|
72
|
+
total_requests: int = 0
|
|
73
|
+
successful_requests: int = 0
|
|
74
|
+
failed_requests: int = 0
|
|
75
|
+
total_latency_ms: float = 0.0
|
|
76
|
+
last_error: Optional[str] = None
|
|
77
|
+
last_error_time: float = 0.0
|
|
78
|
+
consecutive_failures: int = 0
|
|
79
|
+
|
|
80
|
+
# Payload-specific performance
|
|
81
|
+
payload_performance: Dict[str, Dict[str, float]] = field(default_factory=dict)
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def success_rate(self) -> float:
|
|
85
|
+
if self.total_requests == 0:
|
|
86
|
+
return 1.0
|
|
87
|
+
return self.successful_requests / self.total_requests
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def avg_latency_ms(self) -> float:
|
|
91
|
+
if self.successful_requests == 0:
|
|
92
|
+
return float('inf')
|
|
93
|
+
return self.total_latency_ms / self.successful_requests
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def is_healthy(self) -> bool:
|
|
97
|
+
"""Endpoint is healthy if not too many consecutive failures."""
|
|
98
|
+
return self.consecutive_failures < 5
|
|
99
|
+
|
|
100
|
+
def record_success(self, latency_ms: float, payload_type: str = "default"):
|
|
101
|
+
"""Record successful request."""
|
|
102
|
+
self.total_requests += 1
|
|
103
|
+
self.successful_requests += 1
|
|
104
|
+
self.total_latency_ms += latency_ms
|
|
105
|
+
self.consecutive_failures = 0
|
|
106
|
+
|
|
107
|
+
# Track payload-specific performance
|
|
108
|
+
if payload_type not in self.payload_performance:
|
|
109
|
+
self.payload_performance[payload_type] = {"latency_sum": 0, "count": 0}
|
|
110
|
+
self.payload_performance[payload_type]["latency_sum"] += latency_ms
|
|
111
|
+
self.payload_performance[payload_type]["count"] += 1
|
|
112
|
+
|
|
113
|
+
def record_failure(self, error: str):
|
|
114
|
+
"""Record failed request."""
|
|
115
|
+
self.total_requests += 1
|
|
116
|
+
self.failed_requests += 1
|
|
117
|
+
self.last_error = error
|
|
118
|
+
self.last_error_time = time.time()
|
|
119
|
+
self.consecutive_failures += 1
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@dataclass
|
|
123
|
+
class RoutingDecision:
|
|
124
|
+
"""A routing decision with context for learning."""
|
|
125
|
+
endpoint: str
|
|
126
|
+
strategy_used: RoutingStrategy
|
|
127
|
+
payload_type: str
|
|
128
|
+
timestamp: float = field(default_factory=time.time)
|
|
129
|
+
|
|
130
|
+
# Outcome (filled after request)
|
|
131
|
+
success: Optional[bool] = None
|
|
132
|
+
latency_ms: Optional[float] = None
|
|
133
|
+
error: Optional[str] = None
|
|
134
|
+
reward: Optional[float] = None
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class IntelligentRouter:
|
|
138
|
+
"""
|
|
139
|
+
Intelligent request router with Agent Lightning RL integration.
|
|
140
|
+
|
|
141
|
+
Learns optimal routing strategies from success/failure patterns.
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
def __init__(
|
|
145
|
+
self,
|
|
146
|
+
endpoints: List[str],
|
|
147
|
+
strategy: RoutingStrategy = RoutingStrategy.LEARNED,
|
|
148
|
+
auth_token: Optional[str] = None,
|
|
149
|
+
enable_learning: bool = True,
|
|
150
|
+
lightning_project: str = "emergent-router"
|
|
151
|
+
):
|
|
152
|
+
self.endpoints = endpoints
|
|
153
|
+
self.strategy = strategy
|
|
154
|
+
self.auth_token = auth_token or os.getenv("TRANSLATOR_AUTH_TOKEN", "eudaimonia-translator-demo")
|
|
155
|
+
self.enable_learning = enable_learning and AGENT_LIGHTNING_AVAILABLE
|
|
156
|
+
|
|
157
|
+
# Endpoint statistics
|
|
158
|
+
self.stats: Dict[str, EndpointStats] = {
|
|
159
|
+
url: EndpointStats(url=url) for url in endpoints
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
# Round-robin state
|
|
163
|
+
self._rr_index = 0
|
|
164
|
+
|
|
165
|
+
# Recent decisions for learning
|
|
166
|
+
self._recent_decisions: List[RoutingDecision] = []
|
|
167
|
+
|
|
168
|
+
# Agent Lightning setup
|
|
169
|
+
self._lightning_store = None
|
|
170
|
+
if self.enable_learning:
|
|
171
|
+
self._setup_agent_lightning(lightning_project)
|
|
172
|
+
|
|
173
|
+
def _setup_agent_lightning(self, project: str):
|
|
174
|
+
"""Initialize Agent Lightning for RL-based routing."""
|
|
175
|
+
try:
|
|
176
|
+
# Initialize store for collecting routing decisions
|
|
177
|
+
self._lightning_store = agl.LightningStore(
|
|
178
|
+
project=project,
|
|
179
|
+
experiment="routing-optimization"
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
logger.info(f"Agent Lightning initialized for project: {project}")
|
|
183
|
+
except Exception as e:
|
|
184
|
+
logger.warning(f"Failed to initialize Agent Lightning: {e}")
|
|
185
|
+
self.enable_learning = False
|
|
186
|
+
|
|
187
|
+
def _classify_payload(self, payload: Dict[str, Any]) -> str:
|
|
188
|
+
"""Classify payload type for specialized routing."""
|
|
189
|
+
# Simple heuristic classification
|
|
190
|
+
payload_str = str(payload)
|
|
191
|
+
size = len(payload_str)
|
|
192
|
+
|
|
193
|
+
if size < 100:
|
|
194
|
+
return "small"
|
|
195
|
+
elif size < 500:
|
|
196
|
+
return "medium"
|
|
197
|
+
elif size < 2000:
|
|
198
|
+
return "large"
|
|
199
|
+
else:
|
|
200
|
+
return "xlarge"
|
|
201
|
+
|
|
202
|
+
def _select_endpoint_round_robin(self) -> str:
|
|
203
|
+
"""Round-robin endpoint selection."""
|
|
204
|
+
healthy = [url for url, stats in self.stats.items() if stats.is_healthy]
|
|
205
|
+
if not healthy:
|
|
206
|
+
healthy = self.endpoints # Fall back to all if none healthy
|
|
207
|
+
|
|
208
|
+
endpoint = healthy[self._rr_index % len(healthy)]
|
|
209
|
+
self._rr_index += 1
|
|
210
|
+
return endpoint
|
|
211
|
+
|
|
212
|
+
def _select_endpoint_least_latency(self, payload_type: str) -> str:
|
|
213
|
+
"""Select endpoint with lowest average latency."""
|
|
214
|
+
healthy = [(url, stats) for url, stats in self.stats.items() if stats.is_healthy]
|
|
215
|
+
if not healthy:
|
|
216
|
+
return random.choice(self.endpoints)
|
|
217
|
+
|
|
218
|
+
# Check payload-specific latency first
|
|
219
|
+
best_url = None
|
|
220
|
+
best_latency = float('inf')
|
|
221
|
+
|
|
222
|
+
for url, stats in healthy:
|
|
223
|
+
if payload_type in stats.payload_performance:
|
|
224
|
+
perf = stats.payload_performance[payload_type]
|
|
225
|
+
if perf["count"] > 0:
|
|
226
|
+
avg = perf["latency_sum"] / perf["count"]
|
|
227
|
+
if avg < best_latency:
|
|
228
|
+
best_latency = avg
|
|
229
|
+
best_url = url
|
|
230
|
+
|
|
231
|
+
if best_url:
|
|
232
|
+
return best_url
|
|
233
|
+
|
|
234
|
+
# Fall back to overall latency
|
|
235
|
+
return min(healthy, key=lambda x: x[1].avg_latency_ms)[0]
|
|
236
|
+
|
|
237
|
+
def _select_endpoint_least_errors(self) -> str:
|
|
238
|
+
"""Select endpoint with highest success rate."""
|
|
239
|
+
healthy = [(url, stats) for url, stats in self.stats.items() if stats.is_healthy]
|
|
240
|
+
if not healthy:
|
|
241
|
+
return random.choice(self.endpoints)
|
|
242
|
+
|
|
243
|
+
return max(healthy, key=lambda x: x[1].success_rate)[0]
|
|
244
|
+
|
|
245
|
+
def _select_endpoint_learned(self, payload_type: str) -> str:
|
|
246
|
+
"""
|
|
247
|
+
Use Agent Lightning to select endpoint based on learned policy.
|
|
248
|
+
|
|
249
|
+
Falls back to least_latency if learning not available or insufficient data.
|
|
250
|
+
"""
|
|
251
|
+
if not self.enable_learning or not self._lightning_store:
|
|
252
|
+
return self._select_endpoint_least_latency(payload_type)
|
|
253
|
+
|
|
254
|
+
try:
|
|
255
|
+
# Emit routing state to Agent Lightning
|
|
256
|
+
state = {
|
|
257
|
+
"payload_type": payload_type,
|
|
258
|
+
"endpoint_stats": {
|
|
259
|
+
url: {
|
|
260
|
+
"success_rate": stats.success_rate,
|
|
261
|
+
"avg_latency": stats.avg_latency_ms,
|
|
262
|
+
"is_healthy": stats.is_healthy,
|
|
263
|
+
"consecutive_failures": stats.consecutive_failures
|
|
264
|
+
}
|
|
265
|
+
for url, stats in self.stats.items()
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
# Get action from learned policy (or explore)
|
|
270
|
+
# In practice, this would query the trained model
|
|
271
|
+
# For now, use Thompson sampling with learned priors
|
|
272
|
+
|
|
273
|
+
# Calculate expected reward for each endpoint
|
|
274
|
+
scores = {}
|
|
275
|
+
for url, stats in self.stats.items():
|
|
276
|
+
if not stats.is_healthy:
|
|
277
|
+
scores[url] = -1.0
|
|
278
|
+
continue
|
|
279
|
+
|
|
280
|
+
# Base score from success rate and latency
|
|
281
|
+
success_score = stats.success_rate * 0.6
|
|
282
|
+
latency_score = max(0, 1 - (stats.avg_latency_ms / 1000)) * 0.4
|
|
283
|
+
|
|
284
|
+
# Payload-specific bonus
|
|
285
|
+
if payload_type in stats.payload_performance:
|
|
286
|
+
perf = stats.payload_performance[payload_type]
|
|
287
|
+
if perf["count"] >= 5:
|
|
288
|
+
specific_latency = perf["latency_sum"] / perf["count"]
|
|
289
|
+
latency_score = max(0, 1 - (specific_latency / 1000)) * 0.4
|
|
290
|
+
|
|
291
|
+
# Exploration bonus (UCB-style)
|
|
292
|
+
exploration = 0.1 / (1 + stats.total_requests ** 0.5)
|
|
293
|
+
|
|
294
|
+
scores[url] = success_score + latency_score + exploration
|
|
295
|
+
|
|
296
|
+
# Select best scoring endpoint
|
|
297
|
+
best_url = max(scores.items(), key=lambda x: x[1])[0]
|
|
298
|
+
|
|
299
|
+
# Emit action for learning
|
|
300
|
+
if self._lightning_store:
|
|
301
|
+
agl.emit_action(
|
|
302
|
+
action={"selected_endpoint": best_url},
|
|
303
|
+
state=state
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
return best_url
|
|
307
|
+
|
|
308
|
+
except Exception as e:
|
|
309
|
+
logger.debug(f"Learned selection failed, falling back: {e}")
|
|
310
|
+
return self._select_endpoint_least_latency(payload_type)
|
|
311
|
+
|
|
312
|
+
def select_endpoint(self, payload: Dict[str, Any]) -> RoutingDecision:
|
|
313
|
+
"""Select endpoint for request based on strategy."""
|
|
314
|
+
payload_type = self._classify_payload(payload)
|
|
315
|
+
|
|
316
|
+
if self.strategy == RoutingStrategy.ROUND_ROBIN:
|
|
317
|
+
endpoint = self._select_endpoint_round_robin()
|
|
318
|
+
elif self.strategy == RoutingStrategy.RANDOM:
|
|
319
|
+
endpoint = random.choice(self.endpoints)
|
|
320
|
+
elif self.strategy == RoutingStrategy.LEAST_LATENCY:
|
|
321
|
+
endpoint = self._select_endpoint_least_latency(payload_type)
|
|
322
|
+
elif self.strategy == RoutingStrategy.LEAST_ERRORS:
|
|
323
|
+
endpoint = self._select_endpoint_least_errors()
|
|
324
|
+
elif self.strategy == RoutingStrategy.LEARNED:
|
|
325
|
+
endpoint = self._select_endpoint_learned(payload_type)
|
|
326
|
+
else:
|
|
327
|
+
endpoint = random.choice(self.endpoints)
|
|
328
|
+
|
|
329
|
+
decision = RoutingDecision(
|
|
330
|
+
endpoint=endpoint,
|
|
331
|
+
strategy_used=self.strategy,
|
|
332
|
+
payload_type=payload_type
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
self._recent_decisions.append(decision)
|
|
336
|
+
|
|
337
|
+
# Keep only recent decisions
|
|
338
|
+
if len(self._recent_decisions) > 1000:
|
|
339
|
+
self._recent_decisions = self._recent_decisions[-500:]
|
|
340
|
+
|
|
341
|
+
return decision
|
|
342
|
+
|
|
343
|
+
def record_outcome(
|
|
344
|
+
self,
|
|
345
|
+
decision: RoutingDecision,
|
|
346
|
+
success: bool,
|
|
347
|
+
latency_ms: float,
|
|
348
|
+
error: Optional[str] = None
|
|
349
|
+
):
|
|
350
|
+
"""Record the outcome of a routing decision for learning."""
|
|
351
|
+
decision.success = success
|
|
352
|
+
decision.latency_ms = latency_ms
|
|
353
|
+
decision.error = error
|
|
354
|
+
|
|
355
|
+
# Update endpoint stats
|
|
356
|
+
stats = self.stats[decision.endpoint]
|
|
357
|
+
if success:
|
|
358
|
+
stats.record_success(latency_ms, decision.payload_type)
|
|
359
|
+
else:
|
|
360
|
+
stats.record_failure(error or "Unknown error")
|
|
361
|
+
|
|
362
|
+
# Calculate reward for Agent Lightning
|
|
363
|
+
if success:
|
|
364
|
+
# Reward: higher for faster responses
|
|
365
|
+
# Normalize latency to [0, 1] where lower is better
|
|
366
|
+
latency_reward = max(0, 1 - (latency_ms / 1000))
|
|
367
|
+
decision.reward = 0.5 + (latency_reward * 0.5)
|
|
368
|
+
else:
|
|
369
|
+
# Penalty for failure
|
|
370
|
+
decision.reward = -1.0
|
|
371
|
+
|
|
372
|
+
# Emit reward to Agent Lightning
|
|
373
|
+
if self.enable_learning and self._lightning_store:
|
|
374
|
+
try:
|
|
375
|
+
agl.emit_reward(
|
|
376
|
+
reward=decision.reward,
|
|
377
|
+
info={
|
|
378
|
+
"endpoint": decision.endpoint,
|
|
379
|
+
"payload_type": decision.payload_type,
|
|
380
|
+
"latency_ms": latency_ms,
|
|
381
|
+
"success": success,
|
|
382
|
+
"error": error
|
|
383
|
+
}
|
|
384
|
+
)
|
|
385
|
+
except Exception as e:
|
|
386
|
+
logger.debug(f"Failed to emit reward: {e}")
|
|
387
|
+
|
|
388
|
+
async def route_request(
|
|
389
|
+
self,
|
|
390
|
+
payload: Dict[str, Any],
|
|
391
|
+
timeout: float = 30.0,
|
|
392
|
+
max_retries: int = 3
|
|
393
|
+
) -> Tuple[bool, Dict[str, Any], RoutingDecision]:
|
|
394
|
+
"""
|
|
395
|
+
Route a request to the best endpoint with automatic retries.
|
|
396
|
+
|
|
397
|
+
Returns:
|
|
398
|
+
Tuple of (success, response_data, routing_decision)
|
|
399
|
+
"""
|
|
400
|
+
headers = {"Authorization": f"Bearer {self.auth_token}"}
|
|
401
|
+
|
|
402
|
+
last_decision = None
|
|
403
|
+
last_error = None
|
|
404
|
+
|
|
405
|
+
for attempt in range(max_retries):
|
|
406
|
+
decision = self.select_endpoint(payload)
|
|
407
|
+
last_decision = decision
|
|
408
|
+
|
|
409
|
+
start_time = time.perf_counter()
|
|
410
|
+
|
|
411
|
+
try:
|
|
412
|
+
async with httpx.AsyncClient(timeout=timeout) as client:
|
|
413
|
+
response = await client.post(
|
|
414
|
+
f"{decision.endpoint}/translate",
|
|
415
|
+
json={
|
|
416
|
+
"data": payload,
|
|
417
|
+
"source_format": "json",
|
|
418
|
+
"target_format": "emergent"
|
|
419
|
+
},
|
|
420
|
+
headers=headers
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
latency_ms = (time.perf_counter() - start_time) * 1000
|
|
424
|
+
|
|
425
|
+
if response.status_code == 200:
|
|
426
|
+
result = response.json()
|
|
427
|
+
self.record_outcome(decision, True, latency_ms)
|
|
428
|
+
return True, result, decision
|
|
429
|
+
else:
|
|
430
|
+
error = f"HTTP {response.status_code}"
|
|
431
|
+
self.record_outcome(decision, False, latency_ms, error)
|
|
432
|
+
last_error = error
|
|
433
|
+
|
|
434
|
+
except Exception as e:
|
|
435
|
+
latency_ms = (time.perf_counter() - start_time) * 1000
|
|
436
|
+
error = str(e)[:100]
|
|
437
|
+
self.record_outcome(decision, False, latency_ms, error)
|
|
438
|
+
last_error = error
|
|
439
|
+
|
|
440
|
+
# Exponential backoff before retry
|
|
441
|
+
if attempt < max_retries - 1:
|
|
442
|
+
await asyncio.sleep(0.1 * (2 ** attempt))
|
|
443
|
+
|
|
444
|
+
return False, {"error": last_error}, last_decision
|
|
445
|
+
|
|
446
|
+
def get_stats_summary(self) -> Dict[str, Any]:
|
|
447
|
+
"""Get summary of routing statistics."""
|
|
448
|
+
return {
|
|
449
|
+
"endpoints": {
|
|
450
|
+
url: {
|
|
451
|
+
"total_requests": stats.total_requests,
|
|
452
|
+
"success_rate": round(stats.success_rate * 100, 2),
|
|
453
|
+
"avg_latency_ms": round(stats.avg_latency_ms, 2),
|
|
454
|
+
"is_healthy": stats.is_healthy,
|
|
455
|
+
"consecutive_failures": stats.consecutive_failures
|
|
456
|
+
}
|
|
457
|
+
for url, stats in self.stats.items()
|
|
458
|
+
},
|
|
459
|
+
"learning_enabled": self.enable_learning,
|
|
460
|
+
"strategy": self.strategy.value,
|
|
461
|
+
"total_decisions": len(self._recent_decisions)
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
# Convenience function for stress tests
|
|
466
|
+
async def create_learning_router(
|
|
467
|
+
fly_app: str = "emergent-language",
|
|
468
|
+
regions: List[str] = None
|
|
469
|
+
) -> IntelligentRouter:
|
|
470
|
+
"""
|
|
471
|
+
Create an intelligent router for Fly.io deployment.
|
|
472
|
+
|
|
473
|
+
Args:
|
|
474
|
+
fly_app: Fly.io app name
|
|
475
|
+
regions: Optional list of regions to include
|
|
476
|
+
|
|
477
|
+
Returns:
|
|
478
|
+
Configured IntelligentRouter
|
|
479
|
+
"""
|
|
480
|
+
# Default to main endpoint
|
|
481
|
+
endpoints = [f"https://{fly_app}.fly.dev"]
|
|
482
|
+
|
|
483
|
+
# Could add region-specific endpoints if Fly supports it
|
|
484
|
+
# e.g., https://iad.emergent-language.fly.dev
|
|
485
|
+
|
|
486
|
+
return IntelligentRouter(
|
|
487
|
+
endpoints=endpoints,
|
|
488
|
+
strategy=RoutingStrategy.LEARNED,
|
|
489
|
+
enable_learning=True
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
if __name__ == "__main__":
|
|
494
|
+
# Quick test
|
|
495
|
+
async def test():
|
|
496
|
+
router = IntelligentRouter(
|
|
497
|
+
endpoints=["https://emergent-language.fly.dev"],
|
|
498
|
+
strategy=RoutingStrategy.LEARNED
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
# Test routing
|
|
502
|
+
payload = {"task": "analyze", "data": "test"}
|
|
503
|
+
success, result, decision = await router.route_request(payload)
|
|
504
|
+
|
|
505
|
+
print(f"Success: {success}")
|
|
506
|
+
print(f"Decision: {decision}")
|
|
507
|
+
print(f"Stats: {router.get_stats_summary()}")
|
|
508
|
+
|
|
509
|
+
asyncio.run(test())
|