This commit is contained in:
team3
2026-06-04 15:24:33 +02:00
parent d08878289e
commit 1aef82ec40
17 changed files with 440 additions and 117 deletions

View File

@@ -8,16 +8,12 @@ import uuid
from datetime import datetime, timezone
from pathlib import Path
from agents import run_agent, kill_process
from config import (
AGENT_TIMEOUT,
CLAUDE_CLI,
DEFAULT_PROVIDER,
TEMPLATES_DIR,
MAX_CONCURRENT_GENERATIONS,
MODEL_GUIDE,
MODEL_BAUSTEIN_GEN,
MODEL_BAUSTEIN_REWORK,
MODEL_CHAT,
MODEL_PROJECT_INDEX,
STORAGE_DIR,
)
from database import (
@@ -32,15 +28,12 @@ from database import (
from paths import final_paths, temp_paths, project_dir, project_cache_path
_semaphore = asyncio.Semaphore(MAX_CONCURRENT_GENERATIONS)
_active_processes: dict[str, asyncio.subprocess.Process] = {}
_cancelled: set[str] = set()
async def cancel_guide(guide_id: str) -> bool:
_cancelled.add(guide_id)
process = _active_processes.get(guide_id)
if process and process.returncode is None:
process.kill()
kill_process(guide_id)
now = datetime.now(timezone.utc).isoformat()
await update_guide(guide_id, status="error", progress=None, error_msg="Abgebrochen", updated_at=now)
return True
@@ -51,38 +44,6 @@ async def _set_progress(guide_id: str, progress: str) -> None:
await update_guide(guide_id, progress=progress, updated_at=now)
async def _run_claude(guide_id: str, prompt: str, timeout: int, tools: str | None = "Write,Bash,Read,WebSearch,WebFetch", model: str | None = None) -> tuple[int, str, str]:
cmd = [CLAUDE_CLI, "-p"]
if model:
cmd += ["--model", model]
if tools:
cmd += ["--allowedTools", tools]
cmd += ["--dangerously-skip-permissions"]
process = await asyncio.create_subprocess_exec(
*cmd,
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
_active_processes[guide_id] = process
try:
try:
stdout, stderr = await asyncio.wait_for(
process.communicate(input=prompt.encode("utf-8")),
timeout=timeout,
)
except asyncio.TimeoutError:
process.kill()
try:
await asyncio.wait_for(process.wait(), timeout=5)
except asyncio.TimeoutError:
pass
raise
return process.returncode, stdout.decode("utf-8", errors="replace"), stderr.decode("utf-8", errors="replace")
finally:
_active_processes.pop(guide_id, None)
async def _render_pdf(html_path: Path, pdf_path: Path) -> tuple[bool, str]:
proc = await asyncio.create_subprocess_exec(
"weasyprint", str(html_path), str(pdf_path),
@@ -225,7 +186,7 @@ FAIL
"""
async def generate_guide(guide_id: str, topic: str, format_name: str, instructions: str = "", reindex: bool = False) -> None:
async def generate_guide(guide_id: str, topic: str, format_name: str, instructions: str = "", reindex: bool = False, provider: str = DEFAULT_PROVIDER) -> None:
async with _semaphore:
now = datetime.now(timezone.utc).isoformat()
await update_guide(guide_id, status="generating", progress="Recherche…", updated_at=now)
@@ -247,9 +208,9 @@ async def generate_guide(guide_id: str, topic: str, format_name: str, instructio
await _set_progress(guide_id, "Lese Projekt…")
current_step = "Projekt-Einlesen"
index_prompt = _build_project_index_prompt(topic, cache_path, cache_path.exists())
returncode, idx_out, idx_err = await _run_claude(
returncode, idx_out, idx_err = await run_agent(
guide_id, index_prompt, AGENT_TIMEOUT,
tools="Read,Bash,Write", model=MODEL_PROJECT_INDEX,
provider=provider, role="fast", capabilities="files",
)
if guide_id in _cancelled:
return
@@ -264,7 +225,7 @@ async def generate_guide(guide_id: str, topic: str, format_name: str, instructio
# Step 1: Generator-Agent erstellt HTML
await _set_progress(guide_id, "Generiere HTML…")
gen_prompt = _build_generator_prompt(topic, format_name, html_path, instructions, project_content)
returncode, stdout, stderr = await _run_claude(guide_id, gen_prompt, AGENT_TIMEOUT, model=MODEL_GUIDE)
returncode, stdout, stderr = await run_agent(guide_id, gen_prompt, AGENT_TIMEOUT, provider=provider, role="guide", capabilities="full")
if guide_id in _cancelled:
return
@@ -284,7 +245,7 @@ async def generate_guide(guide_id: str, topic: str, format_name: str, instructio
current_step = "Inhalts-Review"
current_timeout = AGENT_TIMEOUT
content_prompt = _build_content_review_prompt(topic, format_name, html_path, project_content)
returncode, review_out, review_err = await _run_claude(guide_id, content_prompt, AGENT_TIMEOUT, model=MODEL_GUIDE)
returncode, review_out, review_err = await run_agent(guide_id, content_prompt, AGENT_TIMEOUT, provider=provider, role="guide", capabilities="full")
if returncode != 0:
await _fail(guide_id, _claude_error("Inhalts-Review-Fehler", returncode, review_out, review_err))
@@ -300,7 +261,7 @@ async def generate_guide(guide_id: str, topic: str, format_name: str, instructio
current_step = "Inhalts-Korrektur"
current_timeout = AGENT_TIMEOUT
fix_prompt = _build_fix_prompt(topic, format_name, html_path, feedback)
returncode, fix_out, fix_err = await _run_claude(guide_id, fix_prompt, AGENT_TIMEOUT, model=MODEL_GUIDE)
returncode, fix_out, fix_err = await run_agent(guide_id, fix_prompt, AGENT_TIMEOUT, provider=provider, role="guide", capabilities="full")
if returncode != 0:
await _fail(guide_id, _claude_error("Fix-Fehler", returncode, fix_out, fix_err))
@@ -326,11 +287,10 @@ async def generate_guide(guide_id: str, topic: str, format_name: str, instructio
except Exception as e:
await _fail(guide_id, str(e)[:2000])
finally:
_active_processes.pop(guide_id, None)
_cancelled.discard(guide_id)
async def rework_guide(guide_id: str, topic: str, format_name: str, instructions: str) -> None:
async def rework_guide(guide_id: str, topic: str, format_name: str, instructions: str, provider: str = DEFAULT_PROVIDER) -> None:
async with _semaphore:
now = datetime.now(timezone.utc).isoformat()
await update_guide(guide_id, status="generating", progress="Überarbeite…", updated_at=now)
@@ -352,7 +312,7 @@ async def rework_guide(guide_id: str, topic: str, format_name: str, instructions
current_timeout = AGENT_TIMEOUT
rework_prompt = _build_rework_prompt(topic, format_name, tmp_html, instructions)
returncode, stdout, stderr = await _run_claude(guide_id, rework_prompt, AGENT_TIMEOUT, model=MODEL_GUIDE)
returncode, stdout, stderr = await run_agent(guide_id, rework_prompt, AGENT_TIMEOUT, provider=provider, role="guide", capabilities="full")
if guide_id in _cancelled:
return
@@ -384,7 +344,6 @@ async def rework_guide(guide_id: str, topic: str, format_name: str, instructions
except Exception as e:
await _fail(guide_id, str(e)[:2000])
finally:
_active_processes.pop(guide_id, None)
_cancelled.discard(guide_id)
tmp_html.unlink(missing_ok=True)
tmp_pdf.unlink(missing_ok=True)
@@ -480,7 +439,7 @@ Orientiere dich an der Spezifikation und Referenz. Kein weiterer Text, nur das J
"""
async def generate_suggestions(topic: str, html_paths: list[Path]) -> None:
async def generate_suggestions(topic: str, html_paths: list[Path], provider: str = DEFAULT_PROVIDER) -> None:
_suggestions_generating.add(topic)
try:
existing = await list_bausteine(topic)
@@ -489,8 +448,8 @@ async def generate_suggestions(topic: str, html_paths: list[Path]) -> None:
await delete_pending_suggestions(topic)
prompt = _build_suggestions_prompt(topic, html_paths, existing_titles)
tools = "Read" if html_paths else None
returncode, stdout, stderr = await _run_claude("suggestions-" + topic, prompt, 1800, tools=tools, model=MODEL_BAUSTEIN_GEN)
capabilities = "read" if html_paths else "none"
returncode, stdout, stderr = await run_agent("suggestions-" + topic, prompt, 1800, provider=provider, role="fast", capabilities=capabilities)
if returncode != 0:
return
@@ -520,10 +479,10 @@ async def generate_suggestions(topic: str, html_paths: list[Path]) -> None:
_suggestions_generating.discard(topic)
async def generate_baustein_detail(baustein_id: str, topic: str, title: str, instructions: str = "") -> None:
async def generate_baustein_detail(baustein_id: str, topic: str, title: str, instructions: str = "", provider: str = DEFAULT_PROVIDER) -> None:
try:
prompt = _build_baustein_detail_prompt(topic, title, instructions)
returncode, stdout, stderr = await _run_claude("baustein-" + baustein_id, prompt, 60, tools=None, model=MODEL_BAUSTEIN_GEN)
returncode, stdout, stderr = await run_agent("baustein-" + baustein_id, prompt, 180, provider=provider, role="fast", capabilities="none")
if returncode != 0:
return
@@ -544,10 +503,10 @@ async def generate_baustein_detail(baustein_id: str, topic: str, title: str, ins
pass
async def rework_baustein(baustein_id: str, topic: str, title: str, current: dict, instructions: str) -> None:
async def rework_baustein(baustein_id: str, topic: str, title: str, current: dict, instructions: str, provider: str = DEFAULT_PROVIDER) -> None:
try:
prompt = _build_baustein_rework_prompt(topic, title, current, instructions)
returncode, stdout, stderr = await _run_claude("baustein-" + baustein_id, prompt, 60, tools=None, model=MODEL_BAUSTEIN_REWORK)
returncode, stdout, stderr = await run_agent("baustein-" + baustein_id, prompt, 180, provider=provider, role="fast", capabilities="none")
if returncode != 0:
return
@@ -654,11 +613,11 @@ WICHTIG Antwortstil:
Gib NUR die Antwort aus, kein Präfix wie "Assistent:"."""
async def chat_with_guide(topic: str, format_name: str, section: str, outline: str, messages: list[dict]) -> str:
async def chat_with_guide(topic: str, format_name: str, section: str, outline: str, messages: list[dict], provider: str = DEFAULT_PROVIDER) -> str:
try:
prompt = _build_guide_chat_prompt(topic, format_name, section, outline, messages)
returncode, stdout, stderr = await _run_claude(
"chat-" + str(uuid.uuid4()), prompt, 120, tools=None, model=MODEL_CHAT
returncode, stdout, stderr = await run_agent(
"chat-" + str(uuid.uuid4()), prompt, 240, provider=provider, role="fast", capabilities="none"
)
if returncode != 0:
return "Entschuldigung, das hat nicht geklappt. Bitte versuche es erneut."
@@ -668,11 +627,11 @@ async def chat_with_guide(topic: str, format_name: str, section: str, outline: s
return "Entschuldigung, das hat nicht geklappt. Bitte versuche es erneut."
async def suggest_topics(problem: str, existing_topics: list[str] | None = None) -> list[dict]:
async def suggest_topics(problem: str, existing_topics: list[str] | None = None, provider: str = DEFAULT_PROVIDER) -> list[dict]:
try:
prompt = _build_topic_suggest_prompt(problem, existing_topics or [])
returncode, stdout, stderr = await _run_claude(
"topic-suggest-" + str(uuid.uuid4()), prompt, 120, tools=None, model=MODEL_BAUSTEIN_GEN
returncode, stdout, stderr = await run_agent(
"topic-suggest-" + str(uuid.uuid4()), prompt, 240, provider=provider, role="fast", capabilities="none"
)
if returncode != 0:
return []
@@ -692,11 +651,11 @@ async def suggest_topics(problem: str, existing_topics: list[str] | None = None)
return []
async def sort_bausteine(topic: str, bausteine: list[dict], instructions: str = "") -> None:
async def sort_bausteine(topic: str, bausteine: list[dict], instructions: str = "", provider: str = DEFAULT_PROVIDER) -> None:
_sorting.add(topic)
try:
prompt = _build_sort_prompt(topic, bausteine, instructions)
returncode, stdout, stderr = await _run_claude("sort-" + topic, prompt, 300, tools=None, model=MODEL_BAUSTEIN_GEN)
returncode, stdout, stderr = await run_agent("sort-" + topic, prompt, 600, provider=provider, role="fast", capabilities="none")
if returncode != 0:
return
ids = _parse_json(stdout)