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,255 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Async Integration Example
|
|
5
|
+
|
|
6
|
+
Demonstrates how to use async LLM cache and retry modules together
|
|
7
|
+
for optimal performance in asyncio applications.
|
|
8
|
+
|
|
9
|
+
This example shows:
|
|
10
|
+
- Async cache integration
|
|
11
|
+
- Async retry with fallback
|
|
12
|
+
- Combined usage patterns
|
|
13
|
+
- Performance monitoring
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import asyncio
|
|
17
|
+
import logging
|
|
18
|
+
import time
|
|
19
|
+
from typing import Optional
|
|
20
|
+
|
|
21
|
+
from scripts.collaboration.llm_cache_async import get_async_llm_cache
|
|
22
|
+
from scripts.collaboration.llm_retry_async import async_retry_with_fallback, get_async_retry_manager
|
|
23
|
+
|
|
24
|
+
logging.basicConfig(level=logging.INFO)
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# Simulated async LLM API calls
|
|
29
|
+
async def mock_openai_api(prompt: str, model: str = "gpt-4") -> str:
|
|
30
|
+
"""Simulate OpenAI API call"""
|
|
31
|
+
await asyncio.sleep(0.1) # Simulate network latency
|
|
32
|
+
return f"OpenAI {model} response to: {prompt}"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
async def mock_anthropic_api(prompt: str, model: str = "claude-3") -> str:
|
|
36
|
+
"""Simulate Anthropic API call"""
|
|
37
|
+
await asyncio.sleep(0.1)
|
|
38
|
+
return f"Anthropic {model} response to: {prompt}"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# Example 1: Basic async cache usage
|
|
42
|
+
async def example_basic_cache():
|
|
43
|
+
"""Example: Basic async cache usage"""
|
|
44
|
+
print("\n=== Example 1: Basic Async Cache ===")
|
|
45
|
+
|
|
46
|
+
cache = get_async_llm_cache()
|
|
47
|
+
prompt = "What is Python?"
|
|
48
|
+
|
|
49
|
+
# First call - cache miss
|
|
50
|
+
start = time.time()
|
|
51
|
+
cached = await cache.get(prompt, "openai", "gpt-4")
|
|
52
|
+
if not cached:
|
|
53
|
+
response = await mock_openai_api(prompt)
|
|
54
|
+
await cache.set(prompt, response, "openai", "gpt-4")
|
|
55
|
+
print(f"Cache miss - API called ({time.time() - start:.3f}s)")
|
|
56
|
+
|
|
57
|
+
# Second call - cache hit
|
|
58
|
+
start = time.time()
|
|
59
|
+
cached = await cache.get(prompt, "openai", "gpt-4")
|
|
60
|
+
print(f"Cache hit - Response: {cached[:50]}... ({time.time() - start:.3f}s)")
|
|
61
|
+
|
|
62
|
+
# Print stats
|
|
63
|
+
stats = cache.get_stats()
|
|
64
|
+
print(f"Cache stats: {stats['hits']} hits, {stats['misses']} misses, {stats['hit_rate']:.1%} hit rate")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# Example 2: Async retry with fallback
|
|
68
|
+
async def example_retry_fallback():
|
|
69
|
+
"""Example: Async retry with fallback"""
|
|
70
|
+
print("\n=== Example 2: Async Retry with Fallback ===")
|
|
71
|
+
|
|
72
|
+
call_count = {"count": 0}
|
|
73
|
+
|
|
74
|
+
@async_retry_with_fallback(
|
|
75
|
+
max_retries=3,
|
|
76
|
+
initial_delay=0.1,
|
|
77
|
+
fallback_backends=["anthropic"]
|
|
78
|
+
)
|
|
79
|
+
async def call_llm(prompt: str, backend: str = "openai"):
|
|
80
|
+
call_count["count"] += 1
|
|
81
|
+
print(f" Attempt {call_count['count']}: Calling {backend}")
|
|
82
|
+
|
|
83
|
+
# Simulate failure on first 2 attempts with openai
|
|
84
|
+
if backend == "openai" and call_count["count"] < 3:
|
|
85
|
+
raise Exception("503 Service Unavailable")
|
|
86
|
+
|
|
87
|
+
if backend == "openai":
|
|
88
|
+
return await mock_openai_api(prompt)
|
|
89
|
+
else:
|
|
90
|
+
return await mock_anthropic_api(prompt)
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
result = await call_llm("Hello, world!")
|
|
94
|
+
print(f"Success: {result}")
|
|
95
|
+
except Exception as e:
|
|
96
|
+
print(f"Failed: {e}")
|
|
97
|
+
|
|
98
|
+
# Print stats
|
|
99
|
+
manager = get_async_retry_manager()
|
|
100
|
+
stats = manager.get_stats()
|
|
101
|
+
print(f"Retry stats: {stats['retries']} retries, {stats['fallbacks']} fallbacks")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# Example 3: Combined cache + retry
|
|
105
|
+
async def example_combined():
|
|
106
|
+
"""Example: Combined cache and retry"""
|
|
107
|
+
print("\n=== Example 3: Combined Cache + Retry ===")
|
|
108
|
+
|
|
109
|
+
cache = get_async_llm_cache()
|
|
110
|
+
|
|
111
|
+
@async_retry_with_fallback(
|
|
112
|
+
max_retries=2,
|
|
113
|
+
initial_delay=0.1,
|
|
114
|
+
fallback_backends=["anthropic"]
|
|
115
|
+
)
|
|
116
|
+
async def cached_llm_call(prompt: str, backend: str = "openai", model: str = "gpt-4"):
|
|
117
|
+
# Try cache first
|
|
118
|
+
cached = await cache.get(prompt, backend, model)
|
|
119
|
+
if cached:
|
|
120
|
+
print(f" Cache hit for {backend}")
|
|
121
|
+
return cached
|
|
122
|
+
|
|
123
|
+
# Cache miss - call API
|
|
124
|
+
print(f" Cache miss - calling {backend} API")
|
|
125
|
+
if backend == "openai":
|
|
126
|
+
response = await mock_openai_api(prompt, model)
|
|
127
|
+
else:
|
|
128
|
+
response = await mock_anthropic_api(prompt, model)
|
|
129
|
+
|
|
130
|
+
# Save to cache
|
|
131
|
+
await cache.set(prompt, response, backend, model)
|
|
132
|
+
return response
|
|
133
|
+
|
|
134
|
+
# First call - cache miss, API call
|
|
135
|
+
result1 = await cached_llm_call("What is async/await?")
|
|
136
|
+
print(f"Result 1: {result1[:50]}...")
|
|
137
|
+
|
|
138
|
+
# Second call - cache hit, no API call
|
|
139
|
+
result2 = await cached_llm_call("What is async/await?")
|
|
140
|
+
print(f"Result 2: {result2[:50]}...")
|
|
141
|
+
|
|
142
|
+
# Print combined stats
|
|
143
|
+
cache_stats = cache.get_stats()
|
|
144
|
+
retry_stats = get_async_retry_manager().get_stats()
|
|
145
|
+
print(f"\nCache: {cache_stats['hit_rate']:.1%} hit rate")
|
|
146
|
+
print(f"Retry: {retry_stats['successful_calls']}/{retry_stats['total_calls']} successful")
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
# Example 4: Concurrent requests
|
|
150
|
+
async def example_concurrent():
|
|
151
|
+
"""Example: Concurrent async requests"""
|
|
152
|
+
print("\n=== Example 4: Concurrent Requests ===")
|
|
153
|
+
|
|
154
|
+
cache = get_async_llm_cache()
|
|
155
|
+
|
|
156
|
+
@async_retry_with_fallback(max_retries=2, initial_delay=0.1)
|
|
157
|
+
async def cached_call(prompt: str, backend: str = "openai"):
|
|
158
|
+
cached = await cache.get(prompt, backend, "gpt-4")
|
|
159
|
+
if cached:
|
|
160
|
+
return cached
|
|
161
|
+
|
|
162
|
+
response = await mock_openai_api(prompt)
|
|
163
|
+
await cache.set(prompt, response, backend, "gpt-4")
|
|
164
|
+
return response
|
|
165
|
+
|
|
166
|
+
# Make multiple concurrent requests
|
|
167
|
+
prompts = [
|
|
168
|
+
"What is Python?",
|
|
169
|
+
"What is JavaScript?",
|
|
170
|
+
"What is Rust?",
|
|
171
|
+
"What is Python?", # Duplicate - should hit cache
|
|
172
|
+
"What is Go?"
|
|
173
|
+
]
|
|
174
|
+
|
|
175
|
+
start = time.time()
|
|
176
|
+
results = await asyncio.gather(*[cached_call(p) for p in prompts])
|
|
177
|
+
elapsed = time.time() - start
|
|
178
|
+
|
|
179
|
+
print(f"Processed {len(prompts)} requests in {elapsed:.3f}s")
|
|
180
|
+
print(f"Average: {elapsed/len(prompts):.3f}s per request")
|
|
181
|
+
|
|
182
|
+
# Print stats
|
|
183
|
+
stats = cache.get_stats()
|
|
184
|
+
print(f"Cache: {stats['hits']} hits, {stats['misses']} misses")
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# Example 5: Error handling and circuit breaker
|
|
188
|
+
async def example_circuit_breaker():
|
|
189
|
+
"""Example: Circuit breaker in action"""
|
|
190
|
+
print("\n=== Example 5: Circuit Breaker ===")
|
|
191
|
+
|
|
192
|
+
failure_count = {"count": 0}
|
|
193
|
+
|
|
194
|
+
@async_retry_with_fallback(
|
|
195
|
+
max_retries=1,
|
|
196
|
+
initial_delay=0.05,
|
|
197
|
+
fallback_backends=["anthropic"]
|
|
198
|
+
)
|
|
199
|
+
async def unreliable_call(prompt: str, backend: str = "openai"):
|
|
200
|
+
failure_count["count"] += 1
|
|
201
|
+
|
|
202
|
+
# Simulate consistent failures to trigger circuit breaker
|
|
203
|
+
if backend == "openai" and failure_count["count"] <= 6:
|
|
204
|
+
raise Exception("503 Service Unavailable")
|
|
205
|
+
|
|
206
|
+
if backend == "openai":
|
|
207
|
+
return await mock_openai_api(prompt)
|
|
208
|
+
else:
|
|
209
|
+
return await mock_anthropic_api(prompt)
|
|
210
|
+
|
|
211
|
+
# Make multiple calls to trigger circuit breaker
|
|
212
|
+
for i in range(8):
|
|
213
|
+
try:
|
|
214
|
+
result = await unreliable_call(f"Request {i+1}")
|
|
215
|
+
print(f" Request {i+1}: Success - {result[:40]}...")
|
|
216
|
+
except Exception as e:
|
|
217
|
+
print(f" Request {i+1}: Failed - {str(e)[:40]}...")
|
|
218
|
+
|
|
219
|
+
await asyncio.sleep(0.05)
|
|
220
|
+
|
|
221
|
+
# Print circuit breaker stats
|
|
222
|
+
manager = get_async_retry_manager()
|
|
223
|
+
stats = manager.get_stats()
|
|
224
|
+
print(f"\nCircuit breaker trips: {stats['circuit_breaker_trips']}")
|
|
225
|
+
print(f"Fallbacks: {stats['fallbacks']}")
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
# Main example runner
|
|
229
|
+
async def main():
|
|
230
|
+
"""Run all examples"""
|
|
231
|
+
print("=" * 60)
|
|
232
|
+
print("Async LLM Optimization Examples")
|
|
233
|
+
print("=" * 60)
|
|
234
|
+
|
|
235
|
+
await example_basic_cache()
|
|
236
|
+
await asyncio.sleep(0.5)
|
|
237
|
+
|
|
238
|
+
await example_retry_fallback()
|
|
239
|
+
await asyncio.sleep(0.5)
|
|
240
|
+
|
|
241
|
+
await example_combined()
|
|
242
|
+
await asyncio.sleep(0.5)
|
|
243
|
+
|
|
244
|
+
await example_concurrent()
|
|
245
|
+
await asyncio.sleep(0.5)
|
|
246
|
+
|
|
247
|
+
await example_circuit_breaker()
|
|
248
|
+
|
|
249
|
+
print("\n" + "=" * 60)
|
|
250
|
+
print("All examples completed!")
|
|
251
|
+
print("=" * 60)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
if __name__ == "__main__":
|
|
255
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
BatchScheduler - 批处理调度器
|
|
5
|
+
|
|
6
|
+
支持并行/串行混合调度,带超时、重试和依赖管理。
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import time
|
|
10
|
+
from typing import List, Optional
|
|
11
|
+
|
|
12
|
+
from .models import (
|
|
13
|
+
TaskDefinition,
|
|
14
|
+
TaskBatch,
|
|
15
|
+
BatchMode,
|
|
16
|
+
ScheduleResult,
|
|
17
|
+
WorkerResult,
|
|
18
|
+
)
|
|
19
|
+
from .worker import Worker
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class BatchScheduler:
|
|
23
|
+
"""
|
|
24
|
+
批处理调度器 - 并行/串行混合任务调度
|
|
25
|
+
|
|
26
|
+
负责 TaskBatch 列表的执行编排:
|
|
27
|
+
- PARALLEL 批次: 并发执行(受 max_concurrency 限制)
|
|
28
|
+
- SEQUENTIAL 批次: 串行逐个执行(带自动重试)
|
|
29
|
+
- 依赖管理: 检查批次间依赖关系,依赖未满足则跳过
|
|
30
|
+
|
|
31
|
+
默认配置:
|
|
32
|
+
- 超时: 300s
|
|
33
|
+
- 重试次数: 2次
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self):
|
|
37
|
+
"""
|
|
38
|
+
初始化批处理调度器
|
|
39
|
+
|
|
40
|
+
使用默认超时(300s)和重试次数(2次)。
|
|
41
|
+
可通过修改 _default_timeout / _default_retries 调整。
|
|
42
|
+
"""
|
|
43
|
+
self._default_timeout = 300
|
|
44
|
+
self._default_retries = 2
|
|
45
|
+
|
|
46
|
+
def schedule(self, batches: List[TaskBatch],
|
|
47
|
+
workers: dict) -> ScheduleResult:
|
|
48
|
+
"""
|
|
49
|
+
执行批处理调度计划
|
|
50
|
+
|
|
51
|
+
按顺序遍历批次列表,对每个批次:
|
|
52
|
+
1. 检查依赖是否满足(未满足则跳过并记录错误)
|
|
53
|
+
2. 根据 mode 选择并行或串行执行
|
|
54
|
+
3. 汇总所有结果和错误
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
batches: 任务批次列表(由 Coordinator.plan_task 生成)
|
|
58
|
+
workers: Worker 字典 {worker_id: Worker 实例}
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
ScheduleResult: 调度结果,包含成功/失败统计、各Worker结果、耗时
|
|
62
|
+
"""
|
|
63
|
+
start_time = time.time()
|
|
64
|
+
all_results = []
|
|
65
|
+
all_errors = []
|
|
66
|
+
total_tasks = 0
|
|
67
|
+
|
|
68
|
+
completed_batches = set()
|
|
69
|
+
for batch in batches:
|
|
70
|
+
if batch.dependencies:
|
|
71
|
+
missing = [d for d in batch.dependencies if d not in completed_batches]
|
|
72
|
+
if missing:
|
|
73
|
+
all_errors.append(f"Batch {batch.batch_id} 依赖未满足: {missing}")
|
|
74
|
+
continue
|
|
75
|
+
|
|
76
|
+
if batch.mode == BatchMode.PARALLEL:
|
|
77
|
+
results, errors = self._execute_parallel(batch, workers)
|
|
78
|
+
else:
|
|
79
|
+
results, errors = self._execute_serial(batch, workers)
|
|
80
|
+
|
|
81
|
+
all_results.extend(results)
|
|
82
|
+
all_errors.extend(errors)
|
|
83
|
+
total_tasks += len(batch.tasks)
|
|
84
|
+
completed_batches.add(batch.batch_id)
|
|
85
|
+
|
|
86
|
+
return ScheduleResult(
|
|
87
|
+
success=len(all_errors) == 0,
|
|
88
|
+
total_tasks=total_tasks,
|
|
89
|
+
completed_tasks=sum(1 for r in all_results if r.success),
|
|
90
|
+
failed_tasks=len(all_errors),
|
|
91
|
+
results=all_results,
|
|
92
|
+
duration_seconds=time.time() - start_time,
|
|
93
|
+
errors=all_errors,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def _execute_parallel(self, batch: TaskBatch,
|
|
97
|
+
workers: dict) -> tuple:
|
|
98
|
+
results = []
|
|
99
|
+
errors = []
|
|
100
|
+
for task in batch.tasks[:batch.max_concurrency]:
|
|
101
|
+
worker = self._find_worker(workers, task.role_id)
|
|
102
|
+
if not worker:
|
|
103
|
+
errors.append(f"No worker available for role {task.role_id}")
|
|
104
|
+
continue
|
|
105
|
+
try:
|
|
106
|
+
result = worker.execute(task)
|
|
107
|
+
results.append(result)
|
|
108
|
+
except Exception as e:
|
|
109
|
+
errors.append(f"Worker {worker.worker_id} error: {e}")
|
|
110
|
+
return results, errors
|
|
111
|
+
|
|
112
|
+
def _execute_serial(self, batch: TaskBatch,
|
|
113
|
+
workers: dict) -> tuple:
|
|
114
|
+
results = []
|
|
115
|
+
errors = []
|
|
116
|
+
for task in batch.tasks:
|
|
117
|
+
worker = self._find_worker(workers, task.role_id)
|
|
118
|
+
if not worker:
|
|
119
|
+
errors.append(f"No worker available for role {task.role_id}")
|
|
120
|
+
continue
|
|
121
|
+
for attempt in range(self._default_retries + 1):
|
|
122
|
+
try:
|
|
123
|
+
result = worker.execute(task)
|
|
124
|
+
results.append(result)
|
|
125
|
+
break
|
|
126
|
+
except Exception as e:
|
|
127
|
+
if attempt == self._default_retries:
|
|
128
|
+
errors.append(f"Task {task.task_id} failed after retries: {e}")
|
|
129
|
+
return results, errors
|
|
130
|
+
|
|
131
|
+
def is_concurrency_safe(self, task: TaskDefinition) -> bool:
|
|
132
|
+
"""
|
|
133
|
+
判断任务是否可安全并发执行
|
|
134
|
+
|
|
135
|
+
当前实现: 只读任务(is_read_only=True) 可并发。
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
task: 任务定义
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
bool: 是否允许并发
|
|
142
|
+
"""
|
|
143
|
+
return task.is_read_only
|
|
144
|
+
|
|
145
|
+
def _find_worker(self, workers: dict, role_id: str) -> Optional[Worker]:
|
|
146
|
+
for w in workers.values():
|
|
147
|
+
if w.role_id == role_id:
|
|
148
|
+
return w
|
|
149
|
+
return None
|