All checks were successful
Deploy mindnet to llm-node / deploy (push) Successful in 2s
89 lines
2.8 KiB
Python
89 lines
2.8 KiB
Python
"""
|
||
app/services/llm_ollama.py — Ollama-Integration & Prompt-Bau (WP-04)
|
||
|
||
Zweck:
|
||
Prompt-Template & (optionaler) lokaler Aufruf von Ollama. Der Aufruf ist
|
||
bewusst gekapselt und kann gefahrlos deaktiviert bleiben, bis ihr ein
|
||
konkretes Modell konfigurieren wollt.
|
||
Kompatibilität:
|
||
Python 3.12+
|
||
Version:
|
||
0.1.0 (Erstanlage)
|
||
Stand:
|
||
2025-10-07
|
||
Bezug:
|
||
WP-04/05 Kontextbereitstellung für LLM
|
||
Nutzung:
|
||
from app.services.llm_ollama import build_prompt, call_ollama
|
||
Änderungsverlauf:
|
||
0.1.0 (2025-10-07) – Erstanlage.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
from typing import List, Dict, Optional
|
||
import subprocess
|
||
import json
|
||
|
||
PROMPT_TEMPLATE = """System: You are a helpful expert.
|
||
User: {question}
|
||
|
||
Context (ranked):
|
||
{contexts}
|
||
|
||
Task: Answer precisely. At the end, list sources (note title + section) and important edge paths.
|
||
"""
|
||
|
||
|
||
def build_context_block(items: List[Dict]) -> str:
|
||
"""Formatiert Top-K-Kontexte (Chunks) für den Prompt."""
|
||
lines = []
|
||
for i, it in enumerate(items, 1):
|
||
note = it.get("note_title", "") or it.get("note_id", "")
|
||
sec = it.get("section", "") or it.get("section_title", "")
|
||
sc = it.get("score", 0)
|
||
txt = it.get("text", "") or it.get("body", "") or ""
|
||
lines.append(f"{i}) {note} — {sec} [score={sc:.2f}]\n{txt}\n")
|
||
return "\n".join(lines)
|
||
|
||
|
||
def build_prompt(question: str, contexts: List[Dict]) -> str:
|
||
"""Setzt Frage + Kontexte in ein konsistentes Template."""
|
||
return PROMPT_TEMPLATE.format(question=question, contexts=build_context_block(contexts))
|
||
|
||
|
||
def call_ollama(prompt: str, model: str = "llama3.1:8b", timeout_s: int = 120) -> Optional[str]:
|
||
"""
|
||
Optionaler lokaler Aufruf von `ollama run`.
|
||
Rückgabe: generierter Text oder None bei Fehler/Abbruch.
|
||
Hinweis: Nur nutzen, wenn Ollama lokal installiert/konfiguriert ist.
|
||
"""
|
||
try:
|
||
proc = subprocess.run(
|
||
["ollama", "run", model],
|
||
input=prompt.encode("utf-8"),
|
||
stdout=subprocess.PIPE,
|
||
stderr=subprocess.PIPE,
|
||
timeout=timeout_s,
|
||
check=False,
|
||
)
|
||
out = proc.stdout.decode("utf-8", errors="replace")
|
||
# viele ollama Builds streamen JSON-Zeilen; robust extrahieren:
|
||
try:
|
||
# Falls JSONL, letztes "response" zusammenfassen
|
||
texts = []
|
||
for line in out.splitlines():
|
||
line = line.strip()
|
||
if not line:
|
||
continue
|
||
try:
|
||
obj = json.loads(line)
|
||
if "response" in obj:
|
||
texts.append(obj["response"])
|
||
except Exception:
|
||
texts.append(line)
|
||
return "".join(texts).strip()
|
||
except Exception:
|
||
return out.strip()
|
||
except Exception:
|
||
return None
|