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
6cf9eb8c
Commit
6cf9eb8c
authored
Jun 19, 2026
by
Kantz
Browse files
simplifications
parent
d9b6c146
Changes
17
Show whitespace changes
Inline
Side-by-side
math-tutor/backend/app/api/chat.py
View file @
6cf9eb8c
...
...
@@ -9,10 +9,11 @@ import app.config as config
from
app.deterministic_services
import
session_store
from
app.deterministic_services
import
context_store
,
retrieval_store
,
task_catalog
from
app.deterministic_services
import
socratic_oranisator
from
app.deterministic_services.orchestrators.registry
import
(
get_default_orchestrator
,
is_valid_orchestrator
,
resolve_orchestrator
,
from
app.deterministic_services.orchestrators
import
(
orchestrator_qa
,
orchestrator_socratic
,
orchestrator_task
,
orchestrator_tutor
,
)
from
fastapi
import
APIRouter
,
HTTPException
,
Path
,
Query
from
pydantic
import
BaseModel
,
Field
...
...
@@ -20,6 +21,13 @@ from pydantic import BaseModel, Field
router
=
APIRouter
()
logger
=
logging
.
getLogger
(
__name__
)
DEFAULT_ORCHESTRATOR
=
"qa"
ORCHESTRATORS
=
{
"qa"
:
orchestrator_qa
,
"tutor"
:
orchestrator_tutor
,
"task"
:
orchestrator_task
,
"socratic"
:
orchestrator_socratic
,
}
class
ChatMessage
(
BaseModel
):
...
...
@@ -87,12 +95,11 @@ def chat(request: ChatRequest) -> ChatResponse:
raise
HTTPException
(
status_code
=
400
,
detail
=
"messages required"
)
requested_orchestrator
=
str
(
request
.
orchestrator
or
""
).
strip
().
lower
()
if
requested_orchestrator
and
not
is_valid_orchestrator
(
requested_orchestrator
)
:
if
requested_orchestrator
and
requested_orchestrator
not
in
ORCHESTRATORS
:
raise
HTTPException
(
status_code
=
422
,
detail
=
"unsupported orchestrator"
)
orchestrator_name
,
orchestrator_impl
=
resolve_orchestrator
(
requested_orchestrator
or
None
)
orchestrator_name
=
requested_orchestrator
or
DEFAULT_ORCHESTRATOR
orchestrator_impl
=
ORCHESTRATORS
[
orchestrator_name
]
try
:
payload_messages
=
[{
"role"
:
m
.
role
,
"content"
:
m
.
text
}
for
m
in
request
.
messages
]
...
...
@@ -203,7 +210,7 @@ def get_archive(chat_id: str = Path(..., min_length=1)) -> ChatArchiveDetail:
history
=
[
ChatMessage
(
role
=
item
[
"role"
],
text
=
item
[
"text"
])
for
item
in
record
[
"history"
]],
selected_task
=
selected_task
,
selected_topic
=
selected_topic
,
orchestrator
=
record
.
get
(
"orchestrator"
)
or
get_default_orchestrator
()
,
orchestrator
=
record
.
get
(
"orchestrator"
)
or
DEFAULT_ORCHESTRATOR
,
)
...
...
@@ -213,9 +220,9 @@ def archive_chat(request: ChatRequest) -> ChatArchiveResponse:
return
ChatArchiveResponse
(
status
=
"skipped"
,
chat_id
=
"unknown"
)
requested_orchestrator
=
str
(
request
.
orchestrator
or
""
).
strip
().
lower
()
if
requested_orchestrator
and
not
is_valid_orchestrator
(
requested_orchestrator
)
:
if
requested_orchestrator
and
requested_orchestrator
not
in
ORCHESTRATORS
:
raise
HTTPException
(
status_code
=
422
,
detail
=
"unsupported orchestrator"
)
archive_orchestrator
=
requested_orchestrator
or
get_default_orchestrator
()
archive_orchestrator
=
requested_orchestrator
or
DEFAULT_ORCHESTRATOR
try
:
chat_id
=
session_store
.
archive_chat
(
...
...
math-tutor/backend/app/deterministic_services/__init__.py
View file @
6cf9eb8c
# Package marker for services
from
app.deterministic_services.retrieval_store
import
Source
,
SourceID
__all__
=
[
"Source"
,
"SourceID"
]
\ No newline at end of file
# Package marker for deterministic services.
math-tutor/backend/app/deterministic_services/context_stores/__init__.py
View file @
6cf9eb8c
from
app.deterministic_services.context_stores
import
context_store_base
from
app.deterministic_services.context_stores
import
context_store_task
from
app.deterministic_services.context_stores
import
context_store_open
__all__
=
[
"context_store_base"
,
"context_store_open"
,
"context_store_task"
]
# Package marker for context store implementations.
math-tutor/backend/app/deterministic_services/context_stores/context_store_base.py
View file @
6cf9eb8c
...
...
@@ -11,7 +11,7 @@ from zoneinfo import ZoneInfo
from
threading
import
Lock
from
typing
import
Any
from
app.deterministic_services
import
Source
from
app.deterministic_services
.retrieval_store
import
Source
from
app.deterministic_services.tool_logging
import
(
format_filename_timestamp
,
format_log_timestamp
,
...
...
@@ -36,12 +36,12 @@ def _resolve_latest_path(chat_id: str) -> str | None:
# timekeeping
# ---
def
_mez_now
()
->
str
:
def
berlin_now_iso
()
->
str
:
return
datetime
.
now
(
ZoneInfo
(
"Europe/Berlin"
)).
strftime
(
"%Y-%m-%dT%H:%M:%SZ"
)
def
_touch
(
sheet
:
dict
[
str
,
Any
])
->
None
:
sheet
[
"updated_at"
]
=
_mez_now
()
sheet
[
"updated_at"
]
=
berlin_now_iso
()
def
touch_sheet
(
sheet
:
dict
[
str
,
Any
])
->
None
:
...
...
@@ -66,7 +66,7 @@ def get_chat_id(messages: list[dict], draft: str | None = None) -> str:
def
init_sheet_base
(
chat_id
:
str
,
messages
:
list
[
dict
])
->
dict
[
str
,
Any
]:
timestamp
=
_mez_now
()
timestamp
=
berlin_now_iso
()
return
{
"chat_id"
:
chat_id
,
"created_at"
:
timestamp
,
...
...
@@ -203,7 +203,7 @@ def get_decisions(sheet: dict[str, Any]) -> list[dict[str, Any]]:
def
add_decision
(
sheet
:
dict
[
str
,
Any
],
decision
:
dict
[
str
,
Any
])
->
None
:
entry
=
{
"timestamp"
:
_mez_now
(),
**
decision
}
entry
=
{
"timestamp"
:
berlin_now_iso
(),
**
decision
}
sheet
.
setdefault
(
"decisions"
,
[]).
append
(
entry
)
_touch
(
sheet
)
...
...
math-tutor/backend/app/deterministic_services/embedding_provider.py
View file @
6cf9eb8c
...
...
@@ -7,7 +7,7 @@ from threading import Lock
from
typing
import
Any
,
Dict
,
Tuple
import
app.config
as
config
from
app.deterministic_services.embeddings
import
BaseEmbeddings
,
EmbeddingFactory
from
app.deterministic_services.embeddings
import
BaseEmbeddings
,
create_embedder
_EMBEDDER_LOCK
=
Lock
()
...
...
@@ -35,7 +35,7 @@ def get_embedder() -> tuple[BaseEmbeddings, bool]:
if
_CACHED_EMBEDDER
is
not
None
and
_CACHED_KEY
==
key
:
return
_CACHED_EMBEDDER
,
True
_CACHED_EMBEDDER
=
EmbeddingFactory
.
create
(
settings
)
_CACHED_EMBEDDER
=
create_embedder
(
settings
)
_CACHED_KEY
=
key
return
_CACHED_EMBEDDER
,
False
...
...
math-tutor/backend/app/deterministic_services/embeddings.py
View file @
6cf9eb8c
# Embeddings-Implementation with Factory-Pattern
from
__future__
import
annotations
import
math
from
typing
import
List
,
Optional
,
Union
from
enum
import
Enum
from
typing
import
List
,
Optional
,
Union
from
openai
import
OpenAI
from
pydantic
import
BaseModel
,
Field
from
sentence_transformers
import
SentenceTransformer
# -----------------------------
# configuration model
# -----------------------------
class
EmbeddingType
(
str
,
Enum
):
OPENAI_LIKE
=
"openai-like"
SENTENCE_TRANSFORMER
=
"sentence-transformer"
class
OpenAILikeConfig
(
BaseModel
):
"""Konfiguration für OpenAI-ähnliche APIs."""
base_url
:
str
=
Field
(...,
description
=
"Base URL der API (z. B. http://localhost:11434/v1)"
)
api_key
:
str
=
Field
(...,
description
=
"API-Key (z. B. 'ollama' für Ollama)"
)
model
:
str
=
Field
(...,
description
=
"Modellname (z. B. 'nomic-embed-text')"
)
target_dim
:
int
=
Field
(
1024
,
description
=
"Ziel-Dimension der Embeddings"
)
base_url
:
str
=
Field
(...,
description
=
"Base URL of the embedding API."
)
api_key
:
str
=
Field
(...,
description
=
"API key for the embedding API."
)
model
:
str
=
Field
(...,
description
=
"Embedding model name."
)
target_dim
:
int
=
Field
(
1024
,
description
=
"Target embedding dimension."
)
class
SentenceTransformerConfig
(
BaseModel
):
"""Konfiguration für lokale SentenceTransformer-Modelle."""
model
:
str
=
Field
(...,
description
=
"Name des SentenceTransformer-Modells (z. B. 'all-MiniLM-L6-v2')"
)
target_dim
:
int
=
Field
(
384
,
description
=
"Ziel-Dimension der Embeddings"
)
model
:
str
=
Field
(...,
description
=
"SentenceTransformer model name."
)
target_dim
:
int
=
Field
(
384
,
description
=
"Target embedding dimension."
)
class
EmbeddingConfig
(
BaseModel
):
"""Gemeinsame Konfiguration für die Factory."""
embedding_type
:
EmbeddingType
=
Field
(...,
description
=
"Typ der Embeddings"
)
embedding_type
:
EmbeddingType
=
Field
(...,
description
=
"Embedding backend type."
)
config
:
Union
[
OpenAILikeConfig
,
SentenceTransformerConfig
]
=
Field
(
...,
description
=
"Spezifische Konfiguration"
)
# -----------------------------
# base class for embeddings
# -----------------------------
...,
description
=
"Backend-specific embedding config."
)
class
BaseEmbeddings
:
"""
Basisklasse für Embedding-Generierung mit gemeinsamen Methoden.
"""
def
__init__
(
self
,
target_dim
:
int
=
384
)
->
None
:
self
.
target_dim
=
target_dim
def
_normalize
(
self
,
vec
:
List
[
float
])
->
List
[
float
]:
"""Normalisiert einen Vektor auf L2-Norm."""
norm
=
math
.
sqrt
(
sum
(
x
*
x
for
x
in
vec
))
if
norm
==
0.0
:
return
vec
return
[
x
/
norm
for
x
in
vec
]
def
_truncate
(
self
,
vec
:
List
[
float
])
->
List
[
float
]:
"""Trunziert oder füllt den Vektor auf die Ziel-Dimension."""
if
len
(
vec
)
<
self
.
target_dim
:
raise
ValueError
(
f
"Embedding dimension
{
len
(
vec
)
}
< target
{
self
.
target_dim
}
"
)
raise
ValueError
(
f
"Embedding dimension
{
len
(
vec
)
}
< target
{
self
.
target_dim
}
"
)
if
len
(
vec
)
>
self
.
target_dim
:
vec
=
vec
[:
self
.
target_dim
]
return
self
.
_normalize
(
vec
)
def
embed_documents
(
self
,
texts
:
List
[
str
])
->
List
[
List
[
float
]]:
"""Generiert Embeddings für eine Liste von Texten."""
return
self
.
_embed
(
texts
)
def
embed_query
(
self
,
text
:
str
)
->
List
[
float
]:
"""Generiert ein Embedding für einen einzelnen Text."""
return
self
.
_embed
(
text
)[
0
]
def
_embed
(
self
,
inputs
:
List
[
str
]
|
str
)
->
List
[
List
[
float
]]:
"""Abstrakte Methode – muss in Unterklassen implementiert werden."""
raise
NotImplementedError
(
"Subclass must implement _embed method."
)
raise
NotImplementedError
# -----------------------------
# subclass
# -----------------------------
class
OpenAILikeEmbeddings
(
BaseEmbeddings
):
"""
Embeddings-Wrapper für OpenAI-ähnliche APIs (z. B. OpenAI, Ollama, TogetherAI).
"""
def
__init__
(
self
,
config
:
OpenAILikeConfig
)
->
None
:
super
().
__init__
(
target_dim
=
config
.
target_dim
)
self
.
base_url
=
config
.
base_url
.
rstrip
(
"/"
)
...
...
@@ -103,11 +68,7 @@ class OpenAILikeEmbeddings(BaseEmbeddings):
self
.
model
=
config
.
model
def
_embed
(
self
,
inputs
:
List
[
str
]
|
str
)
->
List
[
List
[
float
]]:
"""Ruft die externe Embedding-API auf."""
client
=
OpenAI
(
api_key
=
self
.
api_key
,
base_url
=
self
.
base_url
,
)
client
=
OpenAI
(
api_key
=
self
.
api_key
,
base_url
=
self
.
base_url
)
response
=
client
.
embeddings
.
create
(
input
=
inputs
,
model
=
self
.
model
,
...
...
@@ -118,16 +79,12 @@ class OpenAILikeEmbeddings(BaseEmbeddings):
if
not
isinstance
(
data
,
list
):
raise
ValueError
(
"Embedding response missing 'data' list."
)
# Sortiere nach Index, falls nötig
data_sorted
=
sorted
(
data
,
key
=
_embedding_item_index
)
embeddings
:
List
[
List
[
float
]]
=
[]
for
item
in
data_
sorted
:
for
item
in
sorted
(
data
,
key
=
_embedding_item_index
)
:
emb
=
_embedding_item_vector
(
item
)
if
not
isinstance
(
emb
,
list
):
raise
ValueError
(
"Embedding item missing 'embedding' list."
)
embeddings
.
append
(
self
.
_truncate
([
float
(
x
)
for
x
in
emb
]))
return
embeddings
...
...
@@ -144,10 +101,6 @@ def _embedding_item_vector(item: object) -> object:
class
SentenceTransformerEmbeddings
(
BaseEmbeddings
):
"""
Embeddings-Wrapper für lokale SentenceTransformer Modelle.
"""
def
__init__
(
self
,
config
:
SentenceTransformerConfig
)
->
None
:
super
().
__init__
(
target_dim
=
config
.
target_dim
)
self
.
model_name
=
config
.
model
...
...
@@ -155,61 +108,31 @@ class SentenceTransformerEmbeddings(BaseEmbeddings):
@
property
def
model
(
self
)
->
SentenceTransformer
:
"""Liefert das SentenceTransformer-Modell (lazy load)."""
if
self
.
_model
is
None
:
self
.
_model
=
SentenceTransformer
(
self
.
model_name
,
trust_remote_code
=
True
,
)
self
.
_model
=
SentenceTransformer
(
self
.
model_name
,
trust_remote_code
=
True
)
self
.
_model
.
max_seq_length
=
512
return
self
.
_model
def
embed_documents
(
self
,
texts
:
List
[
str
])
->
List
[
List
[
float
]]:
"""Generiert Embeddings für eine Liste von Texten."""
passage_embeddings
=
self
.
model
.
encode
(
vectors
=
self
.
model
.
encode
(
sentences
=
texts
,
task
=
"retrieval"
,
prompt_name
=
"document"
,
)
return
[
self
.
_truncate
([
float
(
x
)
for
x
in
emb
])
for
emb
in
passage_embedding
s
]
return
[
self
.
_truncate
([
float
(
x
)
for
x
in
emb
])
for
emb
in
vector
s
]
def
embed_query
(
self
,
text
:
str
)
->
List
[
float
]:
"""Generiert ein Embedding für einen einzelnen Text."""
query_embeddings
=
self
.
model
.
encode
(
vectors
=
self
.
model
.
encode
(
sentences
=
[
text
],
task
=
"retrieval"
,
prompt_name
=
"query"
,
)
return
self
.
_truncate
([
float
(
x
)
for
x
in
query_embeddings
[
0
]])
# -----------------------------
# Factory: creats embeddings based on config
# -----------------------------
class
EmbeddingFactory
:
"""
Factory-Klasse zur dynamischen Erzeugung von Embeddings-Instanzen.
"""
@
staticmethod
def
create
(
config
:
EmbeddingConfig
)
->
BaseEmbeddings
:
"""
Erzeugt eine Embeddings-Instanz basierend auf der Konfiguration.
Args:
config (EmbeddingConfig): Die Konfiguration mit Typ und Details.
return
self
.
_truncate
([
float
(
x
)
for
x
in
vectors
[
0
]])
Returns:
BaseEmbeddings: Instanz der passenden Embeddings-Klasse.
Raises:
ValueError: Wenn der Typ nicht unterstützt wird.
"""
def
create_embedder
(
config
:
EmbeddingConfig
)
->
BaseEmbeddings
:
if
config
.
embedding_type
==
EmbeddingType
.
OPENAI_LIKE
:
return
OpenAILikeEmbeddings
(
config
=
config
)
el
if
config
.
embedding_type
==
EmbeddingType
.
SENTENCE_TRANSFORMER
:
if
config
.
embedding_type
==
EmbeddingType
.
SENTENCE_TRANSFORMER
:
return
SentenceTransformerEmbeddings
(
config
=
config
)
else
:
raise
ValueError
(
f
"Unsupported embedding type:
{
config
.
embedding_type
}
"
)
raise
ValueError
(
f
"Unsupported embedding type:
{
config
.
embedding_type
}
"
)
math-tutor/backend/app/deterministic_services/orchestrators/orchestrator_base.py
View file @
6cf9eb8c
...
...
@@ -2,13 +2,10 @@ from __future__ import annotations
import
time
from
dataclasses
import
dataclass
from
datetime
import
datetime
from
zoneinfo
import
ZoneInfo
from
typing
import
Any
,
Callable
,
List
,
TypeVar
import
app.config
as
config
from
app.deterministic_services
import
(
Source
,
context_store
,
embedding_provider
,
referenz_decoder
,
...
...
@@ -16,6 +13,8 @@ from app.deterministic_services import (
tool_log_context
,
tool_logging
,
)
from
app.deterministic_services.context_stores.context_store_base
import
berlin_now_iso
from
app.deterministic_services.retrieval_store
import
Source
T
=
TypeVar
(
"T"
)
...
...
@@ -40,16 +39,12 @@ def is_new_chat(messages: list[dict]) -> bool:
# Timekeeping
# ---
def
_utc_now_iso
()
->
str
:
return
datetime
.
now
(
ZoneInfo
(
"Europe/Berlin"
)).
strftime
(
"%Y-%m-%dT%H:%M:%SZ"
)
def
_start_timing
()
->
tuple
[
str
,
float
]:
return
_utc
_now_iso
(),
time
.
perf_counter
()
return
berlin
_now_iso
(),
time
.
perf_counter
()
def
_finish_timing
(
started_perf
:
float
)
->
tuple
[
str
,
float
]:
finished_at
=
_utc
_now_iso
()
finished_at
=
berlin
_now_iso
()
duration_ms
=
round
((
time
.
perf_counter
()
-
started_perf
)
*
1000
,
2
)
return
finished_at
,
duration_ms
...
...
math-tutor/backend/app/deterministic_services/orchestrators/registry.py
deleted
100644 → 0
View file @
d9b6c146
from
__future__
import
annotations
from
typing
import
Any
from
app.deterministic_services.orchestrators
import
(
orchestrator_qa
,
orchestrator_socratic
,
orchestrator_task
,
orchestrator_tutor
,
)
AVAILABLE_ORCHESTRATORS
:
tuple
[
str
,
...]
=
(
"qa"
,
"tutor"
,
"task"
,
"socratic"
)
DEFAULT_ORCHESTRATOR
=
"qa"
_ORCHESTRATOR_MODULES
:
dict
[
str
,
Any
]
=
{
"qa"
:
orchestrator_qa
,
"tutor"
:
orchestrator_tutor
,
"task"
:
orchestrator_task
,
"socratic"
:
orchestrator_socratic
,
}
def
get_default_orchestrator
()
->
str
:
return
DEFAULT_ORCHESTRATOR
def
is_valid_orchestrator
(
value
:
str
)
->
bool
:
return
value
in
_ORCHESTRATOR_MODULES
def
resolve_orchestrator
(
value
:
str
|
None
=
None
)
->
tuple
[
str
,
Any
]:
if
value
:
normalized
=
value
.
strip
().
lower
()
if
normalized
in
_ORCHESTRATOR_MODULES
:
return
normalized
,
_ORCHESTRATOR_MODULES
[
normalized
]
default_name
=
get_default_orchestrator
()
return
default_name
,
_ORCHESTRATOR_MODULES
[
default_name
]
math-tutor/backend/app/deterministic_services/retrieval_store.py
View file @
6cf9eb8c
...
...
@@ -13,14 +13,6 @@ from pydantic import BaseModel
from
app.deterministic_services.vector_store
import
EmbeddingLike
,
embed_query
def
_load_subsection_store
():
from
app.deterministic_services
import
vector_store_subsection
return
vector_store_subsection
class
SourceID
(
BaseModel
):
chapter_title
:
Optional
[
str
]
=
None
section_title
:
Optional
[
str
]
=
None
...
...
@@ -29,16 +21,6 @@ class SourceID(BaseModel):
title
:
str
doc_type
:
str
def
to_dict
(
self
)
->
Dict
[
str
,
Any
]:
return
{
"chapter_title"
:
self
.
chapter_title
,
"section_title"
:
self
.
section_title
,
"subsection_title"
:
self
.
subsection_title
,
"subsubsection_title"
:
self
.
subsubsection_title
,
"title"
:
self
.
title
,
"doc_type"
:
self
.
doc_type
,
}
def
to_string
(
self
)
->
str
:
string_rep
=
self
.
title
if
self
.
subsubsection_title
:
...
...
@@ -59,15 +41,6 @@ class Source(BaseModel):
score
:
float
markdown
:
str
def
to_dict
(
self
)
->
Dict
[
str
,
Any
]:
return
{
"source_id"
:
self
.
source_id
.
to_dict
(),
"retrieved_as"
:
self
.
retrieved_as
,
"source_type"
:
self
.
source_type
,
"score"
:
self
.
score
,
"markdown"
:
self
.
markdown
,
}
def
to_string
(
self
)
->
str
:
return
(
f
"Source(source_id=
{
self
.
source_id
.
to_string
()
}
,
\n
"
...
...
@@ -86,16 +59,6 @@ class Retrieved:
metadata
:
Dict
[
str
,
Any
]
markdown
:
str
def
to_dict
(
self
)
->
Dict
[
str
,
Any
]:
return
{
"uid"
:
self
.
uid
,
"doc_type"
:
self
.
doc_type
,
"score"
:
self
.
score
,
"metadata"
:
self
.
metadata
,
"markdown"
:
self
.
markdown
,
}
def
_row_to_retrieved
(
row
:
Dict
[
str
,
Any
],
source_type
:
Optional
[
str
]
=
None
)
->
Retrieved
:
meta
=
{
"uid"
:
row
[
"uid"
],
...
...
math-tutor/backend/app/deterministic_services/session_store.py
View file @
6cf9eb8c
...
...
@@ -5,22 +5,17 @@ from __future__ import annotations
import
json
import
os
from
collections
import
deque
from
datetime
import
datetime
from
threading
import
Lock
from
typing
import
Any
from
zoneinfo
import
ZoneInfo
from
app.deterministic_services
import
context_store
from
app.deterministic_services.context_stores.context_store_base
import
berlin_now_iso
_LOCK
=
Lock
()
_LOG_DIR
=
os
.
path
.
join
(
"logs"
,
"chat_sessions"
)
_LOG_PATH
=
os
.
path
.
join
(
_LOG_DIR
,
"archive.jsonl"
)
def
_mez_now
()
->
str
:
return
datetime
.
now
(
ZoneInfo
(
"Europe/Berlin"
)).
strftime
(
"%Y-%m-%dT%H:%M:%SZ"
)
def
_extract_selected_task
(
sheet
:
dict
[
str
,
Any
])
->
dict
[
str
,
str
]
|
None
:
file_id
=
str
(
sheet
.
get
(
"task_file_id"
,
""
)).
strip
()
task_id
=
str
(
sheet
.
get
(
"task_id"
,
""
)).
strip
()
...
...
@@ -51,7 +46,7 @@ def archive_chat(
record
=
{
"chat_id"
:
chat_id
,
"saved_at"
:
_mez_now
(),
"saved_at"
:
berlin_now_iso
(),
"orchestrator"
:
str
(
orchestrator
or
""
).
strip
().
lower
()
or
None
,
"history"
:
sheet
.
get
(
"history"
,
[]),
"context_sheet"
:
context_store
.
format_sheet
(
sheet
),
...
...
math-tutor/backend/requirements.txt
View file @
6cf9eb8c
...
...
@@ -2,13 +2,11 @@ fastapi
uvicorn
python-dotenv
mpxpy
pillow
httpx
ollama
openai
mistralai
mcp
sympy
psycopg[binary]
pgvector
pyyaml
...
...
math-tutor/backend/scripts/retrieval_cli.py
View file @
6cf9eb8c
...
...
@@ -3,7 +3,7 @@
from
__future__
import
annotations
from
app.deterministic_services
import
retrieval_store
,
vector_store
from
app.deterministic_services.embeddings
import
BaseEmbeddings
,
EmbeddingFactory
from
app.deterministic_services.embeddings
import
BaseEmbeddings
,
create_embedder
import
argparse
import
os
...
...
@@ -21,7 +21,7 @@ if str(ROOT_DIR) not in sys.path:
def
build_embedder
()
->
BaseEmbeddings
:
return
EmbeddingFactory
.
create
(
config
.
get_embedding_settings
())
return
create_embedder
(
config
.
get_embedding_settings
())
def
cli_init_db
(
args
:
argparse
.
Namespace
)
->
None
:
...
...
math-tutor/backend/test/retrieval_store_test.py
View file @
6cf9eb8c
...
...
@@ -5,7 +5,7 @@ os.environ.setdefault("EMBEDDING_PROVIDER", "sentence-transformer")
os
.
environ
.
setdefault
(
"EMBEDDING_TYPE"
,
"sentence-transformer"
)
from
app
import
config
from
app.deterministic_services.embeddings
import
EmbeddingFactory
from
app.deterministic_services.embeddings
import
create_embedder
from
app.deterministic_services
import
retrieval_store
...
...
@@ -70,7 +70,7 @@ def main() -> None:
args
=
parser
.
parse_args
()
pg_url
=
args
.
pg
or
config
.
get_postgres_url
()
embedder
=
EmbeddingFactory
.
create
(
config
.
get_embedding_settings
())
embedder
=
create_embedder
(
config
.
get_embedding_settings
())
subsection_refs
=
_parse_subsections
(
args
.
subsections
,
args
.
chapter_index
,
args
.
section_index
...
...
math-tutor/backend/test/vector_store_pipeline_unit_test.py
View file @
6cf9eb8c
...
...
@@ -7,8 +7,9 @@ from unittest.mock import patch
os
.
environ
.
setdefault
(
"EMBEDDING_PROVIDER"
,
"sentence-transformer"
)
os
.
environ
.
setdefault
(
"EMBEDDING_TYPE"
,
"sentence-transformer"
)
from
app.deterministic_services
import
Source
as
PackageSource
,
SourceID
as
PackageSourceID
from
app.deterministic_services.retrieval_store
import
(
Source
as
PackageSource
,
SourceID
as
PackageSourceID
,
Retrieved
,
Source
,
SourceID
,
...
...
math-tutor/frontend/src/state/tutorSession.tsx
View file @
6cf9eb8c
...
...
@@ -12,7 +12,7 @@ import {
import
{
t
}
from
"
../i18n
"
;
import
{
DEFAULT_ORCHESTRATOR
,
FALLBACK_
ORCHESTRATORS
,
ORCHESTRATORS
,
isTaskCoupledOrchestrator
,
type
OrchestratorName
,
}
from
"
../utils/orchestrator
"
;
...
...
@@ -102,7 +102,7 @@ export function TutorSessionProvider({ children }: PropsWithChildren) {
const
[
selectedOrchestrator
,
setSelectedOrchestratorState
]
=
useState
<
OrchestratorName
>
(
DEFAULT_ORCHESTRATOR
);
const
[
availableOrchestrators
,
setAvailableOrchestrators
]
=
useState
<
OrchestratorName
[]
>
(
()
=>
[...
FALLBACK_
ORCHESTRATORS
]
()
=>
[...
ORCHESTRATORS
]
);
const
[
isTasksInitialized
,
setIsTasksInitialized
]
=
useState
(
false
);
const
[
taskFiles
,
setTaskFiles
]
=
useState
<
TaskFile
[]
>
([]);
...
...
@@ -313,7 +313,7 @@ export function TutorSessionProvider({ children }: PropsWithChildren) {
const
initTasks
=
useCallback
(
async
()
=>
{
try
{
const
available
=
[...
FALLBACK_
ORCHESTRATORS
];
const
available
=
[...
ORCHESTRATORS
];
const
nextOrchestrator
=
available
.
includes
(
selectedOrchestrator
)
?
selectedOrchestrator
:
DEFAULT_ORCHESTRATOR
;
...
...
@@ -322,7 +322,7 @@ export function TutorSessionProvider({ children }: PropsWithChildren) {
setSelectedOrchestratorState
(
nextOrchestrator
);
await
loadSelectionData
(
nextOrchestrator
);
}
catch
(
error
)
{
setAvailableOrchestrators
([...
FALLBACK_
ORCHESTRATORS
]);
setAvailableOrchestrators
([...
ORCHESTRATORS
]);
setSelectedOrchestratorState
(
DEFAULT_ORCHESTRATOR
);
await
loadSelectionData
(
DEFAULT_ORCHESTRATOR
);
void
error
;
...
...
math-tutor/frontend/src/utils/orchestrator.ts
View file @
6cf9eb8c
...
...
@@ -4,8 +4,6 @@ export type OrchestratorName = (typeof ORCHESTRATORS)[number];
export
const
DEFAULT_ORCHESTRATOR
:
OrchestratorName
=
"
qa
"
;
export
const
FALLBACK_ORCHESTRATORS
:
OrchestratorName
[]
=
[...
ORCHESTRATORS
];
export
const
normalizeOrchestrator
=
(
value
:
string
|
null
|
undefined
):
OrchestratorName
|
null
=>
{
...
...
math-tutor/frontend/src/utils/orchestratorRoutes.ts
deleted
100644 → 0
View file @
d9b6c146
export
{
getSelectionRouteForOrchestrator
,
isSocraticOrchestrator
,
isTaskSelectionOrchestrator
,
type
OrchestratorName
,
}
from
"
./orchestrator
"
;
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