Ani14 commited on
Commit
2496d5a
·
verified ·
1 Parent(s): 76bcc6f

Upload 3 files

Browse files
Files changed (3) hide show
  1. agent.py +206 -39
  2. app.py +96 -43
  3. models.py +13 -4
agent.py CHANGED
@@ -1,67 +1,234 @@
1
  import os
2
- import re
3
  import requests
4
- from typing import List, Dict
 
5
  from langgraph.graph import StateGraph, END, START
6
  from langgraph.checkpoint.base import BaseCheckpointSaver
 
 
7
  from openai import OpenAI
 
8
  from models import AgentState, Message, ExtractedIntelligence
9
 
10
- CALLBACK_URL = "https://hackathon.guvi.in/api/updateHoneyPotFinalResult"
 
11
  HONEYPOT_API_KEY = os.environ.get("HONEYPOT_API_KEY", "sk_test_123456789")
12
 
 
 
 
 
13
  client = OpenAI(
14
  base_url="https://openrouter.ai/api/v1",
15
- api_key=os.environ.get("OPENROUTER_API_KEY")
16
  )
17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  def detect_scam(state: AgentState) -> AgentState:
19
- text = state["conversationHistory"][-1].text.lower()
20
- keywords = ["bank", "upi", "otp", "verify", "urgent", "account"]
21
- state["scamDetected"] = any(k in text for k in keywords)
22
- state["agentNotes"] += "Scam detection executed. "
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  return state
24
 
25
  def extract_intelligence(state: AgentState) -> AgentState:
26
- scammer_text = " ".join(m.text for m in state["conversationHistory"] if m.sender == "scammer")
 
 
 
 
 
 
 
 
 
27
 
28
- data = state["extractedIntelligence"].model_dump()
29
- data["bankAccounts"] += re.findall(r"\b\d{8,20}\b", scammer_text)
30
- data["upiIds"] += re.findall(r"\b[a-zA-Z0-9._-]+@[a-zA-Z0-9_-]+\b", scammer_text)
31
- data["phishingLinks"] += re.findall(r"https?://\S+", scammer_text)
32
- data["phoneNumbers"] += re.findall(r"\+?\d{10,15}", scammer_text)
 
 
 
 
 
 
 
 
 
 
33
 
34
- state["extractedIntelligence"] = ExtractedIntelligence(**{k: list(set(v)) for k, v in data.items()})
35
- state["agentNotes"] += "Intelligence extracted. "
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  return state
37
 
38
  def final_callback(state: AgentState) -> AgentState:
39
- if not state["callbackSent"] and state["scamDetected"]:
40
- payload = {
41
- "sessionId": state["sessionId"],
42
- "scamDetected": state["scamDetected"],
43
- "totalMessagesExchanged": state["totalMessagesExchanged"],
44
- "extractedIntelligence": state["extractedIntelligence"].model_dump(),
45
- "agentNotes": state["agentNotes"],
46
- }
47
- requests.post(
48
- CALLBACK_URL,
49
- json=payload,
50
- headers={"x-api-key": HONEYPOT_API_KEY},
51
- timeout=5
52
- )
 
 
 
 
 
 
 
53
  state["callbackSent"] = True
 
 
 
 
 
 
 
 
 
54
  return state
55
 
56
  def create_honeypot_graph(checkpoint_saver: BaseCheckpointSaver):
57
- g = StateGraph(AgentState)
58
- g.add_node("detect_scam", detect_scam)
59
- g.add_node("extract_intelligence", extract_intelligence)
60
- g.add_node("final_callback", final_callback)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
 
62
- g.add_edge(START, "detect_scam")
63
- g.add_edge("detect_scam", "extract_intelligence")
64
- g.add_edge("extract_intelligence", "final_callback")
65
- g.add_edge("final_callback", END)
66
 
67
- return g.compile(checkpointer=checkpoint_saver)
 
1
  import os
2
+ import json
3
  import requests
4
+ import re
5
+ from typing import List, Dict, Any, Optional
6
  from langgraph.graph import StateGraph, END, START
7
  from langgraph.checkpoint.base import BaseCheckpointSaver
8
+ from langgraph.checkpoint.memory import MemorySaver
9
+ from pydantic import ValidationError
10
  from openai import OpenAI
11
+
12
  from models import AgentState, Message, ExtractedIntelligence
13
 
14
+ # --- Configuration ---
15
+ CALLBACK_URL = "https://hackathon.guvi.in/api/updateHoneyPotFinalResult"
16
  HONEYPOT_API_KEY = os.environ.get("HONEYPOT_API_KEY", "sk_test_123456789")
17
 
18
+ # OpenRouter configuration
19
+ OPENROUTER_API_KEY = os.environ.get("OPENROUTER_API_KEY", "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")
20
+ OPENROUTER_MODEL = os.environ.get("OPENROUTER_MODEL", "openai/gpt-oss-120b:free")
21
+
22
  client = OpenAI(
23
  base_url="https://openrouter.ai/api/v1",
24
+ api_key=OPENROUTER_API_KEY,
25
  )
26
 
27
+ def call_openrouter(messages: List[Dict[str, str]], max_tokens: int = 512) -> str:
28
+ """Call the OpenRouter API to generate a text response."""
29
+ if not OPENROUTER_API_KEY:
30
+ raise ValueError("OPENROUTER_API_KEY is not set.")
31
+
32
+ try:
33
+ response = client.chat.completions.create(
34
+ model=OPENROUTER_MODEL,
35
+ messages=messages,
36
+ max_tokens=max_tokens,
37
+ extra_body={"reasoning": {"enabled": True}}
38
+ )
39
+ return response.choices[0].message.content
40
+ except Exception as e:
41
+ print(f"OpenRouter API error: {e}")
42
+ raise
43
+
44
+ # --- LangGraph Nodes (Functions) ---
45
+
46
  def detect_scam(state: AgentState) -> AgentState:
47
+ """Node 1: Detects scam intent from the latest message."""
48
+ latest_message = state["conversationHistory"][-1]
49
+ text = latest_message.text
50
+ is_scam = False
51
+ reason = "No scam indicators found"
52
+
53
+ prompt = (
54
+ "You are a scam detection assistant. Given the following message, determine if it indicates "
55
+ "a scam. Respond with 'true|<reason>' if the message is a scam or 'false|<reason>' if not. "
56
+ f"Message: {text}"
57
+ )
58
+
59
+ try:
60
+ response = call_openrouter([{"role": "user", "content": prompt}], max_tokens=100)
61
+ first_line = response.strip().split('\n')[0]
62
+ parts = first_line.split('|', 1)
63
+ if parts:
64
+ flag = parts[0].strip().lower()
65
+ is_scam = flag in {"true", "yes"}
66
+ if len(parts) > 1:
67
+ reason = parts[1].strip()
68
+ else:
69
+ reason = "OpenRouter classification did not provide a reason"
70
+ except Exception as e:
71
+ print(f"OpenRouter classification error: {e}. Falling back to heuristic.")
72
+ lower_text = text.lower()
73
+ scam_keywords = ["bank", "account", "blocked", "verify", "otp", "password", "upi", "urgent", "link", "update"]
74
+ for kw in scam_keywords:
75
+ if kw in lower_text:
76
+ is_scam = True
77
+ reason = f"Keyword '{kw}' found in message (fallback)"
78
+ break
79
+
80
+ state["scamDetected"] = is_scam
81
+ if "agentNotes" not in state:
82
+ state["agentNotes"] = ""
83
+ state["agentNotes"] += f"Initial Detection: {reason}. "
84
+ return state
85
+
86
+ def agent_persona_response(state: AgentState) -> AgentState:
87
+ """Node 2: Generates a human‑like response to engage the scammer."""
88
+ if not state["scamDetected"]:
89
+ state["agent_response_text"] = "Thank you for reaching out. Have a nice day!"
90
+ state["should_continue_engagement"] = False
91
+ return state
92
+
93
+ latest_text = state["conversationHistory"][-1].text
94
+ response_text: str = ""
95
+
96
+ prompt = (
97
+ "You are acting as a genuine user in a conversation with a potential scammer. "
98
+ "Here is the scammer's latest message:\n"
99
+ f"{latest_text}\n\n"
100
+ "Respond in a friendly, inquisitive tone that does not reveal suspicion, but encourages the other "
101
+ "person to provide more details (such as why they need your bank details, UPI ID or any links they "
102
+ "shared). Keep your response under 50 words."
103
+ )
104
+
105
+ try:
106
+ response_text = call_openrouter([{"role": "user", "content": prompt}], max_tokens=150)
107
+ response_text = response_text.strip().split('\n')[0]
108
+ except Exception as e:
109
+ print(f"OpenRouter persona generation error: {e}. Falling back to heuristic.")
110
+ response_text = "I'm not sure I understand. Could you please explain further?"
111
+
112
+ agent_message = Message(
113
+ sender="user",
114
+ text=response_text,
115
+ timestamp=state["conversationHistory"][-1].timestamp
116
+ )
117
+ state["conversationHistory"].append(agent_message)
118
+ state["agent_response_text"] = response_text
119
+ state["totalMessagesExchanged"] += 1
120
+ state["should_continue_engagement"] = True
121
  return state
122
 
123
  def extract_intelligence(state: AgentState) -> AgentState:
124
+ """Node 3: Extracts structured intelligence from the conversation."""
125
+ scammer_text = " ".join([m.text for m in state["conversationHistory"] if m.sender == "scammer"])
126
+
127
+ bank_accounts = re.findall(r"\b\d{8,20}\b", scammer_text)
128
+ upilds = re.findall(r"\b[a-zA-Z0-9\.\-_]+@[a-zA-Z0-9\-_]+\b", scammer_text)
129
+ phishing_links = re.findall(r"https?://[^\s]+", scammer_text)
130
+ phone_numbers = re.findall(r"\+?\d{10,15}", scammer_text)
131
+
132
+ scam_keywords_list = ["bank", "account", "blocked", "verify", "otp", "password", "upi", "urgent", "link", "update"]
133
+ found_keywords = [kw for kw in scam_keywords_list if kw.lower() in scammer_text.lower()]
134
 
135
+ current_intel = state.get("extractedIntelligence", ExtractedIntelligence())
136
+ current_data = current_intel.model_dump()
137
+
138
+ new_data = {
139
+ "bankAccounts": bank_accounts,
140
+ "upilds": upilds,
141
+ "phishingLinks": phishing_links,
142
+ "phoneNumbers": phone_numbers,
143
+ "suspiciousKeywords": found_keywords,
144
+ }
145
+
146
+ # Merge and deduplicate
147
+ for key in current_data:
148
+ combined = current_data.get(key, []) + new_data.get(key, [])
149
+ current_data[key] = list(set(combined))
150
 
151
+ state["extractedIntelligence"] = ExtractedIntelligence(**current_data)
152
+ if any(new_data.values()):
153
+ if "agentNotes" not in state:
154
+ state["agentNotes"] = ""
155
+ state["agentNotes"] += "Intelligence updated. "
156
+ return state
157
+
158
+ def decide_engagement_end(state: AgentState) -> AgentState:
159
+ """Node 4: Decides whether to continue or end the conversation."""
160
+ intelligence: ExtractedIntelligence = state.get("extractedIntelligence", ExtractedIntelligence())
161
+ continue_engagement = True
162
+ # End if we have some actionable intelligence
163
+ if intelligence.bankAccounts or intelligence.upilds or intelligence.phishingLinks or intelligence.phoneNumbers:
164
+ continue_engagement = False
165
+
166
+ # Or if message count is high
167
+ if state.get("totalMessagesExchanged", 0) >= 10:
168
+ continue_engagement = False
169
+
170
+ state["should_continue_engagement"] = continue_engagement
171
  return state
172
 
173
  def final_callback(state: AgentState) -> AgentState:
174
+ """Node 5: Sends the mandatory final result callback."""
175
+ if not state["scamDetected"] or state.get("callbackSent", False):
176
+ return state
177
+
178
+ intelligence = state.get("extractedIntelligence", ExtractedIntelligence())
179
+ payload = {
180
+ "sessionId": state.get("sessionId"),
181
+ "scamDetected": state.get("scamDetected", False),
182
+ "totalMessagesExchanged": state.get("totalMessagesExchanged", 0),
183
+ "extractedIntelligence": intelligence.model_dump(),
184
+ "agentNotes": state.get("agentNotes", "")
185
+ }
186
+
187
+ headers = {
188
+ "Content-Type": "application/json",
189
+ "x-api-key": HONEYPOT_API_KEY
190
+ }
191
+
192
+ try:
193
+ response = requests.post(CALLBACK_URL, json=payload, headers=headers, timeout=10)
194
+ response.raise_for_status()
195
  state["callbackSent"] = True
196
+ if "agentNotes" not in state:
197
+ state["agentNotes"] = ""
198
+ state["agentNotes"] += "Final callback sent successfully. "
199
+ except Exception as e:
200
+ print(f"Final callback failed: {e}")
201
+ if "agentNotes" not in state:
202
+ state["agentNotes"] = ""
203
+ state["agentNotes"] += f"Final callback failed: {e}. "
204
+
205
  return state
206
 
207
  def create_honeypot_graph(checkpoint_saver: BaseCheckpointSaver):
208
+ workflow = StateGraph(AgentState)
209
+ workflow.add_node("detect_scam", detect_scam)
210
+ workflow.add_node("extract_intelligence", extract_intelligence)
211
+ workflow.add_node("agent_persona_response", agent_persona_response)
212
+ workflow.add_node("decide_engagement_end", decide_engagement_end)
213
+ workflow.add_node("final_callback", final_callback)
214
+
215
+ workflow.add_edge(START, "detect_scam")
216
+
217
+ def after_detection(state: AgentState) -> str:
218
+ return "extract_intelligence" if state["scamDetected"] else END
219
+
220
+ workflow.add_conditional_edges("detect_scam", after_detection)
221
+ workflow.add_edge("extract_intelligence", "agent_persona_response")
222
+ workflow.add_edge("agent_persona_response", "decide_engagement_end")
223
+
224
+ def after_decision(state: AgentState) -> str:
225
+ if state["should_continue_engagement"]:
226
+ return END
227
+ if not state.get("callbackSent", False):
228
+ return "final_callback"
229
+ return END
230
 
231
+ workflow.add_conditional_edges("decide_engagement_end", after_decision)
232
+ workflow.add_edge("final_callback", END)
 
 
233
 
234
+ return workflow.compile(checkpointer=checkpoint_saver)
app.py CHANGED
@@ -1,63 +1,116 @@
1
  import os
2
  import time
3
- from typing import Optional, Dict, Any
4
- from fastapi import FastAPI, HTTPException, Depends, status
5
  from fastapi.security import APIKeyHeader
 
 
 
 
6
  from langgraph.checkpoint.memory import MemorySaver
7
- from models import HoneypotRequest, HoneypotResponse, ExtractedIntelligence, AgentState
8
- from agent import create_honeypot_graph
 
 
 
 
9
 
 
10
  API_KEY_NAME = "x-api-key"
 
11
  API_KEY = os.environ.get("HONEYPOT_API_KEY", "sk_test_123456789")
12
-
13
  api_key_header = APIKeyHeader(name=API_KEY_NAME, auto_error=False)
14
 
15
- app = FastAPI(title="Agentic Honeypot API")
 
 
 
 
 
16
 
17
- checkpointer = MemorySaver()
 
18
  honeypot_app = create_honeypot_graph(checkpointer)
19
 
20
- async def get_api_key(api_key: str = Depends(api_key_header)):
21
- if api_key != API_KEY:
22
- raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid API Key")
23
- return api_key
 
 
 
 
 
 
24
 
25
  @app.post("/api/honeypot-detection", response_model=HoneypotResponse)
26
  async def honeypot_detection(
27
- request_data: Optional[HoneypotRequest] = None,
28
  api_key: str = Depends(get_api_key)
29
  ) -> Dict[str, Any]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
- if request_data is None:
32
- raise HTTPException(status_code=400, detail="Invalid or empty request body")
33
-
34
- state = AgentState(
35
- sessionId=request_data.sessionId,
36
- conversationHistory=request_data.conversationHistory + [request_data.message],
37
- scamDetected=False,
38
- extractedIntelligence=ExtractedIntelligence(),
39
- agentNotes="",
40
- totalMessagesExchanged=len(request_data.conversationHistory) + 1,
41
- should_continue_engagement=False,
42
- agent_response_text="",
43
- callbackSent=False
44
- )
45
-
46
- start = time.time()
47
- final_state = honeypot_app.invoke(state)
48
- duration = int(time.time() - start)
49
-
50
- return {
51
- "status": "success",
52
- "scamDetected": final_state["scamDetected"],
53
- "engagementMetrics": {
54
- "engagementDurationSeconds": duration,
55
- "totalMessagesExchanged": final_state["totalMessagesExchanged"]
56
- },
57
- "extractedIntelligence": final_state["extractedIntelligence"],
58
- "agentNotes": final_state["agentNotes"]
59
- }
60
 
61
  @app.get("/")
62
- def root():
63
- return {"message": "Agentic Honeypot API running"}
 
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 (
13
+ HoneypotRequest, HoneypotResponse,
14
+ AgentState, ExtractedIntelligence, Message, EngagementMetrics
15
+ )
16
 
17
+ # --- Configuration ---
18
  API_KEY_NAME = "x-api-key"
19
+ # Default key for testing, should be set via environment variable in production
20
  API_KEY = os.environ.get("HONEYPOT_API_KEY", "sk_test_123456789")
 
21
  api_key_header = APIKeyHeader(name=API_KEY_NAME, auto_error=False)
22
 
23
+ # --- Initialization ---
24
+ app = FastAPI(
25
+ title="Agentic Honey-Pot API",
26
+ description="REST API for Scam Detection and Intelligence Extraction (Problem Statement 2).",
27
+ version="1.0.0"
28
+ )
29
 
30
+ # Initialize LangGraph Checkpointer
31
+ checkpointer: BaseCheckpointSaver = MemorySaver()
32
  honeypot_app = create_honeypot_graph(checkpointer)
33
 
34
+ # --- Dependency for API Key Validation ---
35
+ async def get_api_key(api_key_header: str = Depends(api_key_header)):
36
+ if api_key_header is None or api_key_header != API_KEY:
37
+ raise HTTPException(
38
+ status_code=status.HTTP_401_UNAUTHORIZED,
39
+ detail="Invalid API Key or missing 'x-api-key' header.",
40
+ )
41
+ return api_key_header
42
+
43
+ # --- API Endpoints ---
44
 
45
  @app.post("/api/honeypot-detection", response_model=HoneypotResponse)
46
  async def honeypot_detection(
47
+ request_data: HoneypotRequest,
48
  api_key: str = Depends(get_api_key)
49
  ) -> Dict[str, Any]:
50
+ """
51
+ Accepts an incoming message event, runs the LangGraph agent, and returns the response.
52
+ Strictly follows Problem Statement 2 schema.
53
+ """
54
+ session_id = request_data.sessionId
55
+ config = {"configurable": {"thread_id": session_id}}
56
+
57
+ checkpoint = honeypot_app.get_state(config)
58
+ start_time = time.time()
59
+
60
+ if checkpoint and checkpoint.values:
61
+ current_state_dict = checkpoint.values
62
+ # Ensure all required fields exist
63
+ current_state_dict.setdefault("callbackSent", False)
64
+ current_state_dict.setdefault("agentNotes", "")
65
+ current_state_dict.setdefault("extractedIntelligence", ExtractedIntelligence())
66
+ current_state_dict.setdefault("conversationHistory", [])
67
+ current_state_dict.setdefault("totalMessagesExchanged", 0)
68
+ current_state_dict.setdefault("sessionId", session_id)
69
+
70
+ current_state = AgentState(**current_state_dict)
71
+ current_state["conversationHistory"].append(request_data.message)
72
+ current_state["totalMessagesExchanged"] += 1
73
+ input_state = current_state
74
+ else:
75
+ # New session
76
+ initial_history = request_data.conversationHistory + [request_data.message]
77
+ input_state = AgentState(
78
+ sessionId=session_id,
79
+ conversationHistory=initial_history,
80
+ scamDetected=False,
81
+ extractedIntelligence=ExtractedIntelligence(),
82
+ agentNotes="New session started. ",
83
+ totalMessagesExchanged=len(initial_history),
84
+ should_continue_engagement=False,
85
+ agent_response_text="",
86
+ callbackSent=False
87
+ )
88
+
89
+ try:
90
+ final_state_dict = honeypot_app.invoke(input_state, config=config)
91
+ final_state = AgentState(**final_state_dict)
92
+
93
+ engagement_duration = int(time.time() - start_time)
94
+
95
+ # Prepare response strictly matching PDF page 10/11
96
+ return {
97
+ "status": "success",
98
+ "scamDetected": final_state["scamDetected"],
99
+ "engagementMetrics": {
100
+ "engagementDurationSeconds": engagement_duration,
101
+ "totalMessagesExchanged": final_state["totalMessagesExchanged"]
102
+ },
103
+ "extractedIntelligence": final_state["extractedIntelligence"].model_dump(),
104
+ "agentNotes": final_state["agentNotes"]
105
+ }
106
 
107
+ except Exception as e:
108
+ print(f"Error in Honey-Pot: {e}")
109
+ raise HTTPException(
110
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
111
+ detail=f"Internal server error: {str(e)}",
112
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
 
114
  @app.get("/")
115
+ async def root():
116
+ return {"message": "Agentic Honey-Pot API is running. Use /api/honeypot-detection."}
models.py CHANGED
@@ -2,43 +2,52 @@ from typing import TypedDict, List, Optional, Annotated
2
  from operator import add
3
  from pydantic import BaseModel, Field
4
 
5
- # --- 1. API Input/Output Models (Problem Statement 2) ---
6
 
7
  class Message(BaseModel):
 
8
  sender: str = Field(..., description="scammer or user")
9
  text: str = Field(..., description="Message content")
10
  timestamp: str = Field(..., description="ISO-8601 format")
11
 
12
  class Metadata(BaseModel):
 
13
  channel: Optional[str] = Field(None, description="SMS / WhatsApp / Email / Chat")
14
  language: Optional[str] = Field(None, description="Language used")
15
  locale: Optional[str] = Field(None, description="Country or region")
16
 
17
  class HoneypotRequest(BaseModel):
 
18
  sessionId: str = Field(..., description="Unique session ID")
19
  message: Message = Field(..., description="The latest incoming message")
20
- conversationHistory: List[Message] = Field(default_factory=list)
21
  metadata: Optional[Metadata] = None
22
 
23
  class ExtractedIntelligence(BaseModel):
 
24
  bankAccounts: List[str] = Field(default_factory=list)
25
- upiIds: List[str] = Field(default_factory=list)
26
  phishingLinks: List[str] = Field(default_factory=list)
27
  phoneNumbers: List[str] = Field(default_factory=list)
28
  suspiciousKeywords: List[str] = Field(default_factory=list)
29
 
30
  class EngagementMetrics(BaseModel):
 
31
  engagementDurationSeconds: int
32
  totalMessagesExchanged: int
33
 
34
  class HoneypotResponse(BaseModel):
35
- status: str
 
36
  scamDetected: bool
37
  engagementMetrics: EngagementMetrics
38
  extractedIntelligence: ExtractedIntelligence
39
  agentNotes: str
40
 
 
 
41
  class AgentState(TypedDict):
 
42
  sessionId: str
43
  conversationHistory: Annotated[List[Message], add]
44
  scamDetected: bool
 
2
  from operator import add
3
  from pydantic import BaseModel, Field
4
 
5
+ # --- 1. API Input/Output Models (Strictly Problem Statement 2) ---
6
 
7
  class Message(BaseModel):
8
+ """Represents a single message in the conversation."""
9
  sender: str = Field(..., description="scammer or user")
10
  text: str = Field(..., description="Message content")
11
  timestamp: str = Field(..., description="ISO-8601 format")
12
 
13
  class Metadata(BaseModel):
14
+ """Optional metadata about the conversation channel."""
15
  channel: Optional[str] = Field(None, description="SMS / WhatsApp / Email / Chat")
16
  language: Optional[str] = Field(None, description="Language used")
17
  locale: Optional[str] = Field(None, description="Country or region")
18
 
19
  class HoneypotRequest(BaseModel):
20
+ """The incoming request body for the honeypot API."""
21
  sessionId: str = Field(..., description="Unique session ID")
22
  message: Message = Field(..., description="The latest incoming message")
23
+ conversationHistory: List[Message] = Field(default_factory=list, description="All previous messages")
24
  metadata: Optional[Metadata] = None
25
 
26
  class ExtractedIntelligence(BaseModel):
27
+ """Structured data to be extracted from the conversation."""
28
  bankAccounts: List[str] = Field(default_factory=list)
29
+ upilds: List[str] = Field(default_factory=list)
30
  phishingLinks: List[str] = Field(default_factory=list)
31
  phoneNumbers: List[str] = Field(default_factory=list)
32
  suspiciousKeywords: List[str] = Field(default_factory=list)
33
 
34
  class EngagementMetrics(BaseModel):
35
+ """Metrics for the engagement."""
36
  engagementDurationSeconds: int
37
  totalMessagesExchanged: int
38
 
39
  class HoneypotResponse(BaseModel):
40
+ """The outgoing response body from the honeypot API."""
41
+ status: str = Field(..., description="success")
42
  scamDetected: bool
43
  engagementMetrics: EngagementMetrics
44
  extractedIntelligence: ExtractedIntelligence
45
  agentNotes: str
46
 
47
+ # --- 2. LangGraph State Model ---
48
+
49
  class AgentState(TypedDict):
50
+ """The state object for the LangGraph state machine."""
51
  sessionId: str
52
  conversationHistory: Annotated[List[Message], add]
53
  scamDetected: bool