Commit 2789b332 authored by Kantz's avatar Kantz
Browse files

adding comments

parent 8c1db631
# Context store base implementation.
from __future__ import annotations from __future__ import annotations
import hashlib import hashlib
...@@ -30,6 +32,9 @@ def _resolve_latest_path(chat_id: str) -> str | None: ...@@ -30,6 +32,9 @@ def _resolve_latest_path(chat_id: str) -> str | None:
return max(matches, key=os.path.getmtime) return max(matches, key=os.path.getmtime)
return None return None
# ---
# timekeeping
# ---
def _mez_now() -> str: def _mez_now() -> str:
return datetime.now(ZoneInfo("Europe/Berlin")).strftime("%Y-%m-%dT%H:%M:%SZ") return datetime.now(ZoneInfo("Europe/Berlin")).strftime("%Y-%m-%dT%H:%M:%SZ")
...@@ -43,6 +48,10 @@ def touch_sheet(sheet: dict[str, Any]) -> None: ...@@ -43,6 +48,10 @@ def touch_sheet(sheet: dict[str, Any]) -> None:
_touch(sheet) _touch(sheet)
# ---
# setup and formatting
# ---
def get_chat_id(messages: list[dict], draft: str | None = None) -> str: def get_chat_id(messages: list[dict], draft: str | None = None) -> str:
if draft: if draft:
return draft return draft
...@@ -130,6 +139,9 @@ def format_sheet_base(sheet: dict[str, Any]) -> str: ...@@ -130,6 +139,9 @@ def format_sheet_base(sheet: dict[str, Any]) -> str:
parts.append(f"INITIALIZED:\n{get_initialized(sheet)}") parts.append(f"INITIALIZED:\n{get_initialized(sheet)}")
return "\n\n".join(parts) return "\n\n".join(parts)
# ---
# getter and setter
# ---
def set_chat_id(sheet: dict[str, Any], value: str) -> None: def set_chat_id(sheet: dict[str, Any], value: str) -> None:
sheet["chat_id"] = value sheet["chat_id"] = value
......
# Embedding
from __future__ import annotations from __future__ import annotations
import time import time
...@@ -38,6 +40,8 @@ def get_embedder() -> tuple[BaseEmbeddings, bool]: ...@@ -38,6 +40,8 @@ def get_embedder() -> tuple[BaseEmbeddings, bool]:
return _CACHED_EMBEDDER, False return _CACHED_EMBEDDER, False
# Warm up the embedder by initializing it and running a dummy query. This can help reduce latency for the first real query.
def warmup_embedder() -> Dict[str, Any]: def warmup_embedder() -> Dict[str, Any]:
started = time.perf_counter() started = time.perf_counter()
embedder, cache_hit = get_embedder() embedder, cache_hit = get_embedder()
......
# Embeddings-Implementation with Factory-Pattern
from __future__ import annotations from __future__ import annotations
import math import math
...@@ -10,7 +12,7 @@ from sentence_transformers import SentenceTransformer ...@@ -10,7 +12,7 @@ from sentence_transformers import SentenceTransformer
# ----------------------------- # -----------------------------
# Konfigurationsmodelle (optional, aber empfohlen) # configuration model
# ----------------------------- # -----------------------------
class EmbeddingType(str, Enum): class EmbeddingType(str, Enum):
...@@ -44,7 +46,7 @@ class EmbeddingConfig(BaseModel): ...@@ -44,7 +46,7 @@ class EmbeddingConfig(BaseModel):
..., description="Spezifische Konfiguration") ..., description="Spezifische Konfiguration")
# ----------------------------- # -----------------------------
# Basisklasse für Embeddings # base class for embeddings
# ----------------------------- # -----------------------------
...@@ -86,7 +88,7 @@ class BaseEmbeddings: ...@@ -86,7 +88,7 @@ class BaseEmbeddings:
# ----------------------------- # -----------------------------
# Subklassen # subclass
# ----------------------------- # -----------------------------
class OpenAILikeEmbeddings(BaseEmbeddings): class OpenAILikeEmbeddings(BaseEmbeddings):
...@@ -182,7 +184,7 @@ class SentenceTransformerEmbeddings(BaseEmbeddings): ...@@ -182,7 +184,7 @@ class SentenceTransformerEmbeddings(BaseEmbeddings):
# ----------------------------- # -----------------------------
# Factory: Erzeugt die richtige Embeddings-Instanz # Factory: creats embeddings based on config
# ----------------------------- # -----------------------------
class EmbeddingFactory: class EmbeddingFactory:
......
# LLM client with support for multiple providers and tool use, including quota tracking and tool call logging.
import asyncio import asyncio
import inspect import inspect
import json import json
......
# LLM-Quota-Management: tracks the dayly usage of LLM calls and tokens, enforces limits, and provides usage data for monitoring and billing purposes.
# Limits are set in .env
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
......
# decodes referneces from the _task_index.json to match sources, tasks and socratic prompts.
# There must be a easyer way to do this.
from __future__ import annotations from __future__ import annotations
from typing import Dict, Iterable, Protocol, Set, Tuple from typing import Dict, Iterable, Protocol, Set, Tuple
......
# Mask for Vector Store Retrieval.
# dont know why this is here?
from __future__ import annotations from __future__ import annotations
from typing import List from typing import List
......
# Session Store for archiving and retrieving chat sessions.
from __future__ import annotations from __future__ import annotations
import json import json
...@@ -6,6 +8,7 @@ from collections import deque ...@@ -6,6 +8,7 @@ from collections import deque
from datetime import datetime from datetime import datetime
from threading import Lock from threading import Lock
from typing import Any from typing import Any
from zoneinfo import ZoneInfo
from app.deterministic_services import context_store from app.deterministic_services import context_store
...@@ -14,8 +17,8 @@ _LOG_DIR = os.path.join("logs", "chat_sessions") ...@@ -14,8 +17,8 @@ _LOG_DIR = os.path.join("logs", "chat_sessions")
_LOG_PATH = os.path.join(_LOG_DIR, "archive.jsonl") _LOG_PATH = os.path.join(_LOG_DIR, "archive.jsonl")
def _utc_now() -> str: def _mez_now() -> str:
return datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ") 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: def _extract_selected_task(sheet: dict[str, Any]) -> dict[str, str] | None:
...@@ -48,7 +51,7 @@ def archive_chat( ...@@ -48,7 +51,7 @@ def archive_chat(
record = { record = {
"chat_id": chat_id, "chat_id": chat_id,
"saved_at": _utc_now(), "saved_at": _mez_now(),
"orchestrator": str(orchestrator or "").strip().lower() or None, "orchestrator": str(orchestrator or "").strip().lower() or None,
"history": sheet.get("history", []), "history": sheet.get("history", []),
"context_sheet": context_store.format_sheet(sheet), "context_sheet": context_store.format_sheet(sheet),
......
# Socratic Oranisator for managing initial prompts and topic catalog.
from __future__ import annotations from __future__ import annotations
from functools import lru_cache from functools import lru_cache
......
...@@ -21,6 +21,10 @@ TOPIC_MAP_PATH = TASKS_DIR / "_topic_to_index_map.yaml" ...@@ -21,6 +21,10 @@ TOPIC_MAP_PATH = TASKS_DIR / "_topic_to_index_map.yaml"
TASK_ASSET_URL_PREFIX = "/api/tasks/assets" TASK_ASSET_URL_PREFIX = "/api/tasks/assets"
ParentRef = tuple[int, int, int, int] ParentRef = tuple[int, int, int, int]
# ---
# normalizer and parser
# ---
def _normalize_parent_ref_key(value: str) -> str: def _normalize_parent_ref_key(value: str) -> str:
return _normalize_topic_key(value) return _normalize_topic_key(value)
...@@ -113,6 +117,10 @@ def _format_parent_ref_label(ref: ParentRef) -> str: ...@@ -113,6 +117,10 @@ def _format_parent_ref_label(ref: ParentRef) -> str:
return f"Subsection {chap}.{sec}.{sub}" return f"Subsection {chap}.{sec}.{sub}"
return f"Subsubsection {chap}.{sec}.{sub}.{subsub}" return f"Subsubsection {chap}.{sec}.{sub}.{subsub}"
# ---
# topic map loading
# ---
def load_topic_map(path: Path = TOPIC_MAP_PATH) -> dict[str, ParentRef]: def load_topic_map(path: Path = TOPIC_MAP_PATH) -> dict[str, ParentRef]:
if not path.exists(): if not path.exists():
...@@ -133,6 +141,9 @@ def load_topic_map(path: Path = TOPIC_MAP_PATH) -> dict[str, ParentRef]: ...@@ -133,6 +141,9 @@ def load_topic_map(path: Path = TOPIC_MAP_PATH) -> dict[str, ParentRef]:
mapped[key] = _normalize_parent_ref(parsed) mapped[key] = _normalize_parent_ref(parsed)
return mapped return mapped
# ---
# topic summaries loading
# ---
def _extract_topic_summary(text: str) -> str: def _extract_topic_summary(text: str) -> str:
body = text.replace("\r\n", "\n").strip() body = text.replace("\r\n", "\n").strip()
...@@ -288,6 +299,10 @@ def _format_topic_label(value: str) -> str: ...@@ -288,6 +299,10 @@ def _format_topic_label(value: str) -> str:
return cleaned.title() return cleaned.title()
# ---
# topic catalog
# ---
def build_topic_catalog(path: Path = TOPIC_MAP_PATH) -> list[dict[str, Any]]: def build_topic_catalog(path: Path = TOPIC_MAP_PATH) -> list[dict[str, Any]]:
topic_map = load_topic_map(path) topic_map = load_topic_map(path)
topic_summaries = load_topic_summaries() topic_summaries = load_topic_summaries()
...@@ -305,6 +320,10 @@ def build_topic_catalog(path: Path = TOPIC_MAP_PATH) -> list[dict[str, Any]]: ...@@ -305,6 +320,10 @@ def build_topic_catalog(path: Path = TOPIC_MAP_PATH) -> list[dict[str, Any]]:
return response return response
# ---
# text helpers
# ---
def _match_score(query_text: str, candidate_text: str) -> int: def _match_score(query_text: str, candidate_text: str) -> int:
query_tokens = _tokenize(query_text) query_tokens = _tokenize(query_text)
if not query_tokens: if not query_tokens:
...@@ -322,6 +341,10 @@ def _slugify(value: str) -> str: ...@@ -322,6 +341,10 @@ def _slugify(value: str) -> str:
return collapsed.strip("-") return collapsed.strip("-")
# ---
# task image and block normalization
# ---
def _normalize_block_images(images_raw: list[dict[str, str]]) -> list[dict[str, str]]: def _normalize_block_images(images_raw: list[dict[str, str]]) -> list[dict[str, str]]:
normalized_images: list[dict[str, str]] = [] normalized_images: list[dict[str, str]] = []
for item in images_raw: for item in images_raw:
...@@ -369,6 +392,10 @@ def _extract_text_and_images(blocks: Any) -> tuple[list[str], list[dict[str, str ...@@ -369,6 +392,10 @@ def _extract_text_and_images(blocks: Any) -> tuple[list[str], list[dict[str, str
return text_parts, images return text_parts, images
# ---
# task yaml normalization
# ---
def _normalize_yaml_task_entry(task_entry: Any, position: int) -> dict[str, Any] | None: def _normalize_yaml_task_entry(task_entry: Any, position: int) -> dict[str, Any] | None:
if not isinstance(task_entry, dict): if not isinstance(task_entry, dict):
return None return None
...@@ -437,6 +464,10 @@ def _normalize_yaml_task_file(content: Any, path: Path) -> dict[str, Any] | None ...@@ -437,6 +464,10 @@ def _normalize_yaml_task_file(content: Any, path: Path) -> dict[str, Any] | None
} }
# ---
# task file loading
# ---
def load_task_files(tasks_dir: Path = TASKS_DIR) -> list[dict[str, Any]]: def load_task_files(tasks_dir: Path = TASKS_DIR) -> list[dict[str, Any]]:
if not tasks_dir.exists(): if not tasks_dir.exists():
return [] return []
...@@ -459,6 +490,10 @@ def load_cached_task_files() -> list[dict[str, Any]]: ...@@ -459,6 +490,10 @@ def load_cached_task_files() -> list[dict[str, Any]]:
return load_task_files() return load_task_files()
# ---
# task lookup
# ---
def _find_task_file(task_files: list[dict[str, Any]], file_id: str) -> dict[str, Any] | None: def _find_task_file(task_files: list[dict[str, Any]], file_id: str) -> dict[str, Any] | None:
for task_file in task_files: for task_file in task_files:
if str(task_file.get("_file_id", "")) == file_id: if str(task_file.get("_file_id", "")) == file_id:
...@@ -502,6 +537,10 @@ def find_task_details( ...@@ -502,6 +537,10 @@ def find_task_details(
} }
# ---
# selected task and topic state
# ---
def _normalize_task_images(task_entry: dict[str, Any]) -> list[dict[str, str]]: def _normalize_task_images(task_entry: dict[str, Any]) -> list[dict[str, str]]:
images_raw = task_entry.get("images", []) images_raw = task_entry.get("images", [])
if not isinstance(images_raw, list): if not isinstance(images_raw, list):
...@@ -674,6 +713,10 @@ def get_selected_task_parent_refs(sheet: dict[str, Any]) -> list[ParentRef]: ...@@ -674,6 +713,10 @@ def get_selected_task_parent_refs(sheet: dict[str, Any]) -> list[ParentRef]:
return sorted(refs) return sorted(refs)
# ---
# task selection for chat context
# ---
def select_task_for_context( def select_task_for_context(
sheet: dict[str, Any], sheet: dict[str, Any],
query_text: str, query_text: str,
...@@ -737,6 +780,10 @@ def select_task_for_context( ...@@ -737,6 +780,10 @@ def select_task_for_context(
return best_file, best_task return best_file, best_task
# ---
# public catalogs
# ---
def build_task_catalog(task_files: list[dict[str, Any]] | None = None) -> list[dict[str, Any]]: def build_task_catalog(task_files: list[dict[str, Any]] | None = None) -> list[dict[str, Any]]:
catalog = task_files if task_files is not None else load_cached_task_files() catalog = task_files if task_files is not None else load_cached_task_files()
catalog = sorted( catalog = sorted(
......
# logger for the tools while the programm is running.
from __future__ import annotations from __future__ import annotations
from contextvars import ContextVar, Token from contextvars import ContextVar, Token
......
# Logger for tool calls, saving them to JSON files in a structured way.
import json import json
import os import os
from datetime import datetime from datetime import datetime
......
# Vector Store implementation using PostgreSQL with pgvector extension.
from __future__ import annotations from __future__ import annotations
import hashlib import hashlib
...@@ -17,7 +19,7 @@ import app.config ...@@ -17,7 +19,7 @@ import app.config
embedding_dim = app.config.get_embedding_settings().target_dim embedding_dim = app.config.get_embedding_settings().target_dim
# -------------------------------------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------------------------------------
# Einlesen der Dokumente # Reading of Documents
# -------------------------------------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------------------------------------
...@@ -124,7 +126,7 @@ def load_docs(base_dir: Path) -> List[DocRecord]: ...@@ -124,7 +126,7 @@ def load_docs(base_dir: Path) -> List[DocRecord]:
return docs return docs
# -------------------------------------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------------------------------------
# Init der Databse # Init of the database
# -------------------------------------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------------------------------------
...@@ -173,7 +175,7 @@ def init_db(pg_url: str) -> None: ...@@ -173,7 +175,7 @@ def init_db(pg_url: str) -> None:
conn.commit() conn.commit()
# -------------------------------------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------------------------------------
# Einfügen der Dokumente und Embeddings # Insert and update of documents and embeddings
# -------------------------------------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------------------------------------
...@@ -274,7 +276,7 @@ def embed_query(embedder: EmbeddingLike, text: str) -> List[float]: ...@@ -274,7 +276,7 @@ def embed_query(embedder: EmbeddingLike, text: str) -> List[float]:
return embedder.embed_query(text) return embedder.embed_query(text)
# -------------------------------------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------------------------------------
# Retrival der Dokumente # Retrieval of Documents
# -------------------------------------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------------------------------------
...@@ -856,7 +858,7 @@ def retrieve( ...@@ -856,7 +858,7 @@ def retrieve(
return sorted(sources, key=lambda source: source.score, reverse=True) return sorted(sources, key=lambda source: source.score, reverse=True)
# -------------------------------------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------------------------------------
# Retrival mit Parent Referenzen # Retrieval with parent refs
# -------------------------------------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------------------------------------
...@@ -1058,7 +1060,7 @@ def merge_sources(primary: List[Source], additional: List[Source]) -> List[Sourc ...@@ -1058,7 +1060,7 @@ def merge_sources(primary: List[Source], additional: List[Source]) -> List[Sourc
# -------------------------------------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------------------------------------
# Retrival in Sources umwandeln # transform retrieval results to sources
# -------------------------------------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------------------------------------
...@@ -1140,7 +1142,7 @@ def _retrivla_to_sources(retrievd: Dict[str, List[Retrieved]]) -> List[Source]: ...@@ -1140,7 +1142,7 @@ def _retrivla_to_sources(retrievd: Dict[str, List[Retrieved]]) -> List[Source]:
return sources return sources
# -------------------------------------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------------------------------------
# Listen für Filterung # Lists for filtering
# -------------------------------------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------------------------------------
......
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