Spaces:
Sleeping
Sleeping
File size: 7,475 Bytes
24f4ff5 9850c97 24f4ff5 573205a aa4ac8d 1e4ae98 aa4ac8d bd9c6ae 24f4ff5 cb63650 9850c97 1e4ae98 9850c97 1e4ae98 9850c97 aa4ac8d cb63650 aa4ac8d 1e4ae98 aa4ac8d 1e4ae98 a439804 8c67f6c aa4ac8d 2b51b85 1e4ae98 005b7c5 2b51b85 3e0cda0 eac3167 3e0cda0 eac3167 3e0cda0 9850c97 1e4ae98 9850c97 eac3167 3e0cda0 eac3167 4376f5c 3e0cda0 1e4ae98 eac3167 0680215 9850c97 0680215 1e4ae98 0680215 eac3167 1e4ae98 9850c97 3e0cda0 eac3167 3e0cda0 eac3167 1e4ae98 9850c97 1e4ae98 9850c97 3e0cda0 1e4ae98 3e0cda0 eac3167 005b7c5 9850c97 aa4ac8d cb63650 1e4ae98 cb63650 1e4ae98 cb63650 1e4ae98 cb63650 1e4ae98 aa4ac8d 1e4ae98 aa4ac8d 1e4ae98 aa4ac8d 1e4ae98 aa4ac8d 1e4ae98 aa4ac8d 1e4ae98 9850c97 1e4ae98 aa4ac8d eac3167 9850c97 eac3167 9850c97 eac3167 9850c97 eac3167 1e4ae98 eac3167 9850c97 aa4ac8d 9850c97 1e4ae98 cb63650 9850c97 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
import os
os.environ["TOKENIZERS_PARALLELISM"] = "false"
os.environ["TRANSFORMERS_CACHE"] = "/tmp/transformers"
os.environ["HF_HOME"] = "/tmp/huggingface"
os.environ["SENTENCE_TRANSFORMERS_HOME"] = "/tmp/sentence_transformers"
os.environ["TORCH_HOME"] = "/tmp/torch"
import json
from fastapi import FastAPI, HTTPException, UploadFile, File
from pydantic import BaseModel
from typing import Optional
import chromadb
from chromadb.config import Settings
from sentence_transformers import SentenceTransformer
# Import from autonomous agent
from agent_langchain import (
process_with_agent,
get_conversation_history,
classify_ticket,
call_routing,
get_kb_collection,
encoder,
conversations
)
app = FastAPI(title="Smart Helpdesk AI Agent - Autonomous")
# Request Models
class TicketRequest(BaseModel):
text: str
conversation_id: Optional[str] = None
user_email: Optional[str] = None
# Persistent Chroma settings
CHROMA_PATH = "/tmp/chroma"
COLLECTION_NAME = "knowledge_base"
# -------------------------------
# KB Setup Endpoint
# -------------------------------
@app.post("/setup")
async def setup_kb(kb_file: UploadFile = File(...)):
"""Upload and index knowledge base."""
try:
content_bytes = await kb_file.read()
data = json.loads(content_bytes)
if not isinstance(data, list):
raise HTTPException(status_code=400, detail="JSON must be a list of items.")
print(f"📘 Loaded {len(data)} items from {kb_file.filename}")
chroma_client = chromadb.PersistentClient(
path=CHROMA_PATH,
settings=Settings(anonymized_telemetry=False, allow_reset=True)
)
collection = chroma_client.get_or_create_collection(COLLECTION_NAME)
if collection.count() > 0:
print(f"🧹 Clearing {collection.count()} existing records...")
collection.delete(ids=collection.get()['ids'])
texts, ids, metadatas = [], [], []
for i, item in enumerate(data):
text = item.get("answer") or item.get("text") or item.get("content") or ""
item_id = item.get("id") or str(i)
category = item.get("category", "")
if not text:
print(f"⚠️ Skipping item {i} - no text content")
continue
combined_text = f"Category: {category}. {text}" if category else text
texts.append(combined_text)
ids.append(str(item_id))
metadatas.append({"id": str(item_id), "category": category, "original_index": i})
if not texts:
raise HTTPException(status_code=400, detail="No valid text content found in JSON.")
print("🧠 Generating embeddings...")
embeddings = encoder.encode(texts, show_progress_bar=True).tolist()
print("💾 Adding to ChromaDB...")
collection.add(ids=ids, embeddings=embeddings, documents=texts, metadatas=metadatas)
# Update global reference
import agent_langchain
agent_langchain.kb_collection = collection
print(f"✅ Successfully added {collection.count()} records")
return {"message": "Knowledge base initialized", "count": collection.count()}
except json.JSONDecodeError:
raise HTTPException(status_code=400, detail="Invalid JSON file.")
except Exception as e:
import traceback
traceback.print_exc()
raise HTTPException(status_code=500, detail=f"Setup failed: {str(e)}")
# -------------------------------
# MAIN ORCHESTRATE ENDPOINT (Autonomous Agent)
# -------------------------------
@app.post("/orchestrate")
async def orchestrate_endpoint(ticket: TicketRequest):
"""
Main AI Agent endpoint - fully autonomous:
- Decides its own workflow
- Handles multi-turn conversations
- Auto-escalates when needed
- Maintains context
"""
try:
result = process_with_agent(
user_message=ticket.text,
conversation_id=ticket.conversation_id
)
return {
"conversation_id": result["conversation_id"],
"response": result["response"],
"status": result["status"],
"message_count": result["message_count"],
"reasoning_trace": result.get("reasoning_trace", []),
"instructions": {
"continue_conversation": "Include the conversation_id in your next request",
"new_ticket": "Omit conversation_id to start fresh"
}
}
except Exception as e:
import traceback
traceback.print_exc()
raise HTTPException(status_code=500, detail=f"Agent failed: {str(e)}")
# -------------------------------
# Get Conversation History
# -------------------------------
@app.get("/conversation/{conversation_id}")
async def get_conversation(conversation_id: str):
"""Retrieve full conversation history."""
conv = get_conversation_history(conversation_id)
if not conv:
raise HTTPException(status_code=404, detail="Conversation not found")
return {
"conversation_id": conversation_id,
"messages": conv["messages"],
"created_at": conv["created_at"],
"message_count": len(conv["messages"])
}
# -------------------------------
# Individual Tool Endpoints (for testing)
# -------------------------------
@app.post("/classify")
async def classify_endpoint(ticket: TicketRequest):
"""Test classification only."""
classification = classify_ticket(ticket.text)
return {"classification": classification}
@app.post("/route")
async def route_endpoint(ticket: TicketRequest):
"""Test routing only."""
department = call_routing(ticket.text)
return {"department": department}
@app.post("/kb_query")
async def kb_query_endpoint(ticket: TicketRequest):
"""Test KB query only."""
collection = get_kb_collection()
if not collection or collection.count() == 0:
raise HTTPException(status_code=400, detail="KB not set up. Call /setup first.")
try:
query_embedding = encoder.encode([ticket.text])[0].tolist()
result = collection.query(
query_embeddings=[query_embedding],
n_results=1,
include=["documents", "distances", "metadatas"]
)
if not result or not result.get('documents') or len(result['documents'][0]) == 0:
return {"answer": "No relevant KB found.", "confidence": 0.0}
best_doc = result['documents'][0][0]
best_distance = result['distances'][0][0] if result.get('distances') else 1.0
confidence = max(0.0, 1.0 - (best_distance / 2.0))
return {"answer": best_doc, "confidence": round(float(confidence), 3)}
except Exception as e:
import traceback
traceback.print_exc()
raise HTTPException(status_code=500, detail=f"KB query failed: {str(e)}")
# -------------------------------
# Health Check
# -------------------------------
@app.get("/health")
async def health():
collection = get_kb_collection()
kb_status = "initialized" if collection and collection.count() > 0 else "not initialized"
kb_count = collection.count() if collection else 0
return {
"status": "ok",
"kb_status": kb_status,
"kb_records": kb_count,
"active_conversations": len(conversations),
"agent_type": "Autonomous ReAct Agent with Gemini"
} |