Pulastya0 commited on
Commit
9850c97
·
verified ·
1 Parent(s): 5206349

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +101 -45
app.py CHANGED
@@ -1,14 +1,28 @@
1
  import os
 
 
 
 
 
 
 
 
2
  import json
3
  from fastapi import FastAPI, HTTPException, UploadFile, File
4
  from pydantic import BaseModel
5
- from agent_langchain import process_ticket_langchain, classify_ticket, call_routing, kb_collection
6
  import chromadb
7
  from chromadb.config import Settings
8
- from chromadb.api.models import Collection
9
- from chromadb.utils import embedding_functions
10
  from sentence_transformers import SentenceTransformer
 
11
 
 
 
 
 
 
 
 
 
12
 
13
  app = FastAPI(title="Smart Helpdesk AI Agent LangChain")
14
 
@@ -19,16 +33,11 @@ class TicketRequest(BaseModel):
19
  text: str
20
  user_email: str = None
21
 
22
- class SetupRequest(BaseModel):
23
- kb_file: str # path to KB.json
24
-
25
  # -------------------------------
26
  # Persistent Chroma client
27
  # -------------------------------
28
  CHROMA_PATH = "/tmp/chroma"
29
  COLLECTION_NAME = "knowledge_base"
30
- # Global variable for the running app
31
- kb_collection = None
32
 
33
  # -------------------------------
34
  # KB Setup Endpoint
@@ -39,8 +48,6 @@ async def setup_kb(kb_file: UploadFile = File(...)):
39
  Uploads a JSON KB file (flattened), generates embeddings with SentenceTransformer,
40
  and populates a persistent ChromaDB collection.
41
  """
42
- global kb_collection
43
-
44
  try:
45
  # Load JSON from uploaded file
46
  content_bytes = await kb_file.read()
@@ -51,9 +58,14 @@ async def setup_kb(kb_file: UploadFile = File(...)):
51
 
52
  print(f"📘 Loaded {len(data)} items from {kb_file.filename}")
53
 
54
- # Initialize encoder and Chroma
55
- encoder = SentenceTransformer("all-MiniLM-L6-v2")
56
- chroma_client = chromadb.PersistentClient(path=CHROMA_PATH)
 
 
 
 
 
57
  collection = chroma_client.get_or_create_collection(COLLECTION_NAME)
58
 
59
  # Clear existing records
@@ -64,20 +76,36 @@ async def setup_kb(kb_file: UploadFile = File(...)):
64
  # Prepare texts, ids, and metadata
65
  texts, ids, metadatas = [], [], []
66
  for i, item in enumerate(data):
67
- text = item.get("text") or ""
68
  item_id = item.get("id") or str(i)
 
 
 
 
 
69
  texts.append(text)
70
  ids.append(str(item_id))
71
- metadatas.append({"id": str(item_id)})
 
 
 
72
 
73
- # Generate embeddings
74
  print("🧠 Generating embeddings...")
75
  embeddings = encoder.encode(texts, show_progress_bar=True).tolist()
76
 
77
  # Add to ChromaDB
78
  print("💾 Adding to ChromaDB...")
79
- collection.add(ids=ids, embeddings=embeddings, metadatas=metadatas)
80
- kb_collection = collection # assign to global
 
 
 
 
 
 
 
 
81
 
82
  print(f"✅ Successfully added {collection.count()} records to {COLLECTION_NAME}.")
83
  return {"message": "Knowledge base successfully initialized.", "count": collection.count()}
@@ -85,7 +113,9 @@ async def setup_kb(kb_file: UploadFile = File(...)):
85
  except json.JSONDecodeError:
86
  raise HTTPException(status_code=400, detail="Invalid JSON file.")
87
  except Exception as e:
88
- raise HTTPException(status_code=500, detail=f"Setup failed: {e}")
 
 
89
 
90
  # -------------------------------
91
  # Step-by-Step Endpoints
@@ -106,37 +136,47 @@ async def route_endpoint(ticket: TicketRequest):
106
  @app.post("/kb_query")
107
  async def kb_query_endpoint(ticket: TicketRequest):
108
  """Query the flattened KB directly using embeddings and return the best match."""
109
- global kb_collection
110
- if not kb_collection:
 
111
  raise HTTPException(status_code=400, detail="KB not set up. Call /setup first.")
112
 
113
  try:
114
- # Use the same SentenceTransformer as in /setup
115
- encoder = SentenceTransformer("all-MiniLM-L6-v2")
116
- query_vec = encoder.encode([ticket.text])[0]
 
 
 
 
117
 
118
  # Query ChromaDB
119
- result = kb_collection.query(
120
- query_embeddings=[query_vec],
121
  n_results=1,
122
  include=["documents", "distances", "metadatas"]
123
  )
124
 
125
- if not result or len(result['documents'][0]) == 0:
126
  return {"answer": "No relevant KB found.", "confidence": 0.0}
127
 
128
  # Extract best match
129
  best_doc = result['documents'][0][0]
130
- best_distance = result['distances'][0][0] if result.get('distances') else 0.0
131
- confidence = float(1 - best_distance) # Convert distance → confidence
 
 
 
132
 
133
  return {
134
  "answer": best_doc,
135
- "confidence": round(confidence, 3)
136
  }
137
 
138
  except Exception as e:
139
- raise HTTPException(status_code=500, detail=f"KB query failed: {e}")
 
 
140
 
141
  # -------------------------------
142
  # Full Ticket Orchestration
@@ -144,24 +184,40 @@ async def kb_query_endpoint(ticket: TicketRequest):
144
  @app.post("/orchestrate")
145
  async def orchestrate_endpoint(ticket: TicketRequest):
146
  """Full ticket orchestration via LangChain agent with nicely formatted reasoning trace"""
147
- result = process_ticket_langchain(ticket.text)
148
-
149
- # Format reasoning trace for readability
150
- formatted_trace = [{"step": idx + 1, "description": line} for idx, line in enumerate(result.get("reasoning_trace", []))]
151
-
152
- response = {
153
- "status": result["status"],
154
- "classification": result["classification"],
155
- "department": result["department"],
156
- "answer": result["answer"],
157
- "reasoning_trace": formatted_trace
158
- }
 
 
 
 
159
 
160
- return response
 
 
 
 
161
 
162
  # -------------------------------
163
  # Health Check
164
  # -------------------------------
165
  @app.get("/health")
166
  async def health():
167
- return {"status": "ok"}
 
 
 
 
 
 
 
 
 
1
  import os
2
+
3
+ # SET CACHE PATHS BEFORE ANY IMPORTS
4
+ os.environ["TOKENIZERS_PARALLELISM"] = "false"
5
+ os.environ["TRANSFORMERS_CACHE"] = "/tmp/transformers"
6
+ os.environ["HF_HOME"] = "/tmp/huggingface"
7
+ os.environ["SENTENCE_TRANSFORMERS_HOME"] = "/tmp/sentence_transformers"
8
+ os.environ["TORCH_HOME"] = "/tmp/torch"
9
+
10
  import json
11
  from fastapi import FastAPI, HTTPException, UploadFile, File
12
  from pydantic import BaseModel
 
13
  import chromadb
14
  from chromadb.config import Settings
 
 
15
  from sentence_transformers import SentenceTransformer
16
+ import numpy as np
17
 
18
+ # Import from agent_langchain
19
+ from agent_langchain import (
20
+ process_ticket_langchain,
21
+ classify_ticket,
22
+ call_routing,
23
+ get_kb_collection,
24
+ encoder
25
+ )
26
 
27
  app = FastAPI(title="Smart Helpdesk AI Agent LangChain")
28
 
 
33
  text: str
34
  user_email: str = None
35
 
 
 
 
36
  # -------------------------------
37
  # Persistent Chroma client
38
  # -------------------------------
39
  CHROMA_PATH = "/tmp/chroma"
40
  COLLECTION_NAME = "knowledge_base"
 
 
41
 
42
  # -------------------------------
43
  # KB Setup Endpoint
 
48
  Uploads a JSON KB file (flattened), generates embeddings with SentenceTransformer,
49
  and populates a persistent ChromaDB collection.
50
  """
 
 
51
  try:
52
  # Load JSON from uploaded file
53
  content_bytes = await kb_file.read()
 
58
 
59
  print(f"📘 Loaded {len(data)} items from {kb_file.filename}")
60
 
61
+ # Get or create collection using shared function
62
+ chroma_client = chromadb.PersistentClient(
63
+ path=CHROMA_PATH,
64
+ settings=Settings(
65
+ anonymized_telemetry=False,
66
+ allow_reset=True
67
+ )
68
+ )
69
  collection = chroma_client.get_or_create_collection(COLLECTION_NAME)
70
 
71
  # Clear existing records
 
76
  # Prepare texts, ids, and metadata
77
  texts, ids, metadatas = [], [], []
78
  for i, item in enumerate(data):
79
+ text = item.get("text") or item.get("content") or ""
80
  item_id = item.get("id") or str(i)
81
+
82
+ if not text:
83
+ print(f"⚠️ Skipping item {i} - no text content")
84
+ continue
85
+
86
  texts.append(text)
87
  ids.append(str(item_id))
88
+ metadatas.append({"id": str(item_id), "original_index": i})
89
+
90
+ if not texts:
91
+ raise HTTPException(status_code=400, detail="No valid text content found in JSON.")
92
 
93
+ # Generate embeddings using the shared encoder
94
  print("🧠 Generating embeddings...")
95
  embeddings = encoder.encode(texts, show_progress_bar=True).tolist()
96
 
97
  # Add to ChromaDB
98
  print("💾 Adding to ChromaDB...")
99
+ collection.add(
100
+ ids=ids,
101
+ embeddings=embeddings,
102
+ documents=texts,
103
+ metadatas=metadatas
104
+ )
105
+
106
+ # Update the global reference in agent_langchain
107
+ import agent_langchain
108
+ agent_langchain.kb_collection = collection
109
 
110
  print(f"✅ Successfully added {collection.count()} records to {COLLECTION_NAME}.")
111
  return {"message": "Knowledge base successfully initialized.", "count": collection.count()}
 
113
  except json.JSONDecodeError:
114
  raise HTTPException(status_code=400, detail="Invalid JSON file.")
115
  except Exception as e:
116
+ import traceback
117
+ traceback.print_exc()
118
+ raise HTTPException(status_code=500, detail=f"Setup failed: {str(e)}")
119
 
120
  # -------------------------------
121
  # Step-by-Step Endpoints
 
136
  @app.post("/kb_query")
137
  async def kb_query_endpoint(ticket: TicketRequest):
138
  """Query the flattened KB directly using embeddings and return the best match."""
139
+ collection = get_kb_collection()
140
+
141
+ if not collection:
142
  raise HTTPException(status_code=400, detail="KB not set up. Call /setup first.")
143
 
144
  try:
145
+ # Check if collection has data
146
+ count = collection.count()
147
+ if count == 0:
148
+ raise HTTPException(status_code=400, detail="KB is empty. Please upload data via /setup.")
149
+
150
+ # Encode query using the shared encoder
151
+ query_embedding = encoder.encode([ticket.text])[0].tolist()
152
 
153
  # Query ChromaDB
154
+ result = collection.query(
155
+ query_embeddings=[query_embedding],
156
  n_results=1,
157
  include=["documents", "distances", "metadatas"]
158
  )
159
 
160
+ if not result or not result.get('documents') or len(result['documents'][0]) == 0:
161
  return {"answer": "No relevant KB found.", "confidence": 0.0}
162
 
163
  # Extract best match
164
  best_doc = result['documents'][0][0]
165
+ best_distance = result['distances'][0][0] if result.get('distances') else 1.0
166
+
167
+ # Convert L2 distance to confidence score
168
+ # For normalized embeddings, L2 distance ranges from 0 (identical) to ~2.0 (opposite)
169
+ confidence = max(0.0, 1.0 - (best_distance / 2.0))
170
 
171
  return {
172
  "answer": best_doc,
173
+ "confidence": round(float(confidence), 3)
174
  }
175
 
176
  except Exception as e:
177
+ import traceback
178
+ traceback.print_exc()
179
+ raise HTTPException(status_code=500, detail=f"KB query failed: {str(e)}")
180
 
181
  # -------------------------------
182
  # Full Ticket Orchestration
 
184
  @app.post("/orchestrate")
185
  async def orchestrate_endpoint(ticket: TicketRequest):
186
  """Full ticket orchestration via LangChain agent with nicely formatted reasoning trace"""
187
+ try:
188
+ result = process_ticket_langchain(ticket.text)
189
+
190
+ # Format reasoning trace for readability
191
+ formatted_trace = [
192
+ {"step": idx + 1, "description": line}
193
+ for idx, line in enumerate(result.get("reasoning_trace", []))
194
+ ]
195
+
196
+ response = {
197
+ "status": result["status"],
198
+ "classification": result["classification"],
199
+ "department": result["department"],
200
+ "answer": result["answer"],
201
+ "reasoning_trace": formatted_trace
202
+ }
203
 
204
+ return response
205
+ except Exception as e:
206
+ import traceback
207
+ traceback.print_exc()
208
+ raise HTTPException(status_code=500, detail=f"Orchestration failed: {str(e)}")
209
 
210
  # -------------------------------
211
  # Health Check
212
  # -------------------------------
213
  @app.get("/health")
214
  async def health():
215
+ collection = get_kb_collection()
216
+ kb_status = "initialized" if collection and collection.count() > 0 else "not initialized"
217
+ kb_count = collection.count() if collection else 0
218
+
219
+ return {
220
+ "status": "ok",
221
+ "kb_status": kb_status,
222
+ "kb_records": kb_count
223
+ }