[控场AI]
· 11 分钟阅读· 5,786 字

零依赖 WhatsApp AI 智能体:三语线索筛选实战

零依赖 WhatsApp AI 智能体:三语线索筛选实战

一个仅用 Python 标准库、四文件构成的 WhatsApp 商业智能体,脚本优先、LLM 可选,专为印度语境设计。

该项目是一个可自托管的 WhatsApp 商业智能体,全部用 Python 标准库实现,不依赖任何 pip 包,由 agent.py、llm_provider.py、webhook.py 和 demo.html 四个文件组成。它针对印度商业语境做了深度本地化:语言检测支持英语、印地语和 Hinglish,线索提取能解析 lakh、hazar、Rs 50k 等金额表达和 kal、parso、shaam 5 baje 等时间表达。架构上采用脚本状态机优先、LLM 仅作可选升级路径的顺序,LLM 失败时静默降级且不影响线索提取。服务器同时对接 Meta Webhook、Twilio TwiML 和 JSON 聊天 API,并托管演示 UI。项目对真实号码需要 Meta 审批、会话存内存、规则提取有边界等限制做了坦率说明。

一个能在一晚上跑起来的 WhatsApp 商业智能体

在印度等市场,WhatsApp 早已不是聊天工具,而是实际上的商业渠道。客户深夜十一点发来询价,期待立刻得到回复。小商家的选择通常只有三个:雇人盯着手机、购买昂贵的聊天机器人平台、或者让消息一直晾到第二天早上。

GitHub 上的这个开源项目给出了第四种答案:一个一个下午就能读完代码的自托管智能体。它用 Python 标准库写成,不依赖任何 pip 包,支持英语、印地语和 Hinglish 三种语言,能完成线索筛选、FAQ 答疑和预约登记,并且在没有 API Key 的情况下也能纯规则模式本地运行。据项目方 VisionQuantech 介绍,仓库由 Shivam Kumar 开发,核心文件 agent.py 约 38KB。

零依赖 WhatsApp AI 智能体项目概览

四个文件构成完整系统

项目的架构极其克制,只有四个文件:

文件角色关键细节
agent.py对话引擎按发送者隔离会话、语言检测、线索与预约提取、脚本状态机
llm_provider.py免费 LLM 客户端兼容 OpenAI 接口,仅用 urllib,支持 Groq / Gemini / Mistral / OpenRouter / Ollama
webhook.pyHTTP 服务器Meta 验证与入站、Twilio TwiML、JSON 聊天 API、托管演示 UI
demo.html演示聊天界面单文件,对话过程中实时填充线索面板

这种拆分方式让每个模块职责清晰:一个大脑、一个可选的智能层、一个服务器、一个界面。下面重点看前两个模块的设计思路。

语言检测:模仿客户,而不是纠正客户

detect_language() 把每条消息分类为英语、印地语(天城文)或 Hinglish,然后智能体用检测到的语言回复。

实现方式只有两条规则:用天城文 Unicode 范围正则 [\u0900-\u097F] 识别印地语;用一份包含 90 多个罗马字母书写的印地语词汇表(namaste、chahiye、kal、baje、haan、kitna 等)识别 Hinglish。命中两个及以上词汇,或短消息中命中一个,就判定为 Hinglish。

这套启发式方法透明、可调试,对于这个场景,比一个黑盒分类器更实用——出问题时你能直接看到是哪条规则触发的。

Hinglish 是什么:Hinglish 是 Hindi 与 English 的混合语,在印度城市和互联网环境中极为普遍。它通常用拉丁字母(而非天城文)拼写印地语词汇,例如 "Mujhe website chahiye"(我需要一个网站)或 "Budget 20000 tak hai"(预算在 2 万以内)。对机器处理而言,Hinglish 的难点在于没有统一的正字法——同一个词可能有多种拼法,且句子中印地语和英语成分以任意比例混合。这正是项目选择词表统计而非机器学习分类器的现实原因:常见的商业用语高度集中,一份 90 词的种子表就足以覆盖绝大多数询价句式。

为什么不用语言分类器:黑盒模型需要训练数据、推理依赖和额外体积,且在小商家场景下,如果分类错了,开发者无从判断原因。基于 Unicode 范围和词表命中的启发式方法,每条消息的判定过程都可以被打印和复核,修改规则只需改一行词表,维护成本远低于重新训练或调整模型提示词。

线索提取:理解印度商业语境的规则层

提取层是这个项目最接地气的部分。extract_name() 能处理 "my name is…"、"mera naam…"、"main…" 以及裸名字,并带有停用词保护和尾部动词(hoon/hai)清理。

extract_budget() 解析印度人实际使用的金额表达:"2 lakh"、"20-30 hazar"、"₹20000"、"Rs 50k"、"$500",以及裸的 4 到 7 位数字,统一归一化为格式化卢比金额。extract_timeline() 把 urgent/asap/jaldi/turant 映射为"紧急",把周相对短语映射为时间桶。parse_date() 和 parse_time() 则把 kal(明天)、aaj(今天)、parso 解析为具体日期,把 "shaam 5 baje"(傍晚 5 点)解析为具体时间。

README 给出的演示流程可以直观感受:Namaste → Mera naam Rahul hai → Mujhe website chahiye → Budget 20000 tak hai → Next week tak chahiye → Haan → kal → shaam 5 baje → haan,线索面板会同步填充。

印度数字单位速查:印度英语和印地语商业对话中,金额经常不使用完整的数字。lakh(लाख)等于 10 万,crore(करोड़)等于 1000 万,hazar/hazaar/k 等于 1000。因此 "2 lakh" 是 20 万卢比,"20-30 hazar" 是 2 万到 3 万卢比,"Rs 50k" 是 5 万卢比。这些单位在印度之外很少使用,却是本地询价的标准表达,正则若不覆盖就会把整条预算信息漏掉。

时间词的相对性:kal 在印地语中同时表示“昨天”和“明天”,具体含义需要根据动词时态或上下文判断;parso 同理表示“前天”或“后天”。aaj 是今天,shaam 是傍晚,baje 是“点”。项目把这些词映射为具体日期时间桶,而不是保留为模糊文本,这样预约面板才能生成可行动的时间条目。这也解释了为什么 parse_date() 和 parse_time() 必须与线索提取放在同一层处理。

会话模型:没有数据库的记忆

每个发送者对应一个会话字典,里面存着检测到的语言、四个线索字段(姓名、需求、预算、时间线)、进行中的预约、当前对话阶段,以及消息历史——历史上限为最近 20 轮,以约束内存占用。

语言会在每条消息上重新检测,但只有消息非英语或处于首轮时才覆盖会话默认值。这个设计防止对话中途的一句英语澄清把印地语用户的会话语言翻掉。

阶段机按问候 → 资格筛选 → 预约 → 确认推进,但提取器会机会性地并行运行:如果用户在第一条消息里就主动说出预算,系统不会再多问一遍。正是这种"先听、只补缺"的策略,让对话感觉像人而不是表单。

两种回复模式,一条提取管线

handle_message() 先跑脚本状态机,然后——仅在配置了 LLM 且可连通时——向 LLM 请求更好的回复。核心代码注释展示了这个顺序:

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

这个顺序是整个架构最关键的决策。脚本流程保证了一个正确、在轨的回复;LLM 是升级路径,不是依赖项。而线索提取在两种模式下完全一致——你线索面板里的数字永远不依赖模型输出。

llm_provider.chat() 在失败时返回 None 而不是抛异常,因此一个失效的 API Key 会静默降级到脚本模式,而不会打断对话。

脚本状态机与 LLM 的职责分离:脚本状态机是一个显式的有限状态机,阶段之间由规则驱动,每个阶段知道该问什么、已经拿到了哪些字段。它的输出是可预测的、可测试的,并且在没有任何外部服务时也能完整运行。LLM 只负责在脚本回复之上生成更自然、更贴合语境的措辞,而不改变状态机的推进结果。这种设计在 API 抖动、额度耗尽或网络受限时尤其重要——对话不会中断,只是语气变得机械。

机会性提取的含义:与“按表单顺序逐项询问”不同,提取器在每条消息上都会尝试抽取全部四个线索字段。如果客户在第一句话里同时给出了需求和预算,状态机会跳过对应的提问阶段。这要求提取函数彼此独立且可重复调用,也意味着线索面板的数据来源与回复内容解耦,两者不会因为 LLM 返回了不同措辞而出现不一致。

服务器:同时对接 Meta、Twilio 与演示 API

webhook.py 是一个基于标准库 ThreadingHTTPServer 的服务器,共五个路由:

  • GET /webhook:应答 Meta 的验证握手(mode + verify token + challenge 回显)
  • POST /webhook:解析 Meta 嵌套的 entry → changes → messages 载荷,并通过 Graph API(v19.0)回复;未配置凭证时打印 [DRY-RUN] 日志
  • POST /twilio:接收表单编码的 Twilio 入站消息,返回 XML 转义的 TwiML
  • POST /api/chat:以 JSON 暴露智能体(reply、lead、booking、language、stage、llm)
  • GET /api/status:报告 LLM 模式与会话数

GET / 则托管演示聊天 UI。

Meta Webhook 验证机制:WhatsApp Business API 在接入时要求服务端响应 Meta 的 GET 验证请求——请求携带 hub.mode、hub.verify_token 和 hub.challenge 三个参数,服务端需确认 mode 为 subscribe 且 verify token 与后台配置一致,然后原样返回 challenge 字符串。之后所有入站消息以 POST 形式推送,载荷层级为 entry → changes → value → messages,因此解析代码需要逐层遍历。Meta 还要求服务端在数秒内返回 200,否则会重试推送,这也是项目采用轻量标准库服务器而非重型框架的原因之一。

Twilio TwiML 的差异:Twilio 的 WhatsApp 接入使用表单编码的 POST 请求,响应体是 XML 格式的 TwiML(Twilio Markup Language),而非 JSON。这意味着同一个 webhook.py 需要同时处理两种协议、两种内容类型和两种响应格式。把两者放进同一个服务器,使得开发者可以用一套对话逻辑同时服务 Meta 官方通道和 Twilio 通道,也便于在没有 Meta 审批时先用 Twilio 沙盒完成端到端验证。

上手运行

python3 webhook.py
# 打开 http://localhost:8000/ — 在 UI 里聊天,看线索面板填充

不需要任何 pip 安装。若想用免费 LLM 提升回复质量(可选):

# Groq — 免费额度,无需信用卡
 export LLM_PROVIDER=groq
export LLM_MODEL=openai/gpt-oss-120b
export LLM_API_KEY=your_key
python3 webhook.py

其他带免费额度的选项包括 gemini(AI Studio)、mistral、openrouter(:free 模型)和 ollama(本地、无需 Key)。环境变量可控制品牌名(BUSINESS_NAME/AGENT_NAME,默认为 VisionQuantech/Arjun)、Meta 验证令牌和 WhatsApp 凭证。

诚实的能力边界

项目 README 对局限的说明相当坦率,值得单独列出:

  • 真实 WhatsApp 号码需要 Meta 审批。 沙盒测试号可以立即使用;生产环境需要 WhatsApp Business Account、获批的显示名和 Meta 审核,以及超出免费额度后的按对话计费(约每月 1000 条对话)。
  • 规则提取有边界。 正则能覆盖常见场景,且失败可见,但异常措辞会漏——LLM 模式只提升回复质量,不提升提取能力。
  • 会话存在内存中。 重启服务器,对话状态就没了。生产部署需要一个持久化存储。
  • 默认验证令牌必须更换。 默认值(visionquantech123)就在仓库里,README 已明确标注。

路线图与判断

项目规划的后续方向包括:SQLite 持久化会话、写入 Google Calendar 而不是仅总结预约、带对话摘要的人工转接、支持 Meta 24 小时窗口规则的模板消息、资格漏斗与流失阶段分析,以及 WhatsApp 语音消息的语音转文字。

这是一个少见的"AI 智能体"仓库:约 38KB 透明对话逻辑、一个失败安全的供应商客户端、一个同时讲 Meta 和 Twilio 两种协议的服务器,以及关于 Meta 审批要求和免费额度覆盖范围的诚实文档。如果你的客户活在 WhatsApp 上,这是一个今晚就能跑、明天就能扩展的可信起点。

#WhatsApp AI Agent#零依赖聊天机器人#多语言线索筛选#Hinglish 语言检测#WhatsApp Business API#开源客服工具#Python 标准库
分享:

相关推荐