Commit 1d704a9a authored by Kantz's avatar Kantz
Browse files

rm unnecessery files

parent 563f3250
...@@ -3,13 +3,11 @@ from __future__ import annotations ...@@ -3,13 +3,11 @@ from __future__ import annotations
from typing import List, Optional from typing import List, Optional
import os import os
import httpx
from fastapi import APIRouter, HTTPException from fastapi import APIRouter, HTTPException
from dotenv import load_dotenv from dotenv import load_dotenv
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from app.services.embeddings import OpenAILikeEmbeddings from app.services import chat_engine
from app.services import vector_store
router = APIRouter() router = APIRouter()
...@@ -17,20 +15,6 @@ load_dotenv() ...@@ -17,20 +15,6 @@ load_dotenv()
OPENAI_BASE_URL = os.getenv("OPENAI_BASE_URL") OPENAI_BASE_URL = os.getenv("OPENAI_BASE_URL")
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-4o-mini")
SYSTEM_PROMPT = """Du bist ein Mathe-Tutor. Antworte auf Deutsch, klar und korrekt.
Nutze ausschließlich den bereitgestellten Kontext. Wenn nichts zur Frage im Kontext steht, antworte mit "Dazu steht nichts im Material" und nichts weiter.
Gib wenn möglich eine kurze Struktur: (1) Idee, (2) Definition, (3) kurzer Begründungs-/Rechenweg, (4) Mini-Beispiel.
Zitiere Quellen inline mit den eckigen Klammern, die im Kontext vorangestellt sind, z.B. [s2/ss1/c3 | definition | ...].
"""
CONTEXT_LIMITS = {
"direct": 4,
"indirect": 6,
"subsection": 2,
"section": 1,
}
class ChatMessage(BaseModel): class ChatMessage(BaseModel):
...@@ -48,59 +32,6 @@ class ChatResponse(BaseModel): ...@@ -48,59 +32,6 @@ class ChatResponse(BaseModel):
sources: List[str] = [] sources: List[str] = []
def _get_embedder() -> OpenAILikeEmbeddings:
base_url = os.getenv("OPENAI_BASE_URL")
api_key = os.getenv("OPENAI_API_KEY")
model = os.getenv("OPENAI_EMBED_MODEL", "text-embedding-3-large")
if not base_url or not api_key:
raise HTTPException(status_code=500, detail="Missing OPENAI_BASE_URL or OPENAI_API_KEY")
return OpenAILikeEmbeddings(base_url=base_url, api_key=api_key, model=model, target_dim=1024)
def _format_ref(doc: vector_store.Retrieved) -> str:
meta = doc.metadata
sec = meta.get("section_index")
sub = meta.get("subsection_index")
child = meta.get("child_index")
ref = []
if sec is not None:
ref.append(f"s{sec}")
if sub is not None:
ref.append(f"ss{sub}")
if child is not None:
ref.append(f"c{child}")
ref_id = "/".join(ref) if ref else "unknown"
doc_type = meta.get("type") or doc.doc_type
title = (
meta.get("title")
or meta.get("subsection_title")
or meta.get("section_title")
or meta.get("path")
or "Untitled"
)
return f"[{ref_id} | {doc_type} | {title}]"
def _build_context(result: dict) -> str:
blocks: List[str] = []
def add_group(label: str, items: List[vector_store.Retrieved], limit: int) -> None:
if not items:
return
for doc in items[:limit]:
blocks.append(f"{label} {_format_ref(doc)}\n{doc.markdown}")
add_group("DIRECT", result.get("children_direct", []), CONTEXT_LIMITS["direct"])
add_group("INDIRECT", result.get("children_expanded", []), CONTEXT_LIMITS["indirect"])
add_group("SUBSECTION", result.get("subsections", []), CONTEXT_LIMITS["subsection"])
add_group("SECTION", result.get("sections", []), CONTEXT_LIMITS["section"])
if not blocks:
return "KONTEXT: (leer)"
return "KONTEXT:\n" + "\n\n".join(blocks)
@router.post("/api/chat", response_model=ChatResponse) @router.post("/api/chat", response_model=ChatResponse)
def chat(request: ChatRequest) -> ChatResponse: def chat(request: ChatRequest) -> ChatResponse:
if not request.messages: if not request.messages:
...@@ -111,41 +42,19 @@ def chat(request: ChatRequest) -> ChatResponse: ...@@ -111,41 +42,19 @@ def chat(request: ChatRequest) -> ChatResponse:
reply = f"Mock reply to: {last.text}" reply = f"Mock reply to: {last.text}"
return ChatResponse(reply=reply, sources=["doc:example"]) return ChatResponse(reply=reply, sources=["doc:example"])
pg_url = os.getenv("POSTGRES_URL")
if not pg_url:
raise HTTPException(status_code=500, detail="Missing POSTGRES_URL")
query_text = request.messages[-1].text
embedder = _get_embedder()
retrieval = vector_store.retrieve(
pg_url=pg_url,
embedder=embedder,
query=query_text,
k=8,
expand_links=True,
)
context_block = _build_context(retrieval)
messages_payload = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "system", "content": context_block},
] + [{"role": message.role, "content": message.text} for message in request.messages]
payload = {
"model": OPENAI_MODEL,
"messages": messages_payload,
}
headers = {"Authorization": f"Bearer {OPENAI_API_KEY}"}
url = f"{OPENAI_BASE_URL.rstrip('/')}/v1/chat/completions"
try: try:
with httpx.Client(timeout=60.0) as client: context = chat_engine.retrieve_context(
response = client.post(url, headers=headers, json=payload) pg_url=os.getenv("POSTGRES_URL") or "",
response.raise_for_status() query_text=request.messages[-1].text,
except httpx.HTTPError as exc: )
messages_payload = chat_engine.build_messages(
[{"role": m.role, "content": m.text} for m in request.messages],
context,
)
reply = chat_engine.call_chat_api(messages_payload)
except ValueError as exc:
raise HTTPException(status_code=500, detail=str(exc)) from exc
except Exception as exc:
raise HTTPException(status_code=502, detail="chat provider failed") from exc raise HTTPException(status_code=502, detail="chat provider failed") from exc
data = response.json()
reply = data["choices"][0]["message"]["content"]
return ChatResponse(reply=reply, sources=["openai-compatible"]) return ChatResponse(reply=reply, sources=["openai-compatible"])
from __future__ import annotations
from typing import List
import os
import httpx
from openai import OpenAI
from app.services.embeddings import OpenAILikeEmbeddings
from app.services import vector_store
SYSTEM_PROMPT = """Du bist ein Mathe-Tutor. Antworte auf Deutsch, klar und korrekt.
Nutze ausschließlich den bereitgestellten Kontext. Wenn nichts zur Frage im Kontext steht, antworte mit "Dazu steht nichts im Material" und nichts weiter.
Gib wenn möglich eine kurze Struktur: (1) Idee, (2) Definition, (3) kurzer Begründungs-/Rechenweg, (4) Mini-Beispiel.
Zitiere Quellen inline mit den eckigen Klammern, die im Kontext vorangestellt sind, z.B. [s2/ss1/c3 | definition | ...].
"""
CONTEXT_LIMITS = {
"direct": 8,
"indirect": 6,
"subsection": 2,
"section": 1,
}
def _get_embedder() -> OpenAILikeEmbeddings:
base_url = os.getenv("OPENAI_BASE_URL")
api_key = os.getenv("OPENAI_API_KEY")
model = os.getenv("OPENAI_EMBED_MODEL", "text-embedding-3-large")
if not base_url or not api_key:
raise ValueError("Missing OPENAI_BASE_URL or OPENAI_API_KEY")
return OpenAILikeEmbeddings(base_url=base_url, api_key=api_key, model=model, target_dim=1024)
def _format_ref(doc: vector_store.Retrieved) -> str:
meta = doc.metadata
sec = meta.get("section_index")
sub = meta.get("subsection_index")
child = meta.get("child_index")
ref = []
if sec is not None:
ref.append(f"s{sec}")
if sub is not None:
ref.append(f"ss{sub}")
if child is not None:
ref.append(f"c{child}")
ref_id = "/".join(ref) if ref else "unknown"
doc_type = meta.get("type") or doc.doc_type
title = (
meta.get("title")
or meta.get("subsection_title")
or meta.get("section_title")
or meta.get("path")
or "Untitled"
)
return f"[{ref_id} | {doc_type} | {title}]"
def build_context(result: dict) -> str:
blocks: List[str] = []
def add_group(label: str, items: List[vector_store.Retrieved], limit: int) -> None:
if not items:
return
for doc in items[:limit]:
blocks.append(f"{label} {_format_ref(doc)}\n{doc.markdown}")
add_group("DIRECT", result.get("children_direct", []), CONTEXT_LIMITS["direct"])
add_group("INDIRECT", result.get("children_expanded", []), CONTEXT_LIMITS["indirect"])
add_group("SUBSECTION", result.get("subsections", []), CONTEXT_LIMITS["subsection"])
add_group("SECTION", result.get("sections", []), CONTEXT_LIMITS["section"])
if not blocks:
return "KONTEXT: (leer)"
return "KONTEXT:\n" + "\n\n".join(blocks)
def build_messages(messages: list[dict], context: str) -> list[dict]:
return [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "system", "content": context},
] + messages
def retrieve_context(pg_url: str, query_text: str) -> str:
embedder = _get_embedder()
retrieval = vector_store.retrieve(
pg_url=pg_url,
embedder=embedder,
query=query_text,
k=8,
expand_links=True,
)
return build_context(retrieval)
def call_chat_api(messages_payload: list[dict]) -> str:
base_url = os.getenv("OPENAI_BASE_URL")
api_key = os.getenv("OPENAI_API_KEY")
model = os.getenv("OPENAI_MODEL", "gpt-4o-mini")
if not base_url or not api_key:
raise ValueError("Missing OPENAI_BASE_URL or OPENAI_API_KEY")
url = f"{base_url.rstrip('/')}/chat/completions"
payload = {
"model": model,
"messages": messages_payload,
}
headers = {"Authorization": f"Bearer {api_key}"}
with httpx.Client(timeout=60.0) as client:
response = client.post(url, headers=headers, json=payload)
response.raise_for_status()
data = response.json()
return data["choices"][0]["message"]["content"]
def stream_chat(messages_payload: list[dict]):
base_url = os.getenv("OPENAI_BASE_URL")
api_key = os.getenv("OPENAI_API_KEY")
model = os.getenv("OPENAI_MODEL", "gpt-4o-mini")
if not base_url or not api_key:
raise ValueError("Missing OPENAI_BASE_URL or OPENAI_API_KEY")
client = OpenAI(api_key=api_key, base_url=base_url)
return client.chat.completions.create(
model=model,
messages=messages_payload,
stream=True,
)
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