agentforge-multi 0.1.3 → 0.1.4
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.
- package/agentforge +32 -32
- package/package.json +1 -1
package/agentforge
CHANGED
|
@@ -76,11 +76,16 @@ WORKER_TOOLS = [
|
|
|
76
76
|
{
|
|
77
77
|
"type": "function",
|
|
78
78
|
"name": "shell",
|
|
79
|
-
"description":
|
|
79
|
+
"description": (
|
|
80
|
+
"Execute a shell command. Default timeout is 120s. "
|
|
81
|
+
"For long-running training/monitoring commands, set timeout_seconds (max 7200). "
|
|
82
|
+
"For fire-and-forget background jobs use '... &' and a short follow-up tail command."
|
|
83
|
+
),
|
|
80
84
|
"parameters": {
|
|
81
85
|
"type": "object",
|
|
82
86
|
"properties": {
|
|
83
87
|
"command": {"type": "string", "description": "Shell command to run"},
|
|
88
|
+
"timeout_seconds": {"type": "integer", "description": "Max seconds to wait (default 120, max 7200)"},
|
|
84
89
|
},
|
|
85
90
|
"required": ["command"],
|
|
86
91
|
},
|
|
@@ -129,14 +134,12 @@ WORKER_TOOLS = [
|
|
|
129
134
|
]
|
|
130
135
|
|
|
131
136
|
|
|
132
|
-
def
|
|
137
|
+
def _iter_events(payload: dict):
|
|
133
138
|
"""
|
|
134
139
|
ChatGPT backend-api/codex/responses 스트리밍 호출.
|
|
135
|
-
SSE 이벤트를
|
|
136
|
-
_interrupt_event가 set되면 조기 종료.
|
|
140
|
+
SSE 이벤트를 실시간으로 yield. _interrupt_event가 set되면 조기 종료.
|
|
137
141
|
"""
|
|
138
142
|
headers = _get_auth_headers()
|
|
139
|
-
events = []
|
|
140
143
|
try:
|
|
141
144
|
r = _requests.post(
|
|
142
145
|
CHATGPT_RESPONSES_URL, headers=headers,
|
|
@@ -151,23 +154,22 @@ def _stream_response(payload: dict) -> list:
|
|
|
151
154
|
decoded = line.decode("utf-8", errors="replace")
|
|
152
155
|
if decoded.startswith("data: "):
|
|
153
156
|
try:
|
|
154
|
-
|
|
155
|
-
events.append(ev)
|
|
157
|
+
yield json.loads(decoded[6:])
|
|
156
158
|
except Exception:
|
|
157
159
|
pass
|
|
158
160
|
except Exception as e:
|
|
159
|
-
|
|
160
|
-
return events
|
|
161
|
+
yield {"type": "_error", "message": str(e)}
|
|
161
162
|
|
|
162
163
|
|
|
163
164
|
def _execute_tool(name: str, args: dict, workdir: str) -> str:
|
|
164
165
|
"""Worker 도구 실행."""
|
|
165
166
|
try:
|
|
166
167
|
if name == "shell":
|
|
168
|
+
timeout = min(int(args.get("timeout_seconds", 120)), 7200)
|
|
167
169
|
result = subprocess.run(
|
|
168
170
|
args["command"], shell=True,
|
|
169
171
|
capture_output=True, text=True, errors='replace',
|
|
170
|
-
cwd=workdir, timeout=
|
|
172
|
+
cwd=workdir, timeout=timeout,
|
|
171
173
|
)
|
|
172
174
|
out = (result.stdout + result.stderr).strip()
|
|
173
175
|
return (out[:6000] if out else "(no output)") + (
|
|
@@ -465,28 +467,25 @@ def run_worker(prompt: str, workdir: str, model: str | None,
|
|
|
465
467
|
"stream": True,
|
|
466
468
|
}
|
|
467
469
|
|
|
468
|
-
|
|
470
|
+
# 실시간 스트리밍 처리
|
|
471
|
+
text_parts: list[str] = []
|
|
472
|
+
fc_items: list[dict] = [] # {call_id, name, arguments}
|
|
473
|
+
got_error = False
|
|
469
474
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
if
|
|
475
|
+
for ev in _iter_events(payload):
|
|
476
|
+
evtype = ev["type"]
|
|
477
|
+
if evtype == "_error":
|
|
473
478
|
buf.append(f"[red]API 오류: {ev['message']}[/red]")
|
|
474
479
|
status_ref[0] = "error"
|
|
475
480
|
if line_buf.strip():
|
|
476
481
|
buf.append(colorize_line(line_buf))
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
text_parts: list[str] = []
|
|
481
|
-
fc_items: list[dict] = [] # {call_id, name, arguments}
|
|
482
|
-
|
|
483
|
-
for ev in events:
|
|
484
|
-
t = ev["type"]
|
|
485
|
-
if t == "response.output_text.delta":
|
|
482
|
+
got_error = True
|
|
483
|
+
break
|
|
484
|
+
elif evtype == "response.output_text.delta":
|
|
486
485
|
delta = ev.get("delta", "")
|
|
487
486
|
text_parts.append(delta)
|
|
488
487
|
_flush_line(delta)
|
|
489
|
-
elif
|
|
488
|
+
elif evtype == "response.output_item.done" and ev.get("item", {}).get("type") == "function_call":
|
|
490
489
|
item = ev["item"]
|
|
491
490
|
fc_items.append({
|
|
492
491
|
"call_id": item["call_id"],
|
|
@@ -494,6 +493,9 @@ def run_worker(prompt: str, workdir: str, model: str | None,
|
|
|
494
493
|
"arguments": item["arguments"],
|
|
495
494
|
})
|
|
496
495
|
|
|
496
|
+
if got_error:
|
|
497
|
+
return "\n".join(all_text_parts), 1
|
|
498
|
+
|
|
497
499
|
round_text = "".join(text_parts)
|
|
498
500
|
if round_text:
|
|
499
501
|
all_text_parts.append(round_text)
|
|
@@ -542,15 +544,13 @@ def run_evaluator(prompt: str, workdir: str, model: str | None) -> tuple[str, in
|
|
|
542
544
|
"store": False,
|
|
543
545
|
"stream": True,
|
|
544
546
|
}
|
|
545
|
-
|
|
546
|
-
for ev in
|
|
547
|
+
parts: list[str] = []
|
|
548
|
+
for ev in _iter_events(payload):
|
|
547
549
|
if ev["type"] == "_error":
|
|
548
550
|
return f"[Evaluator error] {ev['message']}", 1
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
)
|
|
553
|
-
return text, 0
|
|
551
|
+
if ev["type"] == "response.output_text.delta":
|
|
552
|
+
parts.append(ev.get("delta", ""))
|
|
553
|
+
return "".join(parts), 0
|
|
554
554
|
|
|
555
555
|
|
|
556
556
|
def parse_decision(text: str) -> tuple[str, str]:
|
|
@@ -746,7 +746,7 @@ def run_agent_loop(goal: str, workdir: str, worker_model: str | None,
|
|
|
746
746
|
break # worker 스레드는 _interrupt_event 확인 후 자체 중단
|
|
747
747
|
refresh("worker", iteration)
|
|
748
748
|
time.sleep(0.1)
|
|
749
|
-
t.join()
|
|
749
|
+
t.join(timeout=10) # 최대 10초 대기 후 포기
|
|
750
750
|
refresh("worker", iteration)
|
|
751
751
|
|
|
752
752
|
# ESC로 중단된 경우
|