| import os |
| import json |
| import sys |
| import chromadb |
| from groq import Groq |
| from dotenv import load_dotenv |
|
|
| |
| ROOT_DIR = os.environ.get('WORKSPACE_ROOT', '.') |
| load_dotenv(os.path.join(ROOT_DIR, 'backend/.env')) |
|
|
| GROQ_API_KEY = os.getenv("GROQ_API_KEY") |
| RISK_FACTORS_PATH = os.path.join(ROOT_DIR, 'Excel_Generator/Stats_data_collection/risk_factors.json') |
| DB_PATH = os.path.join(ROOT_DIR, 'Database/ChromaMarket') |
|
|
| def get_market_context(): |
| if not os.path.exists(RISK_FACTORS_PATH): |
| return [] |
| |
| with open(RISK_FACTORS_PATH, 'r') as f: |
| data = json.load(f) |
| |
| chunks = [] |
| |
| |
| if data.get("llm_strategic_analysis"): |
| chunks.append(f"Strategic Analysis: {data['llm_strategic_analysis']}") |
| |
| |
| snap = data.get("data_snapshot", {}) |
| price_info = "Market Prices Snapshot:\n" |
| for k, v in snap.items(): |
| if isinstance(v, (int, float)): |
| price_info += f"- {k.replace('_', ' ').title()}: {v}\n" |
| chunks.append(price_info) |
| |
| |
| for key in ["tpa_regions", "eg_regions"]: |
| if key in snap: |
| region_info = f"Regional Prices ({key.replace('_', ' ').title()}):\n" |
| for r in snap[key]: |
| region_info += f"- {r['region']}: {r['price']} ({r['change']})\n" |
| chunks.append(region_info) |
| |
| |
| alerts = data.get("alerts", []) |
| if alerts: |
| alert_info = "Active Market Alerts:\n" |
| for a in alerts: |
| alert_info += f"- [{a.get('severity', 'INFO')}] {a.get('title')}: {a.get('message')}\n" |
| chunks.append(alert_info) |
| |
| return chunks |
|
|
| def init_rag(): |
| try: |
| client = chromadb.PersistentClient(path=DB_PATH) |
| collection = client.get_or_create_collection(name="market_intelligence") |
| |
| chunks = get_market_context() |
| if not chunks: |
| return collection, False |
| |
| collection.add( |
| documents=chunks, |
| ids=[f"chunk_{i}" for i in range(len(chunks))] |
| ) |
| return collection, True |
| except Exception as e: |
| print(f"⚠️ [RAG] Vector DB initialization failed (likely download timeout). Falling back to keyword search.") |
| return None, False |
|
|
| def manual_retrieval(question, chunks): |
| """Simple keyword-based retrieval as fallback for when embeddings fail.""" |
| |
| q_words = set(question.lower().split()) |
| scored = [] |
| for chunk in chunks: |
| c_words = set(chunk.lower().split()) |
| score = len(q_words.intersection(c_words)) |
| scored.append((score, chunk)) |
| |
| |
| scored.sort(key=lambda x: x[0], reverse=True) |
| return [s[1] for s in scored[:3]] |
|
|
| def query_market(question): |
| if not GROQ_API_KEY: |
| return "Error: GROQ_API_KEY not found in environment." |
|
|
| chunks = get_market_context() |
| collection, is_vector_active = init_rag() |
| |
| context_chunks = [] |
| |
| if is_vector_active and collection: |
| try: |
| |
| results = collection.query( |
| query_texts=[question], |
| n_results=3 |
| ) |
| context_chunks = results['documents'][0] |
| except Exception: |
| context_chunks = manual_retrieval(question, chunks) |
| else: |
| context_chunks = manual_retrieval(question, chunks) |
| |
| context = "\n---\n".join(context_chunks) |
| |
| client = Groq(api_key=GROQ_API_KEY) |
| |
| |
| model_name = "meta-llama/llama-4-scout-17b-16e-instruct" |
| |
| try: |
| completion = client.chat.completions.create( |
| model=model_name, |
| messages=[ |
| { |
| "role": "system", |
| "content": f"You are a Textile Market Expert. Provide a direct, data-rich answer based on the context below. \n\nFORMATTING RULES:\n- DO NOT USE ASTERISKS (**) for bolding. Use ALL CAPS for headers instead.\n- Use a clean vertical list (one item per line).\n- Use double newlines between main points for scannability.\n- Omit all introductory fluff and concluding notes.\n- If comparing prices, use a vertical list format.\n\nCONTEXT:\n{context}" |
| }, |
| { |
| "role": "user", |
| "content": question |
| } |
| ], |
| temperature=0.5, |
| max_tokens=400, |
| top_p=1, |
| stream=False |
| ) |
| return completion.choices[0].message.content |
| except Exception as e: |
| |
| if "404" in str(e) or "not found" in str(e).lower(): |
| completion = client.chat.completions.create( |
| model="llama-3.3-70b-versatile", |
| messages=[ |
| { |
| "role": "system", |
| "content": f"You are a Textile Market Expert. Provide a direct, data-rich answer based on the context below. \n\nFORMATTING RULES:\n- DO NOT USE ASTERISKS (**) for bolding. Use ALL CAPS for headers instead.\n- Use a clean vertical list (one item per line).\n- Use double newlines between main points for scannability.\n- Omit all introductory fluff and concluding notes.\n\nCONTEXT:\n{context}" |
| }, |
| { |
| "role": "user", |
| "content": question |
| } |
| ], |
| temperature=0.5, |
| max_tokens=400 |
| ) |
| return completion.choices[0].message.content |
| return f"Error: {str(e)}" |
|
|
| if __name__ == "__main__": |
| query = "" |
| |
| |
| if not sys.stdin.isatty(): |
| try: |
| raw_input = sys.stdin.read().strip() |
| if raw_input: |
| payload = json.loads(raw_input) |
| |
| if isinstance(payload, list) and len(payload) > 0: |
| query = payload[0] |
| elif isinstance(payload, dict): |
| query = payload.get("question") or payload.get("feedback") or "" |
| except: |
| pass |
|
|
| |
| if not query and len(sys.argv) > 1: |
| query = " ".join(sys.argv[1:]) |
|
|
| if query: |
| ans = query_market(query) |
| print(json.dumps({"response": ans})) |
| else: |
| print(json.dumps({"error": "No question provided"})) |
|
|