Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
math_tutor_dev
public_math_tutor
Commits
528a483a
Commit
528a483a
authored
Mar 31, 2026
by
Kantz
Browse files
V1 des Sokrates
parent
fc2b7b7c
Changes
11
Hide whitespace changes
Inline
Side-by-side
math-tutor/backend/app/LLM_services/socratic_LLM.py
0 → 100644
View file @
528a483a
from
__future__
import
annotations
from
typing
import
Iterable
from
app.deterministic_services
import
llm_client
HINT_SYSTEM_PROMPT
=
"""
Du bist ein didaktischer Mathe-Tutor.
Antworte auf Deutsch, kurz und präzise.
Nutze ausschließlich den bereitgestellten Kontext.
Stelle vor allem Verständnisfragen und leite den Schüler sokratisch.
Gib keine komplette Lösung aus.
Verwende für mathematische Formeln immer die $-Notation, z.B. $a^2 + b^2 = c^2$.
minf steht für minus unendlich.
"""
def
_format_subsection_refs
(
subsection_refs
:
Iterable
[
tuple
[
int
,
int
,
int
]]
|
None
)
->
str
:
if
not
subsection_refs
:
return
"Keine Subsection-Referenzen übergeben."
formatted
=
[
f
"
{
chap
}
:
{
sec
}
:
{
sub
}
"
for
chap
,
sec
,
sub
in
subsection_refs
]
return
", "
.
join
(
formatted
)
def
generate_dialog
(
query
:
str
|
None
,
subsection_refs
:
list
[
tuple
[
int
,
int
,
int
]]
|
None
=
None
,
history
:
list
[
dict
]
|
None
=
None
,
sources
:
str
|
None
=
None
,
)
->
str
:
context_parts
=
[
f
"Subsection-Referenzen:
\n
{
_format_subsection_refs
(
subsection_refs
)
}
"
,
]
if
sources
:
context_parts
.
append
(
f
"Kontext/Sources:
\n
{
sources
}
"
)
messages
=
[{
"role"
:
"system"
,
"content"
:
HINT_SYSTEM_PROMPT
}]
messages
.
append
({
"role"
:
"user"
,
"content"
:
"
\n\n
"
.
join
(
context_parts
)})
if
history
:
messages
.
extend
(
history
)
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."
),
}
)
result
=
llm_client
.
chat
(
messages
=
messages
)
return
llm_client
.
get_message_content
(
result
)
math-tutor/backend/app/api/chat.py
View file @
528a483a
...
...
@@ -75,7 +75,7 @@ def chat(request: ChatRequest) -> ChatResponse:
try
:
payload_messages
=
[{
"role"
:
m
.
role
,
"content"
:
m
.
text
}
for
m
in
request
.
messages
]
if
orchestrator_name
in
{
"task"
,
"feedback"
}:
if
orchestrator_name
in
{
"task"
,
"feedback"
,
"socratic"
}:
result
=
orchestrator_impl
.
run_chat
(
payload_messages
,
draft
=
request
.
draft
,
...
...
math-tutor/backend/app/api/tasks.py
View file @
528a483a
...
...
@@ -10,7 +10,7 @@ from app.deterministic_services import context_store, task_catalog
router
=
APIRouter
()
TASK_ORCHESTRATORS
=
{
"task"
,
"feedback"
}
TASK_ORCHESTRATORS
=
{
"task"
,
"feedback"
,
"socratic"
}
class
TaskItem
(
BaseModel
):
...
...
math-tutor/backend/app/deterministic_services/orchestrators/orchestrator_socratic.py
0 → 100644
View file @
528a483a
from
__future__
import
annotations
from
app.LLM_services
import
socratic_LLM
import
app.config
as
config
from
app.deterministic_services
import
(
context_store
,
retrieval_store
,
task_catalog
,
)
from
app.deterministic_services.orchestrators
import
orchestrator_base
as
base
def
_ensure_context_task_fields
(
state
:
base
.
ChatState
,
query_text
:
str
)
->
tuple
[
str
,
str
]
|
None
:
store_new
=
context_store
.
context_store_new
has_task
=
bool
(
store_new
.
get_task
(
state
.
sheet
))
has_hints
=
bool
(
store_new
.
get_hints
(
state
.
sheet
))
has_solution
=
bool
(
store_new
.
get_solution
(
state
.
sheet
))
if
has_task
and
has_hints
and
has_solution
:
selected
=
task_catalog
.
get_selected_task_ids
(
state
.
sheet
)
if
selected
[
0
]
and
selected
[
1
]:
was_selected
=
task_catalog
.
select_task_by_ids
(
state
.
sheet
,
selected
[
0
],
selected
[
1
],
)
if
was_selected
:
return
selected
[
0
],
selected
[
1
]
sources_text
=
"
\n
"
.
join
([
source
.
to_string
()
for
source
in
context_store
.
get_retrieval
(
state
.
sheet
)])
selection
=
task_catalog
.
select_task_for_context
(
state
.
sheet
,
query_text
=
query_text
,
sources_text
=
sources_text
,
history
=
context_store
.
get_history_turns
(
state
.
sheet
),
)
if
not
selection
:
return
None
task_file
,
task_entry
=
selection
selected_file_id
=
str
(
task_file
.
get
(
"_file_id"
,
""
))
selected_task_id
=
str
(
task_entry
.
get
(
"id"
,
""
)).
zfill
(
2
)
base
.
append_tool_log
(
state
.
tool_log
,
"task_json_selected"
,
{
"tasks_dir"
:
str
(
task_catalog
.
TASKS_DIR
)},
{
"file"
:
task_file
.
get
(
"_path"
,
""
),
"file_id"
:
selected_file_id
,
"task_id"
:
selected_task_id
,
"hint_count"
:
len
(
store_new
.
get_hints
(
state
.
sheet
)),
"has_solution"
:
bool
(
store_new
.
get_solution
(
state
.
sheet
)),
},
)
return
selected_file_id
,
selected_task_id
def
_retrieve_context_for_task
(
state
:
base
.
ChatState
,
query_text
:
str
)
->
int
:
refs
=
task_catalog
.
get_selected_task_subsection_refs
(
state
.
sheet
)
if
not
refs
:
return
0
def
_retrieve
()
->
dict
:
sources
=
retrieval_store
.
retrieve_for_subsections
(
pg_url
=
config
.
get_postgres_url
(),
subsection_refs
=
refs
,
)
context_store
.
update_retrieval_context
(
state
.
sheet
,
sources
)
return
{
"subsection_refs"
:
refs
,
"source_count"
:
len
(
sources
),
}
result
=
base
.
log_timed_call
(
state
.
tool_log
,
"retrieve_context_with_task_subsections"
,
{
"query"
:
query_text
,
"subsection_refs"
:
refs
,
},
_retrieve
,
)
return
int
(
result
.
get
(
"source_count"
,
0
))
def
_on_bootstrap
(
state
:
base
.
ChatState
,
query_text
:
str
)
->
None
:
_ensure_context_task_fields
(
state
,
query_text
)
_retrieve_context_for_task
(
state
,
query_text
)
def
_on_turn_logic
(
state
:
base
.
ChatState
)
->
None
:
_ensure_context_task_fields
(
state
,
state
.
last_user
)
def
_on_build_reply
(
state
:
base
.
ChatState
)
->
str
|
None
:
history_turns
=
context_store
.
get_history_turns
(
state
.
sheet
)
subsection_refs
=
task_catalog
.
get_selected_task_subsection_refs
(
state
.
sheet
)
args
=
{
"query"
:
state
.
last_user
,
"subsection_refs"
:
subsection_refs
,
"history"
:
history_turns
,
"sources"
:
"
\n
"
.
join
([
source
.
to_string
()
for
source
in
context_store
.
get_retrieval
(
state
.
sheet
)]),
}
return
base
.
log_timed_call
(
state
.
tool_log
,
"socratic_generate_dialog"
,
args
,
lambda
:
socratic_LLM
.
generate_dialog
(
**
args
),
)
def
run_chat
(
messages
:
list
[
dict
],
draft
:
str
|
None
=
None
,
selected_task
:
dict
|
None
=
None
,
)
->
dict
:
def
_apply_selected_task
(
state
:
base
.
ChatState
)
->
None
:
if
not
selected_task
:
return
selected_file_id
=
str
(
selected_task
.
get
(
"file_id"
,
""
)).
strip
()
selected_task_id
=
str
(
selected_task
.
get
(
"task_id"
,
""
)).
strip
()
if
selected_file_id
and
selected_task_id
:
task_catalog
.
select_task_by_ids
(
state
.
sheet
,
selected_file_id
,
selected_task_id
,
)
def
on_bootstrap
(
state
:
base
.
ChatState
,
query_text
:
str
)
->
None
:
_apply_selected_task
(
state
)
_on_bootstrap
(
state
,
query_text
)
def
on_turn_logic
(
state
:
base
.
ChatState
)
->
None
:
_apply_selected_task
(
state
)
_on_turn_logic
(
state
)
return
base
.
run_chat_common
(
messages
=
messages
,
draft
=
draft
,
on_bootstrap
=
on_bootstrap
,
on_turn_logic
=
on_turn_logic
,
on_build_reply
=
_on_build_reply
,
init_sheet_fn
=
context_store
.
context_store_new
.
init_sheet
,
)
math-tutor/backend/app/deterministic_services/orchestrators/registry.py
View file @
528a483a
...
...
@@ -6,17 +6,19 @@ import app.config as config
from
app.deterministic_services.orchestrators
import
(
orchestrator_feedback
,
orchestrator_qa
,
orchestrator_socratic
,
orchestrator_task
,
orchestrator_tutor
,
)
AVAILABLE_ORCHESTRATORS
:
tuple
[
str
,
...]
=
(
"qa"
,
"tutor"
,
"task"
,
"feedback"
)
AVAILABLE_ORCHESTRATORS
:
tuple
[
str
,
...]
=
(
"qa"
,
"tutor"
,
"task"
,
"feedback"
,
"socratic"
)
_ORCHESTRATOR_MODULES
:
dict
[
str
,
Any
]
=
{
"qa"
:
orchestrator_qa
,
"tutor"
:
orchestrator_tutor
,
"task"
:
orchestrator_task
,
"feedback"
:
orchestrator_feedback
,
"socratic"
:
orchestrator_socratic
,
}
...
...
math-tutor/frontend/src/api/orchestratorApi.ts
View file @
528a483a
export
type
OrchestratorName
=
"
qa
"
|
"
tutor
"
|
"
task
"
|
"
feedback
"
;
export
type
OrchestratorName
=
"
qa
"
|
"
tutor
"
|
"
task
"
|
"
feedback
"
|
"
socratic
"
;
export
type
OrchestratorConfigResponse
=
{
default_orchestrator
:
OrchestratorName
;
available_orchestrators
:
OrchestratorName
[];
};
const
FALLBACK_ORCHESTRATORS
:
OrchestratorName
[]
=
[
"
qa
"
,
"
tutor
"
,
"
task
"
,
"
feedback
"
];
const
FALLBACK_ORCHESTRATORS
:
OrchestratorName
[]
=
[
"
qa
"
,
"
tutor
"
,
"
task
"
,
"
feedback
"
,
"
socratic
"
];
const
normalizeOrchestrator
=
(
value
:
string
):
OrchestratorName
|
null
=>
{
if
(
value
===
"
qa
"
||
value
===
"
tutor
"
||
value
===
"
task
"
||
value
===
"
feedback
"
)
{
if
(
value
===
"
qa
"
||
value
===
"
tutor
"
||
value
===
"
task
"
||
value
===
"
feedback
"
||
value
===
"
socratic
"
)
{
return
value
;
}
return
null
;
...
...
math-tutor/frontend/src/components/Orchestrator/OrchestratorSelect.tsx
View file @
528a483a
...
...
@@ -14,12 +14,14 @@ type ModeMeta = {
|
"
orchestratorModeQaLabel
"
|
"
orchestratorModeTutorLabel
"
|
"
orchestratorModeTaskLabel
"
|
"
orchestratorModeFeedbackLabel
"
;
|
"
orchestratorModeFeedbackLabel
"
|
"
orchestratorModeSocraticLabel
"
;
descriptionKey
:
|
"
orchestratorModeQaDescription
"
|
"
orchestratorModeTutorDescription
"
|
"
orchestratorModeTaskDescription
"
|
"
orchestratorModeFeedbackDescription
"
;
|
"
orchestratorModeFeedbackDescription
"
|
"
orchestratorModeSocraticDescription
"
;
};
const
modeMetaMap
:
Record
<
OrchestratorName
,
ModeMeta
>
=
{
...
...
@@ -39,6 +41,10 @@ const modeMetaMap: Record<OrchestratorName, ModeMeta> = {
labelKey
:
"
orchestratorModeFeedbackLabel
"
,
descriptionKey
:
"
orchestratorModeFeedbackDescription
"
,
},
socratic
:
{
labelKey
:
"
orchestratorModeSocraticLabel
"
,
descriptionKey
:
"
orchestratorModeSocraticDescription
"
,
},
};
type
ModeDescriptionDisplay
=
"
tooltip
"
|
"
helperText
"
|
"
optionSuffix
"
;
...
...
math-tutor/frontend/src/i18n.ts
View file @
528a483a
...
...
@@ -22,11 +22,14 @@
orchestratorModeTutorLabel
:
"
Tutor
"
,
orchestratorModeTaskLabel
:
"
Task
"
,
orchestratorModeFeedbackLabel
:
"
Feedback
"
,
orchestratorModeSocraticLabel
:
"
Socratic
"
,
orchestratorModeDescriptionTitle
:
"
Mode help
"
,
orchestratorModeQaDescription
:
"
Direct answers based on the script
"
,
orchestratorModeTutorDescription
:
"
Help with your own questions
"
,
orchestratorModeTaskDescription
:
"
Help with textbook exercises
"
,
orchestratorModeFeedbackDescription
:
"
Short feedback on your solution for textbook exercises
"
,
orchestratorModeSocraticDescription
:
"
Socratic guidance based on fixed subsection context for textbook exercises
"
,
directChildren
:
"
Direct children
"
,
taskChildren
:
"
Task sources
"
,
indirectChildren
:
"
Indirect children
"
,
...
...
@@ -106,11 +109,14 @@
orchestratorModeTutorLabel
:
"
Tutor
"
,
orchestratorModeTaskLabel
:
"
Task
"
,
orchestratorModeFeedbackLabel
:
"
Feedback
"
,
orchestratorModeSocraticLabel
:
"
Socratic
"
,
orchestratorModeDescriptionTitle
:
"
Modus-Hilfe
"
,
orchestratorModeQaDescription
:
"
Direkte Antworten basierend auf dem Skript
"
,
orchestratorModeTutorDescription
:
"
Hilfe bei selbst gestellten Fragen
"
,
orchestratorModeTaskDescription
:
"
Hilfe bei Aufgaben aus dem Lehrwerk
"
,
orchestratorModeFeedbackDescription
:
"
Kurzes Feedback zu deiner Lösung bei Aufgaben aus dem Lehrwerk
"
,
orchestratorModeSocraticDescription
:
"
Sokratische Anleitung auf Basis fester Subsection-Kontexte für Aufgaben aus dem Lehrwerk
"
,
directChildren
:
"
Direkte Quellen
"
,
taskChildren
:
"
Aufgaben-Quellen
"
,
indirectChildren
:
"
Indirekte Quellen
"
,
...
...
math-tutor/frontend/src/pages/ChatPage.tsx
View file @
528a483a
...
...
@@ -17,14 +17,20 @@ import sumintLogo from "../../SuMINT-Logo.png";
const
initialMessages
:
ChatMessage
[]
=
[];
const
normalizeOrchestrator
=
(
value
:
string
|
null
|
undefined
):
OrchestratorName
|
null
=>
{
if
(
value
===
"
qa
"
||
value
===
"
tutor
"
||
value
===
"
task
"
||
value
===
"
feedback
"
)
{
if
(
value
===
"
qa
"
||
value
===
"
tutor
"
||
value
===
"
task
"
||
value
===
"
feedback
"
||
value
===
"
socratic
"
)
{
return
value
;
}
return
null
;
};
const
isTaskCoupledOrchestrator
=
(
value
:
string
|
null
|
undefined
):
boolean
=>
value
===
"
task
"
||
value
===
"
feedback
"
;
value
===
"
task
"
||
value
===
"
feedback
"
||
value
===
"
socratic
"
;
type
ArchivedChatSummary
=
{
chat_id
:
string
;
...
...
@@ -214,9 +220,11 @@ export default function ChatPage() {
}
setSelectedOrchestrator
(
deepLinkTarget
.
isTaskOrchestrator
&&
searchParams
.
get
(
"
orchestrator
"
)
===
"
feedback
"
searchParams
.
get
(
"
orchestrator
"
)
===
"
feedback
"
?
"
feedback
"
:
"
task
"
:
searchParams
.
get
(
"
orchestrator
"
)
===
"
socratic
"
?
"
socratic
"
:
"
task
"
);
setTaskRef
({
fileId
:
selectedFile
.
file_id
,
taskId
:
selectedTask
.
task_id
});
unlockTask
();
...
...
math-tutor/frontend/src/pages/TaskSelectionPage.tsx
View file @
528a483a
...
...
@@ -9,7 +9,7 @@ import "../styles/theme.css";
import
sumintLogo
from
"
../../SuMINT-Logo.png
"
;
const
isTaskCoupledOrchestrator
=
(
value
:
OrchestratorName
):
boolean
=>
value
===
"
task
"
||
value
===
"
feedback
"
;
value
===
"
task
"
||
value
===
"
feedback
"
||
value
===
"
socratic
"
;
export
default
function
TaskSelectionPage
()
{
const
navigate
=
useNavigate
();
...
...
math-tutor/frontend/src/state/tutorSession.tsx
View file @
528a483a
...
...
@@ -69,7 +69,7 @@ const isSelectableTaskFile = (file: TaskFile): boolean =>
Array
.
isArray
(
file
.
subsections
)
&&
file
.
subsections
.
length
>
0
;
const
isTaskCoupledOrchestrator
=
(
value
:
OrchestratorName
):
boolean
=>
value
===
"
task
"
||
value
===
"
feedback
"
;
value
===
"
task
"
||
value
===
"
feedback
"
||
value
===
"
socratic
"
;
export
function
TutorSessionProvider
({
children
}:
PropsWithChildren
)
{
const
[
chatSessionId
,
setChatSessionId
]
=
useState
<
string
>
(()
=>
createSessionId
());
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment