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.
Files changed (95) hide show
  1. devsquad-3.6.0.dist-info/METADATA +944 -0
  2. devsquad-3.6.0.dist-info/RECORD +95 -0
  3. devsquad-3.6.0.dist-info/WHEEL +5 -0
  4. devsquad-3.6.0.dist-info/entry_points.txt +2 -0
  5. devsquad-3.6.0.dist-info/licenses/LICENSE +21 -0
  6. devsquad-3.6.0.dist-info/top_level.txt +2 -0
  7. scripts/__init__.py +0 -0
  8. scripts/ai_semantic_matcher.py +512 -0
  9. scripts/alert_manager.py +505 -0
  10. scripts/api/__init__.py +43 -0
  11. scripts/api/models.py +386 -0
  12. scripts/api/routes/__init__.py +20 -0
  13. scripts/api/routes/dispatch.py +348 -0
  14. scripts/api/routes/lifecycle.py +330 -0
  15. scripts/api/routes/metrics_gates.py +347 -0
  16. scripts/api_server.py +318 -0
  17. scripts/auth.py +451 -0
  18. scripts/cli/__init__.py +1 -0
  19. scripts/cli/cli_visual.py +642 -0
  20. scripts/cli.py +1094 -0
  21. scripts/collaboration/__init__.py +212 -0
  22. scripts/collaboration/_version.py +1 -0
  23. scripts/collaboration/agent_briefing.py +656 -0
  24. scripts/collaboration/ai_semantic_matcher.py +260 -0
  25. scripts/collaboration/anchor_checker.py +281 -0
  26. scripts/collaboration/anti_rationalization.py +470 -0
  27. scripts/collaboration/async_integration_example.py +255 -0
  28. scripts/collaboration/batch_scheduler.py +149 -0
  29. scripts/collaboration/checkpoint_manager.py +561 -0
  30. scripts/collaboration/ci_feedback_adapter.py +351 -0
  31. scripts/collaboration/code_map_generator.py +247 -0
  32. scripts/collaboration/concern_pack_loader.py +352 -0
  33. scripts/collaboration/confidence_score.py +496 -0
  34. scripts/collaboration/config_loader.py +188 -0
  35. scripts/collaboration/consensus.py +244 -0
  36. scripts/collaboration/context_compressor.py +533 -0
  37. scripts/collaboration/coordinator.py +668 -0
  38. scripts/collaboration/dispatcher.py +1636 -0
  39. scripts/collaboration/dual_layer_context.py +128 -0
  40. scripts/collaboration/enhanced_worker.py +539 -0
  41. scripts/collaboration/feature_usage_tracker.py +206 -0
  42. scripts/collaboration/five_axis_consensus.py +334 -0
  43. scripts/collaboration/input_validator.py +401 -0
  44. scripts/collaboration/integration_example.py +287 -0
  45. scripts/collaboration/intent_workflow_mapper.py +350 -0
  46. scripts/collaboration/language_parsers.py +269 -0
  47. scripts/collaboration/lifecycle_protocol.py +1446 -0
  48. scripts/collaboration/llm_backend.py +453 -0
  49. scripts/collaboration/llm_cache.py +448 -0
  50. scripts/collaboration/llm_cache_async.py +347 -0
  51. scripts/collaboration/llm_retry.py +387 -0
  52. scripts/collaboration/llm_retry_async.py +389 -0
  53. scripts/collaboration/mce_adapter.py +597 -0
  54. scripts/collaboration/memory_bridge.py +1607 -0
  55. scripts/collaboration/models.py +537 -0
  56. scripts/collaboration/null_providers.py +297 -0
  57. scripts/collaboration/operation_classifier.py +289 -0
  58. scripts/collaboration/output_slicer.py +225 -0
  59. scripts/collaboration/performance_monitor.py +462 -0
  60. scripts/collaboration/permission_guard.py +865 -0
  61. scripts/collaboration/prompt_assembler.py +756 -0
  62. scripts/collaboration/prompt_variant_generator.py +483 -0
  63. scripts/collaboration/protocols.py +267 -0
  64. scripts/collaboration/report_formatter.py +352 -0
  65. scripts/collaboration/retrospective.py +279 -0
  66. scripts/collaboration/role_matcher.py +92 -0
  67. scripts/collaboration/role_template_market.py +352 -0
  68. scripts/collaboration/rule_collector.py +678 -0
  69. scripts/collaboration/scratchpad.py +346 -0
  70. scripts/collaboration/skill_registry.py +151 -0
  71. scripts/collaboration/skillifier.py +878 -0
  72. scripts/collaboration/standardized_role_template.py +317 -0
  73. scripts/collaboration/task_completion_checker.py +237 -0
  74. scripts/collaboration/test_quality_guard.py +695 -0
  75. scripts/collaboration/unified_gate_engine.py +598 -0
  76. scripts/collaboration/usage_tracker.py +309 -0
  77. scripts/collaboration/user_friendly_error.py +176 -0
  78. scripts/collaboration/verification_gate.py +312 -0
  79. scripts/collaboration/warmup_manager.py +635 -0
  80. scripts/collaboration/worker.py +513 -0
  81. scripts/collaboration/workflow_engine.py +684 -0
  82. scripts/dashboard.py +1088 -0
  83. scripts/generate_benchmark_report.py +786 -0
  84. scripts/history_manager.py +604 -0
  85. scripts/mcp_server.py +289 -0
  86. skills/__init__.py +32 -0
  87. skills/dispatch/handler.py +52 -0
  88. skills/intent/handler.py +59 -0
  89. skills/registry.py +67 -0
  90. skills/retrospective/__init__.py +0 -0
  91. skills/retrospective/handler.py +125 -0
  92. skills/review/handler.py +356 -0
  93. skills/security/handler.py +454 -0
  94. skills/test/__init__.py +0 -0
  95. 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