Abhinaycodes's picture
Upload 18 files
e699b35 verified
Raw
History Blame Contribute Delete
8.24 kB
import os
import json
from fastapi import FastAPI, HTTPException, Request
from fastapi.staticfiles import StaticFiles
from fastapi.responses import HTMLResponse
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from google import genai
import uvicorn
import pyTigerGraph as tg
import chromadb
from bert_score import score as bert_score_fn
import warnings
from download_db import download_and_extract_db
warnings.filterwarnings("ignore")
# Trigger DB download if it's missing (for HF Spaces)
download_and_extract_db()
app = FastAPI(title="Financial Corporate GraphRAG")
# Add CORS middleware to allow cross-origin requests
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Mount the current directory to serve index.html and static files
app.mount("/static", StaticFiles(directory="."), name="static")
# Configuration (These should be set in environment variables tomorrow)
TG_HOST = os.environ.get("TG_HOST", "https://your-savanna-url.tigergraph.cloud")
TG_USERNAME = os.environ.get("TG_USERNAME", "tigergraph")
TG_PASSWORD = os.environ.get("TG_PASSWORD", "your_password")
TG_GRAPH = "FinancialGraph"
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
class QueryRequest(BaseModel):
query: str
company: str = None
tg_host: str = None
tg_token: str = None
tg_graph: str = None
gemini_api_key: str = None
@app.get("/", response_class=HTMLResponse)
async def serve_ui():
with open("index.html", "r", encoding="utf-8") as f:
return f.read()
@app.post("/api/basic_rag")
async def execute_basic_rag(req: QueryRequest):
api_key = req.gemini_api_key or os.environ.get("GEMINI_API_KEY")
if not api_key:
raise HTTPException(status_code=500, detail="Gemini API Key missing")
os.environ["GEMINI_API_KEY"] = api_key
try:
db_path = os.path.join(os.path.dirname(__file__), "chroma_db")
if not os.path.exists(db_path):
raise HTTPException(status_code=500, detail="Vector DB not built yet.")
client_db = chromadb.PersistentClient(path=db_path)
collection = client_db.get_collection(name="sec_filings")
# In enterprise RAG, 15-20 chunks are typically required to cover enough semantic ground for complex queries.
results = collection.query(query_texts=[req.query], n_results=15)
chunks = results['documents'][0]
distances = results['distances'][0]
metadatas = results['metadatas'][0]
context_parts = ["[Retrieved context - top-3 chunks from vector store]"]
for i, (chunk, dist, meta) in enumerate(zip(chunks, distances, metadatas)):
sim = max(0.0, 1.0 - (dist / 2.0))
context_parts.append(f"Chunk {i+1} (similarity {sim:.2f}) [Company: {meta.get('company')}]: {chunk}")
retrieved_context = "\n\n".join(context_parts)
genai_client = genai.Client()
prompt = f"You are a helpful assistant. Use the retrieved context below to answer the question accurately. Keep your answer concise (1-2 sentences maximum) so it does not get cut off.\n\nContext:\n{retrieved_context}\n\nQuestion: {req.query}"
response = genai_client.models.generate_content(model='gemini-2.5-flash', contents=prompt)
try:
in_tokens = response.usage_metadata.prompt_token_count
out_tokens = response.usage_metadata.candidates_token_count
except:
in_tokens = out_tokens = 0
try:
_, _, F1 = bert_score_fn([response.text], [retrieved_context], lang="en", verbose=False)
bert_score_f1 = float(F1.item())
except Exception as e:
print(f"BERTScore Error: {e}")
bert_score_f1 = 0.0
return {
"answer": response.text,
"context_used": retrieved_context,
"input_tokens": in_tokens,
"output_tokens": out_tokens,
"bert_score": bert_score_f1
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/rag")
async def execute_graph_rag(req: QueryRequest):
api_key = req.gemini_api_key or os.environ.get("GEMINI_API_KEY")
if not api_key:
raise HTTPException(status_code=500, detail="Gemini API Key missing")
os.environ["GEMINI_API_KEY"] = api_key
try:
# Use UI provided values or fallback to env
host = req.tg_host or TG_HOST
token = req.tg_token or os.environ.get("TG_TOKEN", "")
graph = req.tg_graph or TG_GRAPH
# Step 0: Extract the true company name using an Agentic LLM call
client = genai.Client()
extracted_company = req.company
if len(req.query.split()) > 3: # If it's a sentence instead of just a name
try:
extraction_prompt = f"Extract ONLY the exact primary company name from this query. Preserve exact spelling and punctuation (e.g. if it says 'INC.', keep the period). Do not include any other words. Query: '{req.query}'"
extract_resp = client.models.generate_content(model='gemini-2.5-flash', contents=extraction_prompt)
extracted_company = extract_resp.text.strip().strip(",?\"'")
print(f"Agent extracted company name: {extracted_company}")
except Exception as e:
print(f"Extraction failed: {e}")
pass
# Step 1: Connect to TigerGraph using the Savanna API Key
conn = tg.TigerGraphConnection(host=host, graphname=graph, apiToken=token)
# Step 2: Extract Graph Context based on user query
try:
# Pass the EXACT company name to TigerGraph to perfectly traverse the edges (using 'question' param to match installed query)
graph_context_raw = conn.runInstalledQuery("get_company_context", {"question": extracted_company})
graph_context_str = json.dumps(graph_context_raw, indent=2)
except Exception as query_err:
if "not found" in str(query_err).lower() or "404" in str(query_err):
return {"answer": "Error: Your query is not installed yet! Go to GraphStudio, click the 'Up Arrow' button next to Queries to install it.", "context_used": ""}
raise query_err
# (Simulation is now disabled since the database is live)
# graph_context = f"Company: {req.company} has OWNS relationships with 3 Subsidiaries. FACES_RISK from Supply Chain Disruptions. COMPETES_WITH 5 market leaders."
# Step 3: Pass Graph Context + Query to Gemini
client = genai.Client()
prompt = f"""
You are an elite Financial AI Assistant running on top of TigerGraph.
Answer the user's query using the verified Graph Database Context below.
User Query: {req.query}
TigerGraph Context:
{graph_context_str}
"""
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=prompt
)
try:
in_tokens = response.usage_metadata.prompt_token_count
out_tokens = response.usage_metadata.candidates_token_count
except:
in_tokens = out_tokens = 0
try:
_, _, F1 = bert_score_fn([response.text], [graph_context_str], lang="en", verbose=False)
bert_score_f1 = float(F1.item())
except Exception as e:
print(f"BERTScore Error: {e}")
bert_score_f1 = 0.0
return {
"answer": response.text,
"context_used": graph_context_str,
"input_tokens": in_tokens,
"output_tokens": out_tokens,
"bert_score": bert_score_f1
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
if __name__ == "__main__":
print("Starting GraphRAG Backend on port 8000...")
port = int(os.environ.get("PORT", 8000))
uvicorn.run(app, host="0.0.0.0", port=port)