Commit 8c452e91 authored by Kantz's avatar Kantz
Browse files

funktionsweise mit neuem Retrivalmodus wieder hiergestellt.

parent 3d666534
...@@ -6,7 +6,7 @@ def generate_hint( ...@@ -6,7 +6,7 @@ def generate_hint(
LLM_solution: str, LLM_solution: str,
math_solution: str | None = None, math_solution: str | None = None,
history: str | None = None, history: str | None = None,
retrival: str | None = None, sources: str | None = None,
) -> str: ) -> str:
system_prompt = ( system_prompt = (
"Du bist ein didaktischer Tutor. " "Du bist ein didaktischer Tutor. "
...@@ -28,8 +28,8 @@ def generate_hint( ...@@ -28,8 +28,8 @@ def generate_hint(
prompt += "Mathematische Loesung:\n" + math_solution + "\n" prompt += "Mathematische Loesung:\n" + math_solution + "\n"
if history: if history:
prompt += "\nHistorie:\n" + history + "\n" prompt += "\nHistorie:\n" + history + "\n"
if retrival: if sources:
prompt = "\nKontext:\n" + retrival + "\n" + prompt prompt = "\nKontext:\n" + sources + "\n" + prompt
prompt += "\nGebe einen hilfreichen Tipp zur Lösung der Aufgabe. Halte dich kurz und prägnant." prompt += "\nGebe einen hilfreichen Tipp zur Lösung der Aufgabe. Halte dich kurz und prägnant."
result = llm_client.chat( result = llm_client.chat(
messages=[{"role": "system", "content": system_prompt}, {"role": "user", "content": prompt}], messages=[{"role": "system", "content": system_prompt}, {"role": "user", "content": prompt}],
......
...@@ -6,7 +6,8 @@ from fastapi import APIRouter, HTTPException ...@@ -6,7 +6,8 @@ from fastapi import APIRouter, HTTPException
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from app.deterministic_services import context_store from app.deterministic_services import context_store
from app.deterministic_services.vector_store import Retrieved from app.deterministic_services import Source
router = APIRouter() router = APIRouter()
...@@ -21,7 +22,7 @@ class ContextRequest(BaseModel): ...@@ -21,7 +22,7 @@ class ContextRequest(BaseModel):
draft: Optional[str] = None draft: Optional[str] = None
@router.post("/api/context/retrieval") @router.post("/api/context/retrieval")
def get_retrieval_context(request: ContextRequest) -> dict: def get_retrieval_context(request: ContextRequest) -> List[dict]:
if not request or not request.messages: if not request or not request.messages:
raise HTTPException(status_code=400, detail="messages required") raise HTTPException(status_code=400, detail="messages required")
...@@ -31,29 +32,7 @@ def get_retrieval_context(request: ContextRequest) -> dict: ...@@ -31,29 +32,7 @@ def get_retrieval_context(request: ContextRequest) -> dict:
if not sheet: if not sheet:
raise HTTPException(status_code=404, detail="context sheet not found") raise HTTPException(status_code=404, detail="context sheet not found")
result = context_store.get_retrieval_context(sheet) sources = context_store.get_retrieval(sheet)
if not result or not result.get("query"): if not sources:
return { return []
"chat_id": chat_id, return [source.model_dump() for source in sources]
"query": "",
"children": [],
"children_direct": [],
"children_expanded": [],
"subsections": [],
"sections": [],
"neighbors": [],
}
def pack(items: list[Retrieved]) -> list[dict]:
return [item.to_dict() for item in items]
return {
"chat_id": chat_id,
"query": result["query"],
"children": pack(result["children"]),
"children_direct": pack(result["children_direct"]),
"children_expanded": pack(result["children_expanded"]),
"subsections": pack(result["subsections"]),
"sections": pack(result["sections"]),
"neighbors": pack(result["neighbors"]),
}
...@@ -41,7 +41,7 @@ class QueryRequest(BaseModel): ...@@ -41,7 +41,7 @@ class QueryRequest(BaseModel):
expand_links: bool = True expand_links: bool = True
section_index: Optional[int] = None section_index: Optional[int] = None
subsection_index: Optional[int] = None subsection_index: Optional[int] = None
type_filter: Optional[List[str]] = None source_type_filter: Optional[List[str]] = None
neighbor_expand: int = 0 neighbor_expand: int = 0
...@@ -83,23 +83,11 @@ def query(request: QueryRequest) -> dict: ...@@ -83,23 +83,11 @@ def query(request: QueryRequest) -> dict:
k=request.k, k=request.k,
section_index=request.section_index, section_index=request.section_index,
subsection_index=request.subsection_index, subsection_index=request.subsection_index,
type_filter=request.type_filter, source_type_filter=request.source_type_filter,
expand_links=request.expand_links, expand_links=request.expand_links,
neighbor_expand=request.neighbor_expand, neighbor_expand=request.neighbor_expand,
) )
return result
def pack(items: list[vector_store.Retrieved]) -> list[dict]:
return [item.to_dict() for item in items]
return {
"query": result["query"],
"children": pack(result["children"]),
"children_direct": pack(result["children_direct"]),
"children_expanded": pack(result["children_expanded"]),
"subsections": pack(result["subsections"]),
"sections": pack(result["sections"]),
"neighbors": pack(result["neighbors"]),
}
@router.get("/api/retrieval/sections") @router.get("/api/retrieval/sections")
......
...@@ -78,7 +78,7 @@ def add_retrieval_context( ...@@ -78,7 +78,7 @@ def add_retrieval_context(
query: str, query: str,
sources: list[Source], sources: list[Source],
) -> None: ) -> None:
retrieval_entry = {"query": query, "sources": [source.to_string() for source in sources]} retrieval_entry = {"query": query, "sources": [source.model_dump() for source in sources]}
sheet["retrieval_contexts"].append(retrieval_entry) sheet["retrieval_contexts"].append(retrieval_entry)
sheet["updated_at"] = _utc_now() sheet["updated_at"] = _utc_now()
...@@ -91,7 +91,7 @@ def update_retrieval_context( ...@@ -91,7 +91,7 @@ def update_retrieval_context(
retrievals = sheet.get("retrieval_contexts", []) retrievals = sheet.get("retrieval_contexts", [])
if retrievals: if retrievals:
latest_retrieval = retrievals[-1] latest_retrieval = retrievals[-1]
latest_retrieval["sources"] = [source.to_string() for source in sources] latest_retrieval["sources"] = [source.model_dump() for source in sources]
latest_retrieval["query"] = query latest_retrieval["query"] = query
sheet["updated_at"] = _utc_now() sheet["updated_at"] = _utc_now()
...@@ -167,16 +167,15 @@ def format_sheet(sheet: dict[str, Any]) -> str: ...@@ -167,16 +167,15 @@ def format_sheet(sheet: dict[str, Any]) -> str:
blocks = [] blocks = []
for item in retrievals: for item in retrievals:
query = item.get('query', '') query = item.get('query', '')
context = item.get('context', '')
sources = item.get('sources', []) sources = item.get('sources', [])
# Erstelle eine formatierte Liste der Quellen mit ihren Scores # Erstelle eine formatierte Liste der Quellen mit ihren Scores
source_blocks = [] source_blocks = []
for source in sources: for source in sources:
source_blocks.append(f" - [{source.get('score', 0):.3f}] {source.get('label', '')} {source.get('ref', '')}") source_blocks.append(Source.model_validate(source).to_string())
source_info = "\n".join(source_blocks) if source_blocks else "" source_info = "\n".join(source_blocks) if source_blocks else ""
blocks.append(f"QUERY: {query}\n{context}\nSOURCES:\n{source_info}") blocks.append(f"QUERY: {query}\nSOURCES:\n{source_info}")
parts.append("RETRIEVAL_CONTEXT:\n" + "\n\n".join(blocks)) parts.append("RETRIEVAL_CONTEXT:\n" + "\n\n".join(blocks))
else: else:
parts.append("RETRIEVAL_CONTEXT:\n(leer)") parts.append("RETRIEVAL_CONTEXT:\n(leer)")
...@@ -200,12 +199,13 @@ def format_sheet(sheet: dict[str, Any]) -> str: ...@@ -200,12 +199,13 @@ def format_sheet(sheet: dict[str, Any]) -> str:
return "\n\n".join(parts) return "\n\n".join(parts)
def get_retrival(sheet: dict[str, Any]) -> str: def get_retrieval(sheet: dict[str, Any]) -> list[Source]:
retrievals = sheet.get("retrieval_contexts", []) retrievals = sheet.get("retrieval_contexts", [])
if not retrievals: if not retrievals:
return "" return []
latest = retrievals[-1] latest_retrieval = retrievals[-1]
return latest.get("context", "") sources= latest_retrieval.get("sources", [])
return [Source.model_validate(source) for source in sources]
def save_sheet(sheet: dict[str, Any]) -> None: def save_sheet(sheet: dict[str, Any]) -> None:
os.makedirs(_LOG_DIR, exist_ok=True) os.makedirs(_LOG_DIR, exist_ok=True)
......
...@@ -88,12 +88,13 @@ def run_chat(messages: list[dict], draft: str | None = None) -> dict: ...@@ -88,12 +88,13 @@ def run_chat(messages: list[dict], draft: str | None = None) -> dict:
_bootstrap_context(sheet, full_query, tool_log) _bootstrap_context(sheet, full_query, tool_log)
# Hinweis und Ausgaben generierung # Hinweis und Ausgaben generierung
hint_args = { hint_args = {
"task": context_store.get_task(sheet), "task": context_store.get_task(sheet),
"LLM_solution": context_store.last_LLM_solution(sheet), "LLM_solution": context_store.last_LLM_solution(sheet),
"math_solution": context_store.first_math_solution(sheet), "math_solution": context_store.first_math_solution(sheet),
"history": context_store.format_history(messages), "history": context_store.format_history(messages),
"retrival": context_store.get_retrival(sheet), "sources": "\n".join([source.to_string() for source in context_store.get_retrieval(sheet)]),
} }
reply = hint_LLM.generate_hint(**hint_args) reply = hint_LLM.generate_hint(**hint_args)
_append_tool_log(tool_log, "generate_hint", hint_args, reply) _append_tool_log(tool_log, "generate_hint", hint_args, reply)
......
...@@ -258,7 +258,7 @@ class Source(BaseModel): ...@@ -258,7 +258,7 @@ class Source(BaseModel):
def to_dict(self) -> Dict[str, Any]: def to_dict(self) -> Dict[str, Any]:
return { return {
"source_id": self.source_id, "source_id": self.source_id.to_dict(),
"retrieved_as": self.retrieved_as, "retrieved_as": self.retrieved_as,
"source_type": self.source_type, "source_type": self.source_type,
"score": self.score, "score": self.score,
...@@ -281,6 +281,15 @@ class SourceID(BaseModel): ...@@ -281,6 +281,15 @@ class SourceID(BaseModel):
title: str title: str
doc_type: 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,
"title": self.title,
"doc_type": self.doc_type,
}
def to_string(self) -> str: def to_string(self) -> str:
string_rep = self.title string_rep = self.title
if self.subsection_title: if self.subsection_title:
......
...@@ -36,7 +36,7 @@ def main() -> None: ...@@ -36,7 +36,7 @@ def main() -> None:
"LLM_solution": context_store.last_LLM_solution(sheet), "LLM_solution": context_store.last_LLM_solution(sheet),
"math_solution": context_store.first_math_solution(sheet), "math_solution": context_store.first_math_solution(sheet),
"history": context_store.get_history(sheet), "history": context_store.get_history(sheet),
"retrival": context_store.get_retrival(sheet), "retrival": context_store.get_retrieval(sheet),
} }
reply = hint_LLM.generate_hint(**hint_args) reply = hint_LLM.generate_hint(**hint_args)
......
...@@ -23,11 +23,59 @@ type ArchivedChatDetail = { ...@@ -23,11 +23,59 @@ type ArchivedChatDetail = {
history: ChatMessage[]; history: ChatMessage[];
}; };
type ContextSource = {
source_id: {
chapter_title?: string | null;
section_title?: string | null;
subsection_title?: string | null;
title: string;
doc_type: string;
};
retrieved_as?: string | null;
source_type?: string | null;
score?: number;
markdown?: string | null;
};
const createSessionId = () => const createSessionId = () =>
`session_${Date.now().toString(36)}_${Math.random() `session_${Date.now().toString(36)}_${Math.random()
.toString(36) .toString(36)
.slice(2, 8)}`; .slice(2, 8)}`;
const sourceIdToUid = (source: ContextSource, index: number) => {
const parts = [
source.source_id.chapter_title,
source.source_id.section_title,
source.source_id.subsection_title,
source.source_id.title,
source.source_id.doc_type,
].filter(Boolean);
return parts.length ? parts.join("|") : `source_${index}`;
};
const sourceIdToPath = (source: ContextSource) => {
const parts = [
source.source_id.chapter_title,
source.source_id.section_title,
source.source_id.subsection_title,
].filter(Boolean);
return parts.length ? parts.join(" / ") : null;
};
const toRetrievedDoc = (source: ContextSource, index: number): RetrievedDoc => ({
uid: sourceIdToUid(source, index),
doc_type: source.source_id.doc_type,
score: source.score,
metadata: {
section_title: source.source_id.section_title ?? null,
subsection_title: source.source_id.subsection_title ?? null,
title: source.source_id.title ?? null,
type: source.source_type ?? null,
path: sourceIdToPath(source),
},
markdown: source.markdown || "",
});
export default function App() { export default function App() {
const [messages, setMessages] = useState<ChatMessage[]>(initialMessages); const [messages, setMessages] = useState<ChatMessage[]>(initialMessages);
const [draft, setDraft] = useState(""); const [draft, setDraft] = useState("");
...@@ -64,29 +112,10 @@ export default function App() { ...@@ -64,29 +112,10 @@ export default function App() {
setMessages((prev) => [...prev, userMessage]); setMessages((prev) => [...prev, userMessage]);
setDraft(""); setDraft("");
try { setRetrievalLoading(true);
setRetrievalLoading(true); setRetrievalError(null);
setRetrievalError(null);
const retrievalRequest = fetch(`${backendUrl}/api/retrieval/query`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
query: trimmed,
k: 4,
expand_links: true,
}),
}).then(async (res) => {
if (!res.ok) {
throw new Error(`Retrieval failed: ${res.status}`);
}
const payload = await res.json();
setDirectChildren(payload.children_direct || []);
setIndirectChildren(payload.children_expanded || []);
setSubsections(payload.subsections || []);
setSections(payload.sections || []);
});
try {
const response = await fetch(`${backendUrl}/api/chat`, { const response = await fetch(`${backendUrl}/api/chat`, {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
...@@ -114,8 +143,6 @@ export default function App() { ...@@ -114,8 +143,6 @@ export default function App() {
}, },
]); ]);
} }
await retrievalRequest;
} catch (error) { } catch (error) {
setMessages((prev) => [ setMessages((prev) => [
...prev, ...prev,
...@@ -125,6 +152,63 @@ export default function App() { ...@@ -125,6 +152,63 @@ export default function App() {
text: "Chat request failed. Check the API logs.", text: "Chat request failed. Check the API logs.",
}, },
]); ]);
setRetrievalError("Retrieval failed. Check the API logs.");
setRetrievalLoading(false);
void error;
return;
}
try {
const contextResponse = await fetch(`${backendUrl}/api/context/retrieval`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
messages: [...messages, userMessage].map((message) => ({
role: message.role,
text: message.text,
})),
draft: chatSessionId,
}),
});
if (!contextResponse.ok) {
throw new Error(`Retrieval failed: ${contextResponse.status}`);
}
const sources: ContextSource[] = await contextResponse.json();
const nextDirect: RetrievedDoc[] = [];
const nextIndirect: RetrievedDoc[] = [];
const nextSubsections: RetrievedDoc[] = [];
const nextSections: RetrievedDoc[] = [];
sources.forEach((source, index) => {
const doc = toRetrievedDoc(source, index);
switch (source.retrieved_as) {
case "children_direct":
nextDirect.push(doc);
break;
case "children_expanded":
nextIndirect.push(doc);
break;
case "subsections":
nextSubsections.push(doc);
break;
case "sections":
nextSections.push(doc);
break;
case "neighbors":
nextIndirect.push(doc);
break;
default:
nextIndirect.push(doc);
}
});
setDirectChildren(nextDirect);
setIndirectChildren(nextIndirect);
setSubsections(nextSubsections);
setSections(nextSections);
} catch (error) {
setRetrievalError("Retrieval failed. Check the API logs."); setRetrievalError("Retrieval failed. Check the API logs.");
void error; void error;
} finally { } finally {
......
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