Commit f3d2a6e0 authored by Kantz's avatar Kantz
Browse files

combinding double implementations

parent ca498dfa
from __future__ import annotations
def build_messages(
system_prompt: str,
context_parts: list[str],
history: list[dict] | None = None,
query: str | None = None,
query_instruction: str | None = None,
) -> list[dict[str, str]]:
messages = [{"role": "system", "content": system_prompt}]
messages.append({"role": "user", "content": "\n\n".join(part for part in context_parts if part)})
if history:
messages.extend(history)
if query:
suffix = f"\n\n{query_instruction}" if query_instruction else ""
messages.append(
{
"role": "user",
"content": f"AKTUELLE Eingabe des Studenten (höchste Priorität):\n{query}{suffix}",
}
)
return messages
...@@ -4,6 +4,7 @@ from __future__ import annotations ...@@ -4,6 +4,7 @@ from __future__ import annotations
from typing import Iterable from typing import Iterable
from app.LLM_services.prompt_messages import build_messages
from app.deterministic_services import llm_client from app.deterministic_services import llm_client
...@@ -52,31 +53,18 @@ def generate_dialog( ...@@ -52,31 +53,18 @@ def generate_dialog(
history: list[dict] | None = None, history: list[dict] | None = None,
sources: str | None = None, sources: str | None = None,
) -> str: ) -> str:
effective_parent_refs = parent_refs
context_parts = [ context_parts = [
f"Parent-Referenzen:\n{_format_parent_refs(effective_parent_refs)}", f"Parent-Referenzen:\n{_format_parent_refs(parent_refs)}",
] ]
if sources: if sources:
context_parts.append(f"Kontext/Sources:\n{sources}") context_parts.append(f"Kontext/Sources:\n{sources}")
messages = [{"role": "system", "content": HINT_SYSTEM_PROMPT}] messages = build_messages(
messages.append({"role": "user", "content": "\n\n".join(context_parts)}) HINT_SYSTEM_PROMPT,
context_parts,
if history: history=history,
messages.extend(history) query=query,
query_instruction="Reagiere ausschließlich auf diese Eingabe und bleibe sokratisch. Nutze deine Tools um mir zu helfen.",
if query: )
messages.append(
{
"role": "user",
"content": (
"AKTUELLE Eingabe des Studenten (höchste Priorität):\n"
f"{query}\n\n"
"Reagiere ausschließlich auf diese Eingabe und bleibe sokratisch. Nutze deine Tools um mir zu helfen."
),
}
)
result = llm_client.chat(messages=messages) result = llm_client.chat(messages=messages)
return llm_client.get_message_content(result) return llm_client.get_message_content(result)
# structure of the task-prompt. # structure of the task-prompt.
from __future__ import annotations from __future__ import annotations
from app.LLM_services.prompt_messages import build_messages
from app.deterministic_services import llm_client from app.deterministic_services import llm_client
HINT_SYSTEM_PROMPT = """ HINT_SYSTEM_PROMPT = r"""
Du bist ein didaktischer Mathe-Tutor. Du gibst Feedback zu den Lösungen des Nutzers zur Gegebenen Frage. Du bist ein didaktischer Mathe-Tutor. Du gibst Feedback zu den Lösungen des Nutzers zur Gegebenen Frage.
Vergleiche das Ergebnis mit der korrekten Lösung. Vergleiche das Ergebnis mit der korrekten Lösung.
...@@ -26,6 +28,7 @@ Halte dich kurz und präzise. ...@@ -26,6 +28,7 @@ Halte dich kurz und präzise.
Denke nach bevor du eine Ausgabe macht. Denke nach bevor du eine Ausgabe macht.
""" """
def generate_hint( def generate_hint(
query: str | None, query: str | None,
task: str, task: str,
...@@ -37,25 +40,17 @@ def generate_hint( ...@@ -37,25 +40,17 @@ def generate_hint(
context_parts = [ context_parts = [
f"Hier ist die Zu lösende Aufgaben:{task}\n", f"Hier ist die Zu lösende Aufgaben:{task}\n",
f"Hier ist eine korrekte Lösung als referenz:{solution}\n", f"Hier ist eine korrekte Lösung als referenz:{solution}\n",
f"Hier ist ein exemplarischer Lösungsweg:{hints}\n" f"Hier ist ein exemplarischer Lösungsweg:{hints}\n",
] ]
if sources: if sources:
context_parts.append(f"Kontext/Sources:\n{sources}") context_parts.append(f"Kontext/Sources:\n{sources}")
messages = [{"role": "system", "content": HINT_SYSTEM_PROMPT}] messages = build_messages(
HINT_SYSTEM_PROMPT,
# Kompakter Kontext als eine Nachricht (kein langer Fließtext mit History mischen) context_parts,
messages.append({"role": "user", "content": "\n\n".join(context_parts)}) history=history,
query=query,
# History als echte Turns (und ggf. begrenzen, siehe Punkt 2) query_instruction="Gib einen kurzen Tipp, der genau diese Eingabe adressiert. Nutze deine Tools um mir zu helfen.",
if history: )
messages.extend(history)
# Aktuelle Frage als letzte Nachricht, fett hervorgehoben durch Struktur/Delimiters
messages.append({
"role": "user",
"content": f"AKTUELLE Eingabe des Studenten (höchste Priorität):\n{query}\n\n Gib einen kurzen Tipp, der genau diese Eingabe adressiert. Nutze deine Tools um mir zu helfen."
})
result = llm_client.chat(messages=messages) result = llm_client.chat(messages=messages)
return llm_client.get_message_content(result) return llm_client.get_message_content(result)
import { useEffect, useMemo, useRef } from "react";
import { useNavigate } from "react-router-dom";
import { selectTask, selectTopic } from "../api/taskApi";
import OrchestratorSelect from "../components/Orchestrator/OrchestratorSelect";
import TaskContent from "../components/Task/TaskContent";
import { t } from "../i18n";
import { useTutorSession } from "../state/tutorSession";
import {
getSelectionRouteForOrchestrator,
type OrchestratorName,
} from "../utils/orchestrator";
import "../styles/theme.css";
import sumintLogo from "../../SuMINT-Logo.png";
type SelectionMode = "task" | "socratic";
type SelectionPageProps = {
mode: SelectionMode;
};
export default function SelectionPage({ mode }: SelectionPageProps) {
const navigate = useNavigate();
const {
chatSessionId,
isTasksInitialized,
selectedOrchestrator,
availableOrchestrators,
switchOrchestrator,
isOrchestratorSelectable,
orchestratorError,
selectedTaskRef,
selectedTask,
taskFileOptions,
taskOptions,
selectedTopicRef,
topics,
tasksError,
setTaskFile,
setTaskId,
setTopicKey,
lockTask,
unlockTask,
} = useTutorSession();
const taskDisplayRef = useRef<HTMLDivElement | null>(null);
const isTaskMode = mode === "task";
const topicMenuOptions = useMemo(
() =>
topics.map((option) => ({
value: option.topic_key,
label: option.label || option.topic_key,
})),
[topics]
);
useEffect(() => {
if (!isTasksInitialized) {
return;
}
if (selectedOrchestrator !== mode) {
navigate(getSelectionRouteForOrchestrator(selectedOrchestrator), { replace: true });
return;
}
unlockTask();
}, [isTasksInitialized, mode, navigate, selectedOrchestrator, unlockTask]);
useEffect(() => {
if (!isTaskMode || !taskFileOptions.length) {
return;
}
if (selectedTaskRef && taskFileOptions.some((option) => option.value === selectedTaskRef.fileId)) {
return;
}
setTaskFile(taskFileOptions[0].value);
}, [isTaskMode, selectedTaskRef, setTaskFile, taskFileOptions]);
useEffect(() => {
if (isTaskMode || !topicMenuOptions.length) {
return;
}
if (
selectedTopicRef &&
topicMenuOptions.some((option) => option.value === selectedTopicRef.topicKey)
) {
return;
}
setTopicKey(topicMenuOptions[0].value);
}, [isTaskMode, selectedTopicRef, setTopicKey, topicMenuOptions]);
useEffect(() => {
if (!isTaskMode || !selectedTask?.fullText || !taskDisplayRef.current) {
return;
}
const mathjax = window.MathJax;
if (!mathjax?.typesetPromise) {
return;
}
mathjax.typesetPromise([taskDisplayRef.current]).catch(() => undefined);
}, [isTaskMode, selectedTask?.fullText]);
const handleStart = async () => {
try {
if (isTaskMode) {
if (!selectedTaskRef) {
return;
}
await selectTask({
draft: chatSessionId,
fileId: selectedTaskRef.fileId,
taskId: selectedTaskRef.taskId,
});
} else {
if (!selectedTopicRef) {
return;
}
await selectTopic({
draft: chatSessionId,
topicKey: selectedTopicRef.topicKey,
});
}
lockTask();
navigate("/chat");
} catch (error) {
void error;
}
};
const handleSwitchOrchestrator = (next: OrchestratorName) => {
if (next === selectedOrchestrator) {
return;
}
switchOrchestrator(next);
navigate(getSelectionRouteForOrchestrator(next), { replace: true });
};
if (!isTasksInitialized) {
return <div className="app-loading">{t("loading")}</div>;
}
return (
<div className="app-shell">
<header className="app-header">
<div className="brand">
<img className="brand-logo" src={sumintLogo} alt="SuMINT Logo" />
<div className="brand-text">
<div className="brand-title">Mathe Tutor</div>
<div className="brand-subtitle">
{t(isTaskMode ? "taskSelectionSubtitle" : "socraticSelectionSubtitle")}
</div>
</div>
</div>
</header>
{orchestratorError ? <div className="chat-archive-error">{orchestratorError}</div> : null}
<main className="task-select-main">
<div className="task-select-wrap">
<div className="task-mode-row">
<OrchestratorSelect
value={selectedOrchestrator}
options={availableOrchestrators}
onChange={handleSwitchOrchestrator}
disabled={!isOrchestratorSelectable}
/>
</div>
<section className="task-select-card">
<div className="task-select-header">
<h2 className="task-select-title">
{t(isTaskMode ? "taskSelectionTitle" : "socraticSelectionTitle")}
</h2>
</div>
<div className="task-select-controls">
{isTaskMode ? (
<>
<label className="task-select-label" htmlFor="task-file-select">
{t("taskFile")}
</label>
<select
id="task-file-select"
className="task-select"
value={selectedTaskRef?.fileId || ""}
onChange={(event) => setTaskFile(event.target.value)}
disabled={!taskFileOptions.length}
>
{taskFileOptions.length ? (
taskFileOptions.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))
) : (
<option value="">{t("noTasksAvailable")}</option>
)}
</select>
<label className="task-select-label" htmlFor="task-id-select">
{t("taskId")}
</label>
<select
id="task-id-select"
className="task-select"
value={selectedTaskRef?.taskId || ""}
onChange={(event) => setTaskId(event.target.value)}
disabled={!taskOptions.length}
>
{taskOptions.length ? (
taskOptions.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))
) : (
<option value="">{t("noTasksAvailable")}</option>
)}
</select>
</>
) : (
<>
<label className="task-select-label" htmlFor="socratic-topic-select">
{t("topic")}
</label>
<select
id="socratic-topic-select"
className="task-select"
value={selectedTopicRef?.topicKey || ""}
onChange={(event) => setTopicKey(event.target.value)}
disabled={!topicMenuOptions.length}
>
{topicMenuOptions.length ? (
topicMenuOptions.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))
) : (
<option value="">{t("noTopicsAvailable")}</option>
)}
</select>
</>
)}
</div>
{isTaskMode ? (
<TaskContent
text={selectedTask?.fullText || t("noTaskSelected")}
images={selectedTask?.images || []}
textRef={taskDisplayRef}
/>
) : null}
{tasksError ? <div className="task-panel-error">{tasksError}</div> : null}
<button
type="button"
className="btn primary task-solve-btn"
onClick={handleStart}
disabled={isTaskMode ? !selectedTaskRef : !selectedTopicRef}
>
{t(isTaskMode ? "solveWithTutor" : "startSocratic")}
</button>
</section>
</div>
</main>
</div>
);
}
import { useEffect, useMemo, useRef } from "react"; import SelectionPage from "./SelectionPage";
import { useNavigate } from "react-router-dom";
import { selectTopic } from "../api/taskApi";
import OrchestratorSelect from "../components/Orchestrator/OrchestratorSelect";
import { t } from "../i18n";
import { useTutorSession } from "../state/tutorSession";
import {
getSelectionRouteForOrchestrator,
type OrchestratorName,
} from "../utils/orchestrator";
import "../styles/theme.css";
import sumintLogo from "../../SuMINT-Logo.png";
export default function SocraticSelectionPage() { export default function SocraticSelectionPage() {
const navigate = useNavigate(); return <SelectionPage mode="socratic" />;
const {
chatSessionId,
selectedOrchestrator,
availableOrchestrators,
switchOrchestrator,
isOrchestratorSelectable,
orchestratorError,
topics,
selectedTopicRef,
selectedTopic,
tasksError,
setTopicKey,
lockTask,
unlockTask,
isTasksInitialized,
} = useTutorSession();
const topicDisplayRef = useRef<HTMLDivElement | null>(null);
const topicMenuOptions = useMemo(
() =>
topics.map((option) => ({
value: option.topic_key,
label: option.label || option.topic_key,
})),
[topics]
);
useEffect(() => {
if (!isTasksInitialized) {
return;
}
if (selectedOrchestrator !== "socratic") {
navigate(getSelectionRouteForOrchestrator(selectedOrchestrator), { replace: true });
return;
}
unlockTask();
}, [isTasksInitialized, navigate, selectedOrchestrator, unlockTask]);
useEffect(() => {
if (!selectedTopic?.label || !topicDisplayRef.current) {
return;
}
const mathjax = window.MathJax;
if (!mathjax?.typesetPromise) {
return;
}
mathjax.typesetPromise([topicDisplayRef.current]).catch(() => undefined);
}, [selectedTopic?.label]);
useEffect(() => {
if (!topicMenuOptions.length) {
return;
}
if (
selectedTopicRef &&
topicMenuOptions.some((option) => option.value === selectedTopicRef.topicKey)
) {
return;
}
setTopicKey(topicMenuOptions[0].value);
}, [selectedTopicRef, setTopicKey, topicMenuOptions]);
const handleStartSocratic = async () => {
if (!selectedTopicRef) {
return;
}
try {
await selectTopic({
draft: chatSessionId,
topicKey: selectedTopicRef.topicKey,
});
lockTask();
navigate("/chat");
} catch (error) {
void error;
}
};
const handleSwitchOrchestrator = (next: OrchestratorName) => {
if (next === selectedOrchestrator) {
return;
}
switchOrchestrator(next);
navigate(getSelectionRouteForOrchestrator(next), { replace: true });
};
if (!isTasksInitialized) {
return <div className="app-loading">{t("loading")}</div>;
}
return (
<div className="app-shell">
<header className="app-header">
<div className="brand">
<img className="brand-logo" src={sumintLogo} alt="SuMINT Logo" />
<div className="brand-text">
<div className="brand-title">Mathe Tutor</div>
<div className="brand-subtitle">{t("socraticSelectionSubtitle")}</div>
</div>
</div>
</header>
{orchestratorError ? <div className="chat-archive-error">{orchestratorError}</div> : null}
<main className="task-select-main">
<div className="task-select-wrap">
<div className="task-mode-row">
<OrchestratorSelect
value={selectedOrchestrator}
options={availableOrchestrators}
onChange={handleSwitchOrchestrator}
disabled={!isOrchestratorSelectable}
/>
</div>
<section className="task-select-card">
<div className="task-select-header">
<h2 className="task-select-title">{t("socraticSelectionTitle")}</h2>
</div>
<div className="task-select-controls">
<label className="task-select-label" htmlFor="socratic-topic-select">
{t("topic")}
</label>
<select
id="socratic-topic-select"
className="task-select"
value={selectedTopicRef?.topicKey || ""}
onChange={(event) => setTopicKey(event.target.value)}
disabled={!topicMenuOptions.length}
>
{topicMenuOptions.length ? (
topicMenuOptions.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))
) : (
<option value="">{t("noTopicsAvailable")}</option>
)}
</select>
</div>
{tasksError ? <div className="task-panel-error">{tasksError}</div> : null}
<button
type="button"
className="btn primary task-solve-btn"
onClick={handleStartSocratic}
disabled={!selectedTopicRef}
>
{t("startSocratic")}
</button>
</section>
</div>
</main>
</div>
);
} }
import { useEffect, useRef } from "react"; import SelectionPage from "./SelectionPage";
import { useNavigate } from "react-router-dom";
import OrchestratorSelect from "../components/Orchestrator/OrchestratorSelect";
import TaskContent from "../components/Task/TaskContent";
import { selectTask } from "../api/taskApi";
import { t } from "../i18n";
import { useTutorSession } from "../state/tutorSession";
import {
getSelectionRouteForOrchestrator,
type OrchestratorName,
} from "../utils/orchestrator";
import "../styles/theme.css";
import sumintLogo from "../../SuMINT-Logo.png";
export default function TaskSelectionPage() { export default function TaskSelectionPage() {
const navigate = useNavigate(); return <SelectionPage mode="task" />;
const {
chatSessionId,
isTaskModeEnabled,
isTasksInitialized,
selectedOrchestrator,
availableOrchestrators,
switchOrchestrator,
isOrchestratorSelectable,
orchestratorError,
selectedTaskRef,
selectedTask,
taskFileOptions,
taskOptions,
tasksError,
setTaskFile,
setTaskId,
lockTask,
unlockTask,
} = useTutorSession();
const taskDisplayRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
if (!isTasksInitialized) {
return;
}
if (!isTaskModeEnabled) {
navigate("/chat", { replace: true });
return;
}
if (selectedOrchestrator === "socratic") {
navigate("/select-socratic", { replace: true });
return;
}
unlockTask();
}, [isTaskModeEnabled, isTasksInitialized, navigate, selectedOrchestrator, unlockTask]);
useEffect(() => {
if (!taskFileOptions.length) {
return;
}
if (selectedTaskRef && taskFileOptions.some((option) => option.value === selectedTaskRef.fileId)) {
return;
}
setTaskFile(taskFileOptions[0].value);
}, [isTasksInitialized, selectedTaskRef, setTaskFile, taskFileOptions]);
useEffect(() => {
if (!selectedTask?.fullText || !taskDisplayRef.current) {
return;
}
const mathjax = window.MathJax;
if (!mathjax?.typesetPromise) {
return;
}
mathjax.typesetPromise([taskDisplayRef.current]).catch(() => undefined);
}, [selectedTask?.fullText]);
const handleSolveWithTutor = async () => {
if (!selectedTaskRef) {
return;
}
try {
await selectTask({
draft: chatSessionId,
fileId: selectedTaskRef.fileId,
taskId: selectedTaskRef.taskId,
});
lockTask();
navigate("/chat");
} catch (error) {
void error;
}
};
const handleSwitchOrchestrator = (next: OrchestratorName) => {
if (next === selectedOrchestrator) {
return;
}
switchOrchestrator(next);
navigate(getSelectionRouteForOrchestrator(next), { replace: true });
};
if (!isTasksInitialized) {
return <div className="app-loading">{t("loading")}</div>;
}
return (
<div className="app-shell">
<header className="app-header">
<div className="brand">
<img className="brand-logo" src={sumintLogo} alt="SuMINT Logo" />
<div className="brand-text">
<div className="brand-title">Mathe Tutor</div>
<div className="brand-subtitle">{t("taskSelectionSubtitle")}</div>
</div>
</div>
</header>
{orchestratorError ? <div className="chat-archive-error">{orchestratorError}</div> : null}
<main className="task-select-main">
<div className="task-select-wrap">
<div className="task-mode-row">
<OrchestratorSelect
value={selectedOrchestrator}
options={availableOrchestrators}
onChange={handleSwitchOrchestrator}
disabled={!isOrchestratorSelectable}
/>
</div>
<section className="task-select-card">
<div className="task-select-header">
<h2 className="task-select-title">{t("taskSelectionTitle")}</h2>
</div>
<div className="task-select-controls">
<label className="task-select-label" htmlFor="task-file-select">
{t("taskFile")}
</label>
<select
id="task-file-select"
className="task-select"
value={selectedTaskRef?.fileId || ""}
onChange={(event) => setTaskFile(event.target.value)}
disabled={!taskFileOptions.length}
>
{taskFileOptions.length ? (
taskFileOptions.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))
) : (
<option value="">{t("noTasksAvailable")}</option>
)}
</select>
<label className="task-select-label" htmlFor="task-id-select">
{t("taskId")}
</label>
<select
id="task-id-select"
className="task-select"
value={selectedTaskRef?.taskId || ""}
onChange={(event) => setTaskId(event.target.value)}
disabled={!taskOptions.length}
>
{taskOptions.length ? (
taskOptions.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))
) : (
<option value="">{t("noTasksAvailable")}</option>
)}
</select>
</div>
<TaskContent
text={selectedTask?.fullText || t("noTaskSelected")}
images={selectedTask?.images || []}
textRef={taskDisplayRef}
/>
{tasksError ? <div className="task-panel-error">{tasksError}</div> : null}
<button
type="button"
className="btn primary task-solve-btn"
onClick={handleSolveWithTutor}
disabled={!selectedTaskRef}
>
{t("solveWithTutor")}
</button>
</section>
</div>
</main>
</div>
);
} }
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment