Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -55,8 +55,8 @@ DATA_DIR = Path("/data_V5") if Path("/data_V5").is_dir() else Path("./data_V5")
|
|
| 55 |
CHROMA_PERSIST_DIR = str(DATA_DIR / "chroma_db")
|
| 56 |
TRAIN_DOCS_DIR = Path("./train_data")
|
| 57 |
COLLECTION_NAME = "rag_documents"
|
| 58 |
-
CHUNK_SIZE =
|
| 59 |
-
CHUNK_OVERLAP =
|
| 60 |
TOP_K_RESULTS = 3
|
| 61 |
|
| 62 |
# Settings loaded from data/config.json (runtime config).
|
|
@@ -76,8 +76,11 @@ EMBEDDING_ENDPOINT_URL = _config["embedding"]["endpoint_url"]
|
|
| 76 |
EMBEDDING_MODEL_NAME = _config["embedding"]["model"]
|
| 77 |
|
| 78 |
# LLM (Azure OpenAI)
|
| 79 |
-
|
| 80 |
-
|
|
|
|
|
|
|
|
|
|
| 81 |
|
| 82 |
# Azure API key from environment variable (shared by both LLM and embedding endpoints)
|
| 83 |
AZURE_API_KEY = os.environ.get("AZURE_API_KEY")
|
|
@@ -119,7 +122,7 @@ logger.info(f"ChromaDB collection '{COLLECTION_NAME}' ready. Documents: {collect
|
|
| 119 |
# We call the Azure OpenAI-compatible endpoint directly via requests.
|
| 120 |
# Endpoint URL and model are configured in config.json.
|
| 121 |
|
| 122 |
-
logger.info(f"
|
| 123 |
|
| 124 |
|
| 125 |
# ---------------------------------------------------------------------------
|
|
@@ -272,34 +275,23 @@ def build_rag_prompt(query: str, contexts: list[dict]) -> str:
|
|
| 272 |
prompt = RAG_PROMPT_TEMPLATE.format(context=context_text, question=query)
|
| 273 |
return prompt
|
| 274 |
|
| 275 |
-
def _call_model(cfg: dict, prompt: str) -> dict:
|
| 276 |
-
return call_llm_with_metrics(
|
| 277 |
-
prompt,
|
| 278 |
-
endpoint_url=cfg["endpoint_url"],
|
| 279 |
-
api_key=AZURE_API_KEY,
|
| 280 |
-
model=cfg["model"],
|
| 281 |
-
max_completion_tokens=cfg["max_completion_tokens"],
|
| 282 |
-
temperature=cfg["temperature"],
|
| 283 |
-
top_p=cfg["top_p"],
|
| 284 |
-
)
|
| 285 |
|
|
|
|
|
|
|
|
|
|
| 286 |
|
| 287 |
-
|
| 288 |
-
json_str = raw_content.strip()
|
| 289 |
-
if json_str.startswith("```"):
|
| 290 |
-
json_str = json_str.split("\n", 1)[-1]
|
| 291 |
-
json_str = json_str.rsplit("```", 1)[0].strip()
|
| 292 |
-
try:
|
| 293 |
-
parsed = json.loads(json_str)
|
| 294 |
-
answer = parsed["answer"]
|
| 295 |
-
explanation = parsed.get("explanation", "")
|
| 296 |
-
confident = bool(parsed.get("confident", True)) # absent => on suppose confiant (pas d'escalade)
|
| 297 |
-
return answer, explanation, confident
|
| 298 |
-
except (json.JSONDecodeError, KeyError):
|
| 299 |
-
return raw_content, "LLM did not return a structured explanation.", True
|
| 300 |
|
| 301 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 302 |
start_time = time.perf_counter()
|
|
|
|
|
|
|
| 303 |
contexts = retrieve_relevant_context(query, top_k=top_k)
|
| 304 |
|
| 305 |
if not contexts:
|
|
@@ -308,50 +300,59 @@ def rag_query(query: str, top_k: int = TOP_K_RESULTS) -> dict:
|
|
| 308 |
"answer": "No documents have been ingested yet. Please upload documents first.",
|
| 309 |
"sources": [],
|
| 310 |
"explanation": "No documents found in the vector store to retrieve context from.",
|
| 311 |
-
"total_token": 0,
|
| 312 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 313 |
"run_time_in_ms": elapsed_ms,
|
| 314 |
}
|
| 315 |
|
|
|
|
| 316 |
prompt = build_rag_prompt(query, contexts)
|
| 317 |
|
| 318 |
-
#
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
|
|
|
|
|
|
|
|
|
| 343 |
elapsed_ms = round((time.perf_counter() - start_time) * 1000, 2)
|
| 344 |
|
|
|
|
| 345 |
return {
|
| 346 |
"answer": answer,
|
| 347 |
-
"sources": [{"source":
|
| 348 |
"explanation": explanation,
|
| 349 |
-
"total_token":
|
| 350 |
-
"prompt_tokens":
|
| 351 |
-
"completion_tokens":
|
| 352 |
-
"cached_tokens":
|
| 353 |
-
"co2_grams":
|
| 354 |
-
"energy_kwh":
|
| 355 |
"run_time_in_ms": elapsed_ms,
|
| 356 |
}
|
| 357 |
|
|
@@ -470,7 +471,7 @@ async def health_check():
|
|
| 470 |
"status": "healthy",
|
| 471 |
"documents_in_store": collection.count(),
|
| 472 |
"embedding_model": EMBEDDING_MODEL_NAME,
|
| 473 |
-
"
|
| 474 |
}
|
| 475 |
|
| 476 |
|
|
|
|
| 55 |
CHROMA_PERSIST_DIR = str(DATA_DIR / "chroma_db")
|
| 56 |
TRAIN_DOCS_DIR = Path("./train_data")
|
| 57 |
COLLECTION_NAME = "rag_documents"
|
| 58 |
+
CHUNK_SIZE = 512
|
| 59 |
+
CHUNK_OVERLAP = 50
|
| 60 |
TOP_K_RESULTS = 3
|
| 61 |
|
| 62 |
# Settings loaded from data/config.json (runtime config).
|
|
|
|
| 76 |
EMBEDDING_MODEL_NAME = _config["embedding"]["model"]
|
| 77 |
|
| 78 |
# LLM (Azure OpenAI)
|
| 79 |
+
LLM_ENDPOINT_URL = _config["llm"]["endpoint_url"]
|
| 80 |
+
LLM_MODEL_NAME = _config["llm"]["model"]
|
| 81 |
+
LLM_MAX_TOKENS = _config["llm"].get("max_completion_tokens", 512)
|
| 82 |
+
LLM_TEMPERATURE = _config["llm"].get("temperature", 0.7)
|
| 83 |
+
LLM_TOP_P = _config["llm"].get("top_p", 0.95)
|
| 84 |
|
| 85 |
# Azure API key from environment variable (shared by both LLM and embedding endpoints)
|
| 86 |
AZURE_API_KEY = os.environ.get("AZURE_API_KEY")
|
|
|
|
| 122 |
# We call the Azure OpenAI-compatible endpoint directly via requests.
|
| 123 |
# Endpoint URL and model are configured in config.json.
|
| 124 |
|
| 125 |
+
logger.info(f"LLM configured: {LLM_MODEL_NAME} via {LLM_ENDPOINT_URL}")
|
| 126 |
|
| 127 |
|
| 128 |
# ---------------------------------------------------------------------------
|
|
|
|
| 275 |
prompt = RAG_PROMPT_TEMPLATE.format(context=context_text, question=query)
|
| 276 |
return prompt
|
| 277 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 278 |
|
| 279 |
+
def rag_query(query: str, top_k: int = TOP_K_RESULTS) -> dict:
|
| 280 |
+
"""
|
| 281 |
+
End-to-end RAG pipeline: Query → Retrieve → Generate.
|
| 282 |
|
| 283 |
+
This demonstrates Step 2d: How to make a RAG system end-to-end.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 284 |
|
| 285 |
+
Pipeline steps:
|
| 286 |
+
1. Receive user query
|
| 287 |
+
2. Retrieve relevant context from vector store
|
| 288 |
+
3. Build augmented prompt with context
|
| 289 |
+
4. Call LLM to generate answer
|
| 290 |
+
5. Return answer with source metadata, explanation, token count, and timing
|
| 291 |
+
"""
|
| 292 |
start_time = time.perf_counter()
|
| 293 |
+
|
| 294 |
+
# Step 1: Retrieve relevant document chunks
|
| 295 |
contexts = retrieve_relevant_context(query, top_k=top_k)
|
| 296 |
|
| 297 |
if not contexts:
|
|
|
|
| 300 |
"answer": "No documents have been ingested yet. Please upload documents first.",
|
| 301 |
"sources": [],
|
| 302 |
"explanation": "No documents found in the vector store to retrieve context from.",
|
| 303 |
+
"total_token": 0,
|
| 304 |
+
"prompt_tokens": 0,
|
| 305 |
+
"completion_tokens": 0,
|
| 306 |
+
"cached_tokens": 0,
|
| 307 |
+
"co2_grams": None,
|
| 308 |
+
"energy_kwh": None,
|
| 309 |
"run_time_in_ms": elapsed_ms,
|
| 310 |
}
|
| 311 |
|
| 312 |
+
# Step 2: Build the augmented prompt
|
| 313 |
prompt = build_rag_prompt(query, contexts)
|
| 314 |
|
| 315 |
+
# Step 3: Generate answer from LLM (with token + CO2 metrics)
|
| 316 |
+
llm_result = call_llm_with_metrics(
|
| 317 |
+
prompt,
|
| 318 |
+
endpoint_url=LLM_ENDPOINT_URL,
|
| 319 |
+
api_key=AZURE_API_KEY,
|
| 320 |
+
model=LLM_MODEL_NAME,
|
| 321 |
+
max_completion_tokens=LLM_MAX_TOKENS,
|
| 322 |
+
temperature=LLM_TEMPERATURE,
|
| 323 |
+
top_p=LLM_TOP_P,
|
| 324 |
+
)
|
| 325 |
+
raw_content = llm_result["content"]
|
| 326 |
+
tokens = llm_result["tokens"]
|
| 327 |
+
total_token = tokens["total"]
|
| 328 |
+
|
| 329 |
+
# Parse structured JSON response from LLM (handle markdown code fences)
|
| 330 |
+
json_str = raw_content.strip()
|
| 331 |
+
if json_str.startswith("```"):
|
| 332 |
+
json_str = json_str.split("\n", 1)[-1]
|
| 333 |
+
json_str = json_str.rsplit("```", 1)[0].strip()
|
| 334 |
+
|
| 335 |
+
try:
|
| 336 |
+
parsed = json.loads(json_str)
|
| 337 |
+
answer = parsed["answer"]
|
| 338 |
+
explanation = parsed["explanation"]
|
| 339 |
+
except (json.JSONDecodeError, KeyError):
|
| 340 |
+
answer = raw_content
|
| 341 |
+
explanation = "LLM did not return a structured explanation."
|
| 342 |
+
|
| 343 |
elapsed_ms = round((time.perf_counter() - start_time) * 1000, 2)
|
| 344 |
|
| 345 |
+
# Step 4: Return structured response
|
| 346 |
return {
|
| 347 |
"answer": answer,
|
| 348 |
+
"sources": [{"source": ctx["source"], "score": ctx["similarity_score"], "ref_text": ctx["text"]} for ctx in contexts],
|
| 349 |
"explanation": explanation,
|
| 350 |
+
"total_token": total_token,
|
| 351 |
+
"prompt_tokens": tokens["prompt"],
|
| 352 |
+
"completion_tokens": tokens["completion"],
|
| 353 |
+
"cached_tokens": tokens["cached"],
|
| 354 |
+
"co2_grams": llm_result["co2_grams"],
|
| 355 |
+
"energy_kwh": llm_result["energy_kwh"],
|
| 356 |
"run_time_in_ms": elapsed_ms,
|
| 357 |
}
|
| 358 |
|
|
|
|
| 471 |
"status": "healthy",
|
| 472 |
"documents_in_store": collection.count(),
|
| 473 |
"embedding_model": EMBEDDING_MODEL_NAME,
|
| 474 |
+
"llm_model": LLM_MODEL_NAME,
|
| 475 |
}
|
| 476 |
|
| 477 |
|