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
d7ca098b
Commit
d7ca098b
authored
Feb 10, 2026
by
Kantz
Browse files
toolcalls in llm_clinet verschoben
parent
43e305bb
Changes
3
Hide whitespace changes
Inline
Side-by-side
math-tutor/backend/app/LLM_services/decision_LLM.py
View file @
d7ca098b
from
ollama
import
chat
from
app.deterministic_services
import
llm_client
def
context_decision
(
needs_more_context
:
bool
,
reason
:
str
)
->
dict
:
def
context_decision
(
needs_more_context
:
bool
,
reason
:
str
)
->
dict
:
# Kannst auch einfach nur return {"needs_more_context": needs_more_context, "reason": reason}
# Kannst auch einfach nur return {"needs_more_context": needs_more_context, "reason": reason}
return
{
"needs_more_context"
:
bool
(
needs_more_context
),
"reason"
:
str
(
reason
)}
return
{
"needs_more_context"
:
bool
(
needs_more_context
),
"reason"
:
str
(
reason
)}
CLASSIFIER_SYSTEM
=
"""
CLASSIFIER_SYSTEM
=
"""
Du bist ein Klassifikator für didaktische Tutoring-Hinweise.
Du bist ein Klassifikator für didaktische Tutoring-Hinweise.
...
@@ -41,23 +41,17 @@ def needs_more_context(history: str, context_sheet: str, model: str = "ministral
...
@@ -41,23 +41,17 @@ def needs_more_context(history: str, context_sheet: str, model: str = "ministral
{
"role"
:
"user"
,
"content"
:
user_prompt
},
{
"role"
:
"user"
,
"content"
:
user_prompt
},
]
]
resp
=
chat
(
resp
,
tool_outputs
=
llm_client
.
chat_with_tools
(
model
=
model
,
messages
=
messages
,
messages
=
messages
,
tools
=
[
context_decision
],
tools
=
[
context_decision
],
stream
=
Fals
e
,
use_ollama
=
Tru
e
,
options
=
{
"temperature"
:
0
}
,
return_after_tools
=
True
,
)
)
# 1) Ideal: strukturiertes Toolcall-Objekt
if
tool_outputs
:
if
resp
.
message
.
tool_calls
:
result
=
tool_outputs
[
0
].
get
(
"result"
)
call
=
resp
.
message
.
tool_calls
[
0
]
if
isinstance
(
result
,
dict
)
and
"needs_more_context"
in
result
:
if
call
.
function
.
name
!=
"context_decision"
:
return
result
return
{
"needs_more_context"
:
True
,
"reason"
:
f
"Unexpected tool:
{
call
.
function
.
name
}
"
}
return
{
"needs_more_context"
:
True
,
"reason"
:
str
(
result
)}
args
=
call
.
function
.
arguments
# 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"
:
llm_client
.
get_message_content
(
resp
)
or
"No tool_call returned"
}
return
{
"needs_more_context"
:
True
,
"reason"
:
resp
.
message
.
content
or
"No tool_call returned"
}
math-tutor/backend/app/LLM_services/math_intent_LLM.py
View file @
d7ca098b
import
sympy
as
sp
import
sympy
as
sp
from
ollama
import
chat
from
app.deterministic_services
import
llm_client
def
sympy_solve
(
task
:
str
,
input
:
str
,
symbols
:
list
[
str
]
|
None
=
None
)
->
str
:
def
sympy_solve
(
task
:
str
,
input
:
str
,
symbols
:
list
[
str
]
|
None
=
None
)
->
str
:
"""
"""
...
@@ -42,27 +44,9 @@ def solve_with_tools(user_text: str, model: str = "ministral-3") -> str:
...
@@ -42,27 +44,9 @@ def solve_with_tools(user_text: str, model: str = "ministral-3") -> str:
{
"role"
:
"user"
,
"content"
:
user_text
},
{
"role"
:
"user"
,
"content"
:
user_text
},
]
]
# 1) Modell darf Toolcalls erzeugen
result
,
_
=
llm_client
.
chat_with_tools
(
resp
=
chat
(
model
=
model
,
messages
=
messages
,
tools
=
[
sympy_solve
],
stream
=
False
)
messages
=
messages
,
messages
.
append
(
resp
.
message
)
tools
=
[
sympy_solve
],
use_ollama
=
True
,
# 2) Toolcalls ausführen und Ergebnisse zurückgeben
)
if
resp
.
message
.
tool_calls
:
return
llm_client
.
get_message_content
(
result
)
for
call
in
resp
.
message
.
tool_calls
:
if
call
.
function
.
name
==
"sympy_solve"
:
result
=
sympy_solve
(
**
call
.
function
.
arguments
)
else
:
result
=
f
"Unknown tool:
{
call
.
function
.
name
}
"
messages
.
append
({
"role"
:
"tool"
,
"tool_name"
:
call
.
function
.
name
,
"content"
:
str
(
result
),
})
# 3) Finalen Antwort-Call (Modell integriert Tool-Ergebnis)
final
=
chat
(
model
=
model
,
messages
=
messages
,
tools
=
[
sympy_solve
],
stream
=
False
)
return
final
.
message
.
content
# Falls kein Toolcall: direkt zurück
return
resp
.
message
.
content
math-tutor/backend/app/deterministic_services/llm_client.py
View file @
d7ca098b
import
inspect
import
inspect
import
json
from
typing
import
Any
,
Callable
import
ollama
import
ollama
from
openai
import
OpenAI
from
openai
import
OpenAI
...
@@ -31,7 +33,7 @@ def _chat_openai(messages: list[dict]) -> dict:
...
@@ -31,7 +33,7 @@ def _chat_openai(messages: list[dict]) -> dict:
def
chat
(
def
chat
(
messages
:
list
[
dict
],
messages
:
list
[
dict
],
tools
:
list
[
dict
]
|
None
=
None
,
tools
:
list
[
Callable
[...,
Any
]
]
|
None
=
None
,
use_ollama
:
bool
=
False
,
use_ollama
:
bool
=
False
,
)
->
dict
:
)
->
dict
:
if
not
tools
and
not
use_ollama
:
if
not
tools
and
not
use_ollama
:
...
@@ -61,7 +63,6 @@ def _extract_message(response) -> object:
...
@@ -61,7 +63,6 @@ def _extract_message(response) -> object:
return
response
.
message
return
response
.
message
return
{}
return
{}
def
get_message_content
(
result
:
dict
|
object
)
->
str
:
def
get_message_content
(
result
:
dict
|
object
)
->
str
:
message
=
result
.
get
(
"message"
)
if
isinstance
(
result
,
dict
)
else
result
message
=
result
.
get
(
"message"
)
if
isinstance
(
result
,
dict
)
else
result
if
isinstance
(
message
,
dict
):
if
isinstance
(
message
,
dict
):
...
@@ -71,8 +72,53 @@ def get_message_content(result: dict | object) -> str:
...
@@ -71,8 +72,53 @@ def get_message_content(result: dict | object) -> str:
return
""
return
""
def
get_tool_calls
(
result
:
dict
|
object
)
->
list
:
def
chat_with_tools
(
messages
:
list
[
dict
],
tools
:
list
[
Callable
[...,
Any
]],
use_ollama
:
bool
=
True
,
return_after_tools
:
bool
=
False
,
)
->
tuple
[
dict
,
list
[
dict
[
str
,
Any
]]]:
tool_map
=
{
tool
.
__name__
:
tool
for
tool
in
tools
}
result
=
chat
(
messages
=
messages
,
tools
=
tools
,
use_ollama
=
use_ollama
)
tool_outputs
=
_apply_tool_calls
(
result
,
messages
,
tool_map
)
if
not
tool_outputs
or
return_after_tools
:
return
result
,
tool_outputs
final_result
=
chat
(
messages
=
messages
,
tools
=
tools
,
use_ollama
=
use_ollama
)
return
final_result
,
tool_outputs
def
_apply_tool_calls
(
result
:
dict
|
object
,
messages
:
list
[
dict
],
tool_map
:
dict
[
str
,
Callable
[...,
Any
]],
)
->
list
[
dict
[
str
,
Any
]]:
message
=
result
.
get
(
"message"
)
if
isinstance
(
result
,
dict
)
else
result
message
=
result
.
get
(
"message"
)
if
isinstance
(
result
,
dict
)
else
result
tool_calls
=
_extract_tool_calls
(
message
)
outputs
:
list
[
dict
[
str
,
Any
]]
=
[]
if
tool_calls
:
messages
.
append
(
_message_to_dict
(
message
))
for
call
in
tool_calls
:
name
,
arguments
=
_tool_call_name_args
(
call
)
tool
=
tool_map
.
get
(
name
)
if
not
tool
:
output
=
f
"Unknown tool:
{
name
}
"
else
:
try
:
output
=
tool
(
**
arguments
)
except
Exception
as
exc
:
# pragma: no cover - defensive
output
=
f
"Tool error:
{
exc
}
"
outputs
.
append
({
"name"
:
name
,
"arguments"
:
arguments
,
"result"
:
output
})
messages
.
append
({
"role"
:
"tool"
,
"tool_name"
:
name
,
"content"
:
str
(
output
)})
return
outputs
def
_extract_tool_calls
(
message
:
object
)
->
list
:
if
isinstance
(
message
,
dict
):
if
isinstance
(
message
,
dict
):
return
message
.
get
(
"tool_calls"
)
or
[]
return
message
.
get
(
"tool_calls"
)
or
[]
if
hasattr
(
message
,
"tool_calls"
):
if
hasattr
(
message
,
"tool_calls"
):
...
@@ -80,11 +126,42 @@ def get_tool_calls(result: dict | object) -> list:
...
@@ -80,11 +126,42 @@ def get_tool_calls(result: dict | object) -> list:
return
[]
return
[]
def
normalize_tool_call
(
tool_call
:
object
)
->
dict
:
def
_tool_call_name_args
(
call
:
object
)
->
tuple
[
str
,
dict
[
str
,
Any
]]:
if
isinstance
(
tool_call
,
dict
):
if
isinstance
(
call
,
dict
):
function
=
tool_call
.
get
(
"function"
)
or
{}
function
=
call
.
get
(
"function"
)
or
{}
return
{
"name"
:
function
.
get
(
"name"
),
"arguments"
:
function
.
get
(
"arguments"
)}
name
=
function
.
get
(
"name"
)
or
""
function
=
getattr
(
tool_call
,
"function"
,
None
)
arguments
=
function
.
get
(
"arguments"
)
if
function
:
else
:
return
{
"name"
:
getattr
(
function
,
"name"
,
None
),
"arguments"
:
getattr
(
function
,
"arguments"
,
None
)}
function
=
getattr
(
call
,
"function"
,
None
)
return
{
"name"
:
None
,
"arguments"
:
None
}
name
=
getattr
(
function
,
"name"
,
""
)
if
function
else
""
arguments
=
getattr
(
function
,
"arguments"
,
None
)
if
function
else
None
return
name
,
_parse_tool_arguments
(
arguments
)
def
_parse_tool_arguments
(
arguments
:
object
)
->
dict
[
str
,
Any
]:
if
isinstance
(
arguments
,
dict
):
return
arguments
if
isinstance
(
arguments
,
str
)
and
arguments
.
strip
():
try
:
parsed
=
json
.
loads
(
arguments
)
if
isinstance
(
parsed
,
dict
):
return
parsed
except
json
.
JSONDecodeError
:
return
{}
return
{}
def
_message_to_dict
(
message
:
object
)
->
dict
[
str
,
Any
]:
if
isinstance
(
message
,
dict
):
return
message
role
=
getattr
(
message
,
"role"
,
None
)
content
=
getattr
(
message
,
"content"
,
None
)
tool_calls
=
getattr
(
message
,
"tool_calls"
,
None
)
payload
:
dict
[
str
,
Any
]
=
{}
if
role
is
not
None
:
payload
[
"role"
]
=
role
if
content
is
not
None
:
payload
[
"content"
]
=
content
if
tool_calls
is
not
None
:
payload
[
"tool_calls"
]
=
tool_calls
return
payload
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