init
This commit is contained in:
74
backend/agenten.py
Normal file
74
backend/agenten.py
Normal file
@@ -0,0 +1,74 @@
|
||||
"""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}
|
||||
Reference in New Issue
Block a user