Tonyxay commited on
Commit
f238bde
·
verified ·
1 Parent(s): 2b61bba

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +65 -64
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 = 1200
59
- CHUNK_OVERLAP = 200
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
- SMALL_LLM = _config["llm_small"]
80
- LARGE_LLM = _config["llm_large"]
 
 
 
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"LLMs configurés : small={SMALL_LLM['model']} / large={LARGE_LLM['model']}")
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
- def _parse_llm_json(raw_content: str):
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
- def rag_query(query: str, top_k: int = TOP_K_RESULTS) -> dict:
 
 
 
 
 
 
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, "prompt_tokens": 0, "completion_tokens": 0, "cached_tokens": 0,
312
- "co2_grams": None, "energy_kwh": None,
 
 
 
 
313
  "run_time_in_ms": elapsed_ms,
314
  }
315
 
 
316
  prompt = build_rag_prompt(query, contexts)
317
 
318
- # accumulateurs : on additionne le coût de TOUS les appels
319
- tot = {"total": 0, "prompt": 0, "completion": 0, "cached": 0}
320
- co2, energy = 0.0, 0.0
321
-
322
- def _accumulate(res):
323
- nonlocal co2, energy
324
- for k in tot:
325
- tot[k] += res["tokens"][k]
326
- co2 += res["co2_grams"] or 0
327
- energy += res["energy_kwh"] or 0
328
-
329
- # 1) PETIT modèle d'abord
330
- res_small = _call_model(SMALL_LLM, prompt)
331
- _accumulate(res_small)
332
- answer, explanation, confident = _parse_llm_json(res_small["content"])
333
- model_used = SMALL_LLM["model"]
334
-
335
- # 2) Escalade vers le GROS modèle uniquement si pas confiant
336
- if not confident:
337
- res_large = _call_model(LARGE_LLM, prompt)
338
- _accumulate(res_large)
339
- answer, explanation, _ = _parse_llm_json(res_large["content"])
340
- model_used = LARGE_LLM["model"]
341
-
342
- logger.info(f"Modèle utilisé : {model_used} (confiance petit mod��le : {confident})")
 
 
 
343
  elapsed_ms = round((time.perf_counter() - start_time) * 1000, 2)
344
 
 
345
  return {
346
  "answer": answer,
347
- "sources": [{"source": c["source"], "score": c["similarity_score"], "ref_text": c["text"]} for c in contexts],
348
  "explanation": explanation,
349
- "total_token": tot["total"],
350
- "prompt_tokens": tot["prompt"],
351
- "completion_tokens": tot["completion"],
352
- "cached_tokens": tot["cached"],
353
- "co2_grams": co2 if co2 else None,
354
- "energy_kwh": energy if energy else None,
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
- "llm_models": [SMALL_LLM["model"], LARGE_LLM["model"]],
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