vox-code 2.0.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.
- vox_code-2.0.0.dist-info/METADATA +258 -0
- vox_code-2.0.0.dist-info/RECORD +88 -0
- vox_code-2.0.0.dist-info/WHEEL +4 -0
- vox_code-2.0.0.dist-info/entry_points.txt +3 -0
- voxcli/__init__.py +3 -0
- voxcli/__main__.py +5 -0
- voxcli/agent/__init__.py +12 -0
- voxcli/agent/agent.py +449 -0
- voxcli/agent/agent_budget.py +133 -0
- voxcli/agent/agent_orchestrator.py +414 -0
- voxcli/agent/plan_execute_agent.py +514 -0
- voxcli/agent/roles.py +80 -0
- voxcli/agent/sub_agent.py +351 -0
- voxcli/catalog.py +477 -0
- voxcli/chat.py +91 -0
- voxcli/cli/__init__.py +4 -0
- voxcli/cli/main.py +452 -0
- voxcli/cli/parser.py +71 -0
- voxcli/config.py +518 -0
- voxcli/gui/__main__.py +3 -0
- voxcli/gui/main.py +22 -0
- voxcli/gui/pet/__init__.py +5 -0
- voxcli/gui/pet/base.py +62 -0
- voxcli/gui/pet/coordinator.py +888 -0
- voxcli/gui/pet/data.py +430 -0
- voxcli/gui/pet/widgets.py +683 -0
- voxcli/gui/pet/windows.py +2298 -0
- voxcli/gui/pet/workers.py +54 -0
- voxcli/gui/pet_app.py +7 -0
- voxcli/hitl/__init__.py +11 -0
- voxcli/hitl/handler.py +11 -0
- voxcli/hitl/policy.py +32 -0
- voxcli/hitl/request.py +13 -0
- voxcli/hitl/result.py +11 -0
- voxcli/hitl/terminal_handler.py +64 -0
- voxcli/hitl/tool_registry.py +64 -0
- voxcli/llm/base.py +93 -0
- voxcli/llm/factory.py +178 -0
- voxcli/llm/ollama_client.py +137 -0
- voxcli/llm/openai_compatible.py +249 -0
- voxcli/memory/base.py +16 -0
- voxcli/memory/budget.py +53 -0
- voxcli/memory/compressor.py +198 -0
- voxcli/memory/entry.py +36 -0
- voxcli/memory/long_term.py +126 -0
- voxcli/memory/manager.py +101 -0
- voxcli/memory/retriever.py +72 -0
- voxcli/memory/short_term.py +84 -0
- voxcli/memory/tokenizer.py +21 -0
- voxcli/plan/__init__.py +5 -0
- voxcli/plan/execution_plan.py +225 -0
- voxcli/plan/planner.py +198 -0
- voxcli/plan/task.py +123 -0
- voxcli/policy/audit_log.py +111 -0
- voxcli/policy/command_guard.py +34 -0
- voxcli/policy/exception.py +5 -0
- voxcli/policy/path_guard.py +32 -0
- voxcli/prompting/__init__.py +7 -0
- voxcli/prompting/presenter.py +154 -0
- voxcli/rag/__init__.py +16 -0
- voxcli/rag/analyzer.py +89 -0
- voxcli/rag/chunk.py +17 -0
- voxcli/rag/chunker.py +137 -0
- voxcli/rag/embedding.py +75 -0
- voxcli/rag/formatter.py +40 -0
- voxcli/rag/index.py +96 -0
- voxcli/rag/relation.py +14 -0
- voxcli/rag/retriever.py +58 -0
- voxcli/rag/store.py +155 -0
- voxcli/rag/tokenizer.py +26 -0
- voxcli/runtime/__init__.py +6 -0
- voxcli/runtime/session_controller.py +386 -0
- voxcli/tool/__init__.py +3 -0
- voxcli/tool/tool_registry.py +433 -0
- voxcli/util/animation.py +219 -0
- voxcli/util/ansi.py +82 -0
- voxcli/util/markdown.py +98 -0
- voxcli/web/__init__.py +17 -0
- voxcli/web/base.py +20 -0
- voxcli/web/extractor.py +77 -0
- voxcli/web/factory.py +38 -0
- voxcli/web/fetch_result.py +27 -0
- voxcli/web/fetcher.py +42 -0
- voxcli/web/network_policy.py +49 -0
- voxcli/web/result.py +23 -0
- voxcli/web/searxng.py +55 -0
- voxcli/web/serpapi.py +53 -0
- voxcli/web/zhipu.py +55 -0
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
"""Agent 编排器 - Multi-Agent 系统的主控"""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
import logging
|
|
6
|
+
from concurrent.futures import ThreadPoolExecutor, Future
|
|
7
|
+
from enum import Enum
|
|
8
|
+
from io import StringIO
|
|
9
|
+
from typing import List, Optional, Dict, Set
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
|
|
12
|
+
from ..llm.base import LlmClient, Message
|
|
13
|
+
from ..memory.manager import MemoryManager
|
|
14
|
+
from ..tool import ToolRegistry
|
|
15
|
+
from ..util.ansi import heading, section, subtle
|
|
16
|
+
from .roles import AgentRole, AgentMessage, AgentMessageType
|
|
17
|
+
from .sub_agent import SubAgent
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
_MAX_RETRIES_PER_STEP = 2
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class StepStatus(Enum):
|
|
25
|
+
PENDING = "PENDING"
|
|
26
|
+
RUNNING = "RUNNING"
|
|
27
|
+
COMPLETED = "COMPLETED"
|
|
28
|
+
FAILED = "FAILED"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class ExecutionStep:
|
|
33
|
+
id: str
|
|
34
|
+
description: str
|
|
35
|
+
type: str
|
|
36
|
+
dependencies: List[str] = field(default_factory=list)
|
|
37
|
+
result: Optional[str] = None
|
|
38
|
+
status: StepStatus = StepStatus.PENDING
|
|
39
|
+
|
|
40
|
+
@staticmethod
|
|
41
|
+
def pending(id: str, description: str, type: str,
|
|
42
|
+
dependencies: List[str]) -> "ExecutionStep":
|
|
43
|
+
return ExecutionStep(id=id, description=description, type=type,
|
|
44
|
+
dependencies=dependencies)
|
|
45
|
+
|
|
46
|
+
def with_result(self, result: str) -> "ExecutionStep":
|
|
47
|
+
return ExecutionStep(self.id, self.description, self.type,
|
|
48
|
+
self.dependencies, result, StepStatus.COMPLETED)
|
|
49
|
+
|
|
50
|
+
def with_failed(self, error: str) -> "ExecutionStep":
|
|
51
|
+
return ExecutionStep(self.id, self.description, self.type,
|
|
52
|
+
self.dependencies, error, StepStatus.FAILED)
|
|
53
|
+
|
|
54
|
+
def started(self) -> "ExecutionStep":
|
|
55
|
+
return ExecutionStep(self.id, self.description, self.type,
|
|
56
|
+
self.dependencies, self.result, StepStatus.RUNNING)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class AgentOrchestrator:
|
|
60
|
+
def __init__(self, llm_client: LlmClient,
|
|
61
|
+
tool_registry: Optional[ToolRegistry] = None,
|
|
62
|
+
memory_manager: Optional[MemoryManager] = None):
|
|
63
|
+
self._llm = llm_client
|
|
64
|
+
self._tool_registry = tool_registry or ToolRegistry()
|
|
65
|
+
self._planner = SubAgent("planner", AgentRole.PLANNER, llm_client, self._tool_registry)
|
|
66
|
+
self._workers = [
|
|
67
|
+
SubAgent("worker-1", AgentRole.WORKER, llm_client, self._tool_registry),
|
|
68
|
+
SubAgent("worker-2", AgentRole.WORKER, llm_client, self._tool_registry),
|
|
69
|
+
]
|
|
70
|
+
self._reviewer = SubAgent("reviewer", AgentRole.REVIEWER, llm_client, self._tool_registry)
|
|
71
|
+
self._memory_manager = memory_manager or MemoryManager(llm_client)
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def memory_manager(self) -> MemoryManager:
|
|
75
|
+
return self._memory_manager
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def tool_registry(self) -> ToolRegistry:
|
|
79
|
+
return self._tool_registry
|
|
80
|
+
|
|
81
|
+
# ---- Public API ----
|
|
82
|
+
|
|
83
|
+
def run(self, user_input: str) -> str:
|
|
84
|
+
logger.info("Multi-Agent run started: inputLength=%d", len(user_input) if user_input else 0)
|
|
85
|
+
self._memory_manager.add_user_message(user_input)
|
|
86
|
+
|
|
87
|
+
# 1. Planning phase
|
|
88
|
+
print(heading("📋 第一阶段:规划"))
|
|
89
|
+
print("🧑💼 规划者正在分析任务...\n")
|
|
90
|
+
|
|
91
|
+
plan_msg = AgentMessage.task("orchestrator",
|
|
92
|
+
f"请为以下任务制定执行计划:\n{user_input}")
|
|
93
|
+
plan_result = self._planner.execute(plan_msg)
|
|
94
|
+
self._planner.clear_history()
|
|
95
|
+
|
|
96
|
+
if plan_result.type == AgentMessageType.ERROR:
|
|
97
|
+
return f"❌ 规划阶段失败,规划者 LLM 调用出错:{plan_result.content}"
|
|
98
|
+
if not plan_result.content or not plan_result.content.strip():
|
|
99
|
+
return "❌ 规划失败:规划者未能生成有效计划"
|
|
100
|
+
|
|
101
|
+
# 2. Parse plan
|
|
102
|
+
steps = self._parse_plan(plan_result.content)
|
|
103
|
+
if not steps:
|
|
104
|
+
return f"❌ 规划失败:无法解析执行计划\n原始输出:\n{plan_result.content}"
|
|
105
|
+
|
|
106
|
+
print(heading("📋 执行计划"))
|
|
107
|
+
print(self._summarize_steps(steps) + "\n")
|
|
108
|
+
|
|
109
|
+
# 3. Execution phase
|
|
110
|
+
print(heading("⚡ 第二阶段:执行"))
|
|
111
|
+
retry_count: Dict[str, int] = {}
|
|
112
|
+
single_step_cursor = 0
|
|
113
|
+
batch_index = 0
|
|
114
|
+
|
|
115
|
+
while True:
|
|
116
|
+
executable = self._get_executable_steps(steps)
|
|
117
|
+
if not executable:
|
|
118
|
+
break
|
|
119
|
+
batch_index += 1
|
|
120
|
+
|
|
121
|
+
if len(executable) == 1:
|
|
122
|
+
step = executable[0]
|
|
123
|
+
worker = self._workers[single_step_cursor % len(self._workers)]
|
|
124
|
+
single_step_cursor += 1
|
|
125
|
+
context = self._build_step_context(steps, step)
|
|
126
|
+
self._run_step(step, steps, retry_count, worker, self._reviewer, context)
|
|
127
|
+
worker.clear_history()
|
|
128
|
+
else:
|
|
129
|
+
print(f"⚡ 批次 #{batch_index}:{len(executable)} 个独立步骤并行执行"
|
|
130
|
+
f"(最多 {len(self._workers)} 个并发 Worker)\n")
|
|
131
|
+
self._run_batch_parallel(executable, steps, retry_count)
|
|
132
|
+
|
|
133
|
+
# 4. Report skipped steps
|
|
134
|
+
for step in steps:
|
|
135
|
+
if step.status == StepStatus.PENDING:
|
|
136
|
+
print(f"⏭️ 步骤 [{step.id}] 因前置步骤失败被跳过: {step.description}")
|
|
137
|
+
|
|
138
|
+
# 5. Build result
|
|
139
|
+
final = self._build_final_result(steps)
|
|
140
|
+
self._memory_manager.add_assistant_message(f"[多Agent结果] {final}")
|
|
141
|
+
return final
|
|
142
|
+
|
|
143
|
+
# ---- Internal ----
|
|
144
|
+
|
|
145
|
+
def _parse_plan(self, plan_json: str) -> List[ExecutionStep]:
|
|
146
|
+
try:
|
|
147
|
+
cleaned = plan_json.replace("```json", "").replace("```", "").strip()
|
|
148
|
+
root = json.loads(cleaned)
|
|
149
|
+
steps_node = root.get("steps") or root.get("tasks")
|
|
150
|
+
if not steps_node or not isinstance(steps_node, list):
|
|
151
|
+
logger.warning("Plan JSON has no 'steps' or 'tasks' array")
|
|
152
|
+
return []
|
|
153
|
+
|
|
154
|
+
id_mapping: Dict[str, str] = {}
|
|
155
|
+
steps: List[ExecutionStep] = []
|
|
156
|
+
|
|
157
|
+
for i, node in enumerate(steps_node, 1):
|
|
158
|
+
orig = node.get("id", f"step_{i}")
|
|
159
|
+
new_id = f"step_{i}"
|
|
160
|
+
id_mapping[orig] = new_id
|
|
161
|
+
|
|
162
|
+
for i, node in enumerate(steps_node, 1):
|
|
163
|
+
new_id = f"step_{i}"
|
|
164
|
+
desc = node.get("description", "")
|
|
165
|
+
type_ = node.get("type", "COMMAND")
|
|
166
|
+
deps = [id_mapping.get(d, d) for d in node.get("dependencies", [])]
|
|
167
|
+
steps.append(ExecutionStep.pending(new_id, desc, type_, deps))
|
|
168
|
+
|
|
169
|
+
return steps
|
|
170
|
+
except Exception as e:
|
|
171
|
+
logger.error("Failed to parse plan JSON", exc_info=True)
|
|
172
|
+
return []
|
|
173
|
+
|
|
174
|
+
def _get_executable_steps(self, steps: List[ExecutionStep]) -> List[ExecutionStep]:
|
|
175
|
+
status_map = {s.id: s.status for s in steps}
|
|
176
|
+
return [s for s in steps
|
|
177
|
+
if s.status == StepStatus.PENDING
|
|
178
|
+
and all(status_map.get(d) == StepStatus.COMPLETED for d in s.dependencies)]
|
|
179
|
+
|
|
180
|
+
def _parse_review_approval(self, review_content: Optional[str]) -> bool:
|
|
181
|
+
if not review_content:
|
|
182
|
+
return False
|
|
183
|
+
try:
|
|
184
|
+
cleaned = review_content.replace("```json", "").replace("```", "").strip()
|
|
185
|
+
root = json.loads(cleaned)
|
|
186
|
+
approved = root.get("approved")
|
|
187
|
+
if approved is None:
|
|
188
|
+
return False
|
|
189
|
+
return bool(approved)
|
|
190
|
+
except json.JSONDecodeError:
|
|
191
|
+
lower = review_content.lower()
|
|
192
|
+
has_negative = any(kw in lower for kw in
|
|
193
|
+
["未通过", "不通过", "不合格", "有问题",
|
|
194
|
+
'"approved": false', '"approved":false'])
|
|
195
|
+
has_positive = any(kw in lower for kw in
|
|
196
|
+
["通过", "合格", '"approved": true', '"approved":true'])
|
|
197
|
+
if has_negative:
|
|
198
|
+
return False
|
|
199
|
+
if not has_positive:
|
|
200
|
+
return False
|
|
201
|
+
return True
|
|
202
|
+
|
|
203
|
+
def _parse_review_issues(self, review_content: Optional[str]) -> str:
|
|
204
|
+
if not review_content:
|
|
205
|
+
return "审查未通过,请改进执行结果"
|
|
206
|
+
try:
|
|
207
|
+
cleaned = review_content.replace("```json", "").replace("```", "").strip()
|
|
208
|
+
root = json.loads(cleaned)
|
|
209
|
+
issues = root.get("issues", [])
|
|
210
|
+
if issues:
|
|
211
|
+
return "\n".join(f"- {i}" for i in issues)
|
|
212
|
+
suggestions = root.get("suggestions", [])
|
|
213
|
+
if suggestions:
|
|
214
|
+
return "\n".join(f"- {s}" for s in suggestions)
|
|
215
|
+
summary = root.get("summary", "")
|
|
216
|
+
if summary:
|
|
217
|
+
return summary
|
|
218
|
+
except (json.JSONDecodeError, Exception):
|
|
219
|
+
pass
|
|
220
|
+
return "审查未通过,请改进执行结果"
|
|
221
|
+
|
|
222
|
+
def _run_step(self, step: ExecutionStep, steps: List[ExecutionStep],
|
|
223
|
+
retry_count: Dict[str, int], worker: SubAgent,
|
|
224
|
+
reviewer: SubAgent, context: str):
|
|
225
|
+
print(f"🛠️ {worker.name} 执行步骤 [{step.id}]: {step.description}")
|
|
226
|
+
|
|
227
|
+
task_msg = AgentMessage.task("orchestrator", step.description)
|
|
228
|
+
result = worker.execute_with_context(task_msg, context)
|
|
229
|
+
|
|
230
|
+
if result.type == AgentMessageType.ERROR:
|
|
231
|
+
self._update_step(steps, step.id, step.with_failed(result.content))
|
|
232
|
+
print(f"❌ 步骤 [{step.id}] 执行失败:{result.content}\n")
|
|
233
|
+
return
|
|
234
|
+
|
|
235
|
+
if not result.content or not result.content.strip():
|
|
236
|
+
self._update_step(steps, step.id, step.with_failed("执行结果为空"))
|
|
237
|
+
print(f"❌ 步骤 [{step.id}] 执行失败:结果为空\n")
|
|
238
|
+
return
|
|
239
|
+
|
|
240
|
+
print(f"🔍 {reviewer.name} 正在审查步骤 [{step.id}] 的结果...")
|
|
241
|
+
review_result = reviewer.review(step.description, result.content)
|
|
242
|
+
reviewer.clear_history()
|
|
243
|
+
|
|
244
|
+
if review_result.type == AgentMessageType.ERROR:
|
|
245
|
+
logger.warning("Reviewer failed for step %s: %s", step.id, review_result.content)
|
|
246
|
+
self._update_step(steps, step.id, step.with_result(result.content))
|
|
247
|
+
return
|
|
248
|
+
|
|
249
|
+
approved = self._parse_review_approval(review_result.content)
|
|
250
|
+
accepted = result.content
|
|
251
|
+
|
|
252
|
+
if approved:
|
|
253
|
+
self._update_step(steps, step.id, step.with_result(accepted))
|
|
254
|
+
print(f"✅ 步骤 [{step.id}] 审查通过\n")
|
|
255
|
+
return
|
|
256
|
+
|
|
257
|
+
retries = retry_count.get(step.id, 0)
|
|
258
|
+
issues = self._parse_review_issues(review_result.content)
|
|
259
|
+
logger.info("Step %s rejected (retry %d/%d): %s", step.id, retries, _MAX_RETRIES_PER_STEP, issues)
|
|
260
|
+
|
|
261
|
+
while not approved and retries < _MAX_RETRIES_PER_STEP:
|
|
262
|
+
retries += 1
|
|
263
|
+
retry_count[step.id] = retries
|
|
264
|
+
print(f"⚠️ 步骤 [{step.id}] 审查未通过,正在重新执行...")
|
|
265
|
+
print(f" 反馈: {issues}\n")
|
|
266
|
+
|
|
267
|
+
feedback_context = f"{context}\n\n之前的执行结果被审查拒绝,原因:\n{issues}"
|
|
268
|
+
retry_result = worker.execute_with_context(task_msg, feedback_context)
|
|
269
|
+
|
|
270
|
+
if retry_result.type == AgentMessageType.ERROR:
|
|
271
|
+
issues = f"重试时 LLM 调用失败:{retry_result.content}"
|
|
272
|
+
approved = False
|
|
273
|
+
continue
|
|
274
|
+
if not retry_result.content or not retry_result.content.strip():
|
|
275
|
+
accepted = "执行结果为空"
|
|
276
|
+
approved = False
|
|
277
|
+
issues = "执行结果为空"
|
|
278
|
+
continue
|
|
279
|
+
|
|
280
|
+
accepted = retry_result.content
|
|
281
|
+
retry_review = reviewer.review(step.description, accepted)
|
|
282
|
+
reviewer.clear_history()
|
|
283
|
+
|
|
284
|
+
if retry_review.type == AgentMessageType.ERROR:
|
|
285
|
+
approved = True
|
|
286
|
+
issues = ""
|
|
287
|
+
break
|
|
288
|
+
|
|
289
|
+
approved = self._parse_review_approval(retry_review.content)
|
|
290
|
+
issues = self._parse_review_issues(retry_review.content)
|
|
291
|
+
|
|
292
|
+
self._update_step(steps, step.id, step.with_result(accepted))
|
|
293
|
+
status = "✅" if approved else "⚠️"
|
|
294
|
+
msg = "重试后审查通过" if approved else "超过最大重试次数,保留当前结果"
|
|
295
|
+
print(f"{status} 步骤 [{step.id}] {msg}\n")
|
|
296
|
+
|
|
297
|
+
def _run_batch_parallel(self, batch: List[ExecutionStep], steps: List[ExecutionStep],
|
|
298
|
+
retry_count: Dict[str, int]):
|
|
299
|
+
parallelism = min(len(batch), len(self._workers))
|
|
300
|
+
results: List[Optional[ExecutionStep]] = [None] * len(batch)
|
|
301
|
+
|
|
302
|
+
with ThreadPoolExecutor(max_workers=parallelism) as executor:
|
|
303
|
+
future_map: Dict[Future, int] = {}
|
|
304
|
+
for i, step in enumerate(batch):
|
|
305
|
+
future = executor.submit(self._run_step_isolated, step, steps, retry_count)
|
|
306
|
+
future_map[future] = i
|
|
307
|
+
|
|
308
|
+
for future in future_map:
|
|
309
|
+
idx = future_map[future]
|
|
310
|
+
try:
|
|
311
|
+
results[idx] = future.result()
|
|
312
|
+
except Exception as e:
|
|
313
|
+
step = batch[idx]
|
|
314
|
+
logger.error("Parallel step %s failed", step.id, exc_info=True)
|
|
315
|
+
results[idx] = step.with_failed(str(e))
|
|
316
|
+
|
|
317
|
+
for i, step in enumerate(batch):
|
|
318
|
+
if results[i] is not None:
|
|
319
|
+
self._update_step(steps, step.id, results[i])
|
|
320
|
+
|
|
321
|
+
def _run_step_isolated(self, step: ExecutionStep, steps: List[ExecutionStep],
|
|
322
|
+
retry_count: Dict[str, int]) -> Optional[ExecutionStep]:
|
|
323
|
+
worker_pool = list(self._workers)
|
|
324
|
+
worker = worker_pool[0]
|
|
325
|
+
local_reviewer = SubAgent(f"reviewer-{step.id}", AgentRole.REVIEWER,
|
|
326
|
+
self._llm, self._tool_registry)
|
|
327
|
+
context = self._build_step_context(steps, step)
|
|
328
|
+
|
|
329
|
+
buf = StringIO()
|
|
330
|
+
# We use print capture but here we simulate the same flow
|
|
331
|
+
task_msg = AgentMessage.task("orchestrator", step.description)
|
|
332
|
+
result = worker.execute_with_context(task_msg, context)
|
|
333
|
+
|
|
334
|
+
if result.type == AgentMessageType.ERROR:
|
|
335
|
+
return step.with_failed(result.content)
|
|
336
|
+
if not result.content or not result.content.strip():
|
|
337
|
+
return step.with_failed("执行结果为空")
|
|
338
|
+
|
|
339
|
+
review_result = local_reviewer.review(step.description, result.content)
|
|
340
|
+
approved = self._parse_review_approval(review_result.content)
|
|
341
|
+
|
|
342
|
+
if approved:
|
|
343
|
+
return step.with_result(result.content)
|
|
344
|
+
|
|
345
|
+
# Retries
|
|
346
|
+
retries = 0
|
|
347
|
+
issues = self._parse_review_issues(review_result.content)
|
|
348
|
+
accepted = result.content
|
|
349
|
+
|
|
350
|
+
while not approved and retries < _MAX_RETRIES_PER_STEP:
|
|
351
|
+
retries += 1
|
|
352
|
+
feedback_context = f"{context}\n\n之前的执行结果被审查拒绝,原因:\n{issues}"
|
|
353
|
+
retry_result = worker.execute_with_context(task_msg, feedback_context)
|
|
354
|
+
if retry_result.type == AgentMessageType.ERROR:
|
|
355
|
+
break
|
|
356
|
+
if not retry_result.content or not retry_result.content.strip():
|
|
357
|
+
break
|
|
358
|
+
accepted = retry_result.content
|
|
359
|
+
retry_review = local_reviewer.review(step.description, accepted)
|
|
360
|
+
approved = self._parse_review_approval(retry_review.content)
|
|
361
|
+
issues = self._parse_review_issues(retry_review.content)
|
|
362
|
+
|
|
363
|
+
return step.with_result(accepted)
|
|
364
|
+
|
|
365
|
+
def _build_step_context(self, steps: List[ExecutionStep], current: ExecutionStep) -> str:
|
|
366
|
+
parts = ["总任务上下文:"]
|
|
367
|
+
for step in steps:
|
|
368
|
+
if step.status == StepStatus.COMPLETED and current.id in step.dependencies:
|
|
369
|
+
parts.append(f"已完成的依赖步骤 [{step.id}]: {step.description}")
|
|
370
|
+
if step.result:
|
|
371
|
+
preview = step.result[:500] if len(step.result) > 500 else step.result
|
|
372
|
+
parts.append(f"结果:{preview}")
|
|
373
|
+
parts.append("")
|
|
374
|
+
return "\n".join(parts)
|
|
375
|
+
|
|
376
|
+
@staticmethod
|
|
377
|
+
def _summarize_steps(steps: List[ExecutionStep]) -> str:
|
|
378
|
+
lines = []
|
|
379
|
+
for s in steps:
|
|
380
|
+
deps = ", ".join(s.dependencies) if s.dependencies else "无"
|
|
381
|
+
icon = "✅" if s.status == StepStatus.COMPLETED else "⏳"
|
|
382
|
+
lines.append(f" {icon} {s.id} [{s.type}] {s.description} (依赖: {deps})")
|
|
383
|
+
return "\n".join(lines)
|
|
384
|
+
|
|
385
|
+
@staticmethod
|
|
386
|
+
def _update_step(steps: List[ExecutionStep], step_id: str, updated: ExecutionStep):
|
|
387
|
+
for i, s in enumerate(steps):
|
|
388
|
+
if s.id == step_id:
|
|
389
|
+
steps[i] = updated
|
|
390
|
+
return
|
|
391
|
+
|
|
392
|
+
@staticmethod
|
|
393
|
+
def _build_final_result(steps: List[ExecutionStep]) -> str:
|
|
394
|
+
all_done = all(s.status == StepStatus.COMPLETED for s in steps)
|
|
395
|
+
has_failure = any(s.status == StepStatus.FAILED for s in steps)
|
|
396
|
+
|
|
397
|
+
lines = []
|
|
398
|
+
if all_done:
|
|
399
|
+
lines.append("✅ 多 Agent 协作任务完成!")
|
|
400
|
+
elif has_failure:
|
|
401
|
+
lines.append("⚠️ 多 Agent 协作任务未完全完成,存在失败步骤。")
|
|
402
|
+
else:
|
|
403
|
+
lines.append("⚠️ 多 Agent 协作任务部分完成,仍有未执行步骤。")
|
|
404
|
+
|
|
405
|
+
lines.append("\n📋 执行总结:")
|
|
406
|
+
for s in steps:
|
|
407
|
+
icon = {"COMPLETED": "✅", "FAILED": "❌", "RUNNING": "▶️", "PENDING": "⏳"}.get(
|
|
408
|
+
s.status.value, "⏳")
|
|
409
|
+
lines.append(f"[{s.id}] {icon} {s.description}")
|
|
410
|
+
if s.result:
|
|
411
|
+
preview = s.result[:120] if len(s.result) > 120 else s.result
|
|
412
|
+
lines.append(f" 结果:{preview}")
|
|
413
|
+
|
|
414
|
+
return "\n".join(lines)
|