devsquad 3.6.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.
- devsquad-3.6.0.dist-info/METADATA +944 -0
- devsquad-3.6.0.dist-info/RECORD +95 -0
- devsquad-3.6.0.dist-info/WHEEL +5 -0
- devsquad-3.6.0.dist-info/entry_points.txt +2 -0
- devsquad-3.6.0.dist-info/licenses/LICENSE +21 -0
- devsquad-3.6.0.dist-info/top_level.txt +2 -0
- scripts/__init__.py +0 -0
- scripts/ai_semantic_matcher.py +512 -0
- scripts/alert_manager.py +505 -0
- scripts/api/__init__.py +43 -0
- scripts/api/models.py +386 -0
- scripts/api/routes/__init__.py +20 -0
- scripts/api/routes/dispatch.py +348 -0
- scripts/api/routes/lifecycle.py +330 -0
- scripts/api/routes/metrics_gates.py +347 -0
- scripts/api_server.py +318 -0
- scripts/auth.py +451 -0
- scripts/cli/__init__.py +1 -0
- scripts/cli/cli_visual.py +642 -0
- scripts/cli.py +1094 -0
- scripts/collaboration/__init__.py +212 -0
- scripts/collaboration/_version.py +1 -0
- scripts/collaboration/agent_briefing.py +656 -0
- scripts/collaboration/ai_semantic_matcher.py +260 -0
- scripts/collaboration/anchor_checker.py +281 -0
- scripts/collaboration/anti_rationalization.py +470 -0
- scripts/collaboration/async_integration_example.py +255 -0
- scripts/collaboration/batch_scheduler.py +149 -0
- scripts/collaboration/checkpoint_manager.py +561 -0
- scripts/collaboration/ci_feedback_adapter.py +351 -0
- scripts/collaboration/code_map_generator.py +247 -0
- scripts/collaboration/concern_pack_loader.py +352 -0
- scripts/collaboration/confidence_score.py +496 -0
- scripts/collaboration/config_loader.py +188 -0
- scripts/collaboration/consensus.py +244 -0
- scripts/collaboration/context_compressor.py +533 -0
- scripts/collaboration/coordinator.py +668 -0
- scripts/collaboration/dispatcher.py +1636 -0
- scripts/collaboration/dual_layer_context.py +128 -0
- scripts/collaboration/enhanced_worker.py +539 -0
- scripts/collaboration/feature_usage_tracker.py +206 -0
- scripts/collaboration/five_axis_consensus.py +334 -0
- scripts/collaboration/input_validator.py +401 -0
- scripts/collaboration/integration_example.py +287 -0
- scripts/collaboration/intent_workflow_mapper.py +350 -0
- scripts/collaboration/language_parsers.py +269 -0
- scripts/collaboration/lifecycle_protocol.py +1446 -0
- scripts/collaboration/llm_backend.py +453 -0
- scripts/collaboration/llm_cache.py +448 -0
- scripts/collaboration/llm_cache_async.py +347 -0
- scripts/collaboration/llm_retry.py +387 -0
- scripts/collaboration/llm_retry_async.py +389 -0
- scripts/collaboration/mce_adapter.py +597 -0
- scripts/collaboration/memory_bridge.py +1607 -0
- scripts/collaboration/models.py +537 -0
- scripts/collaboration/null_providers.py +297 -0
- scripts/collaboration/operation_classifier.py +289 -0
- scripts/collaboration/output_slicer.py +225 -0
- scripts/collaboration/performance_monitor.py +462 -0
- scripts/collaboration/permission_guard.py +865 -0
- scripts/collaboration/prompt_assembler.py +756 -0
- scripts/collaboration/prompt_variant_generator.py +483 -0
- scripts/collaboration/protocols.py +267 -0
- scripts/collaboration/report_formatter.py +352 -0
- scripts/collaboration/retrospective.py +279 -0
- scripts/collaboration/role_matcher.py +92 -0
- scripts/collaboration/role_template_market.py +352 -0
- scripts/collaboration/rule_collector.py +678 -0
- scripts/collaboration/scratchpad.py +346 -0
- scripts/collaboration/skill_registry.py +151 -0
- scripts/collaboration/skillifier.py +878 -0
- scripts/collaboration/standardized_role_template.py +317 -0
- scripts/collaboration/task_completion_checker.py +237 -0
- scripts/collaboration/test_quality_guard.py +695 -0
- scripts/collaboration/unified_gate_engine.py +598 -0
- scripts/collaboration/usage_tracker.py +309 -0
- scripts/collaboration/user_friendly_error.py +176 -0
- scripts/collaboration/verification_gate.py +312 -0
- scripts/collaboration/warmup_manager.py +635 -0
- scripts/collaboration/worker.py +513 -0
- scripts/collaboration/workflow_engine.py +684 -0
- scripts/dashboard.py +1088 -0
- scripts/generate_benchmark_report.py +786 -0
- scripts/history_manager.py +604 -0
- scripts/mcp_server.py +289 -0
- skills/__init__.py +32 -0
- skills/dispatch/handler.py +52 -0
- skills/intent/handler.py +59 -0
- skills/registry.py +67 -0
- skills/retrospective/__init__.py +0 -0
- skills/retrospective/handler.py +125 -0
- skills/review/handler.py +356 -0
- skills/security/handler.py +454 -0
- skills/test/__init__.py +0 -0
- skills/test/handler.py +78 -0
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
LLM Retry and Fallback Mechanism
|
|
5
|
+
|
|
6
|
+
Provides robust error handling for LLM API calls:
|
|
7
|
+
- Exponential backoff retry
|
|
8
|
+
- Multiple backend fallback
|
|
9
|
+
- Circuit breaker pattern
|
|
10
|
+
- Rate limiting protection
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
from scripts.collaboration.llm_retry import retry_with_fallback
|
|
14
|
+
|
|
15
|
+
@retry_with_fallback(
|
|
16
|
+
max_retries=3,
|
|
17
|
+
fallback_backends=["openai", "anthropic", "zhipu"]
|
|
18
|
+
)
|
|
19
|
+
def call_llm(prompt: str, backend: str, model: str):
|
|
20
|
+
# Your LLM API call
|
|
21
|
+
return response
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import time
|
|
25
|
+
import logging
|
|
26
|
+
from typing import Callable, Optional, List, Dict, Any
|
|
27
|
+
from functools import wraps
|
|
28
|
+
from dataclasses import dataclass
|
|
29
|
+
from datetime import datetime, timedelta
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class RetryConfig:
|
|
37
|
+
"""重试配置"""
|
|
38
|
+
max_retries: int = 3
|
|
39
|
+
initial_delay: float = 1.0 # 初始延迟(秒)
|
|
40
|
+
max_delay: float = 60.0 # 最大延迟(秒)
|
|
41
|
+
exponential_base: float = 2.0 # 指数基数
|
|
42
|
+
jitter: bool = True # 添加随机抖动
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class CircuitBreakerState:
|
|
47
|
+
"""熔断器状态"""
|
|
48
|
+
failure_count: int = 0
|
|
49
|
+
last_failure_time: Optional[datetime] = None
|
|
50
|
+
state: str = "closed" # closed, open, half_open
|
|
51
|
+
failure_threshold: int = 5
|
|
52
|
+
timeout_seconds: int = 60
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class RateLimitError(Exception):
|
|
56
|
+
"""速率限制错误"""
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class CircuitBreakerError(Exception):
|
|
61
|
+
"""熔断器打开错误"""
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class LLMRetryManager:
|
|
66
|
+
"""
|
|
67
|
+
LLM 重试管理器
|
|
68
|
+
|
|
69
|
+
Features:
|
|
70
|
+
- 指数退避重试
|
|
71
|
+
- 多后端故障转移
|
|
72
|
+
- 熔断器保护
|
|
73
|
+
- 速率限制检测
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def __init__(self):
|
|
77
|
+
self.circuit_breakers: Dict[str, CircuitBreakerState] = {}
|
|
78
|
+
self.stats = {
|
|
79
|
+
"total_calls": 0,
|
|
80
|
+
"successful_calls": 0,
|
|
81
|
+
"failed_calls": 0,
|
|
82
|
+
"retries": 0,
|
|
83
|
+
"fallbacks": 0,
|
|
84
|
+
"circuit_breaks": 0,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
def _get_circuit_breaker(self, backend: str) -> CircuitBreakerState:
|
|
88
|
+
"""获取或创建熔断器"""
|
|
89
|
+
if backend not in self.circuit_breakers:
|
|
90
|
+
self.circuit_breakers[backend] = CircuitBreakerState()
|
|
91
|
+
return self.circuit_breakers[backend]
|
|
92
|
+
|
|
93
|
+
def _check_circuit_breaker(self, backend: str):
|
|
94
|
+
"""检查熔断器状态"""
|
|
95
|
+
cb = self._get_circuit_breaker(backend)
|
|
96
|
+
|
|
97
|
+
if cb.state == "open":
|
|
98
|
+
# 检查是否可以尝试恢复
|
|
99
|
+
if cb.last_failure_time:
|
|
100
|
+
elapsed = (datetime.now() - cb.last_failure_time).total_seconds()
|
|
101
|
+
if elapsed > cb.timeout_seconds:
|
|
102
|
+
cb.state = "half_open"
|
|
103
|
+
logger.info(f"Circuit breaker for {backend} entering half-open state")
|
|
104
|
+
else:
|
|
105
|
+
self.stats["circuit_breaks"] += 1
|
|
106
|
+
raise CircuitBreakerError(
|
|
107
|
+
f"Circuit breaker open for {backend}. "
|
|
108
|
+
f"Retry after {cb.timeout_seconds - elapsed:.0f}s"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
def _record_success(self, backend: str):
|
|
112
|
+
"""记录成功调用"""
|
|
113
|
+
cb = self._get_circuit_breaker(backend)
|
|
114
|
+
if cb.state == "half_open":
|
|
115
|
+
cb.state = "closed"
|
|
116
|
+
cb.failure_count = 0
|
|
117
|
+
logger.info(f"Circuit breaker for {backend} closed")
|
|
118
|
+
self.stats["successful_calls"] += 1
|
|
119
|
+
|
|
120
|
+
def _record_failure(self, backend: str, error: Exception):
|
|
121
|
+
"""记录失败调用"""
|
|
122
|
+
cb = self._get_circuit_breaker(backend)
|
|
123
|
+
cb.failure_count += 1
|
|
124
|
+
cb.last_failure_time = datetime.now()
|
|
125
|
+
|
|
126
|
+
if cb.failure_count >= cb.failure_threshold:
|
|
127
|
+
cb.state = "open"
|
|
128
|
+
logger.warning(
|
|
129
|
+
f"Circuit breaker opened for {backend} "
|
|
130
|
+
f"after {cb.failure_count} failures"
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
self.stats["failed_calls"] += 1
|
|
134
|
+
|
|
135
|
+
def _calculate_delay(self, attempt: int, config: RetryConfig) -> float:
|
|
136
|
+
"""计算重试延迟(指数退避)"""
|
|
137
|
+
delay = min(
|
|
138
|
+
config.initial_delay * (config.exponential_base ** attempt),
|
|
139
|
+
config.max_delay
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
if config.jitter:
|
|
143
|
+
import random
|
|
144
|
+
delay *= (0.5 + random.random()) # 添加 50-150% 的随机抖动
|
|
145
|
+
|
|
146
|
+
return delay
|
|
147
|
+
|
|
148
|
+
def _is_retryable_error(self, error: Exception) -> bool:
|
|
149
|
+
"""判断错误是否可重试"""
|
|
150
|
+
error_msg = str(error).lower()
|
|
151
|
+
|
|
152
|
+
# 可重试的错误类型
|
|
153
|
+
retryable_patterns = [
|
|
154
|
+
"timeout",
|
|
155
|
+
"connection",
|
|
156
|
+
"network",
|
|
157
|
+
"503", # Service Unavailable
|
|
158
|
+
"502", # Bad Gateway
|
|
159
|
+
"500", # Internal Server Error
|
|
160
|
+
"429", # Rate Limit (但需要更长延迟)
|
|
161
|
+
]
|
|
162
|
+
|
|
163
|
+
return any(pattern in error_msg for pattern in retryable_patterns)
|
|
164
|
+
|
|
165
|
+
def _is_rate_limit_error(self, error: Exception) -> bool:
|
|
166
|
+
"""判断是否为速率限制错误"""
|
|
167
|
+
error_msg = str(error).lower()
|
|
168
|
+
return "429" in error_msg or "rate limit" in error_msg
|
|
169
|
+
|
|
170
|
+
def retry_with_fallback(
|
|
171
|
+
self,
|
|
172
|
+
func: Callable,
|
|
173
|
+
args: tuple,
|
|
174
|
+
kwargs: dict,
|
|
175
|
+
config: RetryConfig,
|
|
176
|
+
fallback_backends: Optional[List[str]] = None,
|
|
177
|
+
current_backend: Optional[str] = None
|
|
178
|
+
) -> Any:
|
|
179
|
+
"""
|
|
180
|
+
执行带重试和故障转移的函数调用
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
func: 要调用的函数
|
|
184
|
+
args: 位置参数
|
|
185
|
+
kwargs: 关键字参数
|
|
186
|
+
config: 重试配置
|
|
187
|
+
fallback_backends: 备用后端列表
|
|
188
|
+
current_backend: 当前使用的后端
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
函数执行结果
|
|
192
|
+
|
|
193
|
+
Raises:
|
|
194
|
+
最后一次尝试的异常
|
|
195
|
+
"""
|
|
196
|
+
self.stats["total_calls"] += 1
|
|
197
|
+
last_error = None
|
|
198
|
+
|
|
199
|
+
# 如果指定了当前后端,检查熔断器
|
|
200
|
+
if current_backend:
|
|
201
|
+
try:
|
|
202
|
+
self._check_circuit_breaker(current_backend)
|
|
203
|
+
except CircuitBreakerError as e:
|
|
204
|
+
logger.warning(str(e))
|
|
205
|
+
# 熔断器打开,直接尝试故障转移
|
|
206
|
+
if fallback_backends:
|
|
207
|
+
return self._try_fallback(
|
|
208
|
+
func, args, kwargs, config, fallback_backends, current_backend
|
|
209
|
+
)
|
|
210
|
+
raise
|
|
211
|
+
|
|
212
|
+
# 主后端重试
|
|
213
|
+
for attempt in range(config.max_retries):
|
|
214
|
+
try:
|
|
215
|
+
result = func(*args, **kwargs)
|
|
216
|
+
if current_backend:
|
|
217
|
+
self._record_success(current_backend)
|
|
218
|
+
return result
|
|
219
|
+
|
|
220
|
+
except Exception as e:
|
|
221
|
+
last_error = e
|
|
222
|
+
|
|
223
|
+
if current_backend:
|
|
224
|
+
self._record_failure(current_backend, e)
|
|
225
|
+
|
|
226
|
+
# 检查是否可重试
|
|
227
|
+
if not self._is_retryable_error(e):
|
|
228
|
+
logger.error(f"Non-retryable error: {e}")
|
|
229
|
+
break
|
|
230
|
+
|
|
231
|
+
# 最后一次尝试不需要延迟
|
|
232
|
+
if attempt < config.max_retries - 1:
|
|
233
|
+
delay = self._calculate_delay(attempt, config)
|
|
234
|
+
|
|
235
|
+
# 速率限制错误需要更长延迟
|
|
236
|
+
if self._is_rate_limit_error(e):
|
|
237
|
+
delay *= 3
|
|
238
|
+
logger.warning(f"Rate limit detected, waiting {delay:.1f}s")
|
|
239
|
+
|
|
240
|
+
logger.info(
|
|
241
|
+
f"Retry attempt {attempt + 1}/{config.max_retries} "
|
|
242
|
+
f"after {delay:.1f}s delay"
|
|
243
|
+
)
|
|
244
|
+
time.sleep(delay)
|
|
245
|
+
self.stats["retries"] += 1
|
|
246
|
+
|
|
247
|
+
# 主后端失败,尝试故障转移
|
|
248
|
+
if fallback_backends:
|
|
249
|
+
try:
|
|
250
|
+
return self._try_fallback(
|
|
251
|
+
func, args, kwargs, config, fallback_backends, current_backend
|
|
252
|
+
)
|
|
253
|
+
except Exception as fallback_error:
|
|
254
|
+
logger.error(f"All fallback attempts failed: {fallback_error}")
|
|
255
|
+
last_error = fallback_error
|
|
256
|
+
|
|
257
|
+
# 所有尝试都失败
|
|
258
|
+
raise last_error
|
|
259
|
+
|
|
260
|
+
def _try_fallback(
|
|
261
|
+
self,
|
|
262
|
+
func: Callable,
|
|
263
|
+
args: tuple,
|
|
264
|
+
kwargs: dict,
|
|
265
|
+
config: RetryConfig,
|
|
266
|
+
fallback_backends: List[str],
|
|
267
|
+
exclude_backend: Optional[str]
|
|
268
|
+
) -> Any:
|
|
269
|
+
"""尝试故障转移到备用后端"""
|
|
270
|
+
for backend in fallback_backends:
|
|
271
|
+
if backend == exclude_backend:
|
|
272
|
+
continue
|
|
273
|
+
|
|
274
|
+
try:
|
|
275
|
+
self._check_circuit_breaker(backend)
|
|
276
|
+
except CircuitBreakerError:
|
|
277
|
+
logger.warning(f"Skipping {backend} (circuit breaker open)")
|
|
278
|
+
continue
|
|
279
|
+
|
|
280
|
+
logger.info(f"Attempting fallback to {backend}")
|
|
281
|
+
self.stats["fallbacks"] += 1
|
|
282
|
+
|
|
283
|
+
# 更新 kwargs 中的 backend 参数
|
|
284
|
+
fallback_kwargs = kwargs.copy()
|
|
285
|
+
fallback_kwargs["backend"] = backend # 始终设置 backend 参数
|
|
286
|
+
|
|
287
|
+
try:
|
|
288
|
+
result = func(*args, **fallback_kwargs)
|
|
289
|
+
self._record_success(backend)
|
|
290
|
+
logger.info(f"Fallback to {backend} successful")
|
|
291
|
+
return result
|
|
292
|
+
except Exception as e:
|
|
293
|
+
self._record_failure(backend, e)
|
|
294
|
+
logger.warning(f"Fallback to {backend} failed: {e}")
|
|
295
|
+
continue
|
|
296
|
+
|
|
297
|
+
raise Exception("All fallback backends failed")
|
|
298
|
+
|
|
299
|
+
def get_stats(self) -> Dict[str, Any]:
|
|
300
|
+
"""获取统计信息"""
|
|
301
|
+
total = self.stats["total_calls"]
|
|
302
|
+
success_rate = (
|
|
303
|
+
self.stats["successful_calls"] / total * 100
|
|
304
|
+
if total > 0 else 0
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
**self.stats,
|
|
309
|
+
"success_rate": f"{success_rate:.1f}%",
|
|
310
|
+
"circuit_breakers": {
|
|
311
|
+
backend: {
|
|
312
|
+
"state": cb.state,
|
|
313
|
+
"failure_count": cb.failure_count,
|
|
314
|
+
"last_failure": (
|
|
315
|
+
cb.last_failure_time.isoformat()
|
|
316
|
+
if cb.last_failure_time else None
|
|
317
|
+
)
|
|
318
|
+
}
|
|
319
|
+
for backend, cb in self.circuit_breakers.items()
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
def reset_circuit_breaker(self, backend: str):
|
|
324
|
+
"""手动重置熔断器"""
|
|
325
|
+
if backend in self.circuit_breakers:
|
|
326
|
+
self.circuit_breakers[backend] = CircuitBreakerState()
|
|
327
|
+
logger.info(f"Circuit breaker for {backend} manually reset")
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
# 全局实例
|
|
331
|
+
_retry_manager: Optional[LLMRetryManager] = None
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def get_retry_manager() -> LLMRetryManager:
|
|
335
|
+
"""获取全局重试管理器实例"""
|
|
336
|
+
global _retry_manager
|
|
337
|
+
if _retry_manager is None:
|
|
338
|
+
_retry_manager = LLMRetryManager()
|
|
339
|
+
return _retry_manager
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def retry_with_fallback(
|
|
343
|
+
max_retries: int = 3,
|
|
344
|
+
initial_delay: float = 1.0,
|
|
345
|
+
max_delay: float = 60.0,
|
|
346
|
+
fallback_backends: Optional[List[str]] = None,
|
|
347
|
+
backend_param: str = "backend"
|
|
348
|
+
):
|
|
349
|
+
"""
|
|
350
|
+
装饰器:为 LLM 调用添加重试和故障转移
|
|
351
|
+
|
|
352
|
+
Args:
|
|
353
|
+
max_retries: 最大重试次数
|
|
354
|
+
initial_delay: 初始延迟(秒)
|
|
355
|
+
max_delay: 最大延迟(秒)
|
|
356
|
+
fallback_backends: 备用后端列表
|
|
357
|
+
backend_param: 后端参数名称
|
|
358
|
+
|
|
359
|
+
Example:
|
|
360
|
+
@retry_with_fallback(
|
|
361
|
+
max_retries=3,
|
|
362
|
+
fallback_backends=["openai", "anthropic", "zhipu"]
|
|
363
|
+
)
|
|
364
|
+
def call_llm(prompt: str, backend: str = "openai"):
|
|
365
|
+
# Your API call
|
|
366
|
+
return response
|
|
367
|
+
"""
|
|
368
|
+
def decorator(func: Callable) -> Callable:
|
|
369
|
+
@wraps(func)
|
|
370
|
+
def wrapper(*args, **kwargs):
|
|
371
|
+
config = RetryConfig(
|
|
372
|
+
max_retries=max_retries,
|
|
373
|
+
initial_delay=initial_delay,
|
|
374
|
+
max_delay=max_delay
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
# 获取当前后端
|
|
378
|
+
current_backend = kwargs.get(backend_param)
|
|
379
|
+
|
|
380
|
+
manager = get_retry_manager()
|
|
381
|
+
return manager.retry_with_fallback(
|
|
382
|
+
func, args, kwargs, config,
|
|
383
|
+
fallback_backends, current_backend
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
return wrapper
|
|
387
|
+
return decorator
|