From 63280d88d69d2016c121a5d078434d835f5a9818 Mon Sep 17 00:00:00 2001 From: team3 Date: Fri, 12 Jun 2026 07:53:09 +0200 Subject: [PATCH] Backend: globale Agent-Semaphores (batch 12 / interactive 4) Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/agents.py | 17 +++++++++++++---- backend/config.py | 6 ++++++ backend/generator.py | 14 +++++++------- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/backend/agents.py b/backend/agents.py index 70ea28b..e70bd09 100644 --- a/backend/agents.py +++ b/backend/agents.py @@ -14,12 +14,18 @@ import time import urllib.request from pathlib import Path -from config import PROVIDERS, DEFAULT_PROVIDER +from config import PROVIDERS, DEFAULT_PROVIDER, MAX_CONCURRENT_AGENTS, MAX_CONCURRENT_INTERACTIVE log = logging.getLogger("creator.agents") _active_processes: dict[str, asyncio.subprocess.Process] = {} +# Deckelt die realen CLI-Prozesse — unabhängig von der Pipeline-Semaphore in +# generator.py. Acquire passiert VOR dem Spawn, damit Wartezeit in der Queue +# nicht gegen den Agent-Timeout zählt. +_batch_sem = asyncio.Semaphore(MAX_CONCURRENT_AGENTS) +_interactive_sem = asyncio.Semaphore(MAX_CONCURRENT_INTERACTIVE) + # Capability → Claude --allowedTools _CLAUDE_TOOLS = { "full": "Write,Bash,Read,WebSearch,WebFetch", @@ -73,14 +79,17 @@ async def run_agent( provider: str = DEFAULT_PROVIDER, role: str = "fast", capabilities: str = "none", + lane: str = "batch", ) -> tuple[int, str, str]: if provider not in PROVIDERS: return 1, "", f"Unbekannter Provider: {provider}" if shutil.which(PROVIDERS[provider]["cli"]) is None: return 1, "", f"CLI '{PROVIDERS[provider]['cli']}' nicht installiert (Provider: {provider})" - if PROVIDERS[provider]["cli"] == "opencode": - return await _run_opencode(agent_key, prompt, timeout, provider, role, capabilities) - return await _run_claude_cli(agent_key, prompt, timeout, role, capabilities) + sem = _interactive_sem if lane == "interactive" else _batch_sem + async with sem: + if PROVIDERS[provider]["cli"] == "opencode": + return await _run_opencode(agent_key, prompt, timeout, provider, role, capabilities) + return await _run_claude_cli(agent_key, prompt, timeout, role, capabilities) async def _communicate(agent_key: str, cmd: list[str], stdin_data: bytes | None, timeout: int) -> tuple[int, str, str]: diff --git a/backend/config.py b/backend/config.py index 550caca..42184d3 100644 --- a/backend/config.py +++ b/backend/config.py @@ -9,6 +9,12 @@ PROJECTS_DIR = PROJECT_ROOT / "projects" MAX_CONCURRENT_GENERATIONS = 10 +# Deckel für gleichzeitige CLI-Agenten-Prozesse (über alle Generierungen hinweg). +# Eigene Spur für interaktive Aufrufe (Chat, Elemente), damit sie nicht hinter +# laufenden Writern in der Warteschlange hängen. +MAX_CONCURRENT_AGENTS = 12 +MAX_CONCURRENT_INTERACTIVE = 4 + # Timeouts pro Agenten-Schritt: (Basis-Sekunden, Sekunden pro Baustein/Section). # Gilt für alle Provider gleich — wer zu langsam ist, wird neu gestartet bzw. überholt. TIMEOUTS = { diff --git a/backend/generator.py b/backend/generator.py index 8892964..470b32d 100644 --- a/backend/generator.py +++ b/backend/generator.py @@ -1349,7 +1349,7 @@ async def chat_with_guide(topic: str, format_name: str, section: str, outline: s try: prompt = _build_guide_chat_prompt(topic, format_name, section, outline, messages) returncode, stdout, stderr = await run_agent( - "chat-" + str(uuid.uuid4()), prompt, 240, provider=provider, role="fast", capabilities="none" + "chat-" + str(uuid.uuid4()), prompt, 240, provider=provider, role="fast", capabilities="none", lane="interactive" ) if returncode != 0: return "Entschuldigung, das hat nicht geklappt. Bitte versuche es erneut." @@ -1434,7 +1434,7 @@ async def generate_element(topic: str, hint: str, provider: str = DEFAULT_PROVID context=_topic_context(topic), ) returncode, stdout, _ = await run_agent( - "element-" + str(uuid.uuid4()), prompt, 240, provider=provider, role="fast", capabilities="none" + "element-" + str(uuid.uuid4()), prompt, 240, provider=provider, role="fast", capabilities="none", lane="interactive" ) if returncode != 0: return fallback @@ -1473,7 +1473,7 @@ async def check_element(element: dict, provider: str = DEFAULT_PROVIDER) -> list # Schritt 1: Recherche — breit Kandidaten sammeln prompt = _prompt("Element-Check", topic=element["topic"], element_json=element_json, context=context) returncode, stdout, _ = await run_agent( - "element-check-" + str(uuid.uuid4()), prompt, 240, provider=provider, role="fast", capabilities="none" + "element-check-" + str(uuid.uuid4()), prompt, 240, provider=provider, role="fast", capabilities="none", lane="interactive" ) if returncode != 0: return None @@ -1491,7 +1491,7 @@ async def check_element(element: dict, provider: str = DEFAULT_PROVIDER) -> list context=context, ) returncode, stdout, _ = await run_agent( - "element-verify-" + str(uuid.uuid4()), prompt, 240, provider=provider, role="fast", capabilities="none" + "element-verify-" + str(uuid.uuid4()), prompt, 240, provider=provider, role="fast", capabilities="none", lane="interactive" ) if returncode != 0: return None @@ -1544,7 +1544,7 @@ async def chat_with_element(element: dict, messages: list[dict], provider: str = ) prompt = _prompt("Element-Chat", topic=element["topic"], element_json=_element_json(element), transcript=transcript) returncode, stdout, _ = await run_agent( - "element-chat-" + str(uuid.uuid4()), prompt, 240, provider=provider, role="fast", capabilities="none" + "element-chat-" + str(uuid.uuid4()), prompt, 240, provider=provider, role="fast", capabilities="none", lane="interactive" ) if returncode != 0: return fehler, [] @@ -1564,7 +1564,7 @@ async def style_element(element: dict, provider: str = DEFAULT_PROVIDER) -> list try: prompt = _prompt("Element-Stil", topic=element["topic"], element_json=_element_json(element)) returncode, stdout, _ = await run_agent( - "element-stil-" + str(uuid.uuid4()), prompt, 240, provider=provider, role="fast", capabilities="none" + "element-stil-" + str(uuid.uuid4()), prompt, 240, provider=provider, role="fast", capabilities="none", lane="interactive" ) if returncode != 0: return None @@ -1587,7 +1587,7 @@ async def refine_suggestion(element: dict, suggestion: dict, instruction: str, p instruction=instruction, ) returncode, stdout, _ = await run_agent( - "element-refine-" + str(uuid.uuid4()), prompt, 240, provider=provider, role="fast", capabilities="none" + "element-refine-" + str(uuid.uuid4()), prompt, 240, provider=provider, role="fast", capabilities="none", lane="interactive" ) if returncode != 0: return None