Add TensorX models for Versuch 2

This commit is contained in:
Christoph Schwörer
2026-08-31 16:51:35 +02:00
parent 8a22d586f1
commit ca52aa4701
4 changed files with 751 additions and 176 deletions
+298 -140
View File
@@ -3,7 +3,7 @@
TensorX API Adapter fuer den run-experiment Skill.
Dieser Adapter fuehrt einen Headless-Versuchslauf mit einem OpenAI-kompatiblen
Modell (Z.AI GLM oder Moonshot Kimi) ueber den TensorX API-Gateway durch.
Modell (Z.AI GLM, Qwen oder Moonshot Kimi) ueber den TensorX API-Gateway durch.
Er implementiert einen minimalen Agent-Loop mit Tool-Calling und erfasst
Token-Metadaten (inkl. Reasoning-Tokens) aus jeder API-Antwort.
@@ -15,11 +15,13 @@ Verwendung:
--prompt <Pfad zur combined_prompt.md> \
--root <Root-Verzeichnis der Codebasis> \
--output <Laufverzeichnis/Ergebnisse> \
--model <Modell-ID, z.B. z-ai/glm-5.2 oder moonshotai/kimi-k3> \
--model <Modell-ID, z.B. z-ai/glm-5.3-flash oder qwen/qwen3.8-flash-next> \
--effort <low|medium|high|xhigh> \
[--max-turns 50] \
[--max-turns 0] \
[--subagent-max-turns 0] \
[--temperature 1.0] \
[--timeout 0]
[--timeout 0] \
[--heartbeat-interval 60]
Ausgaben:
<Laufverzeichnis>/RawResult.json – normalisierte Messdaten
@@ -35,8 +37,10 @@ import os
import re
import subprocess
import sys
import threading
import time
import traceback
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime, timezone
from pathlib import Path
@@ -60,9 +64,11 @@ PROVIDERS = {
# Modell-Praefix -> Effort-Parameter-Typ
# z-ai/* Modelle nutzen den 'thinking'-Parameter (level: none|low|medium|high|xhigh)
# qwen/* Modelle nutzen ebenfalls den 'thinking'-Parameter
# moonshotai/* Modelle nutzen 'reasoning_effort' (low|medium|high)
MODEL_EFFORT_TYPE = {
"z-ai": "thinking",
"qwen": "thinking",
"moonshotai": "reasoning_effort",
}
@@ -75,6 +81,76 @@ EFFORT_MAP = {
"max": {"thinking": "xhigh", "reasoning_effort": "high"},
}
ADAPTER_VERSION = "2.1.0"
_STDERR_LOCK = threading.Lock()
def log_stderr(message):
"""Schreibt eine vollständige, sofort sichtbare Zeile threadsicher nach stderr."""
with _STDERR_LOCK:
sys.stderr.write(f"[glm-kimi-adapter] {message}\n")
sys.stderr.flush()
class LivenessMonitor:
"""Gibt periodisch den lokalen Zustand von Haupt- und Subagenten aus.
Ein Lebenszeichen beweist, dass der lokale Adapterprozess lebt. Beim Warten
auf eine nicht gestreamte HTTP-Antwort ist es ausdrücklich kein Nachweis für
serverseitigen Inferenzfortschritt.
"""
def __init__(self, interval_seconds=60):
self.interval_seconds = max(0, interval_seconds)
self._activities = {}
self._lock = threading.Lock()
self._stop = threading.Event()
self._thread = None
def start(self):
if self.interval_seconds <= 0:
return
self._thread = threading.Thread(
target=self._run, name="glm-kimi-liveness", daemon=True
)
self._thread.start()
def stop(self):
self._stop.set()
if self._thread:
self._thread.join(timeout=1)
def set(self, activity_id, description):
with self._lock:
self._activities[activity_id] = {
"description": description,
"since": time.monotonic(),
}
def clear(self, activity_id):
with self._lock:
self._activities.pop(activity_id, None)
def _run(self):
while not self._stop.wait(self.interval_seconds):
now = time.monotonic()
with self._lock:
activities = [
(item["description"], int(now - item["since"]))
for item in self._activities.values()
]
if activities:
states = "; ".join(
f"{description} seit {elapsed_s}s"
for description, elapsed_s in activities
)
log_stderr(
"LIFESIGN: Prozess lebt | " + states
+ " | API-Warten belegt keinen serverseitigen Fortschritt"
)
else:
log_stderr("LIFESIGN: Prozess lebt | aktuell keine blockierende Operation")
def load_cline_api_key():
"""
@@ -449,11 +525,37 @@ def execute_tool(name: str, args: dict, root: str, output_dir: str) -> str:
# Subagent
# ---------------------------------------------------------------------------
def post_chat_completion(url, headers, body, timeout, liveness,
activity_id, activity_label):
"""Fuehrt einen nicht gestreamten API-Aufruf mit sichtbarem Lebenszeichen aus."""
request_timeout = timeout if timeout > 0 else None
liveness.set(activity_id, f"{activity_label}: wartet auf API-Antwort")
log_stderr(f"{activity_label}: API-Aufruf gestartet")
started = time.monotonic()
try:
response = requests.post(
url, headers=headers, json=body, timeout=request_timeout
)
finally:
liveness.clear(activity_id)
elapsed_s = time.monotonic() - started
log_stderr(
f"{activity_label}: API-Antwort nach {elapsed_s:.1f}s "
f"(HTTP {response.status_code})"
)
if response.status_code != 200:
raise RuntimeError(
f"API-Fehler {response.status_code}: {response.text[:2000]}"
)
return response.json()
def run_subagent(provider, model, api_key, effort, root, description,
subagent_type, temperature, timeout, max_turns=15):
subagent_type, temperature, timeout, liveness, agent_id,
max_turns=0):
"""
Startet einen Subagenten mit eigenem Kontext.
Der Subagent erhaelt Read-Only-Tools und eine begrenzte Turn-Anzahl.
Der Subagent erhaelt Read-Only-Tools. max_turns=0 bedeutet unbegrenzt.
Rueckgabe: dict mit result, usage, turns, tool_calls, status.
"""
sys_prompt = SUBAGENT_SYSTEM_PROMPTS.get(
@@ -469,14 +571,19 @@ def run_subagent(provider, model, api_key, effort, root, description,
sub_tool_calls = 0
sub_result = ""
sub_errors = []
completed_normally = False
label = f"Subagent {agent_id} ({subagent_type})"
while sub_turns < max_turns:
log_stderr(f"{label}: gestartet")
while max_turns <= 0 or sub_turns < max_turns:
sub_turns += 1
try:
resp = call_api_subagent(provider, model, messages, api_key,
effort, temperature, timeout)
effort, temperature, timeout, liveness,
agent_id, subagent_type, sub_turns)
except Exception as e:
sub_errors.append(str(e))
log_stderr(f"{label}: FEHLER in Turn {sub_turns}: {e}")
break
u = resp.get("usage", {})
sub_usage["prompt_tokens"] += u.get("prompt_tokens", 0)
@@ -484,6 +591,11 @@ def run_subagent(provider, model, api_key, effort, root, description,
sub_usage["total_tokens"] += u.get("total_tokens", 0)
sub_usage["cached_tokens"] += u.get("prompt_tokens_details", {}).get("cached_tokens", 0)
sub_usage["reasoning_tokens"] += u.get("completion_tokens_details", {}).get("reasoning_tokens", 0)
log_stderr(
f"{label}: Turn {sub_turns} API abgeschlossen "
f"(Antwort-Tokens: {u.get('total_tokens', 0):,}, "
f"kumuliert: {sub_usage['total_tokens']:,})"
)
choices = resp.get("choices", [])
if not choices:
@@ -496,6 +608,7 @@ def run_subagent(provider, model, api_key, effort, root, description,
sub_result = content
tool_calls = msg.get("tool_calls", [])
if not tool_calls:
completed_normally = True
break
for tc in tool_calls:
func = tc.get("function", {})
@@ -505,25 +618,38 @@ def run_subagent(provider, model, api_key, effort, root, description,
except json.JSONDecodeError:
targs = {}
sub_tool_calls += 1
liveness.set(
f"subagent-{agent_id}",
f"{label}: fuehrt Tool {tname} in Turn {sub_turns} aus",
)
# Subagent darf nur Read-Only-Tools nutzen
if tname in ("read_file", "list_directory", "search_files", "execute_command"):
tresult = execute_tool(tname, targs, root, "")
else:
tresult = f"FEHLER: Werkzeug '{tname}' ist fuer Subagenten nicht freigegeben."
try:
if tname in ("read_file", "list_directory", "search_files", "execute_command"):
tresult = execute_tool(tname, targs, root, "")
else:
tresult = f"FEHLER: Werkzeug '{tname}' ist fuer Subagenten nicht freigegeben."
finally:
liveness.clear(f"subagent-{agent_id}")
messages.append({"role": "tool", "tool_call_id": tc.get("id", ""),
"name": tname, "content": tresult})
status = "completed" if completed_normally and not sub_errors else "failed"
log_stderr(
f"{label}: beendet (Status: {status}, Turns: {sub_turns}, "
f"Tool-Calls: {sub_tool_calls}, Tokens: {sub_usage['total_tokens']:,})"
)
return {
"result": sub_result or "(Subagent ohne Ergebnis)",
"usage": sub_usage,
"turns": sub_turns,
"tool_calls": sub_tool_calls,
"errors": sub_errors,
"status": "completed" if sub_result else "failed",
"status": status,
}
def call_api_subagent(provider, model, messages, api_key, effort, temperature, timeout):
def call_api_subagent(provider, model, messages, api_key, effort, temperature,
timeout, liveness, agent_id, subagent_type, turn):
"""API-Aufruf fuer Subagenten (mit SUBAGENT_TOOLS statt TOOLS)."""
url = f"{provider['base_url']}/chat/completions"
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
@@ -538,18 +664,19 @@ def call_api_subagent(provider, model, messages, api_key, effort, temperature, t
body["thinking"] = {"type": "enabled", "level": effort_val}
elif effort_type == "reasoning_effort":
body["reasoning_effort"] = effort_val
resp = requests.post(url, headers=headers, json=body,
timeout=timeout if timeout > 0 else 1800)
if resp.status_code != 200:
raise RuntimeError(f"API-Fehler {resp.status_code}: {resp.text[:2000]}")
return resp.json()
return post_chat_completion(
url, headers, body, timeout, liveness,
f"subagent-{agent_id}",
f"Subagent {agent_id} ({subagent_type}) Turn {turn}",
)
# ---------------------------------------------------------------------------
# API-Aufruf
# ---------------------------------------------------------------------------
def call_api(provider, model, messages, api_key, effort, temperature, timeout):
def call_api(provider, model, messages, api_key, effort, temperature, timeout,
liveness, activity_id="main-api", activity_label="Hauptagent"):
"""Ruft die Chat-Completions-API auf und gibt die JSON-Antwort zurueck."""
url = f"{provider['base_url']}/chat/completions"
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
@@ -565,11 +692,9 @@ def call_api(provider, model, messages, api_key, effort, temperature, timeout):
body["thinking"] = {"type": "enabled", "level": effort_val}
elif effort_type == "reasoning_effort":
body["reasoning_effort"] = effort_val
resp = requests.post(url, headers=headers, json=body,
timeout=timeout if timeout > 0 else 1800)
if resp.status_code != 200:
raise RuntimeError(f"API-Fehler {resp.status_code}: {resp.text[:2000]}")
return resp.json()
return post_chat_completion(
url, headers, body, timeout, liveness, activity_id, activity_label
)
# ---------------------------------------------------------------------------
@@ -577,10 +702,11 @@ def call_api(provider, model, messages, api_key, effort, temperature, timeout):
# ---------------------------------------------------------------------------
def run_agent_loop(provider, model, system_prompt, user_prompt, api_key, effort,
root, output_dir, max_turns, temperature, timeout, mode="solo"):
root, output_dir, max_turns, temperature, timeout, liveness,
mode="solo", subagent_max_turns=0):
"""Fuehrt den Agent-Loop durch und sammelt Metriken."""
# Tools je nach Modus waehlen
if mode == "builtin":
if mode in ("builtin", "custom"):
active_tools = TOOLS # inklusive spawn_subagent
else:
active_tools = [t for t in TOOLS if t["function"]["name"] != "spawn_subagent"]
@@ -600,11 +726,10 @@ def run_agent_loop(provider, model, system_prompt, user_prompt, api_key, effort,
body["thinking"] = {"type": "enabled", "level": effort_val}
elif effort_type == "reasoning_effort":
body["reasoning_effort"] = effort_val
resp = requests.post(url, headers=headers, json=body,
timeout=timeout if timeout > 0 else 1800)
if resp.status_code != 200:
raise RuntimeError(f"API-Fehler {resp.status_code}: {resp.text[:2000]}")
return resp.json()
return post_chat_completion(
url, headers, body, timeout, liveness, "main-api",
f"Hauptagent Turn {turns}",
)
messages = [
{"role": "system", "content": system_prompt},
@@ -622,50 +747,15 @@ def run_agent_loop(provider, model, system_prompt, user_prompt, api_key, effort,
# Subagent-Tracking
subagent_stats = {"spawned": 0, "completed": 0, "failed": 0, "by_type": {}}
subagent_details = []
MAX_SUBAGENTS = 10 # Hartes Limit: danach wird spawn_subagent verweigert
write_file_count = 0 # Zaehlt write_file-Aufrufe
write_reminder_sent = False # Wurde schon eine Schreib-Erinnerung gesendet?
while turns < max_turns:
while max_turns <= 0 or turns < max_turns:
turns += 1
# Schreib-Erinnerung: Wenn nach 1/3 der Turns noch kein write_file,
# oder nach 2/3 der Turns weniger als 3 write_file-Aufrufe
if not write_reminder_sent and turns >= max_turns // 3 and write_file_count == 0:
messages.append({
"role": "user",
"content": (
"WICHTIG: Du hast bisher keine Ergebnisdateien geschrieben. "
"Beginne JETZT damit, deine Analyseergebnisse mit write_file "
"in die vorgegebenen Dateien (StRS.md, SyRS.md, SwRS.md, "
"Traceability.md, Hypothesen.md, Glossar.md, Analysebericht.md) "
"zu schreiben. Schreibe nicht weiter Subagenten — formalisiere "
"deine bisherigen Erkenntnisse in Anforderungen."
),
})
write_reminder_sent = True
sys.stderr.write(f"[glm-kimi-adapter] Schreib-Erinnerung gesendet "
f"(Turn {turns}, 0 write_file-Aufrufe)\n")
elif (not write_reminder_sent and turns >= max_turns * 2 // 3
and write_file_count < 3):
messages.append({
"role": "user",
"content": (
f"WICHTIG: Du hast bisher nur {write_file_count} Ergebnisdatei(en) "
f"geschrieben. Es fehlen noch mehrere der 7 vorgegebenen Dateien "
f"(StRS.md, SyRS.md, SwRS.md, Traceability.md, Hypothesen.md, "
f"Glossar.md, Analysebericht.md). Schreibe die fehlenden Dateien "
f"JETZT mit write_file."
),
})
write_reminder_sent = True
sys.stderr.write(f"[glm-kimi-adapter] Schreib-Erinnerung gesendet "
f"(Turn {turns}, {write_file_count} write_file-Aufrufe)\n")
try:
response = _call_api(messages)
except Exception as e:
errors.append(f"Turn {turns}: API-Fehler: {e}")
log_stderr(f"FEHLER Hauptagent Turn {turns}: {e}")
break
usage = response.get("usage", {})
total_usage["prompt_tokens"] += usage.get("prompt_tokens", 0)
@@ -675,6 +765,11 @@ def run_agent_loop(provider, model, system_prompt, user_prompt, api_key, effort,
total_usage["cached_tokens"] += cached
comp_details = usage.get("completion_tokens_details", {})
total_usage["reasoning_tokens"] += comp_details.get("reasoning_tokens", 0)
log_stderr(
f"Hauptagent Turn {turns} API abgeschlossen "
f"(Antwort-Tokens: {usage.get('total_tokens', 0):,}, "
f"Gesamtlauf kumuliert: {total_usage['total_tokens']:,})"
)
if response.get("model"):
model_reported = response["model"]
choices = response.get("choices", [])
@@ -691,6 +786,7 @@ def run_agent_loop(provider, model, system_prompt, user_prompt, api_key, effort,
tool_calls = msg.get("tool_calls", [])
if not tool_calls:
break
parsed_calls = []
for tc in tool_calls:
func = tc.get("function", {})
tool_name = func.get("name", "")
@@ -701,62 +797,109 @@ def run_agent_loop(provider, model, system_prompt, user_prompt, api_key, effort,
except json.JSONDecodeError:
tool_args = {}
tool_calls_log.append({"turn": turns, "name": tool_name, "args": tool_args})
parsed_calls.append({
"tool_call": tc,
"name": tool_name,
"args": tool_args,
"id": tc_id,
})
# write_file-Zaehler erhoehen
if tool_name == "write_file":
write_file_count += 1
spawn_calls = [call for call in parsed_calls if call["name"] == "spawn_subagent"]
results_by_id = {}
futures = []
executor = None
# Subagent-Spawning behandeln
if tool_name == "spawn_subagent":
if subagent_stats["spawned"] >= MAX_SUBAGENTS:
# Hartes Limit erreicht: verweigere und erzwinge Schreibphase
result = (
"ABGELEHNT: Subagent-Limit erreicht (10/10). "
"Du hast bereits 10 Subagenten gestartet. "
"Schreibe JETZT deine Ergebnisdateien mit write_file: "
"StRS.md, SyRS.md, SwRS.md, Traceability.md, "
"Hypothesen.md, Glossar.md, Analysebericht.md. "
"Starte keine weiteren Subagenten."
)
sys.stderr.write(f"[glm-kimi-adapter] Subagent verweigert "
f"(Limit {MAX_SUBAGENTS} erreicht)\n")
else:
sa_desc = tool_args.get("description", "")
sa_type = tool_args.get("subagent_type", "general-purpose")
subagent_stats["spawned"] += 1
subagent_stats["by_type"][sa_type] = subagent_stats["by_type"].get(sa_type, 0) + 1
sys.stderr.write(f"[glm-kimi-adapter] Subagent "
f"{subagent_stats['spawned']}/{MAX_SUBAGENTS} "
f"gestartet (Typ: {sa_type})\n")
if spawn_calls:
# Bewusst kein Adapterlimit: Anzahl und Typen bestimmt allein das Modell.
executor = ThreadPoolExecutor(
max_workers=len(spawn_calls),
thread_name_prefix=f"subagent-turn-{turns}",
)
for call in spawn_calls:
sa_desc = call["args"].get("description", "")
sa_type = call["args"].get("subagent_type", "general-purpose")
subagent_stats["spawned"] += 1
agent_id = subagent_stats["spawned"]
subagent_stats["by_type"][sa_type] = (
subagent_stats["by_type"].get(sa_type, 0) + 1
)
log_stderr(
f"Subagent {agent_id} zur parallelen Ausfuehrung eingeplant "
f"(Typ: {sa_type}, Hauptagent-Turn: {turns})"
)
future = executor.submit(
run_subagent,
provider, model, api_key, effort, root, sa_desc, sa_type,
temperature, timeout, liveness, agent_id,
subagent_max_turns,
)
futures.append((call, future, agent_id, sa_type, sa_desc))
# Nicht-Subagenten-Tools laufen, waehrend die Subagenten parallel arbeiten.
for call in parsed_calls:
if call["name"] == "spawn_subagent":
continue
activity_id = f"main-tool-{call['id']}"
liveness.set(
activity_id,
f"Hauptagent Turn {turns}: fuehrt Tool {call['name']} aus",
)
try:
results_by_id[call["id"]] = execute_tool(
call["name"], call["args"], root, output_dir
)
finally:
liveness.clear(activity_id)
if futures:
liveness.set(
"main-subagent-wait",
f"Hauptagent Turn {turns}: wartet auf {len(futures)} parallele Subagenten",
)
try:
for call, future, agent_id, sa_type, sa_desc in futures:
try:
sa_result = run_subagent(
provider, model, api_key, effort, root,
sa_desc, sa_type, temperature, timeout
)
subagent_stats["completed" if sa_result["status"] == "completed" else "failed"] += 1
sa_u = sa_result["usage"]
total_usage["prompt_tokens"] += sa_u["prompt_tokens"]
total_usage["completion_tokens"] += sa_u["completion_tokens"]
total_usage["total_tokens"] += sa_u["total_tokens"]
total_usage["cached_tokens"] += sa_u["cached_tokens"]
total_usage["reasoning_tokens"] += sa_u["reasoning_tokens"]
subagent_details.append({
"id": subagent_stats["spawned"],
"type": sa_type,
"description": sa_desc[:200],
"turns": sa_result["turns"],
"tool_calls": sa_result["tool_calls"],
"tokens": sa_u["total_tokens"],
"status": sa_result["status"],
})
result = sa_result["result"]
sa_result = future.result()
except Exception as e:
subagent_stats["failed"] += 1
result = f"FEHLER: Subagent fehlgeschlagen: {e}"
else:
result = execute_tool(tool_name, tool_args, root, output_dir)
messages.append({"role": "tool", "tool_call_id": tc_id,
"name": tool_name, "content": result})
sa_result = {
"result": f"FEHLER: Subagent fehlgeschlagen: {e}",
"usage": {key: 0 for key in total_usage},
"turns": 0,
"tool_calls": 0,
"errors": [str(e)],
"status": "failed",
}
log_stderr(f"Subagent {agent_id}: FEHLER: {e}")
status_key = (
"completed" if sa_result["status"] == "completed" else "failed"
)
subagent_stats[status_key] += 1
sa_u = sa_result["usage"]
for key in total_usage:
total_usage[key] += sa_u.get(key, 0)
subagent_details.append({
"id": agent_id,
"type": sa_type,
"description": sa_desc[:200],
"turns": sa_result["turns"],
"tool_calls": sa_result["tool_calls"],
"tokens": sa_u.get("total_tokens", 0),
"status": sa_result["status"],
"errors": sa_result.get("errors", []),
})
results_by_id[call["id"]] = sa_result["result"]
finally:
liveness.clear("main-subagent-wait")
executor.shutdown(wait=True)
# Tool-Antworten muessen in derselben Reihenfolge wie die Tool-Calls folgen.
for call in parsed_calls:
messages.append({
"role": "tool",
"tool_call_id": call["id"],
"name": call["name"],
"content": results_by_id[call["id"]],
})
end_time = time.time()
duration_s = end_time - start_time
@@ -774,8 +917,8 @@ def run_agent_loop(provider, model, system_prompt, user_prompt, api_key, effort,
name = tc["name"]
tool_call_types[name] = tool_call_types.get(name, 0) + 1
return {
"is_error": len(errors) > 0 and not final_content,
"subtype": "success" if final_content else "error",
"is_error": bool(errors),
"subtype": "error" if errors else "success",
"duration_ms": int(duration_s * 1000),
"duration_api_ms": int(duration_s * 1000),
"num_turns": turns, "model": model_reported, "model_requested": model,
@@ -806,7 +949,7 @@ def run_agent_loop(provider, model, system_prompt, user_prompt, api_key, effort,
"tool_call_types": tool_call_types,
"written_files": written_files, "result": final_content,
"finish_reason": finish_reason, "errors": errors, "session_id": "",
"adapter": "python-glm-kimi", "adapter_version": "1.1.0",
"adapter": "python-glm-kimi", "adapter_version": ADAPTER_VERSION,
"mode": mode,
"subagent_stats": subagent_stats,
"subagent_details": subagent_details,
@@ -818,19 +961,21 @@ def run_agent_loop(provider, model, system_prompt, user_prompt, api_key, effort,
# ---------------------------------------------------------------------------
def main():
parser = argparse.ArgumentParser(description="TensorX API Adapter fuer run-experiment (GLM/Kimi)")
parser = argparse.ArgumentParser(description="TensorX API Adapter fuer run-experiment (GLM/Qwen/Kimi)")
parser.add_argument("--prompt", required=True, help="Pfad zur combined_prompt.md")
parser.add_argument("--root", required=True, help="Root-Verzeichnis der Codebasis")
parser.add_argument("--output", required=True, help="Ausgabeverzeichnis (Ergebnisse)")
parser.add_argument("--model", required=True, help="Modell-ID (z.B. z-ai/glm-5.2, moonshotai/kimi-k3)")
parser.add_argument("--model", required=True, help="Modell-ID (z.B. z-ai/glm-5.3-flash, qwen/qwen3.8-flash-next)")
parser.add_argument("--provider", default="tensorx", help="API-Provider (default: tensorx)")
parser.add_argument("--api-key", default=None, help="API-Key (default: aus Cline providers.json)")
parser.add_argument("--effort", default="high", choices=["low", "medium", "high", "xhigh", "max"])
parser.add_argument("--mode", default="solo", choices=["solo", "builtin", "custom"], help="Agentenmodus (solo=keine Subagenten, builtin=eingebaute, custom=vordefinierte Agenten aus Datei)")
parser.add_argument("--agents", default=None, help="Pfad zu Agenten-Definitionen (JSON) fuer Modus 'custom'")
parser.add_argument("--max-turns", type=int, default=50)
parser.add_argument("--max-turns", type=int, default=0, help="Maximale Hauptagent-Turns (0=unbegrenzt)")
parser.add_argument("--subagent-max-turns", type=int, default=0, help="Maximale Turns je Subagent (0=unbegrenzt)")
parser.add_argument("--temperature", type=float, default=1.0)
parser.add_argument("--timeout", type=int, default=0, help="Timeout in Sek (0=keins)")
parser.add_argument("--heartbeat-interval", type=int, default=60, help="Sekunden zwischen Lebenszeichen (0=aus)")
parser.add_argument("--result-dir", default=None, help="Verzeichnis fuer RawResult.json")
args = parser.parse_args()
@@ -868,21 +1013,18 @@ def main():
"eines Legacy-ERP-Systems. Du analysierst die Codebasis im Arbeitsverzeichnis "
"und erstellst eine Anforderungsspezifikation nach ISO/IEC/IEEE 29148:2018.\n\n"
"Werkzeuge: read_file, list_directory, search_files, execute_command, write_file.\n\n"
"WICHTIG: Beginne deine Arbeit mit dem Schreiben von Ergebnisdateien (write_file), "
"nicht mit dem Erkunden. Erstelle zuerst die Skelette der 7 Ergebnisdateien "
"(StRS.md, SyRS.md, SwRS.md, Traceability.md, Hypothesen.md, Glossar.md, "
"Analysebericht.md) mit Platzhalter-Inhalt, bevor du in die Details gehst. "
"Wenn du Ergebnisse hast, schreibe sie SOFORT mit write_file — beschreibe nicht, "
"was du schreiben wirst, schreibe es."
"was du schreiben wirst, schreibe es. Wenn du Anforderungen formuliert hast, "
"schreibe die Dateien (StRS.md, SyRS.md, SwRS.md, Traceability.md, Hypothesen.md, "
"Glossar.md, Analysebericht.md) sofort — nichtmal davor nachfragen oder zusammenfassen."
)
if args.mode == "builtin":
system_prompt += (
"\nZusaetzlich steht spawn_subagent zur Verfuegung: Starte einen "
"Subagenten mit eigenem Kontext fuer isolierte Teilaufgaben (z.B. "
"Analyse eines einzelnen Moduls). Der Subagent kann nur lesen, nicht "
"schreiben. Nutze Subagenten, um die Breite der Analyse zu erhoehen: "
"delegiere Modul-Analysen an Subagenten, waehrend du die "
"Gesamtstruktur und die Ergebnisdateien verwaltest."
"\nZusaetzlich steht spawn_subagent fuer isolierte Teilaufgaben zur "
"Verfuegung. Entscheide selbst, ob du Subagenten einsetzt sowie welche "
"Typen und wie viele du in einem Turn parallel startest. Der Adapter "
"setzt dafuer kein Anzahl- oder Turn-Limit. Subagenten koennen nur "
"lesen, nicht schreiben; die Ergebnisdateien verwaltest du selbst."
)
elif args.mode == "custom":
# Custom agents laden
@@ -925,13 +1067,24 @@ def main():
sys.stderr.write(f"[glm-kimi-adapter] Modell: {args.model}\n")
sys.stderr.write(f"[glm-kimi-adapter] Effort: {args.effort}\n")
sys.stderr.write(f"[glm-kimi-adapter] Mode: {args.mode}\n")
sys.stderr.write(f"[glm-kimi-adapter] Adapter-Version: {ADAPTER_VERSION}\n")
sys.stderr.write(
f"[glm-kimi-adapter] Limits: Hauptagent-Turns="
f"{'unbegrenzt' if args.max_turns <= 0 else args.max_turns}, "
f"Subagent-Turns={'unbegrenzt' if args.subagent_max_turns <= 0 else args.subagent_max_turns}, "
f"API-Timeout={'keiner' if args.timeout <= 0 else str(args.timeout) + 's'}\n"
)
liveness = LivenessMonitor(args.heartbeat_interval)
liveness.start()
try:
result = run_agent_loop(
provider=provider, model=args.model, system_prompt=system_prompt,
user_prompt=user_prompt, api_key=api_key, effort=args.effort,
root=args.root, output_dir=str(output_dir), max_turns=args.max_turns,
temperature=args.temperature, timeout=args.timeout, mode=args.mode,
temperature=args.temperature, timeout=args.timeout, liveness=liveness,
mode=args.mode, subagent_max_turns=args.subagent_max_turns,
)
except Exception as e:
tb = traceback.format_exc()
@@ -945,11 +1098,13 @@ def main():
"reasoning_tokens": 0},
"modelUsage": {}, "tool_calls": [], "tool_call_count": 0,
"tool_call_types": {}, "written_files": [], "result": "",
"errors": [str(e)], "adapter": "python-glm-kimi", "adapter_version": "1.1.0",
"errors": [str(e)], "adapter": "python-glm-kimi", "adapter_version": ADAPTER_VERSION,
"mode": args.mode,
"subagent_stats": {"spawned": 0, "completed": 0, "failed": 0, "by_type": {}},
"subagent_details": [],
}
finally:
liveness.stop()
end_iso = datetime.now(timezone.utc).isoformat()
result["start_time"] = start_iso
@@ -965,6 +1120,9 @@ def main():
sa = result.get("subagent_stats", {})
sys.stderr.write(f"[glm-kimi-adapter] Subagenten: {sa.get('spawned',0)} (completed: {sa.get('completed',0)}, failed: {sa.get('failed',0)})\n")
sys.stderr.write(f"[glm-kimi-adapter] Ergebnisdateien: {len(result['written_files'])}\n")
if result.get("errors"):
for error in result["errors"]:
sys.stderr.write(f"[glm-kimi-adapter] FEHLER: {error}\n")
sys.stderr.write(f"[glm-kimi-adapter] RawResult: {raw_result_path}\n")
sys.exit(1 if result["is_error"] else 0)