Alle 9 Fixes sind implementiert. Hier die Zusammenfassung:
Fix 1 -- Opening-Prompt: processSessionOpening in serviceCommcoach.py prüft jetzt ob es die erste Session ist (isFirstSession) und gibt der AI einen expliziten Prompt, der das Erfinden von Kontext verbietet.
Fix 2 -- Stabiler Transcript: onresult in CommcoachCoachingView.tsx nutzt jetzt processedResultIndexRef um nur neue Results zu verarbeiten. Finalisierte Teile werden stabil akkumuliert, kein Flackern mehr.
Fix 3 -- Hintergrundgeräusche-Timeout: Neuer silenceTimerRef mit 5s Timeout. Wenn nach onspeechstart kein Text kommt, wird isUserSpeaking automatisch zurückgesetzt.
Fix 4 -- Stop-Button: "Stop" Button erscheint im Session-Header wenn TTS läuft (via isTtsPlaying State, synchronisiert per 200ms Interval mit isTtsPlayingRef).
Fix 5 -- Weitersprechen-Button: lastTtsAudioRef speichert das zuletzt gespielte Audio. stopTts setzt wasInterrupted = true. "Weitersprechen" Button erscheint nach Unterbrechung und spielt das Audio erneut ab.
Fix 6 -- Paralleles TTS: Neue _generateAndEmitTts() Hilfsfunktion. In processMessage und processSessionOpening wird TTS als asyncio.create_task parallel zu _emitChunkedResponse gestartet.
Fix 7 -- JSON-Response: Die AI antwortet jetzt als JSON mit text, speech, documents. Neuer Prompt-Block wird in buildCoachingSystemPrompt angehängt. _parseAiJsonResponse() und _saveGeneratedDocument() im Backend. processMessage und processSessionOpening nutzen die neue Struktur.
Fix 8 -- Loading-States: Neuer actionLoading State in useCommcoach. Alle async Funktionen setzen setActionLoading('key') vor dem Await und null im finally. Buttons zeigen Loading-Text und werden disabled.
Fix 9 -- Umlaute: Alle deutschen Strings in allen CommCoach-Dateien (Backend + Frontend) korrigiert: ae->ä, oe->ö, ue->ü.
This commit is contained in:
parent
92d9a2a0d5
commit
12b0d3d36e
6 changed files with 212 additions and 168 deletions
|
|
@ -87,6 +87,74 @@ CHUNK_WORD_SIZE = 4
|
||||||
CHUNK_DELAY_SECONDS = 0.05
|
CHUNK_DELAY_SECONDS = 0.05
|
||||||
|
|
||||||
|
|
||||||
|
def _parseAiJsonResponse(rawText: str) -> Dict[str, Any]:
|
||||||
|
"""Parse the structured JSON response from AI. Strips optional markdown code fences."""
|
||||||
|
text = rawText.strip()
|
||||||
|
if text.startswith("```"):
|
||||||
|
lines = text.split("\n")
|
||||||
|
lines = lines[1:]
|
||||||
|
if lines and lines[-1].strip() == "```":
|
||||||
|
lines = lines[:-1]
|
||||||
|
text = "\n".join(lines)
|
||||||
|
try:
|
||||||
|
return json.loads(text)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
logger.warning(f"AI JSON parse failed, using raw text: {text[:200]}")
|
||||||
|
return {"text": rawText.strip(), "speech": "", "documents": []}
|
||||||
|
|
||||||
|
|
||||||
|
async def _generateAndEmitTts(sessionId: str, speechText: str, currentUser, mandateId: str,
|
||||||
|
instanceId: str, interface):
|
||||||
|
"""Generate TTS audio from speech text and emit as SSE event."""
|
||||||
|
if not speechText:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
from modules.interfaces.interfaceVoiceObjects import getVoiceInterface
|
||||||
|
import base64
|
||||||
|
voiceInterface = getVoiceInterface(currentUser, mandateId)
|
||||||
|
profile = interface.getProfile(str(currentUser.id), instanceId)
|
||||||
|
language = profile.get("preferredLanguage", "de-DE") if profile else "de-DE"
|
||||||
|
voiceName = profile.get("preferredVoice") if profile else None
|
||||||
|
ttsResult = await voiceInterface.textToSpeech(
|
||||||
|
text=_stripMarkdownForTts(speechText),
|
||||||
|
languageCode=language,
|
||||||
|
voiceName=voiceName,
|
||||||
|
)
|
||||||
|
if ttsResult and isinstance(ttsResult, dict):
|
||||||
|
audioBytes = ttsResult.get("audioContent")
|
||||||
|
if audioBytes:
|
||||||
|
audioB64 = base64.b64encode(
|
||||||
|
audioBytes if isinstance(audioBytes, bytes) else audioBytes.encode()
|
||||||
|
).decode()
|
||||||
|
await emitSessionEvent(sessionId, "ttsAudio", {"audio": audioB64, "format": "mp3"})
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"TTS failed for session {sessionId}: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
async def _saveGeneratedDocument(doc: Dict[str, Any], contextId: str, userId: str,
|
||||||
|
mandateId: str, instanceId: str, interface, sessionId: str):
|
||||||
|
"""Save a document generated by AI and emit SSE event."""
|
||||||
|
from .datamodelCommcoach import CoachingDocument
|
||||||
|
try:
|
||||||
|
title = doc.get("title", "Dokument")
|
||||||
|
content = doc.get("content", "")
|
||||||
|
docData = CoachingDocument(
|
||||||
|
contextId=contextId,
|
||||||
|
userId=userId,
|
||||||
|
mandateId=mandateId,
|
||||||
|
instanceId=instanceId,
|
||||||
|
fileName=f"{title}.md",
|
||||||
|
mimeType="text/markdown",
|
||||||
|
fileSize=len(content.encode()),
|
||||||
|
extractedText=content,
|
||||||
|
summary=title,
|
||||||
|
).model_dump()
|
||||||
|
created = interface.createDocument(docData)
|
||||||
|
await emitSessionEvent(sessionId, "documentCreated", created)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Failed to save generated document: {e}")
|
||||||
|
|
||||||
|
|
||||||
async def _emitChunkedResponse(sessionId: str, createdMsg: Dict[str, Any], fullText: str):
|
async def _emitChunkedResponse(sessionId: str, createdMsg: Dict[str, Any], fullText: str):
|
||||||
"""Emit response as messageChunk events for progressive display, then the full message."""
|
"""Emit response as messageChunk events for progressive display, then the full message."""
|
||||||
msgId = createdMsg.get("id")
|
msgId = createdMsg.get("id")
|
||||||
|
|
@ -199,7 +267,7 @@ class CommcoachService:
|
||||||
try:
|
try:
|
||||||
summaryPrompt = aiPrompts.buildEarlierConversationSummaryPrompt(toSummarize)
|
summaryPrompt = aiPrompts.buildEarlierConversationSummaryPrompt(toSummarize)
|
||||||
summaryResponse = await self._callAi(
|
summaryResponse = await self._callAi(
|
||||||
"Du fasst Coaching-Gespraeche praezise zusammen.", summaryPrompt
|
"Du fasst Coaching-Gespräche präzise zusammen.", summaryPrompt
|
||||||
)
|
)
|
||||||
if summaryResponse and summaryResponse.errorCount == 0 and summaryResponse.content:
|
if summaryResponse and summaryResponse.errorCount == 0 and summaryResponse.content:
|
||||||
earlierSummary = summaryResponse.content.strip()
|
earlierSummary = summaryResponse.content.strip()
|
||||||
|
|
@ -236,7 +304,7 @@ class CommcoachService:
|
||||||
)
|
)
|
||||||
|
|
||||||
if retrievalResult.get("intent") == RetrievalIntent.SUMMARIZE_ALL:
|
if retrievalResult.get("intent") == RetrievalIntent.SUMMARIZE_ALL:
|
||||||
systemPrompt += "\n\nWICHTIG: Der Benutzer moechte eine Gesamtzusammenfassung. Erstelle eine umfassende Zusammenfassung aller genannten Sessions und der aktuellen Session."
|
systemPrompt += "\n\nWICHTIG: Der Benutzer möchte eine Gesamtzusammenfassung. Erstelle eine umfassende Zusammenfassung aller genannten Sessions und der aktuellen Session."
|
||||||
|
|
||||||
# Call AI
|
# Call AI
|
||||||
await emitSessionEvent(sessionId, "status", {"label": "Coach denkt nach..."})
|
await emitSessionEvent(sessionId, "status", {"label": "Coach denkt nach..."})
|
||||||
|
|
@ -248,47 +316,38 @@ class CommcoachService:
|
||||||
await emitSessionEvent(sessionId, "error", {"message": f"AI error: {str(e)}"})
|
await emitSessionEvent(sessionId, "error", {"message": f"AI error: {str(e)}"})
|
||||||
return createdUserMsg
|
return createdUserMsg
|
||||||
|
|
||||||
responseText = aiResponse.content.strip() if aiResponse and aiResponse.errorCount == 0 else "Entschuldigung, ich konnte gerade nicht antworten. Bitte versuche es erneut."
|
responseRaw = aiResponse.content.strip() if aiResponse and aiResponse.errorCount == 0 else ""
|
||||||
|
|
||||||
|
if not responseRaw:
|
||||||
|
parsed = {"text": "Entschuldigung, ich konnte gerade nicht antworten. Bitte versuche es erneut.", "speech": "", "documents": []}
|
||||||
|
else:
|
||||||
|
parsed = _parseAiJsonResponse(responseRaw)
|
||||||
|
|
||||||
|
textContent = parsed.get("text", "")
|
||||||
|
speechContent = parsed.get("speech", "")
|
||||||
|
documents = parsed.get("documents", [])
|
||||||
|
|
||||||
|
for doc in documents:
|
||||||
|
await _saveGeneratedDocument(doc, contextId, self.userId, self.mandateId, self.instanceId, interface, sessionId)
|
||||||
|
|
||||||
# Store assistant message
|
|
||||||
assistantMsg = CoachingMessage(
|
assistantMsg = CoachingMessage(
|
||||||
sessionId=sessionId,
|
sessionId=sessionId,
|
||||||
contextId=contextId,
|
contextId=contextId,
|
||||||
userId=self.userId,
|
userId=self.userId,
|
||||||
role=CoachingMessageRole.ASSISTANT,
|
role=CoachingMessageRole.ASSISTANT,
|
||||||
content=responseText,
|
content=textContent,
|
||||||
contentType=CoachingMessageContentType.TEXT,
|
contentType=CoachingMessageContentType.TEXT,
|
||||||
).model_dump()
|
).model_dump()
|
||||||
createdAssistantMsg = interface.createMessage(assistantMsg)
|
createdAssistantMsg = interface.createMessage(assistantMsg)
|
||||||
|
|
||||||
# Update session message count
|
|
||||||
messages = interface.getMessages(sessionId)
|
messages = interface.getMessages(sessionId)
|
||||||
interface.updateSession(sessionId, {"messageCount": len(messages)})
|
interface.updateSession(sessionId, {"messageCount": len(messages)})
|
||||||
|
|
||||||
await _emitChunkedResponse(sessionId, createdAssistantMsg, responseText)
|
ttsTask = asyncio.create_task(
|
||||||
|
_generateAndEmitTts(sessionId, speechContent, self.currentUser, self.mandateId, self.instanceId, interface)
|
||||||
if responseText:
|
)
|
||||||
try:
|
await _emitChunkedResponse(sessionId, createdAssistantMsg, textContent)
|
||||||
from modules.interfaces.interfaceVoiceObjects import getVoiceInterface
|
await ttsTask
|
||||||
import base64
|
|
||||||
voiceInterface = getVoiceInterface(self.currentUser, self.mandateId)
|
|
||||||
profile = interface.getProfile(self.userId, self.instanceId)
|
|
||||||
language = profile.get("preferredLanguage", "de-DE") if profile else "de-DE"
|
|
||||||
voiceName = profile.get("preferredVoice") if profile else None
|
|
||||||
ttsResult = await voiceInterface.textToSpeech(
|
|
||||||
text=_stripMarkdownForTts(responseText),
|
|
||||||
languageCode=language,
|
|
||||||
voiceName=voiceName,
|
|
||||||
)
|
|
||||||
if ttsResult and isinstance(ttsResult, dict):
|
|
||||||
audioBytes = ttsResult.get("audioContent")
|
|
||||||
if audioBytes:
|
|
||||||
audioB64 = base64.b64encode(
|
|
||||||
audioBytes if isinstance(audioBytes, bytes) else audioBytes.encode()
|
|
||||||
).decode()
|
|
||||||
await emitSessionEvent(sessionId, "ttsAudio", {"audio": audioB64, "format": "mp3"})
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"TTS failed for text message session {sessionId}: {e}")
|
|
||||||
|
|
||||||
await emitSessionEvent(sessionId, "complete", {})
|
await emitSessionEvent(sessionId, "complete", {})
|
||||||
return createdAssistantMsg
|
return createdAssistantMsg
|
||||||
|
|
@ -326,11 +385,15 @@ class CommcoachService:
|
||||||
documentSummaries=documentSummaries,
|
documentSummaries=documentSummaries,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
isFirstSession = not previousSessionSummaries or len(previousSessionSummaries) == 0
|
||||||
|
|
||||||
if persona and persona.get("key") != "coach":
|
if persona and persona.get("key") != "coach":
|
||||||
personaLabel = persona.get("label", "Gespraechspartner")
|
personaLabel = persona.get("label", "Gesprächspartner")
|
||||||
openingUserPrompt = f"Beginne das Gespraech in deiner Rolle als {personaLabel}. Stelle dich kurz vor und eroeffne die Situation gemaess deiner Rollenbeschreibung."
|
openingUserPrompt = f"Beginne das Gespräch in deiner Rolle als {personaLabel}. Stelle dich kurz vor und eröffne die Situation gemäss deiner Rollenbeschreibung."
|
||||||
|
elif isFirstSession:
|
||||||
|
openingUserPrompt = "Dies ist die ERSTE Session zu diesem Thema. Begrüsse den Benutzer, stelle das Thema kurz vor und stelle eine offene Einstiegsfrage. Erfinde KEINE vorherigen Gespräche oder Zusammenfassungen."
|
||||||
else:
|
else:
|
||||||
openingUserPrompt = "Beginne die Coaching-Session mit einer kurzen Begruesssung, fasse in einem Satz zusammen wo wir stehen (falls vorherige Sessions), und stelle eine gezielte Einstiegsfrage zum Thema."
|
openingUserPrompt = "Begrüsse den Benutzer zurück, fasse in einem Satz zusammen wo wir stehen, und stelle eine gezielte Einstiegsfrage."
|
||||||
|
|
||||||
try:
|
try:
|
||||||
aiResponse = await self._callAi(systemPrompt, openingUserPrompt)
|
aiResponse = await self._callAi(systemPrompt, openingUserPrompt)
|
||||||
|
|
@ -340,46 +403,41 @@ class CommcoachService:
|
||||||
await emitSessionEvent(sessionId, "complete", {})
|
await emitSessionEvent(sessionId, "complete", {})
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
openingContent = (
|
responseRaw = (
|
||||||
aiResponse.content.strip()
|
aiResponse.content.strip()
|
||||||
if aiResponse and aiResponse.errorCount == 0
|
if aiResponse and aiResponse.errorCount == 0
|
||||||
else f"Willkommen zur Coaching-Session zum Thema \"{context.get('title')}\". Was moechtest du heute besprechen?"
|
else ""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not responseRaw:
|
||||||
|
parsed = {"text": f"Willkommen zur Coaching-Session zum Thema \"{context.get('title')}\". Was möchtest du heute besprechen?", "speech": "", "documents": []}
|
||||||
|
else:
|
||||||
|
parsed = _parseAiJsonResponse(responseRaw)
|
||||||
|
|
||||||
|
textContent = parsed.get("text", "")
|
||||||
|
speechContent = parsed.get("speech", "")
|
||||||
|
documents = parsed.get("documents", [])
|
||||||
|
|
||||||
|
for doc in documents:
|
||||||
|
await _saveGeneratedDocument(doc, contextId, self.userId, self.mandateId, self.instanceId, interface, sessionId)
|
||||||
|
|
||||||
assistantMsg = CoachingMessage(
|
assistantMsg = CoachingMessage(
|
||||||
sessionId=sessionId,
|
sessionId=sessionId,
|
||||||
contextId=contextId,
|
contextId=contextId,
|
||||||
userId=self.userId,
|
userId=self.userId,
|
||||||
role=CoachingMessageRole.ASSISTANT,
|
role=CoachingMessageRole.ASSISTANT,
|
||||||
content=openingContent,
|
content=textContent,
|
||||||
contentType=CoachingMessageContentType.TEXT,
|
contentType=CoachingMessageContentType.TEXT,
|
||||||
).model_dump()
|
).model_dump()
|
||||||
createdMsg = interface.createMessage(assistantMsg)
|
createdMsg = interface.createMessage(assistantMsg)
|
||||||
interface.updateSession(sessionId, {"messageCount": 1})
|
interface.updateSession(sessionId, {"messageCount": 1})
|
||||||
|
|
||||||
await _emitChunkedResponse(sessionId, createdMsg, openingContent)
|
ttsTask = asyncio.create_task(
|
||||||
if openingContent:
|
_generateAndEmitTts(sessionId, speechContent, self.currentUser, self.mandateId, self.instanceId, interface)
|
||||||
try:
|
)
|
||||||
from modules.interfaces.interfaceVoiceObjects import getVoiceInterface
|
await _emitChunkedResponse(sessionId, createdMsg, textContent)
|
||||||
import base64
|
await ttsTask
|
||||||
voiceInterface = getVoiceInterface(self.currentUser, self.mandateId)
|
|
||||||
profile = interface.getProfile(self.userId, self.instanceId)
|
|
||||||
language = profile.get("preferredLanguage", "de-DE") if profile else "de-DE"
|
|
||||||
voiceName = profile.get("preferredVoice") if profile else None
|
|
||||||
ttsResult = await voiceInterface.textToSpeech(
|
|
||||||
text=_stripMarkdownForTts(openingContent),
|
|
||||||
languageCode=language,
|
|
||||||
voiceName=voiceName,
|
|
||||||
)
|
|
||||||
if ttsResult and isinstance(ttsResult, dict):
|
|
||||||
audioBytes = ttsResult.get("audioContent")
|
|
||||||
if audioBytes:
|
|
||||||
audioB64 = base64.b64encode(
|
|
||||||
audioBytes if isinstance(audioBytes, bytes) else audioBytes.encode()
|
|
||||||
).decode()
|
|
||||||
await emitSessionEvent(sessionId, "ttsAudio", {"audio": audioB64, "format": "mp3"})
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"TTS failed for opening: {e}")
|
|
||||||
await emitSessionEvent(sessionId, "complete", {})
|
await emitSessionEvent(sessionId, "complete", {})
|
||||||
|
|
||||||
logger.info(f"CommCoach session opening completed: {sessionId}")
|
logger.info(f"CommCoach session opening completed: {sessionId}")
|
||||||
|
|
@ -425,36 +483,7 @@ class CommcoachService:
|
||||||
await emitSessionEvent(sessionId, "error", {"message": msg, "detail": sttError})
|
await emitSessionEvent(sessionId, "error", {"message": msg, "detail": sttError})
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
# Process through normal pipeline
|
|
||||||
result = await self.processMessage(sessionId, contextId, transcribedText, interface)
|
result = await self.processMessage(sessionId, contextId, transcribedText, interface)
|
||||||
|
|
||||||
# Generate TTS for the response
|
|
||||||
assistantContent = result.get("content", "")
|
|
||||||
if assistantContent:
|
|
||||||
await emitSessionEvent(sessionId, "status", {"label": "Antwort wird gesprochen..."})
|
|
||||||
try:
|
|
||||||
profile = interface.getProfile(self.userId, self.instanceId)
|
|
||||||
voiceName = profile.get("preferredVoice") if profile else None
|
|
||||||
|
|
||||||
ttsResult = await voiceInterface.textToSpeech(
|
|
||||||
text=_stripMarkdownForTts(assistantContent),
|
|
||||||
languageCode=language,
|
|
||||||
voiceName=voiceName,
|
|
||||||
)
|
|
||||||
if ttsResult and isinstance(ttsResult, dict):
|
|
||||||
import base64
|
|
||||||
audioBytes = ttsResult.get("audioContent")
|
|
||||||
if audioBytes:
|
|
||||||
audioB64 = base64.b64encode(
|
|
||||||
audioBytes if isinstance(audioBytes, bytes) else audioBytes.encode()
|
|
||||||
).decode()
|
|
||||||
await emitSessionEvent(sessionId, "ttsAudio", {
|
|
||||||
"audio": audioB64,
|
|
||||||
"format": "mp3",
|
|
||||||
})
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"TTS failed for session {sessionId}: {e}")
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
async def completeSession(self, sessionId: str, interface) -> Dict[str, Any]:
|
async def completeSession(self, sessionId: str, interface) -> Dict[str, Any]:
|
||||||
|
|
@ -484,7 +513,7 @@ class CommcoachService:
|
||||||
# Generate summary
|
# Generate summary
|
||||||
try:
|
try:
|
||||||
summaryPrompt = aiPrompts.buildSummaryPrompt(messages, context.get("title", "Coaching"))
|
summaryPrompt = aiPrompts.buildSummaryPrompt(messages, context.get("title", "Coaching"))
|
||||||
summaryResponse = await self._callAi("Du bist ein praeziser Zusammenfasser.", summaryPrompt)
|
summaryResponse = await self._callAi("Du bist ein präziser Zusammenfasser.", summaryPrompt)
|
||||||
summary = summaryResponse.content.strip() if summaryResponse and summaryResponse.errorCount == 0 else None
|
summary = summaryResponse.content.strip() if summaryResponse and summaryResponse.errorCount == 0 else None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Summary generation failed: {e}")
|
logger.warning(f"Summary generation failed: {e}")
|
||||||
|
|
@ -507,7 +536,7 @@ class CommcoachService:
|
||||||
# Extract tasks
|
# Extract tasks
|
||||||
try:
|
try:
|
||||||
taskPrompt = aiPrompts.buildTaskExtractionPrompt(messages)
|
taskPrompt = aiPrompts.buildTaskExtractionPrompt(messages)
|
||||||
taskResponse = await self._callAi("Du extrahierst Aufgaben aus Gespraechen.", taskPrompt)
|
taskResponse = await self._callAi("Du extrahierst Aufgaben aus Gesprächen.", taskPrompt)
|
||||||
if taskResponse and taskResponse.errorCount == 0:
|
if taskResponse and taskResponse.errorCount == 0:
|
||||||
extractedTasks = aiPrompts.parseJsonResponse(taskResponse.content, [])
|
extractedTasks = aiPrompts.parseJsonResponse(taskResponse.content, [])
|
||||||
if isinstance(extractedTasks, list):
|
if isinstance(extractedTasks, list):
|
||||||
|
|
|
||||||
|
|
@ -24,16 +24,16 @@ def buildResumeGreetingPrompt(messages: List[Dict[str, Any]], contextTitle: str)
|
||||||
for msg in recent:
|
for msg in recent:
|
||||||
role = "Benutzer" if msg.get("role") == "user" else "Coach"
|
role = "Benutzer" if msg.get("role") == "user" else "Coach"
|
||||||
conversation += f"\n{role}: {msg.get('content', '')[:200]}"
|
conversation += f"\n{role}: {msg.get('content', '')[:200]}"
|
||||||
return f"""Der User kehrt zur laufenden Coaching-Session zum Thema "{contextTitle}" zurueck.
|
return f"""Der User kehrt zur laufenden Coaching-Session zum Thema "{contextTitle}" zurück.
|
||||||
Bisheriger Verlauf:
|
Bisheriger Verlauf:
|
||||||
{conversation}
|
{conversation}
|
||||||
|
|
||||||
Erstelle eine kurze, freundliche Begruesssung fuer den Wiedereinstieg (2-3 Saetze):
|
Erstelle eine kurze, freundliche Begrüssung für den Wiedereinstieg (2-3 Sätze):
|
||||||
- Begruesse den User zurueck
|
- Begrüsse den User zurück
|
||||||
- Fasse in einem Satz zusammen, worum es zuletzt ging
|
- Fasse in einem Satz zusammen, worum es zuletzt ging
|
||||||
- Lade ein, dort weiterzumachen oder eine neue Frage zu stellen
|
- Lade ein, dort weiterzumachen oder eine neue Frage zu stellen
|
||||||
|
|
||||||
Antworte NUR mit der Begruesssung, keine Erklaerungen."""
|
Antworte NUR mit der Begrüssung, keine Erklärungen."""
|
||||||
|
|
||||||
|
|
||||||
def buildEarlierConversationSummaryPrompt(messages: List[Dict[str, Any]]) -> str:
|
def buildEarlierConversationSummaryPrompt(messages: List[Dict[str, Any]]) -> str:
|
||||||
|
|
@ -43,12 +43,12 @@ def buildEarlierConversationSummaryPrompt(messages: List[Dict[str, Any]]) -> str
|
||||||
role = "Benutzer" if msg.get("role") == "user" else "Coach"
|
role = "Benutzer" if msg.get("role") == "user" else "Coach"
|
||||||
conversation += f"\n{role}: {msg.get('content', '')}"
|
conversation += f"\n{role}: {msg.get('content', '')}"
|
||||||
|
|
||||||
return f"""Fasse das folgende Coaching-Gespraech in 4-6 Saetzen zusammen.
|
return f"""Fasse das folgende Coaching-Gespräch in 4-6 Sätzen zusammen.
|
||||||
Behalte: Kernthemen, wichtige Erkenntnisse, erwaehnte Aufgaben, emotionale Wendepunkte, Fortschritte.
|
Behalte: Kernthemen, wichtige Erkenntnisse, erwähnte Aufgaben, emotionale Wendepunkte, Fortschritte.
|
||||||
Entferne Wiederholungen und Fuelltext.
|
Entferne Wiederholungen und Fülltext.
|
||||||
Antworte NUR mit der Zusammenfassung, keine Erklaerungen.
|
Antworte NUR mit der Zusammenfassung, keine Erklärungen.
|
||||||
|
|
||||||
Gespraech:
|
Gespräch:
|
||||||
{conversation}"""
|
{conversation}"""
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -115,46 +115,61 @@ def buildCoachingSystemPrompt(
|
||||||
if persona.get("systemPromptOverride"):
|
if persona.get("systemPromptOverride"):
|
||||||
prompt = persona["systemPromptOverride"]
|
prompt = persona["systemPromptOverride"]
|
||||||
else:
|
else:
|
||||||
personaLabel = persona.get("label", "Gespraechspartner")
|
personaLabel = persona.get("label", "Gesprächspartner")
|
||||||
personaDescription = persona.get("description", "")
|
personaDescription = persona.get("description", "")
|
||||||
personaGender = persona.get("gender", "")
|
personaGender = persona.get("gender", "")
|
||||||
genderHint = " (weiblich)" if personaGender == "f" else " (maennlich)" if personaGender == "m" else ""
|
genderHint = " (weiblich)" if personaGender == "f" else " (männlich)" if personaGender == "m" else ""
|
||||||
prompt = f"""Du spielst die Rolle von "{personaLabel}"{genderHint} in einem Roleplay-Szenario zum Thema: "{contextTitle}" (Kategorie: {contextCategory}).
|
prompt = f"""Du spielst die Rolle von "{personaLabel}"{genderHint} in einem Roleplay-Szenario zum Thema: "{contextTitle}" (Kategorie: {contextCategory}).
|
||||||
|
|
||||||
Rollenbeschreibung: {personaDescription}
|
Rollenbeschreibung: {personaDescription}
|
||||||
|
|
||||||
WICHTIG fuer dein Verhalten:
|
WICHTIG für dein Verhalten:
|
||||||
- Bleibe KONSEQUENT in deiner Rolle. Du bist NICHT der Coach, du bist {personaLabel}.
|
- Bleibe KONSEQUENT in deiner Rolle. Du bist NICHT der Coach, du bist {personaLabel}.
|
||||||
- Reagiere authentisch und emotional gemaess deiner Rollenbeschreibung.
|
- Reagiere authentisch und emotional gemäss deiner Rollenbeschreibung.
|
||||||
- Verwende eine Sprache und Tonalitaet, die zu deiner Rolle passt.
|
- Verwende eine Sprache und Tonalität, die zu deiner Rolle passt.
|
||||||
- Der Benutzer uebt ein Gespraech mit dir. Gib ihm realistische Reaktionen.
|
- Der Benutzer übt ein Gespräch mit dir. Gib ihm realistische Reaktionen.
|
||||||
- Wenn der Benutzer gut kommuniziert, zeige das durch angemessene positive Reaktionen.
|
- Wenn der Benutzer gut kommuniziert, zeige das durch angemessene positive Reaktionen.
|
||||||
- Wenn der Benutzer schlecht kommuniziert, eskaliere entsprechend deiner Rolle.
|
- Wenn der Benutzer schlecht kommuniziert, eskaliere entsprechend deiner Rolle.
|
||||||
|
|
||||||
Kommunikationsstil:
|
Kommunikationsstil:
|
||||||
- Sprich natuerlich, wie die beschriebene Person sprechen wuerde.
|
- Sprich natürlich, wie die beschriebene Person sprechen würde.
|
||||||
- Verwende keine Emojis.
|
- Verwende keine Emojis.
|
||||||
- Antworte in der Sprache des Benutzers.
|
- Antworte in der Sprache des Benutzers.
|
||||||
- Halte Antworten realistisch kurz (wie in einem echten Gespraech, 2-4 Saetze).
|
- Halte Antworten realistisch kurz (wie in einem echten Gespräch)."""
|
||||||
- WICHTIG: Schreibe reinen Redetext ohne jegliche Formatierung. Kein Markdown, keine Sternchen, keine Hashes, keine Aufzaehlungszeichen, keine Backticks. Deine Antworten werden direkt vorgelesen."""
|
|
||||||
else:
|
else:
|
||||||
prompt = f"""Du bist ein erfahrener Kommunikations-Coach fuer Fuehrungskraefte. Du arbeitest mit dem Benutzer am Thema: "{contextTitle}" (Kategorie: {contextCategory}).
|
prompt = f"""Du bist ein erfahrener Kommunikations-Coach für Führungskräfte. Du arbeitest mit dem Benutzer am Thema: "{contextTitle}" (Kategorie: {contextCategory}).
|
||||||
|
|
||||||
Deine Rolle:
|
Deine Rolle:
|
||||||
- Stelle gezielte diagnostische Rueckfragen, um das Problem/Thema besser zu verstehen
|
- Stelle gezielte diagnostische Rückfragen, um das Problem/Thema besser zu verstehen
|
||||||
- Gib konkrete, praxisnahe Tipps und Uebungen
|
- Gib konkrete, praxisnahe Tipps und Übungen
|
||||||
- Baue auf fruehere Sessions auf (Kontext-Kontinuitaet)
|
- Baue auf frühere Sessions auf (Kontext-Kontinuität)
|
||||||
- Erkenne Fortschritte und benenne sie
|
- Erkenne Fortschritte und benenne sie
|
||||||
- Schlage am Ende der Session konkrete naechste Schritte vor (als Tasks)
|
- Schlage am Ende der Session konkrete nächste Schritte vor (als Tasks)
|
||||||
- Kommuniziere empathisch, klar und auf Augenhoehe
|
- Kommuniziere empathisch, klar und auf Augenhöhe
|
||||||
|
|
||||||
Kommunikationsstil:
|
Kommunikationsstil:
|
||||||
- Duze den Benutzer
|
- Duze den Benutzer
|
||||||
- Sei direkt aber wertschaetzend
|
- Sei direkt aber wertschätzend
|
||||||
- Verwende keine Emojis
|
- Verwende keine Emojis
|
||||||
- Antworte in der Sprache des Benutzers
|
- Antworte in der Sprache des Benutzers
|
||||||
- Halte Antworten fokussiert (max 3-4 Absaetze)
|
- Halte Antworten fokussiert (max 3-4 Absätze)"""
|
||||||
- WICHTIG: Schreibe reinen Redetext ohne jegliche Formatierung. Kein Markdown, keine Sternchen, keine Hashes, keine Aufzaehlungszeichen, keine Backticks. Deine Antworten werden direkt vorgelesen."""
|
|
||||||
|
prompt += """
|
||||||
|
|
||||||
|
Antwortformat:
|
||||||
|
Du antwortest IMMER als reines JSON-Objekt mit exakt diesen Feldern:
|
||||||
|
{"text": "...", "speech": "...", "documents": []}
|
||||||
|
|
||||||
|
"text": Dein schriftlicher Chat-Text. Details, Struktur, Übungen, Beispiele. Markdown-Formatierung erlaubt.
|
||||||
|
"speech": Dein gesprochener Kommentar. Natürlich, wie ein Gespräch. Fasse zusammen, kommentiere, motiviere, stelle Fragen. Lies NICHT den Text vor, ergänze ihn mündlich. 2-4 Sätze, reiner Redetext ohne Formatierung.
|
||||||
|
"documents": Optionale Dokumente (Zusammenfassungen, Checklisten, Übungen). Nur wenn sinnvoll. Jedes Dokument: {"title": "...", "content": "..."}. Sonst leeres Array [].
|
||||||
|
|
||||||
|
Kanalverteilung:
|
||||||
|
- Fakten, Listen, Übungen -> text
|
||||||
|
- Empathie, Einordnung, Nachfragen -> speech
|
||||||
|
- Materialien zum Aufbewahren -> documents
|
||||||
|
|
||||||
|
WICHTIG: Antworte NUR mit dem JSON-Objekt. Kein Text vor oder nach dem JSON."""
|
||||||
|
|
||||||
if contextDescription:
|
if contextDescription:
|
||||||
prompt += f"\n\nKontext-Beschreibung: {contextDescription}"
|
prompt += f"\n\nKontext-Beschreibung: {contextDescription}"
|
||||||
|
|
@ -168,7 +183,7 @@ Kommunikationsstil:
|
||||||
prompt += f"\n\nBisherige Erkenntnisse:\n" + "\n".join(f"- {i}" for i in insightTexts)
|
prompt += f"\n\nBisherige Erkenntnisse:\n" + "\n".join(f"- {i}" for i in insightTexts)
|
||||||
|
|
||||||
if rollingOverview:
|
if rollingOverview:
|
||||||
prompt += f"\n\nGesamtueberblick bisheriger Sessions:\n{rollingOverview[:600]}"
|
prompt += f"\n\nGesamtüberblick bisheriger Sessions:\n{rollingOverview[:600]}"
|
||||||
|
|
||||||
if summaries:
|
if summaries:
|
||||||
prompt += "\n\nBisherige Sessions (Zusammenfassungen):"
|
prompt += "\n\nBisherige Sessions (Zusammenfassungen):"
|
||||||
|
|
@ -209,7 +224,7 @@ Kommunikationsstil:
|
||||||
prompt += f"\n\nAbgeschlossene Aufgaben: {len(doneTasks)}"
|
prompt += f"\n\nAbgeschlossene Aufgaben: {len(doneTasks)}"
|
||||||
|
|
||||||
if earlierSummary:
|
if earlierSummary:
|
||||||
prompt += f"\n\nAelterer Gespraechsverlauf (zusammengefasst):\n{earlierSummary[:800]}"
|
prompt += f"\n\nÄlterer Gesprächsverlauf (zusammengefasst):\n{earlierSummary[:800]}"
|
||||||
|
|
||||||
if documentSummaries:
|
if documentSummaries:
|
||||||
prompt += "\n\nRelevante Dokumente zum Kontext:"
|
prompt += "\n\nRelevante Dokumente zum Kontext:"
|
||||||
|
|
@ -236,12 +251,12 @@ def buildSummaryPrompt(messages: List[Dict[str, Any]], contextTitle: str) -> str
|
||||||
return f"""Erstelle eine kompakte Zusammenfassung dieser Coaching-Session zum Thema "{contextTitle}".
|
return f"""Erstelle eine kompakte Zusammenfassung dieser Coaching-Session zum Thema "{contextTitle}".
|
||||||
|
|
||||||
Struktur:
|
Struktur:
|
||||||
1. **Kernthema**: Was wurde besprochen (1-2 Saetze)
|
1. **Kernthema**: Was wurde besprochen (1-2 Sätze)
|
||||||
2. **Erkenntnisse**: Was wurde erkannt/gelernt (Stichpunkte)
|
2. **Erkenntnisse**: Was wurde erkannt/gelernt (Stichpunkte)
|
||||||
3. **Naechste Schritte**: Konkrete Aufgaben fuer den Benutzer (Stichpunkte)
|
3. **Nächste Schritte**: Konkrete Aufgaben für den Benutzer (Stichpunkte)
|
||||||
4. **Fortschritt**: Einschaetzung des Fortschritts
|
4. **Fortschritt**: Einschätzung des Fortschritts
|
||||||
|
|
||||||
Gespraech:
|
Gespräch:
|
||||||
{conversation}
|
{conversation}
|
||||||
|
|
||||||
Antworte auf Deutsch, sachlich und kompakt."""
|
Antworte auf Deutsch, sachlich und kompakt."""
|
||||||
|
|
@ -258,21 +273,21 @@ def buildScoringPrompt(messages: List[Dict[str, Any]], contextCategory: str) ->
|
||||||
Kategorie: {contextCategory}
|
Kategorie: {contextCategory}
|
||||||
|
|
||||||
Bewerte folgende Dimensionen auf einer Skala von 0-100:
|
Bewerte folgende Dimensionen auf einer Skala von 0-100:
|
||||||
- empathy: Einfuehlungsvermoegen
|
- empathy: Einfühlungsvermögen
|
||||||
- clarity: Klarheit der Kommunikation
|
- clarity: Klarheit der Kommunikation
|
||||||
- assertiveness: Durchsetzungsfaehigkeit
|
- assertiveness: Durchsetzungsfähigkeit
|
||||||
- listening: Zuhoerfaehigkeit
|
- listening: Zuhörfähigkeit
|
||||||
- selfReflection: Selbstreflexion
|
- selfReflection: Selbstreflexion
|
||||||
|
|
||||||
Antworte AUSSCHLIESSLICH als JSON-Array:
|
Antworte AUSSCHLIESSLICH als JSON-Array:
|
||||||
[
|
[
|
||||||
{{"dimension": "empathy", "score": 65, "trend": "improving", "evidence": "Zeigt zunehmendes Verstaendnis..."}},
|
{{"dimension": "empathy", "score": 65, "trend": "improving", "evidence": "Zeigt zunehmendes Verständnis..."}},
|
||||||
{{"dimension": "clarity", "score": 70, "trend": "stable", "evidence": "..."}}
|
{{"dimension": "clarity", "score": 70, "trend": "stable", "evidence": "..."}}
|
||||||
]
|
]
|
||||||
|
|
||||||
Trend: "improving", "stable", oder "declining" basierend auf dem Gespraechsverlauf.
|
Trend: "improving", "stable", oder "declining" basierend auf dem Gesprächsverlauf.
|
||||||
|
|
||||||
Gespraech:
|
Gespräch:
|
||||||
{conversation}"""
|
{conversation}"""
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -284,7 +299,7 @@ Antworte AUSSCHLIESSLICH als JSON-Array von Strings:
|
||||||
|
|
||||||
Zusammenfassung: {summary[:500]}
|
Zusammenfassung: {summary[:500]}
|
||||||
|
|
||||||
Nur konkrete Themen (z.B. Delegation, Feedback-Gespraech, Konflikt mit Vorgesetztem)."""
|
Nur konkrete Themen (z.B. Delegation, Feedback-Gespräch, Konflikt mit Vorgesetztem)."""
|
||||||
|
|
||||||
|
|
||||||
def buildFullContextSummaryPrompt(
|
def buildFullContextSummaryPrompt(
|
||||||
|
|
@ -315,15 +330,15 @@ def buildFullContextSummaryPrompt(
|
||||||
return f"""Erstelle eine kompakte Gesamtzusammenfassung aller Coaching-Sessions zum Thema "{contextTitle}".
|
return f"""Erstelle eine kompakte Gesamtzusammenfassung aller Coaching-Sessions zum Thema "{contextTitle}".
|
||||||
|
|
||||||
Struktur:
|
Struktur:
|
||||||
1. **Gesamtueberblick**: Was wurde ueber alle Sessions hinweg besprochen
|
1. **Gesamtüberblick**: Was wurde über alle Sessions hinweg besprochen
|
||||||
2. **Entwicklung**: Wie hat sich das Thema/thematische Schwerpunkte entwickelt
|
2. **Entwicklung**: Wie hat sich das Thema/thematische Schwerpunkte entwickelt
|
||||||
3. **Offene Punkte**: Was steht noch aus
|
3. **Offene Punkte**: Was steht noch aus
|
||||||
4. **Empfehlung**: Kurzer naechster Fokus
|
4. **Empfehlung**: Kurzer nächster Fokus
|
||||||
|
|
||||||
Inhalt:
|
Inhalt:
|
||||||
{combined[:6000]}
|
{combined[:6000]}
|
||||||
|
|
||||||
Antworte auf Deutsch, sachlich, 4-6 Absaetze."""
|
Antworte auf Deutsch, sachlich, 4-6 Absätze."""
|
||||||
|
|
||||||
|
|
||||||
def buildRollingOverviewPrompt(sessionSummaries: List[Dict[str, Any]], contextTitle: str) -> str:
|
def buildRollingOverviewPrompt(sessionSummaries: List[Dict[str, Any]], contextTitle: str) -> str:
|
||||||
|
|
@ -336,7 +351,7 @@ def buildRollingOverviewPrompt(sessionSummaries: List[Dict[str, Any]], contextTi
|
||||||
parts.append(f"- {dateStr}: {summary[:300]}")
|
parts.append(f"- {dateStr}: {summary[:300]}")
|
||||||
|
|
||||||
combined = "\n".join(parts)
|
combined = "\n".join(parts)
|
||||||
return f"""Fasse die folgenden Coaching-Sessions zum Thema "{contextTitle}" in 4-6 Saetzen zusammen.
|
return f"""Fasse die folgenden Coaching-Sessions zum Thema "{contextTitle}" in 4-6 Sätzen zusammen.
|
||||||
Behalte: Kernthemen, Fortschritte, wichtige Erkenntnisse, offene Punkte.
|
Behalte: Kernthemen, Fortschritte, wichtige Erkenntnisse, offene Punkte.
|
||||||
Entferne Wiederholungen.
|
Entferne Wiederholungen.
|
||||||
|
|
||||||
|
|
@ -356,15 +371,15 @@ def buildInsightPrompt(messages: List[Dict[str, Any]], summary: Optional[str] =
|
||||||
summarySection = f"\nZusammenfassung: {summary[:500]}" if summary else ""
|
summarySection = f"\nZusammenfassung: {summary[:500]}" if summary else ""
|
||||||
|
|
||||||
return f"""Generiere 1-3 kurze Coaching-Insights aus dieser Session.
|
return f"""Generiere 1-3 kurze Coaching-Insights aus dieser Session.
|
||||||
Ein Insight ist eine praegende Erkenntnis oder ein Aha-Moment des Benutzers.
|
Ein Insight ist eine prägende Erkenntnis oder ein Aha-Moment des Benutzers.
|
||||||
|
|
||||||
Antworte AUSSCHLIESSLICH als JSON-Array:
|
Antworte AUSSCHLIESSLICH als JSON-Array:
|
||||||
[{{"text": "Erkenntnis in einem Satz"}}]
|
[{{"text": "Erkenntnis in einem Satz"}}]
|
||||||
|
|
||||||
Nur echte Erkenntnisse, keine Banalitaeten. Wenn keine klaren Insights: leeres Array [].
|
Nur echte Erkenntnisse, keine Banalitäten. Wenn keine klaren Insights: leeres Array [].
|
||||||
{summarySection}
|
{summarySection}
|
||||||
|
|
||||||
Gespraech:
|
Gespräch:
|
||||||
{conversation}"""
|
{conversation}"""
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -376,7 +391,7 @@ def buildTaskExtractionPrompt(messages: List[Dict[str, Any]]) -> str:
|
||||||
role = "Benutzer" if msg.get("role") == "user" else "Coach"
|
role = "Benutzer" if msg.get("role") == "user" else "Coach"
|
||||||
conversation += f"\n{role}: {msg.get('content', '')}"
|
conversation += f"\n{role}: {msg.get('content', '')}"
|
||||||
|
|
||||||
return f"""Extrahiere konkrete Aufgaben/naechste Schritte aus diesem Coaching-Gespraech.
|
return f"""Extrahiere konkrete Aufgaben/nächste Schritte aus diesem Coaching-Gespräch.
|
||||||
Nur Aufgaben, die der Benutzer selbst umsetzen soll.
|
Nur Aufgaben, die der Benutzer selbst umsetzen soll.
|
||||||
|
|
||||||
Antworte AUSSCHLIESSLICH als JSON-Array:
|
Antworte AUSSCHLIESSLICH als JSON-Array:
|
||||||
|
|
@ -387,7 +402,7 @@ Antworte AUSSCHLIESSLICH als JSON-Array:
|
||||||
priority: "low", "medium", oder "high"
|
priority: "low", "medium", oder "high"
|
||||||
Maximal 3 Aufgaben. Wenn keine klar erkennbar: leeres Array [].
|
Maximal 3 Aufgaben. Wenn keine klar erkennbar: leeres Array [].
|
||||||
|
|
||||||
Gespraech:
|
Gespräch:
|
||||||
{conversation}"""
|
{conversation}"""
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,7 @@ def buildSessionMarkdown(session: Dict[str, Any], messages: List[Dict[str, Any]]
|
||||||
lines += ["", "## Zusammenfassung", "", summary]
|
lines += ["", "## Zusammenfassung", "", summary]
|
||||||
|
|
||||||
if messages:
|
if messages:
|
||||||
lines += ["", "## Gespraechsverlauf", ""]
|
lines += ["", "## Gesprächsverlauf", ""]
|
||||||
for msg in messages:
|
for msg in messages:
|
||||||
role = "Du" if msg.get("role") == "user" else "Coach"
|
role = "Du" if msg.get("role") == "user" else "Coach"
|
||||||
content = msg.get("content", "")
|
content = msg.get("content", "")
|
||||||
|
|
@ -228,7 +228,7 @@ def _buildPdfContent(context, sessions, tasks, scores, isDossier=True, messages=
|
||||||
sections.append({
|
sections.append({
|
||||||
"id": "chat",
|
"id": "chat",
|
||||||
"content_type": "heading",
|
"content_type": "heading",
|
||||||
"elements": [{"text": "Gespraechsverlauf", "level": 2}],
|
"elements": [{"text": "Gesprächsverlauf", "level": 2}],
|
||||||
})
|
})
|
||||||
sections.append({
|
sections.append({
|
||||||
"id": "chat_content",
|
"id": "chat_content",
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ BADGE_DEFINITIONS: Dict[str, Dict[str, Any]] = {
|
||||||
},
|
},
|
||||||
"high_score": {
|
"high_score": {
|
||||||
"label": "Bestleistung",
|
"label": "Bestleistung",
|
||||||
"description": "Durchschnittsscore ueber 80 in einer Session",
|
"description": "Durchschnittsscore über 80 in einer Session",
|
||||||
"icon": "medal",
|
"icon": "medal",
|
||||||
},
|
},
|
||||||
"multi_context": {
|
"multi_context": {
|
||||||
|
|
|
||||||
|
|
@ -21,18 +21,18 @@ BUILTIN_PERSONAS: List[Dict[str, Any]] = [
|
||||||
{
|
{
|
||||||
"key": "critical_cfo_f",
|
"key": "critical_cfo_f",
|
||||||
"label": "Kritische CFO",
|
"label": "Kritische CFO",
|
||||||
"description": "Sandra Meier, CFO eines mittelstaendischen Unternehmens. Analytisch, zahlengetrieben, ungeduldig bei vagen Aussagen. "
|
"description": "Sandra Meier, CFO eines mittelständischen Unternehmens. Analytisch, zahlengetrieben, ungeduldig bei vagen Aussagen. "
|
||||||
"Hinterfragt jeden Vorschlag nach ROI und Wirtschaftlichkeit. Spricht schnell und direkt. "
|
"Hinterfragt jeden Vorschlag nach ROI und Wirtschaftlichkeit. Spricht schnell und direkt. "
|
||||||
"Erwartet praezise Antworten und belastbare Daten. Wird irritiert bei Ausweichen oder Unsicherheit.",
|
"Erwartet präzise Antworten und belastbare Daten. Wird irritiert bei Ausweichen oder Unsicherheit.",
|
||||||
"gender": "f",
|
"gender": "f",
|
||||||
"category": "builtin",
|
"category": "builtin",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "difficult_employee_m",
|
"key": "difficult_employee_m",
|
||||||
"label": "Schwieriger Mitarbeiter",
|
"label": "Schwieriger Mitarbeiter",
|
||||||
"description": "Thomas Huber, langjaeheriger Mitarbeiter der sich uebergangen fuehlt. Defensiv, emotional, nimmt Kritik persoenlich. "
|
"description": "Thomas Huber, langjähriger Mitarbeiter der sich übergangen fühlt. Defensiv, emotional, nimmt Kritik persönlich. "
|
||||||
"Verweist staendig auf seine Erfahrung und fruehhere Verdienste. Reagiert mit Widerstand auf Veraenderungen. "
|
"Verweist ständig auf seine Erfahrung und frühere Verdienste. Reagiert mit Widerstand auf Veränderungen. "
|
||||||
"Braucht das Gefuehl, gehoert und wertgeschaetzt zu werden, bevor er sich oeffnet.",
|
"Braucht das Gefühl, gehört und wertgeschätzt zu werden, bevor er sich öffnet.",
|
||||||
"gender": "m",
|
"gender": "m",
|
||||||
"category": "builtin",
|
"category": "builtin",
|
||||||
},
|
},
|
||||||
|
|
@ -41,7 +41,7 @@ BUILTIN_PERSONAS: List[Dict[str, Any]] = [
|
||||||
"label": "Unsichere neue Mitarbeiterin",
|
"label": "Unsichere neue Mitarbeiterin",
|
||||||
"description": "Lisa Brunner, seit drei Wochen im Team. Fachlich kompetent aber unsicher in der neuen Umgebung. "
|
"description": "Lisa Brunner, seit drei Wochen im Team. Fachlich kompetent aber unsicher in der neuen Umgebung. "
|
||||||
"Stellt viele Fragen, traut sich aber nicht, eigene Ideen einzubringen. Braucht klare Orientierung "
|
"Stellt viele Fragen, traut sich aber nicht, eigene Ideen einzubringen. Braucht klare Orientierung "
|
||||||
"und ermutigende Fuehrung. Reagiert positiv auf Lob und konkrete Anleitungen.",
|
"und ermutigende Führung. Reagiert positiv auf Lob und konkrete Anleitungen.",
|
||||||
"gender": "f",
|
"gender": "f",
|
||||||
"category": "builtin",
|
"category": "builtin",
|
||||||
},
|
},
|
||||||
|
|
@ -49,33 +49,33 @@ BUILTIN_PERSONAS: List[Dict[str, Any]] = [
|
||||||
"key": "board_member_m",
|
"key": "board_member_m",
|
||||||
"label": "Verwaltungsrat",
|
"label": "Verwaltungsrat",
|
||||||
"description": "Dr. Peter Keller, erfahrener Verwaltungsrat. Formell, strategisch denkend, zeitlich unter Druck. "
|
"description": "Dr. Peter Keller, erfahrener Verwaltungsrat. Formell, strategisch denkend, zeitlich unter Druck. "
|
||||||
"Erwartet praegnante Praesentationen auf den Punkt. Unterbricht bei zu vielen Details. "
|
"Erwartet prägnante Präsentationen auf den Punkt. Unterbricht bei zu vielen Details. "
|
||||||
"Interessiert sich fuer das grosse Bild, Risiken und strategische Implikationen. Ungeduldig bei Smalltalk.",
|
"Interessiert sich für das grosse Bild, Risiken und strategische Implikationen. Ungeduldig bei Smalltalk.",
|
||||||
"gender": "m",
|
"gender": "m",
|
||||||
"category": "builtin",
|
"category": "builtin",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "angry_customer_f",
|
"key": "angry_customer_f",
|
||||||
"label": "Aufgebrachte Kundin",
|
"label": "Aufgebrachte Kundin",
|
||||||
"description": "Maria Rossi, Geschaeftskunde die wuetend ist wegen einer fehlerhaften Lieferung. Emotional, laut, "
|
"description": "Maria Rossi, Geschäftskunde die wütend ist wegen einer fehlerhaften Lieferung. Emotional, laut, "
|
||||||
"droht mit Vertragsaufloesung. Will sofortige Loesungen, keine Erklaerungen oder Entschuldigungen. "
|
"droht mit Vertragsauflösung. Will sofortige Lösungen, keine Erklärungen oder Entschuldigungen. "
|
||||||
"Kann beruhigt werden durch empathisches Zuhoeren und konkrete Sofortmassnahmen.",
|
"Kann beruhigt werden durch empathisches Zuhören und konkrete Sofortmassnahmen.",
|
||||||
"gender": "f",
|
"gender": "f",
|
||||||
"category": "builtin",
|
"category": "builtin",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "resistant_manager_m",
|
"key": "resistant_manager_m",
|
||||||
"label": "Widerstaendiger Abteilungsleiter",
|
"label": "Widerständiger Abteilungsleiter",
|
||||||
"description": "Martin Weber, Abteilungsleiter seit 15 Jahren. Blockiert systematisch Veraenderungsprojekte mit "
|
"description": "Martin Weber, Abteilungsleiter seit 15 Jahren. Blockiert systematisch Veränderungsprojekte mit "
|
||||||
"Argumenten wie 'Das haben wir immer so gemacht' und 'Das funktioniert in der Praxis nicht'. "
|
"Argumenten wie 'Das haben wir immer so gemacht' und 'Das funktioniert in der Praxis nicht'. "
|
||||||
"Schuetzt sein Team vor zusaetzlicher Belastung. Respektiert nur Argumente mit konkretem Nutzen fuer seine Abteilung.",
|
"Schützt sein Team vor zusätzlicher Belastung. Respektiert nur Argumente mit konkretem Nutzen für seine Abteilung.",
|
||||||
"gender": "m",
|
"gender": "m",
|
||||||
"category": "builtin",
|
"category": "builtin",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "ambitious_colleague_f",
|
"key": "ambitious_colleague_f",
|
||||||
"label": "Ehrgeizige Kollegin",
|
"label": "Ehrgeizige Kollegin",
|
||||||
"description": "Anna Fischer, gleichrangige Kollegin die um dieselbe Befoerderung konkurriert. Charmant aber strategisch. "
|
"description": "Anna Fischer, gleichrangige Kollegin die um dieselbe Beförderung konkurriert. Charmant aber strategisch. "
|
||||||
"Versucht subtil, die Ideen anderer als ihre eigenen darzustellen. Konkurriert um Ressourcen und "
|
"Versucht subtil, die Ideen anderer als ihre eigenen darzustellen. Konkurriert um Ressourcen und "
|
||||||
"Sichtbarkeit beim Management. Kann kooperativ werden, wenn man ihr Win-Win-Szenarien aufzeigt.",
|
"Sichtbarkeit beim Management. Kann kooperativ werden, wenn man ihr Win-Win-Szenarien aufzeigt.",
|
||||||
"gender": "f",
|
"gender": "f",
|
||||||
|
|
@ -83,20 +83,20 @@ BUILTIN_PERSONAS: List[Dict[str, Any]] = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "partner_supportive_f",
|
"key": "partner_supportive_f",
|
||||||
"label": "Verstaendnisvolle Lebenspartnerin",
|
"label": "Verständnisvolle Lebenspartnerin",
|
||||||
"description": "Claudia, deine Lebenspartnerin. Grundsaetzlich unterstuetzend, aber zunehmend besorgt ueber deine "
|
"description": "Claudia, deine Lebenspartnerin. Grundsätzlich unterstützend, aber zunehmend besorgt über deine "
|
||||||
"Work-Life-Balance. Moechte ueber Arbeitsbelastung sprechen und gemeinsame Zeit einfordern. "
|
"Work-Life-Balance. Möchte über Arbeitsbelastung sprechen und gemeinsame Zeit einfordern. "
|
||||||
"Reagiert emotional auf Abweisung, ist aber offen fuer kompromissorientierte Gespraeche. "
|
"Reagiert emotional auf Abweisung, ist aber offen für kompromissorientierte Gespräche. "
|
||||||
"Wuenscht sich, dass du mehr von deinen Gefuehlen teilst.",
|
"Wünscht sich, dass du mehr von deinen Gefühlen teilst.",
|
||||||
"gender": "f",
|
"gender": "f",
|
||||||
"category": "builtin",
|
"category": "builtin",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "partner_critical_m",
|
"key": "partner_critical_m",
|
||||||
"label": "Kritischer Lebenspartner",
|
"label": "Kritischer Lebenspartner",
|
||||||
"description": "Michael, dein Lebenspartner. Frustriert ueber deine haeufige Abwesenheit und staendiges Arbeiten. "
|
"description": "Michael, dein Lebenspartner. Frustriert über deine häufige Abwesenheit und ständiges Arbeiten. "
|
||||||
"Drueckt Enttaeuschung offen aus, manchmal mit Sarkasmus. Fuehlt sich vernachlaessigt und "
|
"Drückt Enttäuschung offen aus, manchmal mit Sarkasmus. Fühlt sich vernachlässigt und "
|
||||||
"hinterfragt deine Prioritaeten. Braucht das Gefuehl, dass die Beziehung dir genauso wichtig ist "
|
"hinterfragt deine Prioritäten. Braucht das Gefühl, dass die Beziehung dir genauso wichtig ist "
|
||||||
"wie die Karriere. Reagiert positiv auf ehrliche Selbstreflexion.",
|
"wie die Karriere. Reagiert positiv auf ehrliche Selbstreflexion.",
|
||||||
"gender": "m",
|
"gender": "m",
|
||||||
"category": "builtin",
|
"category": "builtin",
|
||||||
|
|
|
||||||
|
|
@ -65,14 +65,14 @@ class TestBuildCoachingSystemPrompt:
|
||||||
def test_promptLanguageIsGerman(self):
|
def test_promptLanguageIsGerman(self):
|
||||||
context = {"title": "Test", "category": "custom"}
|
context = {"title": "Test", "category": "custom"}
|
||||||
prompt = buildCoachingSystemPrompt(context, [], [])
|
prompt = buildCoachingSystemPrompt(context, [], [])
|
||||||
assert "Fuehrungskraefte" in prompt or "Coach" in prompt
|
assert "Führungskräfte" in prompt or "Coach" in prompt
|
||||||
|
|
||||||
def test_withEarlierSummary(self):
|
def test_withEarlierSummary(self):
|
||||||
context = {"title": "Test", "category": "custom"}
|
context = {"title": "Test", "category": "custom"}
|
||||||
messages = [{"role": "user", "content": "Recent question"}]
|
messages = [{"role": "user", "content": "Recent question"}]
|
||||||
earlierSummary = "User discussed delegation. Coach suggested practice."
|
earlierSummary = "User discussed delegation. Coach suggested practice."
|
||||||
prompt = buildCoachingSystemPrompt(context, messages, [], earlierSummary=earlierSummary)
|
prompt = buildCoachingSystemPrompt(context, messages, [], earlierSummary=earlierSummary)
|
||||||
assert "Aelterer Gespraechsverlauf" in prompt
|
assert "Älterer Gesprächsverlauf" in prompt
|
||||||
assert "delegation" in prompt.lower()
|
assert "delegation" in prompt.lower()
|
||||||
assert "Recent question" in prompt
|
assert "Recent question" in prompt
|
||||||
|
|
||||||
|
|
@ -81,7 +81,7 @@ class TestBuildCoachingSystemPrompt:
|
||||||
prompt = buildCoachingSystemPrompt(
|
prompt = buildCoachingSystemPrompt(
|
||||||
context, [], [], rollingOverview="User arbeitet an Delegation. Fortschritt sichtbar."
|
context, [], [], rollingOverview="User arbeitet an Delegation. Fortschritt sichtbar."
|
||||||
)
|
)
|
||||||
assert "Gesamtueberblick" in prompt
|
assert "Gesamtüberblick" in prompt
|
||||||
assert "Delegation" in prompt
|
assert "Delegation" in prompt
|
||||||
|
|
||||||
def test_withRetrievedSession(self):
|
def test_withRetrievedSession(self):
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue