This commit is contained in:
Team3
2026-06-01 14:27:00 +02:00
parent aedb44ac6e
commit bd4639b3aa
8 changed files with 454 additions and 9 deletions

View File

@@ -30,3 +30,4 @@ CLAUDE_CLI = "claude"
MODEL_GUIDE = "claude-opus-4-8"
MODEL_BAUSTEIN_GEN = "claude-sonnet-4-6"
MODEL_BAUSTEIN_REWORK = "claude-sonnet-4-6"
MODEL_CHAT = "claude-sonnet-4-6"

View File

@@ -16,6 +16,7 @@ from config import (
MODEL_GUIDE,
MODEL_BAUSTEIN_GEN,
MODEL_BAUSTEIN_REWORK,
MODEL_CHAT,
STORAGE_DIR,
)
from database import (
@@ -556,6 +557,48 @@ def _build_topic_suggest_prompt(problem: str, existing_topics: list[str]) -> str
return template.replace("{problem}", problem).replace("{existing}", existing)
def _build_guide_chat_prompt(topic: str, format_name: str, section: str, outline: str, messages: list[dict]) -> str:
transcript = "\n".join(
f"{'Nutzer' if m.get('role') == 'user' else 'Assistent'}: {m.get('content', '')}"
for m in messages
)
outline_block = outline.strip() or "(keine)"
section_block = section.strip() or "(kein Abschnitt erkannt)"
return f"""Du bist ein hilfreicher Tutor zum Lern-Guide "{topic}" (Format: {format_name}). Ein Leser stellt dir Fragen, während er den Guide liest.
GLIEDERUNG DES GUIDES:
{outline_block}
AKTUELLER ABSCHNITT, DEN DER LESER GERADE LIEST:
{section_block}
BISHERIGER CHAT-VERLAUF:
{transcript}
Antworte als Assistent auf die letzte Nutzer-Nachricht.
WICHTIG Antwortstil:
- KURZ und EINFACH: 13 Sätze, klare Sprache.
- Keine Einleitung, keine Wiederholung der Frage, kein Markdown-Drumherum.
- Beantworte nur die Frage; nutze den Abschnitt und die Gliederung als Kontext.
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:
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
)
if returncode != 0:
return "Entschuldigung, das hat nicht geklappt. Bitte versuche es erneut."
reply = stdout.strip()
return reply or "Entschuldigung, ich habe keine Antwort erhalten."
except Exception:
return "Entschuldigung, das hat nicht geklappt. Bitte versuche es erneut."
async def suggest_topics(problem: str, existing_topics: list[str] | None = None) -> list[dict]:
try:
prompt = _build_topic_suggest_prompt(problem, existing_topics or [])

View File

@@ -75,3 +75,18 @@ class TopicSuggestRequest(BaseModel):
class TopicSuggestion(BaseModel):
title: str
reason: str
class ChatMessage(BaseModel):
role: Literal["user", "assistant"]
content: str = Field(min_length=1, max_length=8000)
class GuideChatRequest(BaseModel):
section: str = Field(default="", max_length=20000)
outline: str = Field(default="", max_length=8000)
messages: list[ChatMessage] = Field(min_length=1)
class GuideChatResponse(BaseModel):
reply: str

View File

@@ -11,11 +11,12 @@ from database import (
create_baustein as db_create_baustein, list_bausteine, get_baustein, delete_baustein as db_delete_baustein,
list_suggestions, get_suggestion, update_suggestion, delete_suggestion,
)
from generator import generate_guide, rework_guide, cancel_guide, generate_suggestions, generate_baustein_detail, rework_baustein, sort_bausteine, suggest_topics, is_suggestions_generating, is_sorting
from generator import generate_guide, rework_guide, cancel_guide, generate_suggestions, generate_baustein_detail, rework_baustein, sort_bausteine, suggest_topics, chat_with_guide, is_suggestions_generating, is_sorting
from models import (
GuideCreateRequest, GuideReworkRequest, GuideResponse,
BausteinCreateRequest, BausteinReworkRequest, BausteinSortRequest, BausteinResponse, SuggestionResponse,
TopicSuggestRequest, TopicSuggestion,
GuideChatRequest, GuideChatResponse,
)
from paths import final_paths
@@ -89,6 +90,18 @@ async def rework(guide_id: str, req: GuideReworkRequest):
return {"ok": True}
@router.post("/guides/{guide_id}/chat", response_model=GuideChatResponse)
async def guide_chat(guide_id: str, req: GuideChatRequest):
guide = await get_guide(guide_id)
if guide is None:
raise HTTPException(404, "Guide nicht gefunden")
reply = await chat_with_guide(
guide["topic"], guide["format"], req.section, req.outline,
[m.model_dump() for m in req.messages],
)
return {"reply": reply}
@router.post("/guides/{guide_id}/cancel")
async def cancel(guide_id: str):
cancelled = await cancel_guide(guide_id)