raushan-in commited on
Commit
b99d8fb
·
1 Parent(s): 552ae33

Revert "Add application file"

Browse files

This reverts commit 552ae33dc13d3d90cadc397659ae86ee7c1cc07a.

Dockerfile.client DELETED
@@ -1,14 +0,0 @@
1
- FROM python:3.12.3-slim
2
-
3
- WORKDIR /app
4
-
5
- COPY requirements_client.txt ./
6
- RUN pip install --no-cache-dir -r requirements_client.txt
7
-
8
- COPY . .
9
-
10
- EXPOSE 8088
11
-
12
- HEALTHCHECK CMD curl --fail http://localhost:8088/_stcore/health
13
-
14
- ENTRYPOINT ["streamlit", "run", "src/interface.py", "--server.port=8088", "--server.address=0.0.0.0"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Dockerfile.server DELETED
@@ -1,17 +0,0 @@
1
- FROM python:3.12.3-slim
2
-
3
- # Set environment variables
4
- ENV PYTHONUNBUFFERED 1
5
- WORKDIR /app
6
-
7
- COPY requirements_server.txt ./
8
- RUN pip install --no-cache-dir -r requirements_server.txt
9
-
10
- # Copy the application code
11
- COPY . .
12
-
13
- # Expose the port
14
- EXPOSE 8080
15
-
16
- # Start the backend app
17
- CMD ["python", "src/main.py"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
README.md CHANGED
@@ -1,111 +1,11 @@
1
- # DAPA (Digital Arrest Protection App)
2
-
3
- **Inspired by the Hindi word 'धप्पा' (meaning ‘busted’), A tool to combat digital scams.**
4
-
5
- ---
6
-
7
- ## Overview
8
- DAPA enables users to report financial scams and frauds efficiently via a user-friendly platform. The system facilitates the following:
9
-
10
- 1. **Report a Scam**: Users can provide details such as the scammer’s phone number and a brief description of the fraud. DAPA categorizes the scam and records the report.
11
- 2. **Search a Phone Number**: Users can check whether a specific number has been previously flagged for fraudulent activities.
12
-
13
- With its streamlined architecture powered by AI, DAPA aims to mitigate the growing epidemic of financial scams by creating a comprehensive record of scam incidents.
14
-
15
- ---
16
-
17
- ## Why DAPA Matters
18
- ### The Growing Threat of Financial Scams:
19
- - **India**:
20
- - According to reports, the annual financial loss due to digital scams and frauds in India amounts to **₹60,000 crore**.
21
- - A surge in digital adoption has inadvertently made India a hotspot for cybercriminal activities, including the **Digital Arrest Scam** where scammers impersonate law enforcement officials to defraud victims of significant amounts.
22
-
23
- - **Global**:
24
- - Financial scams cost individuals and businesses **over $5 trillion annually worldwide**.
25
- - An increasing number of scams now exploit vulnerabilities in mobile and social media platforms.
26
-
27
- ### Spotlight on the Digital Arrest Scam:
28
- The Digital Arrest Scam, a notable threat in India, sees fraudsters pretending to be law enforcement officials to extort money by intimidation. Victims are coerced into paying under the pretense of avoiding legal trouble. (Sources: **The Hindu**)
29
-
30
- Prime Minister Modi have highlighted the urgent need to address these issues through education and vigilance.
31
-
32
- ---
33
-
34
- ## Why an AI Chatbot Instead of Traditional Form-Based Tools?
35
- DAPA leverages an AI chatbot over traditional form-based tools for the following reasons:
36
-
37
- 1. **Localized Language Support**:
38
- - Users can narrate their experiences in their own local language, including dialects, through platforms like WhatsApp. The AI chatbot interprets, summarizes, and processes this information seamlessly.
39
-
40
- 2. **Ease of Use**:
41
- - Unlike rigid forms, a conversational AI provides a more intuitive, human-like interface, enabling users to express their ordeal naturally without worrying about form structure or specific formats.
42
-
43
- 3. **Advanced Understanding**:
44
- - The AI model not only captures and categorizes scam details but also creates concise summaries, ensuring that valuable information is accurately stored in a centralized database for combating scams.
45
-
46
- 4. **Centralized Scam Pattern Analysis**:
47
- - The summaries generated by the chatbot help uncover common scam patterns. By analyzing these trends, DAPA contributes to developing preventive measures and combating scams more effectively at scale.
48
-
49
- 5. **Accessibility**:
50
- - By integrating with popular platforms like WhatsApp, the AI chatbot increases accessibility, making it easier for users in remote or underserved areas to report scams.
51
-
52
- 6. **Insights**:
53
- - With AI, identifying patterns and emerging threats faster than traditional tools, thereby enhancing preventative measures.
54
-
55
  ---
56
-
57
- ## Features
58
- ### Core Features:
59
- - **Fraud Reporting**:
60
- - Easy submission of scammer details.
61
- - Categorization of the scam using AI-based inference.
62
- - Centralized record maintenance for tracking scam patterns.
63
-
64
- - **Fraud Search**:
65
- - Quick lookup of phone numbers to check for prior scam reports.
66
-
67
- ### Technology Stack:
68
- - **Backend**: Python, FastAPI, LangGraph
69
- - **Frontend**: Streamlit
70
- - **Database**: PostgreSQL
71
- - **AI Model**: GROQ LLaMA 3 (Configurable)
72
-
73
  ---
74
 
75
- ## Setup Instructions
76
- ### Environment Configuration
77
- Create the environment file:
78
- ```bash
79
- cp example.env .env
80
- ```
81
- - Add Groq token
82
-
83
- ### Build and Run the Application
84
- Build the application container using Docker:
85
- ```bash
86
- docker compose up --build -d
87
- ```
88
-
89
- ### Access the App:
90
- - Chatbot: [http://localhost:8088/](http://localhost:8088/)
91
- - API Documentation: [http://localhost:8080/docs](http://localhost:8080/docs)
92
-
93
- ---
94
-
95
- ## Architecture Diagram
96
- ![flow](https://github.com/user-attachments/assets/c51bd311-7b9b-4e9c-888b-190fc08e4da0)
97
-
98
- ---
99
-
100
- ## Future Roadmap
101
- - **WhatsApp Integration**: Users will be able to report and identify scams via WhatsApp for enhanced convenience. This will be achieved through a webhook system.
102
- - **Enhanced AI Capabilities**: Continuous improvement of the scam categorization model to ensure accuracy and adaptability to emerging scam types.
103
- ---
104
-
105
- ## Contact
106
- For improvements or collaboration, feel free to connect:
107
- [Raushan's LinkedIn Profile](https://www.linkedin.com/in/raushan-in/)
108
-
109
- Together, let’s combat financial fraud and make the digital world safer for everyone.
110
-
111
- **DAPA – Digital Arrest Protection App**
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Dapa
3
+ emoji: 🐢
4
+ colorFrom: indigo
5
+ colorTo: indigo
6
+ sdk: docker
7
+ pinned: false
8
+ license: mit
 
 
 
 
 
 
 
 
 
 
9
  ---
10
 
11
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
compose.yml DELETED
@@ -1,65 +0,0 @@
1
- services:
2
- dapa_be:
3
- build:
4
- context: .
5
- dockerfile: Dockerfile.server
6
- container_name: dapa_backend
7
- depends_on:
8
- - dapa_pg
9
- ports:
10
- - "8080:8080"
11
- env_file:
12
- - .env
13
- environment:
14
- - DATABASE_URL=postgresql+asyncpg://${POSTGRES_USER}:${POSTGRES_PASSWORD}@dapa_pg:${POSTGRES_PORT}/${POSTGRES_DB}
15
- networks:
16
- - dapa-network
17
-
18
-
19
- dapa_streamlit_fe:
20
- build:
21
- context: .
22
- dockerfile: Dockerfile.client
23
- container_name: dapa_streamlit
24
- ports:
25
- - "8088:8088"
26
- depends_on:
27
- - dapa_be
28
- environment:
29
- - BACKEND_URL=http://dapa_backend:8080
30
- networks:
31
- - dapa-network
32
- develop:
33
- watch:
34
- - path: src/client/
35
- action: sync+restart
36
- target: /app/client/
37
- - path: src/schema/
38
- action: sync+restart
39
- target: /app/schema/
40
- - path: src/interface.py
41
- action: sync+restart
42
- target: /app/interface.py
43
-
44
- dapa_pg:
45
- image: postgres:latest
46
- container_name: dapa_pg
47
- ports:
48
- - "5432:5432"
49
- env_file:
50
- - .env
51
- networks:
52
- - dapa-network
53
- volumes:
54
- - pg_db_1:/var/lib/postgresql/data
55
-
56
- volumes:
57
- pg_db_1:
58
-
59
- networks:
60
- dapa-network:
61
-
62
- # To build and run the app:
63
- # docker compose up --build -d
64
- # or for dev easy
65
- # docker compose watch
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
example.env DELETED
@@ -1,13 +0,0 @@
1
- MODE=dev
2
-
3
- GROQ_API_KEY=
4
- GROQ_MODEL=llama-3.3-70b-versatile
5
-
6
- LANGCHAIN_TRACING_V2=false
7
- LANGCHAIN_API_KEY=
8
-
9
- # POSTGRES_DB
10
- POSTGRES_USER=username
11
- POSTGRES_PASSWORD=password
12
- POSTGRES_DB=pgdb_name
13
- POSTGRES_PORT=5432
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements_client.txt DELETED
@@ -1,4 +0,0 @@
1
- httpx
2
- pydantic
3
- python-dotenv
4
- streamlit
 
 
 
 
 
requirements_server.txt DELETED
@@ -1,45 +0,0 @@
1
- annotated-types==0.7.0
2
- anyio==4.8.0
3
- asyncpg==0.30.0
4
- certifi==2024.12.14
5
- charset-normalizer==3.4.1
6
- click==8.1.8
7
- distro==1.9.0
8
- fastapi==0.115.6
9
- greenlet==3.1.1
10
- groq==0.15.0
11
- h11==0.14.0
12
- httpcore==1.0.7
13
- httpx==0.28.1
14
- idna==3.10
15
- jsonpatch==1.33
16
- jsonpointer==3.0.0
17
- langchain-core==0.3.29
18
- langchain-groq==0.2.3
19
- langgraph==0.2.62
20
- langgraph-checkpoint==2.0.9
21
- langgraph-sdk==0.1.51
22
- langsmith==0.2.10
23
- msgpack==1.1.0
24
- numexpr==2.10.2
25
- numpy==2.2.1
26
- orjson==3.10.14
27
- packaging==24.2
28
- psycopg2-binary==2.9.10
29
- pydantic==2.10.5
30
- pydantic-settings==2.7.1
31
- pydantic_core==2.27.2
32
- python-dotenv==1.0.1
33
- PyYAML==6.0.2
34
- requests==2.32.3
35
- requests-toolbelt==1.0.0
36
- setuptools==69.5.1
37
- sniffio==1.3.1
38
- SQLAlchemy==2.0.37
39
- sqlmodel==0.0.22
40
- starlette==0.41.3
41
- tenacity==9.0.0
42
- typing_extensions==4.12.2
43
- urllib3==2.3.0
44
- uvicorn==0.34.0
45
- wheel==0.43.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/agent.py DELETED
@@ -1,61 +0,0 @@
1
- """AI agents"""
2
-
3
- from langchain_core.language_models.chat_models import BaseChatModel
4
- from langchain_core.messages import AIMessage, SystemMessage
5
- from langchain_core.runnables import (
6
- RunnableConfig,
7
- RunnableLambda,
8
- RunnableSerializable,
9
- )
10
- from langchain_groq import ChatGroq
11
- from langgraph.checkpoint.memory import MemorySaver
12
- from langgraph.graph import END, MessagesState, StateGraph
13
- from langgraph.prebuilt import ToolNode
14
-
15
- from prompts import instructions
16
- from settings import settings
17
- from tools import register_scam, search_scam
18
-
19
- llm = ChatGroq(
20
- model=settings.GROQ_MODEL, temperature=settings.GROQ_MODEL_TEMP, streaming=False
21
- )
22
-
23
-
24
- tools = [register_scam, search_scam]
25
-
26
-
27
- class AgentState(MessagesState, total=False):
28
- """`total=False` is PEP589 specs.
29
-
30
- documentation: https://typing.readthedocs.io/en/latest/spec/typeddict.html#totality
31
- """
32
-
33
-
34
- def wrap_model(model: BaseChatModel) -> RunnableSerializable[AgentState, AIMessage]:
35
- model_with_tools = model.bind_tools(tools)
36
- preprocessor = RunnableLambda(
37
- lambda state: [SystemMessage(content=instructions)] + state["messages"],
38
- name="StateModifier",
39
- )
40
- return preprocessor | model_with_tools
41
-
42
-
43
- async def acall_model(state: AgentState, config: RunnableConfig) -> AgentState:
44
- model_runnable = wrap_model(llm)
45
- response = await model_runnable.ainvoke(state, config)
46
- # We return a list, because this will get added to the existing list
47
- return {"messages": [response]}
48
-
49
-
50
- # Define the graph
51
- agent = StateGraph(AgentState)
52
- agent.add_node("model", acall_model)
53
- agent.add_node("tools", ToolNode(tools))
54
-
55
- agent.set_entry_point("model")
56
-
57
- # Add edges (transitions)
58
- agent.add_edge("model", "tools")
59
- agent.add_edge("tools", END)
60
-
61
- cyber_guard = agent.compile(checkpointer=MemorySaver())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/auth.py DELETED
@@ -1,23 +0,0 @@
1
- from typing import Annotated
2
-
3
- from fastapi import Depends, HTTPException, status
4
- from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
5
-
6
- from settings import settings
7
-
8
-
9
- def verify_bearer(
10
- http_auth: Annotated[
11
- HTTPAuthorizationCredentials | None,
12
- Depends(
13
- HTTPBearer(
14
- description="Please provide AUTH_SECRET api key.", auto_error=False
15
- )
16
- ),
17
- ],
18
- ) -> None:
19
- if not settings.AUTH_SECRET:
20
- return
21
- auth_secret = settings.AUTH_SECRET.get_secret_value()
22
- if not http_auth or http_auth.credentials != auth_secret:
23
- raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/database.py DELETED
@@ -1,67 +0,0 @@
1
- import re
2
- from contextlib import asynccontextmanager
3
- from datetime import datetime
4
-
5
- from fastapi import Depends
6
- from pydantic import validator
7
- from sqlalchemy.ext.asyncio import create_async_engine
8
- from sqlalchemy.orm import sessionmaker
9
- from sqlmodel import Field, SQLModel
10
- from sqlmodel.ext.asyncio.session import AsyncSession
11
-
12
- from scams import scam_categories
13
- from settings import settings
14
-
15
- database_url = settings.DATABASE_URL.get_secret_value()
16
-
17
- engine = create_async_engine(database_url, echo=settings.is_dev())
18
-
19
- async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
20
-
21
-
22
- async def create_db_and_tables():
23
- async with engine.begin() as conn:
24
- await conn.run_sync(SQLModel.metadata.create_all)
25
-
26
-
27
- @asynccontextmanager
28
- async def get_session() -> AsyncSession:
29
- """
30
- Dependency function to provide an async database session.
31
- Ensures proper cleanup after use.
32
- """
33
- async with async_session() as session:
34
- try:
35
- yield session
36
- finally:
37
- await session.close()
38
-
39
-
40
- class Scammer(SQLModel, table=True):
41
- """Scammer ORM Model."""
42
-
43
- id: int = Field(default=None, primary_key=True)
44
- scammer_mobile: str = Field(index=True, description="Scammer mobile number")
45
- scam_id: int = Field(description="Scam ID of the scam type")
46
- reporter_ordeal: str = Field(description="Summary of the scam")
47
- reporter_mobile: str = Field(description="Reporter mobile number")
48
- created_at: datetime = Field(
49
- default_factory=datetime.utcnow, description="Timestamp of report creation"
50
- )
51
-
52
- @validator("scammer_mobile", "reporter_mobile", pre=True)
53
- def validate_mobile_number(cls, value: str) -> str:
54
- """Validate mobile numbers using a regex."""
55
- pattern = r"^\+\d{1,3}-?\d{6,14}$" # E.164 format
56
- if not re.match(pattern, value):
57
- raise ValueError(f"Invalid mobile number: {value}")
58
- return value
59
-
60
- @validator("scam_id")
61
- def validate_scam_id(cls, value: int) -> int:
62
- """Validate if scam_id exists in scam_categories."""
63
- if value not in scam_categories.keys():
64
- raise ValueError(
65
- f"Invalid scam_id: {value}. Must be one of {list(scam_categories.keys())}."
66
- )
67
- return value
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/interface.py DELETED
@@ -1,88 +0,0 @@
1
- import asyncio
2
- import os
3
-
4
- import httpx
5
- import streamlit as st
6
- from dotenv import load_dotenv
7
-
8
- APP_TITLE = "DAPA"
9
- APP_ICON = "🛡️"
10
-
11
- # Load environment variables
12
- load_dotenv()
13
- BACKEND_URL = os.getenv("BACKEND_URL")
14
- CHAT_API = BACKEND_URL + "/chat"
15
-
16
-
17
- async def get_response(user_message: str, thread_id: str | None = None) -> dict:
18
- payload = {"user_message": user_message, "thread_id": thread_id}
19
- async with httpx.AsyncClient(timeout=10.0) as client:
20
- try:
21
- response = await client.post(CHAT_API, json=payload)
22
- response.raise_for_status()
23
- return response.json()
24
- except httpx.HTTPError as e:
25
- return {"error": f"Error connecting to backend: {str(e)}"}
26
-
27
-
28
- async def main():
29
- st.set_page_config(page_title=APP_TITLE, page_icon=APP_ICON)
30
-
31
- if "thread_id" not in st.session_state:
32
- st.session_state.thread_id = None
33
- if "messages" not in st.session_state:
34
- st.session_state.messages = []
35
-
36
- st.title(f"{APP_ICON} DAPA AI Assistant")
37
- st.write(
38
- "This bot helps you identify or report phone numbers involved in financial fraud or cyber scams."
39
- "Please describe your incident below."
40
- )
41
-
42
- for message in st.session_state.messages:
43
- responder_type = message["responder"]
44
- if responder_type == "tool":
45
- st.chat_message("tool", avatar="🛡️").write(message["content"])
46
- else:
47
- st.chat_message(responder_type).write(message["content"])
48
-
49
- # User input
50
- if user_input := st.chat_input("Type your message here..."):
51
- st.session_state.messages.append({"responder": "human", "content": user_input})
52
- st.chat_message("human").write(user_input)
53
-
54
- response = await get_response(user_input, st.session_state.thread_id)
55
-
56
- if "error" in response:
57
- st.error(response["error"])
58
- else:
59
- response_message = response["response_message"]
60
- responder = response["responder"]
61
- st.session_state.thread_id = response["thread_id"]
62
-
63
- # Append the response to session state
64
- st.session_state.messages.append(
65
- {"responder": responder, "content": response_message}
66
- )
67
- if responder == "tool":
68
- st.chat_message("tool", avatar="🛡️").write(response_message)
69
- else:
70
- st.chat_message(responder).write(response_message)
71
-
72
- with st.sidebar:
73
- st.header(f"{APP_ICON} {APP_TITLE}")
74
- st.write("DAPA chatbot for secure reporting of scams.")
75
-
76
- # Privacy Section
77
- with st.expander("🔒 Privacy"):
78
- st.write(
79
- "Query and response in this app are anonymously recorded and saved to LangSmith for product evaluation and improvement purposes."
80
- )
81
-
82
- st.markdown(
83
- "Made with ❤️ by [Raushan](https://www.linkedin.com/in/raushan-in/) in Trier"
84
- )
85
-
86
-
87
- if __name__ == "__main__":
88
- asyncio.run(main())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/main.py DELETED
@@ -1,23 +0,0 @@
1
- import uvicorn
2
- from fastapi import FastAPI
3
-
4
- from database import create_db_and_tables
5
- from routes import bot_router
6
- from settings import settings
7
-
8
-
9
- async def lifespan(app):
10
- await create_db_and_tables()
11
- yield
12
-
13
-
14
- app = FastAPI(title="DAPA", summary="Digital Arrest Protection App", lifespan=lifespan)
15
-
16
- # Endpoint router
17
- app.include_router(bot_router)
18
-
19
-
20
- if __name__ == "__main__":
21
- uvicorn.run(
22
- "main:app", host=settings.HOST, port=settings.PORT, reload=settings.is_dev()
23
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/prompts.py DELETED
@@ -1,56 +0,0 @@
1
- from scams import scam_categories_str
2
-
3
- instructions = f"""
4
- You are an AI bot named DAPA. Your job is to assist users in reporting potential digital financial scams or fraud via mobile communication.
5
- DAPA stands for Digital Arrest Protection App.
6
-
7
- # Follow these instructions strictly:
8
- - Concise Responses: Keep your replies clear and short.
9
- - Only register a scam if the description indicates financial fraud or money involved; otherwise, guide the user to report the issue to appropriate authorities.
10
- - Language Adaptability: Respond in the user’s preferred language but use English for tool inputs.
11
- - Validate Inputs: Collect all required details from the user before using any tool.
12
- - Scammer’s Mobile Number: Must be in +XX-<mobile_number> format.
13
- - Scam Type: Identify Scam Type from reporter’s ordeal. Show the scam name (e.g., "Fake Job Scam") to the user, but pass the corresponding ID (e.g., 9) to the tool.
14
- - Display Scam Name only instead of Scam ID for human user understanding.
15
- - Reporter’s Mobile Number: Must also be in +XX-<mobile_number> format.
16
- - Only respond to cases involving cyber scams that are financial in nature and connected to a mobile number.
17
- - Avoid assisting with unrelated queries (e.g., general protection tips, general knowledge, mathematical, language or programming questions).
18
- - Confirm Before Registering: Always confirm the scammer’s mobile number before registering. Register only if the user explicitly agrees.
19
- - If the user provide 0 as the country code for the scammer's mobile number, even after explicitly being asked, use the reporter's country code as a fallback.
20
- - Prioritize Scammer Search When Only Mobile Number is Provided.
21
- - Before searching for a scammer's mobile number, format it into the standard format with country code (+XX-<mobile_number>).
22
- - Keep responses concise and ask for one piece of information at a time to avoid overwhelming the user.
23
- - Validate that all required details (scammer's number, description, and reporter's number) are provided before attempting to register the report.
24
- - If the country code is not provided, do not make assumptions. Always ask the user to provide.
25
- - In case of a ValueError when using the tool, Correct the parameters or missing value and try again.
26
-
27
- Tool Usage: Use tools only after collecting and validating all inputs.
28
- Pass scam ID to the tool but show scam name to the user for clarity.
29
- Use the Register Scam tool only after explicit user confirmation.
30
- Use the Search Scam tool only if the user requests to check a specific number.
31
-
32
- # Predefined Scam Categories: Only use the following scam types:
33
-
34
- {scam_categories_str}
35
-
36
- # Example Scenarios:
37
-
38
- 1. Reporting a Scam
39
- User: Hi.
40
- DAPA: Hi there! 😊 I’m here to assist you.
41
-
42
- I can help you in two ways:
43
-
44
- 1. **Report a Scam:** Please provide the following details:
45
- - Scammer’s mobile number (with country code).
46
- - A brief description of your experience (up to 50 words).
47
- - Your mobile number.
48
-
49
- 2. **Identify a Suspicious Number:**
50
- Provide the mobile number and type "search". I’ll check if the number has been reported before.
51
-
52
- 2. What is Digital Arrest?
53
- DAPA: A digital arrest scam is an online scam that defrauds victims of their hard-earned money.
54
- The scammers intimidate the victims and falsely accuse them of illegal activities.
55
- They later demand money and puts them under pressure for making the payment.
56
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/routes.py DELETED
@@ -1,32 +0,0 @@
1
- """
2
- Endpoints defination
3
- """
4
-
5
- from fastapi import APIRouter, Depends, HTTPException
6
-
7
- from auth import verify_bearer
8
- from schema import SingleResponse, UserInput
9
- from utils import get_llm_response, infer_chat_message
10
-
11
- bot_router = APIRouter(tags=["bot"], dependencies=[Depends(verify_bearer)])
12
-
13
-
14
- @bot_router.post("/chat")
15
- async def invoke(user_input: UserInput) -> SingleResponse:
16
- """
17
- Invoke an agent with user input to retrieve a final response.
18
-
19
- Use thread_id to persist and continue a multi-turn conversation.
20
- """
21
- try:
22
- response, thread_id = await get_llm_response(user_input)
23
- last_message = infer_chat_message(response["messages"][-1])
24
- response = {
25
- "response_message": last_message.content,
26
- "responder": last_message.type,
27
- "thread_id": thread_id,
28
- }
29
- return SingleResponse(**response)
30
- except Exception as e:
31
- print(e)
32
- raise HTTPException(status_code=500, detail="Unexpected error")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/scams.py DELETED
@@ -1,51 +0,0 @@
1
- scam_categories = {
2
- 1: [
3
- "Fake Authority Call",
4
- "Scammers impersonating law enforcement officials (e.g., CBI, customs, police) or service agents, coercing victims into making payments or money.",
5
- ],
6
- 2: [
7
- "Service Disconnection Scam",
8
- "Threats of service disconnection unless immediate verification or payment is made.",
9
- ],
10
- 3: [
11
- "UPI Scam",
12
- "Scammers claim accidental UPI payments and request refunds, or attempt scams related to UPI, PhonePe, Google Pay, or any quick payment interface.",
13
- ],
14
- 4: ["OTP Scam", "Scammers request OTPs to gain unauthorized access to accounts."],
15
- 5: [
16
- "Fake Buyer/Seller Scam",
17
- "Scammers pose as buyers requesting refunds or as fraudulent sellers asking for advance payments.",
18
- ],
19
- 6: [
20
- "Phishing or Link Scam",
21
- "Fraudulent SMS or calls designed to gain unauthorized access to banking platforms by sending malicious links or asking for sensitive details.",
22
- ],
23
- 7: [
24
- "Video Call Scam",
25
- "Blackmail involving compromising video calls, or screenshots, used to demand money.",
26
- ],
27
- 8: [
28
- "Fake Bank Staff Scam",
29
- "Calls from scammers posing as bank officials, requesting sensitive banking details.",
30
- ],
31
- 9: [
32
- "Fake Job Scam",
33
- "Scammers posing as recruiters, demanding service or registration fees for fake job offers.",
34
- ],
35
- 10: [
36
- "Lottery Scam",
37
- "Messages claiming lottery wins, lucky draws, or prizes, and requesting fees for processing.",
38
- ],
39
- 11: [
40
- "Fake Identity Scam",
41
- "Scammers imitate known individuals and request money transfers.",
42
- ],
43
- 12: [
44
- "Other Cyber Scam",
45
- "Any other scam conducted via phone that involves monetary fraud.",
46
- ],
47
- }
48
-
49
- scam_categories_str = "\n".join(
50
- [f"{k}: {v[0]}-{v[1]}" for k, v in scam_categories.items()]
51
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/schema.py DELETED
@@ -1,83 +0,0 @@
1
- from typing import Any, Literal, NotRequired
2
-
3
- from pydantic import BaseModel, Field
4
- from typing_extensions import TypedDict
5
-
6
-
7
- class UserInput(BaseModel):
8
- """Basic user input for the agent."""
9
-
10
- user_message: str = Field(
11
- description="User message to the AI agent.",
12
- examples=["Hello, I want to report."],
13
- )
14
- thread_id: str | None = Field(
15
- description="Thread ID to persist and continue a multi-turn conversation.",
16
- default=None,
17
- examples=["847c6285-8fc9-4560-a83f-4e628xx09254"],
18
- )
19
-
20
-
21
- class SingleResponse(BaseModel):
22
- """Basic user input for the agent."""
23
-
24
- response_message: str = Field(
25
- description="Response content based on user input.",
26
- examples=["Hello, I am a bot."],
27
- )
28
- responder: Literal["human", "ai", "tool", "custom"] = Field(
29
- description="Generator of response.",
30
- examples=["human", "ai", "tool", "custom"],
31
- )
32
- thread_id: str | None = Field(
33
- description="Thread ID to persist and continue a multi-turn conversation.",
34
- default=None,
35
- examples=["847c6285-8fc9-4560-a83f-4e628xx09254"],
36
- )
37
-
38
-
39
- class ToolCall(TypedDict):
40
- """Represents a request to call a tool."""
41
-
42
- name: str
43
- """The name of the tool to be called."""
44
- args: dict[str, Any]
45
- """The arguments to the tool call."""
46
- id: str | None
47
- """An identifier associated with the tool call."""
48
- type: NotRequired[Literal["tool_call"]]
49
-
50
-
51
- class Chat(BaseModel):
52
- """Message in a chat."""
53
-
54
- type: Literal["human", "ai", "tool", "custom"] = Field(
55
- description="Role of the message.",
56
- examples=["human", "ai", "tool", "custom"],
57
- )
58
- content: str = Field(
59
- description="Content of the message.",
60
- examples=["Hello, world!"],
61
- )
62
- tool_calls: list[ToolCall] = Field(
63
- description="Tool calls in the message.",
64
- default=[],
65
- )
66
- tool_call_id: str | None = Field(
67
- description="Tool call that this message is responding to.",
68
- default=None,
69
- examples=["call_Jja7J89XsjrOLA5r!MEOW!SL"],
70
- )
71
- run_id: str | None = Field(
72
- description="Run ID of the message.",
73
- default=None,
74
- examples=["847c6285-8fc9-4560-a83f-4e6285809254"],
75
- )
76
- response_metadata: dict[str, Any] = Field(
77
- description="Response metadata. For example: response headers, logprobs, token counts.",
78
- default={},
79
- )
80
- custom_data: dict[str, Any] = Field(
81
- description="Custom message data.",
82
- default={},
83
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/settings.py DELETED
@@ -1,66 +0,0 @@
1
- from typing import Any
2
-
3
- from dotenv import find_dotenv
4
- from pydantic import SecretStr, computed_field
5
- from pydantic_settings import BaseSettings, SettingsConfigDict
6
-
7
-
8
- class Settings(BaseSettings):
9
- """
10
- Environment specific configuration
11
- """
12
-
13
- model_config = SettingsConfigDict(
14
- env_file=find_dotenv(),
15
- env_file_encoding="utf-8",
16
- env_ignore_empty=True,
17
- extra="ignore",
18
- validate_default=False,
19
- )
20
- # path
21
- MODE: str | None = None # dev, prod
22
- HOST: str = "0.0.0.0"
23
- PORT: int = 8080
24
-
25
- # Secret keys
26
- AUTH_SECRET: SecretStr | None = None
27
- JWT_ALGORITHM: str = "HS256"
28
-
29
- # LLM API keys
30
- GROQ_API_KEY: SecretStr
31
- GROQ_MODEL: str = "llama3-8b-8192"
32
- GROQ_MODEL_TEMP: float = 0.5
33
-
34
- # Tracing for Langchain
35
- LANGCHAIN_TRACING_V2: bool = (
36
- False # Data will be sent to Langchain for monitering and improvement, If enabled.
37
- )
38
- LANGCHAIN_PROJECT: str = "default"
39
- LANGCHAIN_ENDPOINT: str = "https://api.smith.langchain.com"
40
- LANGCHAIN_API_KEY: SecretStr | None = None # LangSmith API key
41
-
42
- # DB
43
- DATABASE_URL: SecretStr
44
-
45
- def model_post_init(self, __context: Any) -> None:
46
- """
47
- Validate the settings after initialization
48
- """
49
- if self.LANGCHAIN_TRACING_V2 and self.LANGCHAIN_API_KEY is None:
50
- raise ValueError("Tracing is enabled, but LANGCHAIN_API_KEY is missing!")
51
-
52
- if self.GROQ_API_KEY is None:
53
- raise ValueError(
54
- "GROQ_API_KEY is required! This key enables the application to connect with an advanced language model that understands queries and provides intelligent responses. You can generate your API at https://console.groq.com/keys ."
55
- )
56
-
57
- @computed_field
58
- @property
59
- def BASE_URL(self) -> str:
60
- return f"http://{self.HOST}:{self.PORT}"
61
-
62
- def is_dev(self) -> bool:
63
- return self.MODE == "dev"
64
-
65
-
66
- settings = Settings()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/tools.py DELETED
@@ -1,76 +0,0 @@
1
- from langchain_core.tools import tool
2
- from sqlmodel import select
3
-
4
- from database import Scammer, get_session
5
-
6
-
7
- @tool
8
- async def register_scam(
9
- scammer_mobile: str, scam_id: int, reporter_ordeal: str, reporter_mobile: str
10
- ) -> str:
11
- """
12
- Registers a report of a scam incident into the database.
13
-
14
- Parameters:
15
- - scammer_mobile (str): The mobile_number of the alleged scammer.
16
- Must be formatted as "+XX-<mobile_number>", where "+XX" is the country code.
17
- - scam_id (int): The unique identifier for the type of scam.
18
- - reporter_ordeal (str): A summary of the ordeal narrated by the reporter.
19
- Should not exceed 50 words.
20
- - reporter_mobile (str): The mobile_number of the person reporting the scam.
21
- Must be formatted as "+XX-<mobile_number>", where "+XX" is the country code.
22
-
23
- Returns:
24
- str: A confirmation message if the report is registered successfully, or an error
25
- message if an exception occurs during the registration process.
26
- """
27
- try:
28
- scammer = Scammer(
29
- scammer_mobile=scammer_mobile,
30
- scam_id=scam_id,
31
- reporter_ordeal=reporter_ordeal,
32
- reporter_mobile=reporter_mobile,
33
- )
34
- async with get_session() as session:
35
- session.add(scammer)
36
- await session.commit()
37
- return f"{scammer_mobile} has been registered as a scammer. ✅ Thank you for combating scams! 🥇"
38
- except ValueError as e:
39
- print(repr(exc))
40
- return f"ValueError: {repr(exc)}"
41
- except Exception as exc:
42
- print(repr(exc))
43
- return f"An error occurred in registering a report for {scammer_mobile}."
44
-
45
-
46
- register_scam.name = "Register Scam"
47
-
48
-
49
- @tool
50
- async def search_scam(scammer_mobile: str) -> str:
51
- """
52
- Searches the database for scam reports associated with the provided mobile number.
53
-
54
- Parameters:
55
- scammer_mobile (str): The mobile number of the alleged scammer, formatted as "+XX-<mobile_number>",
56
- where "+XX" is the country code.
57
-
58
- Returns:
59
- str: If a scam report is found, returns a string representation of the scam count.
60
- If no scams are found, returns a message indicating that the mobile number is not reported.
61
- If an error occurs during the search process, returns an error message.
62
- """
63
- try:
64
- async with get_session() as session:
65
- statement = select(Scammer).where(Scammer.scammer_mobile == scammer_mobile)
66
- result = await session.exec(statement)
67
- scams = result.all()
68
- if not scams:
69
- return f"{scammer_mobile} has never been reported for scams or fraudulent activity."
70
- return f"{scammer_mobile} has been reported as a scammer {len(scams)} times in the past. 🚨 Be alert! ⚠️"
71
- except Exception as exc:
72
- print(repr(exc))
73
- return f"An error occurred while searching scam for {scammer_mobile}."
74
-
75
-
76
- search_scam.name = "Search Scam"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/utils.py DELETED
@@ -1,79 +0,0 @@
1
- from uuid import uuid4
2
-
3
- from langchain_core.messages import (
4
- AIMessage,
5
- BaseMessage,
6
- ChatMessage,
7
- HumanMessage,
8
- ToolMessage,
9
- )
10
- from langchain_core.runnables import RunnableConfig
11
-
12
- from agent import cyber_guard
13
- from schema import Chat
14
- from settings import settings
15
-
16
-
17
- async def get_llm_response(user_input):
18
- thread_id = user_input.thread_id or str(uuid4())
19
- kwargs = {
20
- "input": {"messages": [HumanMessage(content=user_input.user_message)]},
21
- "config": RunnableConfig(
22
- configurable={"thread_id": thread_id, "model": settings.GROQ_MODEL}
23
- ),
24
- }
25
- response = await cyber_guard.ainvoke(**kwargs)
26
- return response, thread_id
27
-
28
-
29
- def convert_message_content_to_string(content: str | list[str | dict]) -> str:
30
- if isinstance(content, str):
31
- return content
32
- text: list[str] = []
33
- for content_item in content:
34
- if isinstance(content_item, str):
35
- text.append(content_item)
36
- continue
37
- if content_item["type"] == "text":
38
- text.append(content_item["text"])
39
- return "".join(text)
40
-
41
-
42
- def infer_chat_message(message: BaseMessage) -> Chat:
43
- """Create a Chat from a LangChain message."""
44
- match message:
45
- case HumanMessage():
46
- human_message = Chat(
47
- type="human",
48
- content=convert_message_content_to_string(message.content),
49
- )
50
- return human_message
51
- case AIMessage():
52
- ai_message = Chat(
53
- type="ai",
54
- content=convert_message_content_to_string(message.content),
55
- )
56
- if message.tool_calls:
57
- ai_message.tool_calls = message.tool_calls
58
- if message.response_metadata:
59
- ai_message.response_metadata = message.response_metadata
60
- return ai_message
61
- case ToolMessage():
62
- tool_message = Chat(
63
- type="tool",
64
- content=convert_message_content_to_string(message.content),
65
- tool_call_id=message.tool_call_id,
66
- )
67
- return tool_message
68
- case ChatMessage():
69
- if message.role == "custom":
70
- custom_message = Chat(
71
- type="custom",
72
- content="",
73
- custom_data=message.content[0],
74
- )
75
- return custom_message
76
- else:
77
- raise ValueError(f"Unsupported chat message role: {message.role}")
78
- case _:
79
- raise ValueError(f"Unsupported message type: {message.__class__.__name__}")