Commit d8327473 authored by Kantz's avatar Kantz
Browse files

auräumaktion

parent d58cbed7
......@@ -15,7 +15,11 @@ cd math-tutor/frontend
npm install
npm run dev
```
Um es im Netzwerk verfügbar zu machen mit: npm run dev -- --host 0.0.0.0
To make it accessible over the network.
Add the frontend- and backend-adress in the .env file in the frontend- and backend-folder.
python -m uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
npm run dev -- --host 0.0.0.0
## Database (pgvector) setup
Ensure `POSTGRES_URL` and embedding env vars are in `backend/.env`:
......@@ -51,7 +55,7 @@ python -m scripts.retrieval_cli query --q "Was ist eine Teilmenge?" --k 8 --expa
## Configuration
System prompt (LLM):
Differend Orchestrators:
- Edit `math-tutor/backend/app/api/chat.py` and update `SYSTEM_PROMPT`.
Retrieval settings:
......
from __future__ import annotations
from logging import config
from typing import List, Optional
import logging
......@@ -8,9 +9,12 @@ from fastapi import APIRouter, HTTPException, Path, Query
from pydantic import BaseModel, Field
from app.deterministic_services import session_store
from app.deterministic_services.orchestrators import orchestrator_tutor as orchestrator
from app.deterministic_services.orchestrators import orchestrator_QA
import app.config as config
if config.get_orchestrator() == "tutor":
from app.deterministic_services.orchestrators import orchestrator_tutor as orchestrator
else:
from app.deterministic_services.orchestrators import orchestrator_QA as orchestrator
router = APIRouter()
......
......@@ -6,7 +6,6 @@ from fastapi import APIRouter, HTTPException
from pydantic import BaseModel, Field
from app.deterministic_services import context_store
from app.deterministic_services import Source
router = APIRouter()
......
from __future__ import annotations
import logging
import os
from typing import Any, Dict
import httpx
import psycopg
from fastapi import APIRouter
import app.config as config
router = APIRouter()
logger = logging.getLogger(__name__)
def _check_ollama() -> dict:
base_url = os.getenv("OLLAMA_URL", "http://localhost:11434").rstrip("/")
base_url = config.get_ollama_settings().base_url.rstrip("/")
url = f"{base_url}/api/tags"
try:
with httpx.Client(timeout=5.0) as client:
......@@ -34,14 +35,13 @@ def _normalize_openai_models_url(base_url: str) -> str:
def _check_openai() -> dict:
base_url = os.getenv("OPENAI_BASE_URL")
api_key = os.getenv("OPENAI_API_KEY")
if not base_url or not api_key:
settings = config.get_openai_base_settings()
if not settings:
return {"status": "missing_config"}
url = _normalize_openai_models_url(base_url)
url = _normalize_openai_models_url(settings.base_url)
try:
headers = {"Authorization": f"Bearer {api_key}"}
headers = {"Authorization": f"Bearer {settings.api_key}"}
with httpx.Client(timeout=5.0) as client:
response = client.get(url, headers=headers)
if response.status_code in (401, 403):
......@@ -53,8 +53,9 @@ def _check_openai() -> dict:
def _check_postgres() -> dict:
pg_url = os.getenv("POSTGRES_URL")
if not pg_url:
try:
pg_url = config.get_postgres_url()
except ValueError:
return {"status": "missing_config"}
try:
with psycopg.connect(pg_url, connect_timeout=5) as conn:
......
from __future__ import annotations
from __future__ import annotations
import os
from pathlib import Path
from typing import List, Optional
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel, Field
from app import config
from app.deterministic_services.embeddings import OpenAILikeEmbeddings
from app.deterministic_services import vector_store
router = APIRouter()
def _get_embedder() -> OpenAILikeEmbeddings:
settings = config.get_embedding_settings()
return OpenAILikeEmbeddings(
base_url=settings.base_url,
api_key=settings.api_key,
model=settings.model,
target_dim=settings.target_dim,
)
class InitDbRequest(BaseModel):
pg_url: Optional[str] = None
class IngestRequest(BaseModel):
pg_url: Optional[str] = None
base_dir: str = Field(default="markdown")
class QueryRequest(BaseModel):
pg_url: Optional[str] = None
query: str = Field(..., min_length=1)
k: int = 4
expand_links: bool = True
section_index: Optional[int] = None
subsection_index: Optional[int] = None
source_type_filter: Optional[List[str]] = None
neighbor_expand: int = 0
@router.post("/api/retrieval/init-db")
def init_db(request: InitDbRequest) -> dict:
pg_url = request.pg_url or os.getenv("POSTGRES_URL")
if not pg_url:
raise HTTPException(status_code=500, detail="Missing POSTGRES_URL")
vector_store.init_db(pg_url)
return {"status": "ok"}
@router.post("/api/retrieval/ingest")
def ingest(request: IngestRequest) -> dict:
pg_url = request.pg_url or os.getenv("POSTGRES_URL")
if not pg_url:
raise HTTPException(status_code=500, detail="Missing POSTGRES_URL")
base_dir = Path(request.base_dir)
if not base_dir.exists():
raise HTTPException(status_code=400, detail="base_dir does not exist")
embedder = _get_embedder()
docs = vector_store.load_docs(base_dir)
embeddings = vector_store.embed_documents(embedder, [doc.markdown for doc in docs])
upserted = vector_store.upsert_docs(pg_url, docs, embeddings)
return {"status": "ok", "upserted": upserted}
@router.post("/api/retrieval/query")
def query(request: QueryRequest) -> dict:
pg_url = request.pg_url or os.getenv("POSTGRES_URL")
if not pg_url:
raise HTTPException(status_code=500, detail="Missing POSTGRES_URL")
embedder = _get_embedder()
result = vector_store.retrieve(
pg_url=pg_url,
embedder=embedder,
query=request.query,
k=request.k,
section_index=request.section_index,
subsection_index=request.subsection_index,
source_type_filter=request.source_type_filter,
expand_links=request.expand_links,
neighbor_expand=request.neighbor_expand,
)
return result
@router.get("/api/retrieval/sections")
def sections(pg_url: Optional[str] = None) -> list[dict]:
url = pg_url or os.getenv("POSTGRES_URL")
if not url:
raise HTTPException(status_code=500, detail="Missing POSTGRES_URL")
return vector_store.list_sections(url)
@router.get("/api/retrieval/subsections")
def subsections(pg_url: Optional[str] = None, section_index: Optional[int] = None) -> list[dict]:
url = pg_url or os.getenv("POSTGRES_URL")
if not url:
raise HTTPException(status_code=500, detail="Missing POSTGRES_URL")
return vector_store.list_subsections(url, section_index)
......@@ -14,6 +14,10 @@ class EmbeddingSettings(BaseModel):
model: str
target_dim: int = 1024
def get_orchestrator() -> str:
return os.getenv("ORCHESTRATOR", "qa").lower()
def get_embedding_settings() -> EmbeddingSettings:
embedding_type = os.getenv("EMBEDDING_TYPE", "openai-like")
if embedding_type == "sentence-transformer":
......@@ -56,6 +60,11 @@ class OpenAIChatSettings:
timeout: float | None
temperature: float | None
@dataclass(frozen=True)
class OpenAIBaseSettings:
base_url: str
api_key: str
@dataclass(frozen=True)
class MathpixSettings:
app_id: str
......@@ -91,17 +100,26 @@ def get_ollama_settings() -> OllamaSettings:
temperature=_read_float(os.getenv("OLLAMA_TEMPERATURE")),
)
def get_openai_base_settings() -> OpenAIBaseSettings | None:
base_url = os.getenv("OPENAI_BASE_URL")
api_key = os.getenv("OPENAI_API_KEY")
if not base_url or not api_key:
return None
return OpenAIBaseSettings(
base_url=base_url,
api_key=api_key,
)
def get_openai_chat_settings() -> OpenAIChatSettings | None:
model = os.getenv("OPENAI_CHAT_MODEL")
if not model:
return None
base_url = os.getenv("OPENAI_BASE_URL")
api_key = os.getenv("OPENAI_API_KEY")
if not base_url or not api_key:
base_settings = get_openai_base_settings()
if not base_settings:
raise ValueError("Missing OPENAI_BASE_URL or OPENAI_API_KEY for chat")
return OpenAIChatSettings(
base_url=base_url,
api_key=api_key,
base_url=base_settings.base_url,
api_key=base_settings.api_key,
model=model,
timeout=_read_float(os.getenv("OPENAI_CHAT_TIMEOUT")),
temperature=_read_float(os.getenv("OPENAI_CHAT_TEMPERATURE")),
......
from app.deterministic_services import context_store, retrieval_service, tool_logging
from app.deterministic_services import referenz_decoder
from app.LLM_services import qa_LLM
from typing import List
from app.deterministic_services import context_store, tool_logging, vector_store, Source, referenz_decoder
from app.LLM_services import qa_LLM
from app.deterministic_services.embeddings import EmbeddingFactory
import app.config as config
def _is_new_chat(messages: list[dict]) -> bool:
return not any(m.get("role") == "assistant" for m in messages)
......@@ -13,7 +15,7 @@ def _append_tool_log(tool_log: list[dict], name: str, args: dict, response: obje
def _bootstrap_context(sheet: dict, query_text: str, tool_log: list[dict]) -> None:
# Erstelle einen neuen Retrieval-Block oder aktualisiere den bestehenden
sources = retrieval_service.retrieve_context(query_text=query_text)
sources = _retrieve_context(query_text=query_text)
retrievals = sheet.get("retrieval_contexts", [])
if retrievals:
......@@ -36,6 +38,17 @@ def _extract_user_messages(messages: list[dict]) -> list[str]:
user_contents.append(content)
return user_contents
def _retrieve_context(query_text: str, pg_url: str | None = None) -> List[Source]:
embedder = EmbeddingFactory.create(config.get_embedding_settings())
url = pg_url or config.get_postgres_url()
sources = vector_store.retrieve(
pg_url=url,
embedder=embedder,
query=query_text,
k=8,
expand_links=True,
)
return sources
def run_chat(messages: list[dict], draft: str | None = None) -> dict:
# Input-Fehlerbehandlung
......
from app.deterministic_services import context_store, retrieval_service, tool_logging
from app.deterministic_services import referenz_decoder
from typing import List
from app.deterministic_services import context_store, tool_logging, vector_store, Source, referenz_decoder
from app.LLM_services import hint_LLM, decision_LLM, math_intent_LLM, solver_LLM
from app.deterministic_services.embeddings import EmbeddingFactory
import app.config as config
def _is_new_chat(messages: list[dict]) -> bool:
......@@ -12,7 +16,7 @@ def _append_tool_log(tool_log: list[dict], name: str, args: dict, response: obje
def _bootstrap_context(sheet: dict, query_text: str, tool_log: list[dict]) -> None:
# Erstelle einen neuen Retrieval-Block oder aktualisiere den bestehenden
sources = retrieval_service.retrieve_context(query_text=query_text)
sources = _retrieve_context(query_text=query_text)
retrievals = sheet.get("retrieval_contexts", [])
if retrievals:
......@@ -40,6 +44,18 @@ def _extract_user_messages(messages: list[dict]) -> list[str]:
user_contents.append(content)
return user_contents
def _retrieve_context(query_text: str, pg_url: str | None = None) -> List[Source]:
embedder = EmbeddingFactory.create(config.get_embedding_settings())
url = pg_url or config.get_postgres_url()
sources = vector_store.retrieve(
pg_url=url,
embedder=embedder,
query=query_text,
k=8,
expand_links=True,
)
return sources
def run_chat(messages: list[dict], draft: str | None = None) -> dict:
# Input-Fehlerbehandlung
......
# app/deterministic_services/retrieval_service.py
from __future__ import annotations
from typing import List
from app.deterministic_services import Source
from app import config
from app.deterministic_services.embeddings import EmbeddingFactory
from app.deterministic_services import vector_store
def retrieve_context(query_text: str, pg_url: str | None = None) -> List[Source]:
embedder = EmbeddingFactory.create(config.get_embedding_settings())
url = pg_url or config.get_postgres_url()
sources = vector_store.retrieve(
pg_url=url,
embedder=embedder,
query=query_text,
k=8,
expand_links=True,
)
return sources
\ No newline at end of file
......@@ -2,7 +2,7 @@ from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.api import canvas, chat, health, retrieval, context
from app.api import canvas, chat, health, context
from app.config import get_frontend_url
......@@ -24,6 +24,5 @@ app.add_middleware(
app.include_router(chat.router)
app.include_router(canvas.router)
app.include_router(retrieval.router)
app.include_router(context.router)
app.include_router(health.router)
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