75 lines
3.7 KiB
Python
75 lines
3.7 KiB
Python
"""Ein Agent = eine Claude-Agent-SDK-Query mit dem Themenverzeichnis als Arbeitsverzeichnis."""
|
|
import asyncio
|
|
import logging
|
|
|
|
from claude_agent_sdk import (AssistantMessage, ClaudeAgentOptions, ResultMessage, TextBlock, ToolUseBlock, query)
|
|
|
|
from . import config, state
|
|
|
|
log = logging.getLogger("buchwerk")
|
|
|
|
SYSTEM_ZUSATZ = (
|
|
"Du bist ein Agent in einer Pipeline, die ein deutschsprachiges Sachbuch erstellt. Das aktuelle Arbeitsverzeichnis "
|
|
"ist das Buchprojekt; verlasse es nicht (außer für Klone und Downloads, die du unter quellen/ ablegst). "
|
|
"Es gibt keinen menschlichen Ansprechpartner: Rückfragen sind nicht möglich, entscheide selbst. "
|
|
"Arbeite gründlich und bis zum Ende der Aufgabe. Antworte auf Deutsch. Deine letzte Nachricht ist der Abschlussbericht."
|
|
)
|
|
|
|
|
|
def _kurz(block: ToolUseBlock) -> str:
|
|
i = block.input or {}
|
|
for k in ("file_path", "command", "pattern", "query", "url", "description"):
|
|
if k in i:
|
|
return f"{block.name} {str(i[k])[:160]}"
|
|
return block.name
|
|
|
|
|
|
async def laufen(name: str, agent_id: str, stufe_id: str, titel: str, prompt: str, *,
|
|
effort: str = config.EFFORT, max_turns: int = config.MAX_TURNS) -> dict:
|
|
"""Führt einen Agenten aus; schreibt Ereignisse und Bericht. → {"status", "bericht", "kosten", "tokens"}"""
|
|
a = state.agent_eintrag(name, agent_id, stufe_id, titel)
|
|
state.ereignis(name, agent_id, "start", titel)
|
|
options = ClaudeAgentOptions(
|
|
model=config.MODELL, effort=effort, cwd=str(state.verzeichnis(name)),
|
|
permission_mode="bypassPermissions", max_turns=max_turns,
|
|
system_prompt={"type": "preset", "preset": "claude_code", "append": SYSTEM_ZUSATZ},
|
|
)
|
|
letzter_text, kosten, tokens, status, fehler = "", 0.0, 0, "fehler", ""
|
|
try:
|
|
async for m in query(prompt=prompt, options=options):
|
|
if isinstance(m, AssistantMessage):
|
|
for b in m.content:
|
|
if isinstance(b, ToolUseBlock):
|
|
a["werkzeuge"] += 1
|
|
if a["werkzeuge"] % 5 == 1:
|
|
state.ereignis(name, agent_id, "tool", _kurz(b))
|
|
elif isinstance(b, TextBlock) and b.text.strip():
|
|
letzter_text = b.text
|
|
elif isinstance(m, ResultMessage):
|
|
kosten = m.total_cost_usd or 0.0
|
|
u = m.usage or {}
|
|
tokens = sum(int(u.get(k) or 0) for k in
|
|
("input_tokens", "output_tokens", "cache_creation_input_tokens", "cache_read_input_tokens"))
|
|
if m.result and m.result.strip():
|
|
letzter_text = m.result
|
|
if m.is_error or m.subtype != "success":
|
|
fehler = f"{m.subtype}: " + "; ".join(str(e) for e in (m.errors or [])[:3])
|
|
else:
|
|
status = "fertig"
|
|
except asyncio.CancelledError:
|
|
state.agent_fertig(name, a, "abgebrochen", letzter_text, kosten, tokens)
|
|
state.ereignis(name, agent_id, "ende", "abgebrochen")
|
|
raise
|
|
except Exception as e: # ProcessError, CLIConnectionError, …
|
|
fehler = f"{type(e).__name__}: {e}"
|
|
log.exception("Agent %s", agent_id)
|
|
bericht = letzter_text.strip()
|
|
if status != "fertig":
|
|
bericht = f"**Fehler:** {fehler}\n\n{bericht}".strip()
|
|
state.agent_fertig(name, a, status, bericht, kosten, tokens)
|
|
if status == "fertig":
|
|
state.bericht_schreiben(name, agent_id, bericht)
|
|
state.ereignis(name, agent_id, "ende" if status == "fertig" else "fehler",
|
|
f"{status} · {kosten:.2f} USD · {a['werkzeuge']} Werkzeugaufrufe" + (f" · {fehler}" if fehler else ""))
|
|
return {"status": status, "bericht": bericht, "kosten": kosten, "tokens": tokens}
|