Ani14 commited on
Commit
f22c0e9
·
verified ·
1 Parent(s): c9e48e1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +164 -133
app.py CHANGED
@@ -1,148 +1,179 @@
1
- from fastapi import FastAPI, Request, Depends, HTTPException
2
- from fastapi.responses import JSONResponse
3
- from datetime import datetime
4
- import uuid
5
  import os
6
- from typing import Any, Dict
 
 
 
 
7
 
8
- from models import Message, ExtractedIntelligence
 
 
9
  from agent import create_honeypot_graph, final_callback
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
- app = FastAPI()
12
-
13
- # -----------------------------
14
- # API KEY AUTH
15
- # -----------------------------
16
- HONEYPOT_API_KEY = os.environ.get("HONEYPOT_API_KEY", "sk_test_123456789")
17
-
18
- def get_api_key(request: Request):
19
- api_key = request.headers.get("x-api-key")
20
- if not api_key or api_key != HONEYPOT_API_KEY:
21
- raise HTTPException(status_code=401, detail="Invalid API Key")
22
- return api_key
23
-
24
- # -----------------------------
25
- # INIT GRAPH
26
- # -----------------------------
27
- graph = create_honeypot_graph()
28
-
29
- # -----------------------------
30
- # UTILS
31
- # -----------------------------
32
- def normalize_timestamp(ts: Any) -> str:
33
- """
34
- Convert numeric or invalid timestamps into ISO string
35
- """
36
- if ts is None:
37
- return datetime.utcnow().isoformat() + "Z"
38
-
39
- if isinstance(ts, (int, float)):
40
- return datetime.utcfromtimestamp(ts / 1000).isoformat() + "Z"
41
-
42
- return str(ts)
43
 
44
- def safe_message_from_payload(payload: Any) -> Message:
 
 
 
 
45
  """
46
- Convert ANY payload into a valid Message object
47
  """
48
- if isinstance(payload, dict):
49
- sender = payload.get("sender", "scammer")
50
- text = str(payload.get("text", "Hello"))
51
- timestamp = normalize_timestamp(payload.get("timestamp"))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  else:
53
- sender = "scammer"
54
- text = str(payload)
55
- timestamp = normalize_timestamp(None)
56
-
57
- return Message(sender=sender, text=text, timestamp=timestamp)
 
 
 
 
 
 
 
 
 
 
58
 
59
- # -----------------------------
60
- # MAIN ENDPOINT (TESTER SAFE)
61
- # -----------------------------
62
- @app.post("/api/honeypot-detection")
63
- async def honeypot_detection(
64
- request: Request,
65
- api_key: str = Depends(get_api_key)
66
- ):
67
  try:
68
- body = await request.json()
69
- except Exception:
70
- body = {}
71
-
72
- # SESSION ID
73
- session_id = body.get("sessionId") or str(uuid.uuid4())
74
-
75
- # MESSAGE
76
- msg_payload = body.get("message", "Hello")
77
- new_message = safe_message_from_payload(msg_payload)
78
-
79
- # CONVERSATION HISTORY
80
- history_payload = body.get("conversationHistory", [])
81
- conversation_history = []
82
-
83
- if isinstance(history_payload, list):
84
- for item in history_payload:
85
- conversation_history.append(safe_message_from_payload(item))
86
-
87
- conversation_history.append(new_message)
88
-
89
- # INITIAL STATE
90
- state: Dict[str, Any] = {
91
- "sessionId": session_id,
92
- "conversationHistory": conversation_history,
93
- "scamDetected": False,
94
- "agentNotes": "",
95
- "extractedIntelligence": ExtractedIntelligence(),
96
- "callbackSent": False,
97
- }
98
 
99
- # RUN AGENT
100
- try:
101
- result = graph.invoke(state)
102
  except Exception as e:
103
- return JSONResponse(
104
- status_code=200,
105
- content={
106
- "status": "success",
107
- "scamDetected": False,
108
- "agentNotes": f"Agent fallback triggered: {str(e)}",
109
- "extractedIntelligence": ExtractedIntelligence().model_dump(),
110
- "engagementMetrics": {
111
- "totalMessagesExchanged": len(conversation_history),
112
- "engagementDurationSeconds": 0
113
- }
114
- }
115
  )
116
 
117
- # RESPONSE (PDF-COMPLIANT)
118
- return {
119
- "status": "success",
120
- "scamDetected": result.get("scamDetected", False),
121
- "engagementMetrics": {
122
- "totalMessagesExchanged": len(conversation_history),
123
- "engagementDurationSeconds": 5
124
- },
125
- "extractedIntelligence": result.get("extractedIntelligence").model_dump(),
126
- "agentNotes": result.get("agentNotes", "")
127
- }
128
-
129
- # -----------------------------
130
- # MANUAL CALLBACK TEST
131
- # -----------------------------
132
  @app.post("/api/trigger-final-callback")
133
- async def trigger_callback(
134
- session_id: str,
135
- api_key: str = Depends(get_api_key)
136
- ):
137
- state = {
138
- "sessionId": session_id,
139
- "scamDetected": True,
140
- "extractedIntelligence": ExtractedIntelligence(),
141
- "agentNotes": "Manual callback trigger",
142
- "callbackSent": False,
143
- "totalMessagesExchanged": 1
144
- }
145
-
146
- final_callback(state)
147
-
148
- return {"status": "callback triggered"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
+ import time
3
+ from fastapi import FastAPI, Request, HTTPException, Depends, status
4
+ from fastapi.security import APIKeyHeader
5
+ from typing import Dict, Any
6
+ from datetime import datetime
7
 
8
+ # LangGraph and Model Imports
9
+ from langgraph.checkpoint.memory import MemorySaver
10
+ from langgraph.checkpoint.base import BaseCheckpointSaver
11
  from agent import create_honeypot_graph, final_callback
12
+ from models import HoneypotRequest, HoneypotResponse, AgentState, ExtractedIntelligence, Message
13
+
14
+ # --- Configuration ---
15
+ API_KEY_NAME = "x-api-key"
16
+ API_KEY = os.environ.get("HONEYPOT_API_KEY", "sk_test_123456789") # Default for local testing
17
+ api_key_header = APIKeyHeader(name=API_KEY_NAME, auto_error=False)
18
+
19
+ # --- Initialization ---
20
+ app = FastAPI(
21
+ title="Agentic Honey-Pot API",
22
+ description="REST API for Scam Detection and Intelligence Extraction using LangGraph and Qwen 2.5 3B.",
23
+ version="1.0.0"
24
+ )
25
+
26
+ # Initialize LangGraph Checkpointer (Use MemorySaver for simplicity, replace with a database for production)
27
+ # For Hugging Face Spaces, a persistent volume or database is recommended for checkpointer.
28
+ # For this example, we use MemorySaver, which will reset on Space restart.
29
+ checkpointer: BaseCheckpointSaver = MemorySaver()
30
+ honeypot_app = create_honeypot_graph(checkpointer)
31
+
32
+ # --- Dependency for API Key Validation ---
33
+
34
+ async def get_api_key(api_key_header: str = Depends(api_key_header)):
35
+ if api_key_header is None or api_key_header != API_KEY:
36
+ raise HTTPException(
37
+ status_code=status.HTTP_401_UNAUTHORIZED,
38
+ detail="Invalid API Key or missing 'x-api-key' header.",
39
+ )
40
+ return api_key_header
41
 
42
+ # --- API Endpoints ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
+ @app.post("/api/honeypot-detection", response_model=HoneypotResponse)
45
+ async def honeypot_detection(
46
+ request_data: HoneypotRequest,
47
+ api_key: str = Depends(get_api_key)
48
+ ) -> Dict[str, Any]:
49
  """
50
+ Accepts an incoming message event, runs the LangGraph agent, and returns the response.
51
  """
52
+ session_id = request_data.sessionId
53
+
54
+ # 1. Load or Initialize State
55
+ # LangGraph uses the thread_id for checkpointing
56
+ config = {"configurable": {"thread_id": session_id}}
57
+
58
+ # Check if a checkpoint exists for this session
59
+ # Get the latest checkpoint using the graph's state loader
60
+ checkpoint = honeypot_app.get_state(config)
61
+
62
+ if checkpoint:
63
+ # Load existing state and append the new message
64
+ # checkpoint.values contains the state dictionary saved at the last checkpoint
65
+ current_state_dict = checkpoint.values if hasattr(checkpoint, "values") else checkpoint.dict["values"]
66
+ # Add default for new fields if missing
67
+ if "callbackSent" not in current_state_dict:
68
+ current_state_dict["callbackSent"] = False
69
+ if "agentNotes" not in current_state_dict:
70
+ # Initialize agentNotes if missing to avoid KeyError in state machine
71
+ current_state_dict["agentNotes"] = ""
72
+ if "extractedIntelligence" not in current_state_dict:
73
+ # Initialize extractedIntelligence if missing
74
+ current_state_dict["extractedIntelligence"] = ExtractedIntelligence()
75
+ # Ensure conversation history exists
76
+ if "conversationHistory" not in current_state_dict:
77
+ current_state_dict["conversationHistory"] = []
78
+ if "totalMessagesExchanged" not in current_state_dict:
79
+ current_state_dict["totalMessagesExchanged"] = 0
80
+ # Ensure sessionId is present
81
+ if "sessionId" not in current_state_dict:
82
+ current_state_dict["sessionId"] = session_id
83
+ current_state = AgentState(**current_state_dict)
84
+
85
+ # Append the new message from the scammer
86
+ current_state["conversationHistory"].append(request_data.message)
87
+ current_state["totalMessagesExchanged"] += 1
88
+
89
+ # The LangGraph will continue from the last node
90
+ input_state = current_state
91
+ start_time = time.time()
92
+
93
  else:
94
+ # New conversation: Initialize the state
95
+ initial_history = request_data.conversationHistory + [request_data.message]
96
+
97
+ input_state = AgentState(
98
+ sessionId=session_id,
99
+ conversationHistory=initial_history,
100
+ scamDetected=False,
101
+ extractedIntelligence=ExtractedIntelligence(),
102
+ agentNotes="New session started. ",
103
+ totalMessagesExchanged=len(initial_history),
104
+ should_continue_engagement=False,
105
+ agent_response_text="",
106
+ callbackSent=False
107
+ )
108
+ start_time = time.time()
109
 
110
+ # 2. Invoke LangGraph
 
 
 
 
 
 
 
111
  try:
112
+ # Invoke the graph with the updated state
113
+ final_state_dict = honeypot_app.invoke(input_state, config=config)
114
+ final_state = AgentState(**final_state_dict)
115
+
116
+ end_time = time.time()
117
+ engagement_duration = end_time - start_time
118
+
119
+ # 3. Prepare API Response
120
+ response_data = {
121
+ "status": "success",
122
+ "scamDetected": final_state["scamDetected"],
123
+ "engagementMetrics": {
124
+ "engagementDurationSeconds": round(engagement_duration, 2),
125
+ "totalMessagesExchanged": final_state["totalMessagesExchanged"]
126
+ },
127
+ "extractedIntelligence": final_state["extractedIntelligence"].model_dump(),
128
+ "agentNotes": final_state["agentNotes"]
129
+ }
130
+
131
+ # The LangGraph handles engagement end and final callback internally.
132
+
133
+ return response_data
 
 
 
 
 
 
 
 
134
 
 
 
 
135
  except Exception as e:
136
+ print(f"An error occurred during LangGraph invocation: {e}")
137
+ raise HTTPException(
138
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
139
+ detail=f"Internal server error during agent processing: {str(e)}",
 
 
 
 
 
 
 
 
140
  )
141
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  @app.post("/api/trigger-final-callback")
143
+ async def trigger_callback(session_id: str, api_key: str = Depends(get_api_key)):
144
+ """
145
+ Manually triggers the final result callback for a specific session.
146
+ This is useful for testing or for an external system to signal the end of engagement.
147
+ """
148
+ config = {"configurable": {"thread_id": session_id}}
149
+ checkpoint = honeypot_app.get_state(config)
150
+
151
+ if not checkpoint:
152
+ raise HTTPException(status_code=404, detail=f"Session ID {session_id} not found.")
153
+
154
+ current_state_dict = checkpoint.values if hasattr(checkpoint, "values") else checkpoint.dict["values"]
155
+ # Ensure mandatory fields exist for older checkpoints
156
+ if "callbackSent" not in current_state_dict:
157
+ current_state_dict["callbackSent"] = False
158
+ if "agentNotes" not in current_state_dict:
159
+ current_state_dict["agentNotes"] = ""
160
+ if "extractedIntelligence" not in current_state_dict:
161
+ current_state_dict["extractedIntelligence"] = ExtractedIntelligence()
162
+ if "conversationHistory" not in current_state_dict:
163
+ current_state_dict["conversationHistory"] = []
164
+ if "totalMessagesExchanged" not in current_state_dict:
165
+ current_state_dict["totalMessagesExchanged"] = len(current_state_dict.get("conversationHistory", []))
166
+ # Ensure sessionId is present
167
+ if "sessionId" not in current_state_dict:
168
+ current_state_dict["sessionId"] = session_id
169
+ current_state = AgentState(**current_state_dict)
170
+
171
+ # Trigger the final callback if not already sent
172
+ if not current_state.get("callbackSent", False):
173
+ final_callback(current_state)
174
+
175
+ return {"status": "success", "message": f"Final callback triggered for session {session_id}."}
176
+
177
+ @app.get("/")
178
+ async def root():
179
+ return {"message": "Agentic Honey-Pot API is running. Use /api/honeypot-detection endpoint."}