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
43e305bb
Commit
43e305bb
authored
Feb 10, 2026
by
Kantz
Browse files
überarbeitung des decision_LLM und integration der neuen Varianten
parent
d7152516
Changes
6
Hide whitespace changes
Inline
Side-by-side
math-tutor/README.md
View file @
43e305bb
...
@@ -66,3 +66,4 @@ Retrieval settings:
...
@@ -66,3 +66,4 @@ Retrieval settings:
python -m test.hint_test ...
python -m test.hint_test ...
python -m test.vector_store_test --query "Was ist eine Teilmenge?" --k 8 --expand
python -m test.vector_store_test --query "Was ist eine Teilmenge?" --k 8 --expand
python -m test.math_intent_test --input "Integrate x^2" --input "Was ist 2+2?"
python -m test.math_intent_test --input "Integrate x^2" --input "Was ist 2+2?"
python -m test.decision_test --chat-id draft_session_mlgeyn5z_unpxyh
\ No newline at end of file
math-tutor/backend/app/LLM_services/decision_LLM.py
View file @
43e305bb
import
json
from
ollama
import
chat
from
app.deterministic_services
import
llm_client
def
context_decision
(
needs_more_context
:
bool
,
reason
:
str
)
->
dict
:
# Kannst auch einfach nur return {"needs_more_context": needs_more_context, "reason": reason}
return
{
"needs_more_context"
:
bool
(
needs_more_context
),
"reason"
:
str
(
reason
)}
CLASSIFIER_PROMPT
=
(
"Du bist ein Klassifikator. Entscheide, ob die vorhandenen Informationen "
"aus Historie und Kontextblatt ausreichen, um einen naechsten didaktisch "
"wertvollen Hinweis zu geben. Antworte ausschliesslich mit gueltigem JSON "
"im Format {
\"
needs_more_context
\"
: true/false,
\"
reason
\"
:
\"
...
\"
}."
)
CLASSIFIER_SYSTEM
=
"""
Du bist ein Klassifikator für didaktische Tutoring-Hinweise.
def
needs_more_context
(
history
:
str
,
context_sheet
:
str
)
->
dict
:
Aufgabe:
prompt
=
(
Entscheide, ob Historie + Kontextblatt ausreichen, um den NÄCHSTEN didaktisch wertvollen Hinweis zu geben.
Wenn irgendetwas Wesentliches fehlt, ist needs_more_context=true.
Der Kontext ist ausreichend wenn:
1. Die aktuelle Problemstellung und Lösungsidee klar ist.
2. aktueller Lösungsstand klar ist? (was wurde schon probiert)
3. der typischer nächster Schritt ableitbar ist.
4. es keine fehlen Variablen/Definitionen/Teilaufgabe gibt.
WICHTIG:
- Antworte NICHT mit freiem Text.
- Rufe IMMER das Tool context_decision auf.
- needs_more_context ist boolean.
- reason ist eine kurze Begründung (1–3 Sätze), konkret was fehlt oder warum es reicht.
- Löse nicht die Aufgabe, sondern bewerte nur die Situation bzgl. der nächsten didaktischen Schritte.
"""
def
needs_more_context
(
history
:
str
,
context_sheet
:
str
,
model
:
str
=
"ministral-3"
)
->
dict
:
user_prompt
=
(
"Kontextblatt:
\n
"
"Kontextblatt:
\n
"
+
context_sheet
f
"
{
context_sheet
}
\n\n
"
+
"
\n\n
Historie:
\n
"
"
Historie:
\n
"
+
history
f
"
{
history
}
\n\n
"
+
"
\n\n
Antwortformat: JSON
."
"Treffe eine Entscheidung
."
)
)
result
=
llm_client
.
chat
(
messages
=
[
messages
=
[
{
"role"
:
"user"
,
"content"
:
prompt
},
{
"role"
:
"system"
,
"content"
:
CLASSIFIER_SYSTEM
},
{
"role"
:
"system"
,
"content"
:
CLASSIFIER_PROMPT
},
{
"role"
:
"user"
,
"content"
:
user_prompt
},
],
]
use_ollama
=
True
,
resp
=
chat
(
model
=
model
,
messages
=
messages
,
tools
=
[
context_decision
],
stream
=
False
,
options
=
{
"temperature"
:
0
},
)
)
content
=
llm_client
.
get_message_content
(
result
)
try
:
# 1) Ideal: strukturiertes Toolcall-Objekt
payload
=
json
.
loads
(
content
)
if
resp
.
message
.
tool_calls
:
return
{
call
=
resp
.
message
.
tool_calls
[
0
]
"needs_more_context"
:
bool
(
payload
.
get
(
"needs_more_context"
)),
if
call
.
function
.
name
!=
"context_decision"
:
"reason"
:
content
,
return
{
"needs_more_context"
:
True
,
"reason"
:
f
"Unexpected tool:
{
call
.
function
.
name
}
"
}
}
except
json
.
JSONDecodeError
:
args
=
call
.
function
.
arguments
return
{
"needs_more_context"
:
True
,
"reason"
:
content
}
# Tool ausführen (oder alternativ direkt args zurückgeben)
return
context_decision
(
**
args
)
# 2) Fallback: falls Modell doch keinen Toolcall gemacht hat
return
{
"needs_more_context"
:
True
,
"reason"
:
resp
.
message
.
content
or
"No tool_call returned"
}
math-tutor/backend/app/LLM_services/hint_LLM.py
View file @
43e305bb
...
@@ -11,7 +11,7 @@ def generate_hint(
...
@@ -11,7 +11,7 @@ def generate_hint(
)
->
str
:
)
->
str
:
system_prompt
=
(
system_prompt
=
(
"Du bist ein didaktischer Tutor. "
"Du bist ein didaktischer Tutor. "
"Gebe dem Nutzer hilfreiche Tipp
s
um mathematische Aufgaben zu lösen."
"Gebe dem Nutzer
einen
hilfreiche
n
Tipp um mathematische Aufgaben zu lösen."
"Gebe keine Beispiele oder Erklärungen, sondern nur den Tipp."
"Gebe keine Beispiele oder Erklärungen, sondern nur den Tipp."
"Gebe keine komplette Lösung der Aufgabe."
"Gebe keine komplette Lösung der Aufgabe."
"Die Mathematische Lösung hat immer Vorrang vor der LLM Lösung."
"Die Mathematische Lösung hat immer Vorrang vor der LLM Lösung."
...
@@ -33,7 +33,7 @@ def generate_hint(
...
@@ -33,7 +33,7 @@ def generate_hint(
prompt
=
"
\n
Kontext:
\n
"
+
sources
+
"
\n
"
+
prompt
prompt
=
"
\n
Kontext:
\n
"
+
sources
+
"
\n
"
+
prompt
if
query
:
if
query
:
prompt
+=
"
\n
Aktuelle Frage:"
+
query
prompt
+=
"
\n
Aktuelle Frage:"
+
query
prompt
+=
"
\n
Gebe einen hilfreichen Tipp zu
r Lösung
der Aufgabe der mir bei meiner aktuellen Frage hilft. Halte dich kurz und prägnant."
prompt
+=
"
\n
Gebe einen hilfreichen Tipp zu
m nächsten Schritt
der Aufgabe der mir bei meiner aktuellen Frage hilft. Halte dich kurz und prägnant."
result
=
llm_client
.
chat
(
result
=
llm_client
.
chat
(
messages
=
[{
"role"
:
"system"
,
"content"
:
system_prompt
},
{
"role"
:
"user"
,
"content"
:
prompt
}],
messages
=
[{
"role"
:
"system"
,
"content"
:
system_prompt
},
{
"role"
:
"user"
,
"content"
:
prompt
}],
)
)
...
...
math-tutor/backend/app/deterministic_services/context_store.py
View file @
43e305bb
...
@@ -98,16 +98,10 @@ def update_retrieval_context(
...
@@ -98,16 +98,10 @@ def update_retrieval_context(
def
add_math_solution
(
def
add_math_solution
(
sheet
:
dict
[
str
,
Any
],
sheet
:
dict
[
str
,
Any
],
task
:
str
,
input_text
:
str
,
symbols
:
list
[
str
]
|
None
,
solution
:
str
,
solution
:
str
,
)
->
None
:
)
->
None
:
sheet
[
"math_solutions"
].
append
(
sheet
[
"math_solutions"
].
append
(
{
{
"task"
:
task
,
"input"
:
input_text
,
"symbols"
:
symbols
or
[],
"solution"
:
solution
,
"solution"
:
solution
,
}
}
)
)
...
...
math-tutor/backend/app/deterministic_services/orchestrators/orchestrator_tutor.py
View file @
43e305bb
...
@@ -24,17 +24,10 @@ def _bootstrap_context(sheet: dict, query_text: str, tool_log: list[dict]) -> No
...
@@ -24,17 +24,10 @@ def _bootstrap_context(sheet: dict, query_text: str, tool_log: list[dict]) -> No
context_store
.
add_retrieval_context
(
sheet
,
query_text
,
sources
)
context_store
.
add_retrieval_context
(
sheet
,
query_text
,
sources
)
_append_tool_log
(
tool_log
,
"retrieve_context"
,
{
"query"
:
query_text
},
{
"sources"
:
[
source
.
to_string
()
for
source
in
sources
]})
_append_tool_log
(
tool_log
,
"retrieve_context"
,
{
"query"
:
query_text
},
{
"sources"
:
[
source
.
to_string
()
for
source
in
sources
]})
math_request
=
math_intent_LLM
.
extract_math_request
(
query_text
)
math_solution
=
math_intent_LLM
.
solve_with_tools
(
query_text
)
if
math_request
:
if
math_solution
:
solution
=
math_tool
.
sympy_solve
(
**
math_request
)
context_store
.
add_math_solution
(
sheet
,
math_solution
)
context_store
.
add_math_solution
(
_append_tool_log
(
tool_log
,
"math_intent_LLM"
,
{
"query"
:
query_text
},
math_solution
)
sheet
,
math_request
[
"task"
],
math_request
[
"input"
],
math_request
.
get
(
"symbols"
),
solution
,
)
_append_tool_log
(
tool_log
,
"sympy_solve"
,
math_request
,
solution
)
def
_extract_user_messages
(
messages
:
list
[
dict
])
->
list
[
str
]:
def
_extract_user_messages
(
messages
:
list
[
dict
])
->
list
[
str
]:
"""
"""
...
...
math-tutor/backend/test/decision_test.py
0 → 100644
View file @
43e305bb
import
argparse
import
json
from
typing
import
Any
,
Iterable
from
app.LLM_services
import
decision_LLM
from
app.deterministic_services
import
context_store
def
_load_sheet_from_path
(
path
:
str
)
->
dict
[
str
,
Any
]:
with
open
(
path
,
"r"
,
encoding
=
"utf-8"
)
as
handle
:
return
json
.
load
(
handle
)
def
_resolve_sheet
(
args
:
argparse
.
Namespace
)
->
dict
[
str
,
Any
]:
if
args
.
sheet
:
return
_load_sheet_from_path
(
args
.
sheet
)
if
args
.
chat_id
:
sheet
=
context_store
.
load_sheet
(
args
.
chat_id
)
if
sheet
is
None
:
raise
FileNotFoundError
(
f
"Kein Context-Sheet gefunden fuer chat_id=
{
args
.
chat_id
}
"
)
return
sheet
raise
ValueError
(
"Bitte --sheet oder --chat-id angeben."
)
def
_last_user_inputs
(
history
:
list
[
dict
[
str
,
Any
]],
count
:
int
)
->
list
[
str
]:
if
count
<=
0
:
return
[]
user_msgs
=
[
msg
.
get
(
"content"
,
""
)
for
msg
in
history
if
msg
.
get
(
"role"
)
==
"user"
]
return
[
text
for
text
in
user_msgs
[
-
count
:]
if
text
]
def
main
()
->
None
:
parser
=
argparse
.
ArgumentParser
(
description
=
"Isolierter Decision-LLM Test mit Context Sheet."
)
parser
.
add_argument
(
"--sheet"
,
help
=
"Pfad zu einem Context Sheet JSON."
)
parser
.
add_argument
(
"--chat-id"
,
help
=
"Chat-ID zum Laden aus logs/context_sheets."
)
parser
.
add_argument
(
"--last-user-count"
,
type
=
int
,
default
=
1
,
help
=
"Wie viele der letzten Nutzer-Eingaben ausgegeben werden sollen."
,
)
args
=
parser
.
parse_args
()
sheet
=
_resolve_sheet
(
args
)
history_text
=
context_store
.
get_history
(
sheet
)
context_text
=
context_store
.
format_sheet
(
sheet
)
decision
=
decision_LLM
.
needs_more_context
(
history_text
,
context_text
)
last_inputs
=
_last_user_inputs
(
sheet
.
get
(
"history"
,
[]),
args
.
last_user_count
)
print
(
"LAST_USER_INPUTS:"
,
json
.
dumps
(
last_inputs
,
ensure_ascii
=
True
))
print
(
"OUTPUT:"
,
json
.
dumps
(
decision
,
ensure_ascii
=
True
))
if
__name__
==
"__main__"
:
main
()
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