[KongchangAI]
· 3 min read· 1,995 words

Zero-Dependency WhatsApp AI Agent: A Trilingual Lead Qualification Walkthrough

Zero-Dependency WhatsApp AI Agent: A Trilingual Lead Qualification Walkthrough

A four-file WhatsApp business agent using only the Python standard library, script-first with optional LLM, designed for Indian context.

This project is a self-hostable WhatsApp business agent implemented entirely with the Python standard library and no pip dependencies, consisting of four files: agent.py, llm_provider.py, webhook.py, and demo.html. It is deeply localized for Indian business context: language detection supports English, Hindi, and Hinglish, and lead extraction parses amount expressions like lakh, hazar, and Rs 50k as well as time expressions like kal, parso, and shaam 5 baje. Architecturally, a scripted state machine takes priority, with the LLM as an optional upgrade path; LLM failures degrade silently and do not affect lead extraction. The server simultaneously connects to Meta Webhook, Twilio TwiML, and a JSON chat API, and hosts a demo UI. The project candidly documents limitations such as Meta approval for real numbers, in-memory sessions, and the boundaries of rule-based extraction.

A WhatsApp Business Agent You Can Get Running in One Evening

In markets like India, WhatsApp is no longer just a chat app — it is effectively a business channel. Customers send price inquiries at 11 p.m. and expect an immediate reply. Small merchants typically have three options: hire someone to watch the phone, buy an expensive chatbot platform, or let messages sit until the next morning.

An open-source project on GitHub offers a fourth answer: a self-hosted agent whose code you can read in an afternoon. Written with only the Python standard library, it has no pip dependencies, supports English, Hindi, and Hinglish, and handles lead qualification, FAQ answering, and appointment booking. It can also run entirely locally in rule-based mode without any API key. According to the project owner VisionQuantech, the repository was developed by Shivam Kumar, and the core file agent.py is about 38KB.

Zero-dependency WhatsApp AI agent project overview

A Complete System in Four Files

The architecture is extremely restrained — just four files:

FileRoleKey details
agent.pyConversation enginePer-sender session isolation, language detection, lead and booking extraction, scripted state machine
llm_provider.pyFree LLM clientOpenAI-compatible interface, uses only urllib, supports Groq / Gemini / Mistral / OpenRouter / Ollama
webhook.pyHTTP serverMeta verification and inbound, Twilio TwiML, JSON chat API, hosted demo UI
demo.htmlDemo chat interfaceSingle file, populates the lead panel in real time as the conversation progresses

This split keeps each module's responsibility clear: one brain, one optional intelligence layer, one server, one interface. Let's focus on the design of the first two modules.

Language Detection: Mimic the Customer, Don't Correct Them

detect_language() classifies each message as English, Hindi (Devanagari), or Hinglish, and the agent replies in the detected language.

The implementation uses just two rules: a Devanagari Unicode range regex [\u0900-\u097F] identifies Hindi; a vocabulary list of over 90 Hindi words written in Roman script (namaste, chahiye, kal, baje, haan, kitna, etc.) identifies Hinglish. Hitting two or more of these words — or one in a short message — classifies the message as Hinglish.

This heuristic approach is transparent and debuggable, and for this use case it is more practical than a black-box classifier — when something goes wrong, you can see exactly which rule fired.

What is Hinglish: Hinglish is a mix of Hindi and English, extremely common in Indian cities and online. It typically spells Hindi words in Latin script (rather than Devanagari), for example "Mujhe website chahiye" (I need a website) or "Budget 20000 tak hai" (The budget is up to 20,000). For machine processing, the difficulty of Hinglish is that there is no unified orthography — the same word may have multiple spellings, and Hindi and English components mix in arbitrary proportions within a sentence. This is exactly why the project chose vocabulary-based statistics rather than a machine learning classifier: common business phrases are highly concentrated, and a 90-word seed list covers the vast majority of inquiry patterns.

Why not use a language classifier: A black-box model requires training data, inference dependencies, and extra size. In a small-merchant scenario, if it misclassifies, the developer has no way to determine why. Heuristics based on Unicode range and word-list hits allow every message's decision to be printed and reviewed; modifying rules only requires changing a line in the word list, and maintenance cost is far lower than retraining or tuning model prompts.

Lead Extraction: A Rule Layer That Understands Indian Business Context

The extraction layer is the most grounded part of this project. extract_name() handles "my name is…", "mera naam…", "main…", and bare names, with stop-word protection and trailing verb (hoon/hai) cleanup.

extract_budget() parses the amount expressions Indians actually use: "2 lakh", "20-30 hazar", "₹20000", "Rs 50k", "$500", and bare 4-7 digit numbers, all normalized into a formatted rupee amount. extract_timeline() maps urgent/asap/jaldi/turant to "urgent" and relative week phrases to time buckets. parse_date() and parse_time() resolve kal (tomorrow), aaj (today), parso into concrete dates, and "shaam 5 baje" (5 p.m.) into a concrete time.

The demo flow in the README gives an intuitive feel: Namaste → Mera naam Rahul hai → Mujhe website chahiye → Budget 20000 tak hai → Next week tak chahiye → Haan → kal → shaam 5 baje → haan, with the lead panel filling in as it goes.

Quick reference for Indian number units: In Indian English and Hindi business conversations, amounts often do not use full numbers. lakh (लाख) equals 100,000, crore (करोड़) equals 10 million, and hazar/hazaar/k equals 1,000. So "2 lakh" is 200,000 rupees, "20-30 hazar" is 20,000 to 30,000 rupees, and "Rs 50k" is 50,000 rupees. These units are rarely used outside India, but they are standard in local inquiries; if the regex does not cover them, the entire budget information is lost.

The relativity of time words: In Hindi, kal means both "yesterday" and "tomorrow"; the specific meaning must be inferred from verb tense or context; parso similarly means "the day before yesterday" or "the day after tomorrow." aaj is today, shaam is evening, and baje is "o'clock." The project maps these words to concrete date-time buckets rather than leaving them as vague text, so the booking panel can generate actionable time entries. This also explains why parse_date() and parse_time() must be handled in the same layer as lead extraction.

Session Model: Memory Without a Database

Each sender corresponds to a session dictionary containing the detected language, four lead fields (name, need, budget, timeline), an in-progress booking, the current conversation stage, and message history — capped at the most recent 20 turns to constrain memory usage.

Language is re-detected on every message, but the session default is only overwritten when the message is non-English or it is the first turn. This design prevents a mid-conversation English clarification from flipping the session language of a Hindi user.

The stage machine advances through greeting → qualification → booking → confirmation, but the extractors run opportunistically in parallel: if the user volunteers the budget in the first message, the system does not ask for it again. It is precisely this "listen first, only fill the gaps" strategy that makes the conversation feel human rather than like a form.

Two Reply Modes, One Extraction Pipeline

handle_message() first runs the scripted state machine, and then — only when an LLM is configured and reachable — requests a better reply from the LLM. The core code comment shows this order:

reply, llm_used = self._scripted(s, text)
if not llm_used:
    maybe = self._llm_reply(s, text)
    if maybe:
        reply, llm_used = maybe, True

This order is the most critical decision in the entire architecture. The scripted flow guarantees a correct, on-track reply; the LLM is an upgrade path, not a dependency. And lead extraction is identical in both modes — the numbers in your lead panel never depend on model output.

llm_provider.chat() returns None on failure rather than throwing, so an invalid API key silently degrades to scripted mode without interrupting the conversation.

Separation of responsibilities between the scripted state machine and the LLM: The scripted state machine is an explicit finite state machine, with rule-driven transitions between stages; each stage knows what to ask and which fields have already been captured. Its output is predictable, testable, and runs completely without any external service. The LLM is only responsible for generating more natural, context-appropriate phrasing on top of the scripted reply, without changing the state machine's progression. This design is especially important when APIs are flaky, quotas are exhausted, or networks are restricted — the conversation does not break, it just becomes more mechanical in tone.

What opportunistic extraction means: Unlike "asking item by item in form order," the extractor attempts to pull all four lead fields from every message. If the customer provides both the need and the budget in the first sentence, the state machine skips the corresponding question stages. This requires the extraction functions to be independent and repeatedly callable, and it also means the lead panel's data source is decoupled from the reply content — the two will not become inconsistent because the LLM returned different phrasing.

Server: Connecting to Meta, Twilio, and a Demo API at the Same Time

webhook.py is a server based on the standard library ThreadingHTTPServer, with five routes:

  • GET /webhook: responds to Meta's verification handshake (mode + verify token + challenge echo)
  • POST /webhook: parses Meta's nested entry → changes → messages payload and replies via the Graph API (v19.0); prints [DRY-RUN] logs when credentials are not configured
  • POST /twilio: receives form-encoded Twilio inbound messages and returns XML-escaped TwiML
  • POST /api/chat: exposes the agent as JSON (reply, lead, booking, language, stage, llm)
  • GET /api/status: reports LLM mode and session count

GET / hosts the demo chat UI.

Meta Webhook verification mechanism: When integrating with the WhatsApp Business API, the server must respond to Meta's GET verification request — the request carries hub.mode, hub.verify_token, and hub.challenge; the server confirms that mode is subscribe and the verify token matches the backend configuration, then returns the challenge string as-is. After that, all inbound messages are pushed as POSTs with a payload hierarchy of entry → changes → value → messages, so parsing code must traverse layer by layer. Meta also requires the server to return 200 within seconds, otherwise it retries the push; this is one reason the project uses a lightweight standard library server rather than a heavy framework.

The difference with Twilio TwiML: Twilio's WhatsApp integration uses form-encoded POST requests, and the response body is XML-formatted TwiML (Twilio Markup Language), not JSON. This means the same webhook.py must handle two protocols, two content types, and two response formats. Putting both in the same server lets developers serve both Meta's official channel and Twilio's channel with a single set of conversation logic, and makes it easy to complete end-to-end validation with the Twilio sandbox before Meta approval.

Getting Started

python3 webhook.py
# Open http://localhost:8000/ — chat in the UI and watch the lead panel fill in

No pip install required. If you want to use a free LLM to improve reply quality (optional):

# Groq — free tier, no credit card required
 export LLM_PROVIDER=groq
export LLM_MODEL=openai/gpt-oss-120b
export LLM_API_KEY=your_key
python3 webhook.py

Other options with free tiers include gemini (AI Studio), mistral, openrouter (:free models), and ollama (local, no key needed). Environment variables control the brand name (BUSINESS_NAME/AGENT_NAME, defaulting to VisionQuantech/Arjun), the Meta verification token, and WhatsApp credentials.

Honest Boundaries of Capability

The project README is quite candid about limitations, and they deserve to be listed separately:

  • A real WhatsApp number requires Meta approval. Sandbox test numbers work immediately; production requires a WhatsApp Business Account, an approved display name, Meta review, and per-conversation billing beyond the free tier (roughly 1,000 conversations per month).
  • Rule extraction has boundaries. Regex covers common scenarios and failures are visible, but unusual phrasing will be missed — the LLM mode only improves reply quality, not extraction capability.
  • Sessions are stored in memory. Restart the server and conversation state is gone. Production deployment requires persistent storage.
  • The default verification token must be changed. The default value (visionquantech123) is in the repository, and the README clearly flags this.

Roadmap and Judgment

Planned next directions include: SQLite-persisted sessions, writing to Google Calendar instead of only summarizing bookings, human handoff with conversation summaries, template messages supporting Meta's 24-hour window rule, qualification funnel and drop-off stage analytics, and speech-to-text for WhatsApp voice messages.

This is a rare kind of "AI agent" repository: about 38KB of transparent conversation logic, a fail-safe vendor client, a server that speaks both Meta and Twilio protocols, and honest documentation about Meta approval requirements and free-tier coverage. If your customers live on WhatsApp, this is a credible starting point you can run tonight and extend tomorrow.

#WhatsApp AI agent#zero dependency Python#Hinglish chatbot#lead qualification#WhatsApp Business API#self-hosted agent
Share:

Related articles