Primary-text index for personas, faster retrieval, OpenAI model options
- Contemporary and Kafka personas search only "Die Verwandlung" (own index and tool description); per-persona subtitle - New contemporary portrait and avatar alt text - ChatGPT: gpt-6.1-sol, reasoning_effort low via Responses API; default in the model menu - GPT-Realtime-2.1 Mini as third option (realtime_chat.py, WebSocket, text only) - Load .env for OPENAI_API_KEY - Reranker in fp16 on GPU with 25 candidates; reranker, embeddings and BM25 shared and built at server start - BM25 stem cache (first search ~31s -> ~1.7s) - Timing output per LLM call and search Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -18,7 +18,10 @@ from personas import DEFAULT_PERSONA, PERSONAS, PUBLIC_FIELDS
|
||||
load_dotenv()
|
||||
|
||||
OLLAMA_MODEL = "gemma4:12b"
|
||||
OPENAI_MODEL = "gpt-6-luna"
|
||||
OPENAI_MODEL = "gpt-6.1-sol"
|
||||
REALTIME_MODEL = "gpt-realtime-2.1-mini" # OpenAI Realtime API, text only (realtime_chat.py)
|
||||
# Model name per provider, for the console trace
|
||||
MODEL_NAMES = {"ollama": OLLAMA_MODEL, "openai": OPENAI_MODEL, "realtime": REALTIME_MODEL}
|
||||
# One RAG instance per (LLM provider, vector store), for on-the-fly provider switching.
|
||||
# Each persona names its vector store in personas.py; personas sharing one share the instance.
|
||||
rags = {}
|
||||
@@ -30,16 +33,23 @@ for vectorstore in sorted({p["vectorstore"] for p in PERSONAS.values()}):
|
||||
openai_model=OPENAI_MODEL,
|
||||
)
|
||||
if os.environ.get("OPENAI_API_KEY"):
|
||||
try:
|
||||
rags[("openai", vectorstore)] = LocalRAG(
|
||||
vectorstore_path=vectorstore,
|
||||
llm_provider="openai",
|
||||
ollama_model=OLLAMA_MODEL,
|
||||
openai_model=OPENAI_MODEL,
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"OpenAI RAG not available for {vectorstore}: {e}")
|
||||
openai_available = any(provider == "openai" for provider, _ in rags)
|
||||
for provider in ("openai", "realtime"):
|
||||
try:
|
||||
rags[(provider, vectorstore)] = LocalRAG(
|
||||
vectorstore_path=vectorstore,
|
||||
llm_provider=provider,
|
||||
ollama_model=OLLAMA_MODEL,
|
||||
openai_model=OPENAI_MODEL,
|
||||
realtime_model=REALTIME_MODEL,
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"{provider} RAG not available for {vectorstore}: {e}")
|
||||
available = {provider for provider, _ in rags}
|
||||
openai_available = "openai" in available
|
||||
# Load the reranker and build the BM25 indexes at startup (shared copies) so the first search
|
||||
# is not delayed
|
||||
for rag in rags.values():
|
||||
rag.preload()
|
||||
|
||||
app = FastAPI(title="Local RAG Chat", version="1.0.0")
|
||||
# Images for the chat GUI (researcher portrait, avatar)
|
||||
@@ -54,7 +64,7 @@ class ChatMessage(BaseModel):
|
||||
class ChatRequest(BaseModel):
|
||||
message: str
|
||||
history: list[ChatMessage] = [] # previous turns for conversation context
|
||||
llm_provider: str = "ollama" # "ollama" | "openai"
|
||||
llm_provider: str = "ollama" # "ollama" | "openai" | "realtime"
|
||||
persona: str = DEFAULT_PERSONA # key in personas.PERSONAS
|
||||
|
||||
|
||||
@@ -83,7 +93,7 @@ def chat_view():
|
||||
|
||||
def _get_rag(provider: str, persona: str):
|
||||
"""Return the RAG instance for the provider and the persona's vector store.
|
||||
Fall back to Ollama if OpenAI is unavailable."""
|
||||
Fall back to Ollama if the provider is unavailable."""
|
||||
vectorstore = PERSONAS[persona]["vectorstore"]
|
||||
return rags.get((provider, vectorstore)) or rags[("ollama", vectorstore)]
|
||||
|
||||
@@ -93,7 +103,7 @@ def chat(request: ChatRequest):
|
||||
"""Handle a chat message and return the RAG answer."""
|
||||
if not request.message or not request.message.strip():
|
||||
return ChatResponse(answer="", error="Message cannot be empty")
|
||||
if request.llm_provider == "openai" and not openai_available:
|
||||
if request.llm_provider in ("openai", "realtime") and request.llm_provider not in available:
|
||||
return ChatResponse(answer="", error="OpenAI not configured. Set OPENAI_API_KEY.")
|
||||
if request.persona not in PERSONAS:
|
||||
return ChatResponse(answer="", error=f"Unknown persona: {request.persona}")
|
||||
@@ -120,8 +130,8 @@ def chat(request: ChatRequest):
|
||||
print(f" [{i + 1}] {chunk.get('source', '')} p.{chunk.get('page', '?')} s={chunk.get('score')} | {preview!r}")
|
||||
else:
|
||||
print(f"\n[RAG] No corpus search for this message")
|
||||
provider_label = "OpenAI" if request.llm_provider == "openai" else "Ollama"
|
||||
model_name = OPENAI_MODEL if request.llm_provider == "openai" else OLLAMA_MODEL
|
||||
provider_label = {"openai": "OpenAI", "realtime": "OpenAI Realtime"}.get(request.llm_provider, "Ollama")
|
||||
model_name = MODEL_NAMES.get(request.llm_provider, OLLAMA_MODEL)
|
||||
print(f"[RAG] LLM response ({provider_label} / {model_name}, {request.persona}):\n{answer}")
|
||||
|
||||
return ChatResponse(answer=answer, retrieved=retrieved, searches=searches)
|
||||
@@ -139,7 +149,7 @@ def health():
|
||||
@app.get("/api/providers")
|
||||
def providers():
|
||||
"""Return which LLM providers are available."""
|
||||
return {"ollama": True, "openai": openai_available}
|
||||
return {"ollama": True, "openai": openai_available, "realtime": "realtime" in available}
|
||||
|
||||
|
||||
@app.get("/api/personas")
|
||||
|
||||
Reference in New Issue
Block a user