Commit 563f3250 authored by Kantz's avatar Kantz
Browse files

retrival einbinden

parent eaecc684
...@@ -3,11 +3,14 @@ from __future__ import annotations ...@@ -3,11 +3,14 @@ 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
import httpx
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from app.services.embeddings import OpenAILikeEmbeddings
from app.services import vector_store
router = APIRouter() router = APIRouter()
load_dotenv() load_dotenv()
...@@ -17,16 +20,19 @@ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") ...@@ -17,16 +20,19 @@ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-4o-mini") OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-4o-mini")
SYSTEM_PROMPT = """Du bist ein Mathe-Tutor. Antworte auf Deutsch, klar und korrekt. SYSTEM_PROMPT = """Du bist ein Mathe-Tutor. Antworte auf Deutsch, klar und korrekt.
Nutze ausschließlich den bereitgestellten Kontext. 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: Gib wenn möglich eine kurze Struktur: (1) Idee, (2) Definition, (3) kurzer Begründungs-/Rechenweg, (4) Mini-Beispiel.
(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 | ...]. Zitiere Quellen inline mit den eckigen Klammern, die im Kontext vorangestellt sind, z.B. [s2/ss1/c3 | definition | ...].
Wenn nichts zur Frage im Kontext steht, antworte mit "Dazu steht nichts im Material" und gib nichts weiter aus.
""" """
CONTEXT_LIMITS = {
"direct": 4,
"indirect": 6,
"subsection": 2,
"section": 1,
}
class ChatMessage(BaseModel): class ChatMessage(BaseModel):
role: str = Field(..., pattern="^(user|assistant)$") role: str = Field(..., pattern="^(user|assistant)$")
text: str = Field(..., min_length=1) text: str = Field(..., min_length=1)
...@@ -42,6 +48,59 @@ class ChatResponse(BaseModel): ...@@ -42,6 +48,59 @@ 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:
...@@ -52,10 +111,25 @@ def chat(request: ChatRequest) -> ChatResponse: ...@@ -52,10 +111,25 @@ 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"])
messages_payload = [{"role": "system", "content": SYSTEM_PROMPT}] + [ pg_url = os.getenv("POSTGRES_URL")
{"role": message.role, "content": message.text} if not pg_url:
for message in request.messages 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 = { payload = {
"model": OPENAI_MODEL, "model": OPENAI_MODEL,
......
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