继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

用GPT-4打造 WhatsApp智能助手

回首忆惘然
关注TA
已关注
手记 379
粉丝 73
获赞 413

由 DALL-E 创建

如何使用Meta的API构建自己的由LLM支持的Whatsapp聊天机器人

在人工智能和企业管理领域,一个游戏规则改变者是将人工智能代理集成到广泛使用的通信工具中。想象一下,通过熟悉的聊天界面,您能够实时请求数据、接收更新并实现任务自动化,所有这些都可以通过直接与您的企业的管理AI或个人助理AI在WhatsApp上的实时互动来实现。

在这系列关于创建一个由人工智能驱动的业务经理的第三部分中,我将引导您完成将AI代理连接到WhatsApp的步骤,以增强其功能并扩大其覆盖范围。我们的目标是创建一个能够与所有相关数据库表交互,并且甚至能够自己创建表格并提供所需工具的人工智能助手。作为主要的展示,我将重点介绍如何追踪支出、发票等业务。不过,您也可以轻松地将相同逻辑应用于创建个人助手,以帮助您跟踪任务、项目和想法等。

这是系列的第三部分。在我们开始之前,对于所有等待的读者,我为长时间的延误表示歉意。最近几个月我忙于开始一份新的AI软件工程师的工作,并适应新的工作生活平衡。我已经准备了这篇文章的后续部分,我们将探讨代理工作流的重大变化,以及具有更多额外功能的复杂工作流。在前两篇文章中使用的某些变通方法当时是可靠的工具调用所必需的,但现在由于像GPT-4和GPT-4o-mini这样的性能更好的模型,这些方法已经不再需要了。如果你是工具调用和代理工作流开发的新手,我仍然建议从头两部分开始。我发现从零开始构建某物非常有用,然后再依赖像LangChain或更具体地说是LangGraph这样的框架,以实现高度可定制的代理工作流(我将在不久的将来介绍这些框架)。

目前,我们必须退一步,先专注于基础设施。我认为在大多数项目里,尤其是在AI软件项目中,首先创建一个功能完整的端到端产品,然后再陷入功能蔓延陷阱,这是很好的做法。我经常发现自己过分思考初始设计选择,在我脑海中构想出一个过于复杂的产品。为了克服这一点,专注于在几天的开发时间内构建一个可以运行的完整的端到端产品确实有助于建立一个清晰的基础。之后,你就会知道哪些功能需要优先考虑,并能够收集初步反馈。这会启动一个渐进式的开发过程,这始终是我接手一个项目的初衷。

回顾之前的部分

我们在本系列的早期部分中,奠定了我们基于人工智能的业务经理的基础框架。

  • 第一部分:目标是开发一个能够与工具对象交互并减少底层语言模型(LLM)生成的工具参数中的幻觉的原型代理工作流。
  • 第二部分:我们集中于定义基本功能特性,组织项目仓库,以及使用SQLModel定义数据库模式。我们还添加了用于数据添加和查询的SQL工具,并更新了Tool类以适应SQLModel对象。此外,我们介绍了一个TaskAgent类来实现代理的自动化启动,并设置了一个OpenAIAgent以支持上下文感知工具的应用。
基于本文的讨论范围

还是老规矩,我们先来定义一下这篇文章的范围。

  1. 将AI代理与WhatsApp集成使用MetaAPI
    设置并配置MetaAPI以实现与WhatsApp业务版的集成。
    确保AI代理可以通过WhatsApp进行消息的发送与接收。
  2. 使用Ngrok设置Webhooks并在本地运行
    创建一个FastAPI应用程序来处理来自WhatsApp的webhook事件。
    使用ngrok将本地的FastAPI服务器暴露到互联网上。
    配置MetaAPI将webhook事件发送到ngrok提供的URL

因为我们正要部署到一个服务器上,我们也需要调整我们的项目架构。因此,我们将构建一个FastAPI服务器,因此我更倾向于使用领域驱动设计(DDD)。(你可以在这里查看仓库结构:这里

1. 设置 WhatsApp 云 API:

首先,你需要熟悉Meta提供的云API服务。你也可以通过使用像Twilio这样的SaaS产品来达到同样的效果,这些产品提供了更友好的集成方式。鉴于最近的数据泄露事件以及为了降低成本,我更偏好使用Meta提供的API。

1.1 前提条件
  1. 在开始之前,您需要按照以下步骤注册一个Meta开发者账户:如何注册Meta开发者账户。在注册过程中,您需要使用电话号码进行身份验证。需要注意的是,这不会是您最终的WhatsApp客户端的电话号码。相反,平台会分配给您一个测试电话号码,之后可以更改为您选择的其他电话号码。
  2. 注册后,转到您的仪表板并创建一个应用。
  3. 此外,您需要一个Meta企业账户(MBA),该账户将与您的真实账户关联,或者您可以创建一个新的MBA进行关联。您也可以跳过这一步,因为在下一步中,您将被自动提示链接或创建一个MBA。
1.2 将 WhatsApp 功能添加到您的应用程序

在你创建了一个Meta开发者账户内的应用后,你将被要求添加产品。在这里你需要选择WhatsApp并按照设置流程操作。如果没有这样做,你可以在这里创建一个Meta商业账户。完成之后,你将拥有一个测试的WhatsApp商业账户和一个测试电话号码。

1.3 添加一个收款人号码
  1. 在应用仪表板的左侧菜单中,导航到
    WhatsApp > API 设置
    那里,在 发送和接收消息 下,选择 To 字段并选择 管理电话号码列表。在这里,可以添加允许发送和接收消息的电话号码。理想情况下,应该使用您的电话号码,因为您想测试您的应用程序。在将此 WhatsApp API 账户链接到真实电话号码之前,您只能添加最多 5 个收件人号码。
  2. WhatsApp > API 设置 中,现在可以发送一条测试消息,在 from 字段中填入您的测试电话号码,在 to 字段中填入收件人号码(您的电话号码)。
  3. 生成一个访问令牌。这是您的 WHATSAPP_API_TOKEN,稍后在步骤 6 中会用到。

ngrok 的截图(图源:作者)

我们已经成功设置了所需的云API功能。接下来,我们将创建一个Webhook,以便与我们的AI助手应用进行通信。

为了做到这一点,我们需要在我们的后端应用中创建并提供一个端口。这意味着我们的Python后端必须可以通过一个URL来访问。这个URL将作为Webhook接口,AI助手可以通过它发送和接收数据。

2. 创建一个 FastAPI 接口

为了被Webhook接受并处理,我们的服务器需要响应一个特定的GET请求。在我们添加URL时,Webhook会发送这个特定的GET请求。Webhook会发送三个查询参数:

这些参数包括 hub.mode, hub.challenge, 和 hub.verify.token. 它们用于验证订阅。

验证令牌在通过Cloud API创建webhook时被定义。您的后端应该验证此令牌是否与您定义的一致,并返回响应hub.challenge对象。请首先使用pip install fastapi uvicorn安装FastAPI和Uvicorn。

2.1 创建一个 main.py 文件

创建一个名为 main.py 的文件,文件内容如下所示:

    从 fastapi 导入 FastAPI, Query, HTTPException  # 快速API相关导入

    VERIFICATION_TOKEN = "abcdefg12345"  # 验证token

    app = FastAPI()  # 初始化FastAPI应用

    @app.get("/")  
    def verify_whatsapp(  
        hub_mode: str = Query("subscribe", description="webhook的模式", alias="hub.mode"),  # webhook模式
        hub_challenge: int = Query(..., description="用于验证webhook的挑战", alias="hub.challenge"),  # 验证挑战
        hub_verify_token: str = Query(..., description="验证token", alias="hub.verify_token"),  # 验证token
    ):  
        如果 hub_mode == "subscribe" and hub_verify_token == VERIFICATION_TOKEN:  # 如果hub模式是订阅且验证token正确
            return hub_challenge  # 返回挑战值
        raise HTTPException(status_code=403, detail="无效的验证token")  # 抛出HTTP异常

    @app.get("/health")  
    def health():  
        return {'status': '健康'}  # 返回健康状态

    @app.get("/readiness")  
    def readiness():  
        return {'status': '就绪'}  # 返回就绪状态

在第三行,你可以定义一个 VERIFICATION_TOKEN(验证令牌),之后 webhook 会使用它来验证后端是否由你控制。这里的 VERIFICATION_TOKEN 被定义为 "abcdefg12345",但你可以定义自己的 token。

我会继续修改剩下的部分,很快就会更新下一节了!

2.2 运运行应用。

用 Uvicorn 运行应用:

uvicorn main:app --reload

(启动一个带有自动重新加载功能的Uvicorn服务器)

2.3 在本地运行你的API

你的后端现在本地运行在 http://localhost:8000http://127.0.0.1:8000

我们现在提供以下接口:

  • 验证 WhatsApp 的 webhook: [http://127.0.0.1:8000/?hub.mode=subscribe&hub.challenge=1234&hub.verify_token=abcdefg12345](http://127.0.0.1:8000/?hub.mode=subscribe&hub.challenge=1234&hub.verify_token=abcdefg12345)
  • 健康检查点: <http://127.0.0.1:8000/health>
  • 就绪检查点: <http://127.0.0.1:8000/readiness>

你可以使用健康端点来检查你的应用是否正在运行。在浏览器中打开 http://127.0.0.1:8000/health,你会看到一个JSON格式的响应:“{“status”: “healthy”}”

3. 使用 Ngrok 运行代理服务器

由于我们的服务器是本地运行的,WhatsApp Webhook 无法调用验证端点。我们需要一个公共 URL 来供 webhook 使用,这样 webhook 就可以访问了。有两种选择:将应用程序部署到云服务器或创建一个代理隧道。鉴于我们还在开发阶段,我们将使用第二种选择。

  1. 前往ngrok 注册页面,在那里创建一个免费帐户。
  2. 根据您的系统,您可以使用 Brew 或 Chocolatey,或者直接从网站下载安装包进行安装。参阅:设置与安装
  3. 安装完成后,您可以在终端中使用以下命令添加您的认证代码。请将 $YOUR-AUTHENTICATION_TOKEN 替换成您的 ngrok 认证令牌,您可以在 ngrok 仪表板的“您的认证令牌”部分找到它。
  4. 通过在终端中运行以下命令,开始从本地主机的 8000 端口转发流量:
    > ngrok config add-authtoken $YOUR-AUTHENTICATION_TOKEN  
    > ngrok http http://localhost:8000  

    转发到 https://<随机字符串>.ngrok.io -> http://localhost:8000

你的本地服务器现在可以通过ngrok提供的公共网址访问。你应该看到类似这样的内容:

将: https://<random-string>.ngrok.io 转发为 http://localhost:8000

使用 ngrok 提供的 HTTPS 链接来设置 webhook。

4 实现 webhook

我们现在回到Meta的云API,来实现所需的webhook。

  1. 导航到 Meta for Developers 并选择之前创建的应用。
  2. 在左侧菜单中转到 WhatsApp > 设置
  3. Webhook 部分,将你的 ngrok HTTPS 转发 URL 粘贴入 回调 URL 字段,并将 main.py 中定义的 VERIFICATION_TOKEN 填入 验证令牌 字段。
  4. 点击确认保存按钮,并等待 webhook 验证你的后端。
  5. Webhook 字段 部分,开启 订阅的 messages

这样就行了!你现在应该能在你的Python后端收到来自WhatsApp的消息。

4.1, 理解 Webhooks 和聊天

Webhook 是一种 HTTP 回呼,允许程序在某些事件发生时(例如收到新消息或状态更改时)实时获取更新。Webhook 通过将包含事件数据的 HTTP 请求发送到预先设定的 URL(我们这里使用的是 ngrok 代理服务器的 URL),从而实现系统集成和自动化。

要理解 Meta 中 webhook 的工作原理和费用,先了解一些基本的对话原则会很有帮助。

当用户开始和WhatsApp API进行“对话”时:
1. 用户发送了一条消息:这会打开一个24小时的窗口,在这期间,你可以回复文字、图片或其他媒体的消息,并且无需额外费用。

2. 业务主动联系用户:如果在过去的24小时内没有收到用户消息,您的AI助手必须使用一个预先批准的消息模板来启动对话。你可以添加自定义模板,但需要经过Meta批准。

只要用户继续回复消息,24小时的时间窗口就会随着每条新消息而重置。这样可以实现持续互动,且不会产生额外费用。对话大约花费0.00到0.08美元。具体价格取决于对话类型(营销、实用或服务)和您的位置。请注意,目前服务对话似乎是免费的。您可以在Whatsapp定价页面查看具体价格。

5. 构建接收消息端点

现在我们已经能够在后端接收到消息了。因为我们已经订阅了消息对象,每次有消息发送到你的测试号码时,webhook将会创建一个 POST 请求到你在上一步定义的回调 URL。接下来我们需要在 FastAPI 应用中构建一个 POST 请求的处理端点在我们的 FastAPI 应用中。

我们先来定义需求。

  • 返回 200 HTTP 状态码: 如果不这样做,会使得 CloudAPI 在长达 7 天内重试发送消息,因此这是通知 CloudAPI 消息已成功接收的关键。
  • 提取电话号码和消息: 入站请求的负载包含电话号码和消息,我们需要在后端进行处理。
  • 过滤入站对象: 由于 CloudAPI 可能会为同一条消息发送多个事件(例如发送、接收和阅读),因此后端需要确保只处理一条消息的实例。
  • 处理不同类型的消息: 后端可以处理不同类型的消息,例如文本、语音消息和图片。为了保持本文简洁,我们仅介绍图片处理的基础架构,而不详细展开。
  • 使用 LLM-Agent 工作流进行处理: 提取的信息通过我们在此系列中开发的 LLM-Agent 工作流进行处理。您也可以使用其他代理实现,如 Langchain 或 Langgraph。
5.1 定义模型和架构

我们将通过 webhook 接收有效负载。您可以在 Meta 的文档中找到样例:示例有效负载

我更喜欢用 Pydantic 来为我的 Python 代码添加类型安全性。此外,类型注解和 Pydantic 在 FastAPI 应用中非常匹配。所以,我们先定义一下端点中用到的模型:

    # app/schema.py 定义了各种模型类,用于处理应用程序中的数据结构
    from typing import List, Optional
    from pydantic import BaseModel, Field

    class Profile(BaseModel):
        name: str

    class Contact(BaseModel):
        profile: Profile
        wa_id: str

    class Text(BaseModel):
        body: str

    class Image(BaseModel):
        mime_type: str
        sha256: str
        id: str

    class Audio(BaseModel):
        mime_type: str
        sha256: str
        id: str
        voice: bool

    class Message(BaseModel):
        from_: str = Field(..., alias="from")
        id: str
        timestamp: str
        text: Text | None = None
        image: Image | None = None
        audio: Audio | None = None
        type: str

    class Metadata(BaseModel):
        display_phone_number: str
        phone_number_id: str

    class Value(BaseModel):
        messaging_product: str
        metadata: Metadata
        contacts: List[Contact] | None = None
        messages: List[Message] | None = None

    class Change(BaseModel):
        value: Value
        field: str
        statuses: List[dict] | None = None

    class Entry(BaseModel):
        id: str
        changes: List[Change]

    class Payload(BaseModel):
        object: str
        entry: List[Entry]

    class User(BaseModel):
        id: int
        first_name: str
        last_name: str
        phone: str
        role: str

    class UserMessage(BaseModel):
        user: User
        message: str | None = None
        image: Image | None = None
        audio: Audio | None = None
5.2 解析收到的消息

下面,我们将创建一些辅助函数(helper functions),在FastAPI中用于依赖注入。

    # app/main.py  

    from app.domain import message_service  

    # 解析消息,如果有效载荷中没有消息,则返回None
    def parse_message(payload: Payload) -> Message | None:    
        if not payload.entry[0].changes[0].value.messages:    
            return None    
        return payload.entry[0].changes[0].value.messages[0]    

    # 根据解析的消息来获取当前用户,如果没有消息则返回None
    def get_current_user(message: Annotated[Message, Depends(parse_message)]) -> User | None:    
        if not message:    
            return None    
        return message_service.authenticate_user_by_phone_number(message.from_)    

    # 解析音频文件,如果消息类型是音频,则返回音频对象
    def parse_audio_file(message: Annotated[Message, Depends(parse_message)]) -> Audio | None:    
        if message and message.type == "audio":    
            return message.audio    
        return None    

    # 解析图片文件,如果消息类型是图片,则返回图片对象
    def parse_image_file(message: Annotated[Message, Depends(parse_message)]) -> Image | None:    
        if message and message.type == "image":    
            return message.image    
        return None    

    # 提取消息内容,如果存在音频则转录音频,否则返回消息文本内容
    def message_extractor(    
            message: Annotated[Message, Depends(parse_message)],    
            audio: Annotated[Audio, Depends(parse_audio_file)],    
    ):    
        if audio:    
            return message_service.transcribe_audio(audio)    
        if message and message.text:    
            return message.text.body    
        return None
  • 解析负载: parse_message 函数从传入的负载中提取第一条消息,如果有消息的话。如果找不到任何消息,该函数将返回 None,从而确保只处理有效消息。
  • 用户认证: get_current_user 函数使用 parse_message 依赖注入来提取消息,然后根据消息关联的电话号码验证用户。我们在这里确保只有经过认证的用户才能发送消息。
  • 音频和图片解析: 这些函数根据消息类型分别提取音频或图片文件,如果消息类型是“音频”或“图片”类型。这使得应用程序能够处理不同类型的媒体。
  • 消息提取: message_extractor 函数尝试从消息中提取文本或将音频转录为文本。这确保了无论消息类型如何,内容都可以被处理。

在这里我们有一个来自领域逻辑层的导入。整个message_service脚本是用来存放与这个实现相关的所有特定领域代码的,比如authenticate_user_by_phone_numbertranscribe_audio

5.3 实现 POST 接口
    # app/main.py  
    import threading    
    from typing_extensions import Annotated    
    from fastapi import APIRouter, Query, HTTPException, Depends    
    from app.domain import message_service    
    from app.schema import Payload, Message, Audio, Image, User    

    # ... rest of the code ...  

    @app.post("/", status_code=200)    
    def receive_whatsapp(    
            user: Annotated[User, Depends(get_current_user)],    
            user_message: Annotated[str, Depends(message_extractor)],    
            image: Annotated[Image, Depends(parse_image_file)],    
    ):    
        if not user and not user_message and not image:    
            return {"状态": "成功"}    
        if not user:    
            raise HTTPException(status_code=401, detail="未授权访问")    
        if image:    
            print("图片已接收")    
            return    
        if user_message:    
            thread = threading.Thread(  
                target=message_service.respond_and_send_message,   
                args=(user_message, user)  
            )    
            thread.daemon = True    
            thread.start()    
        return {"状态": "成功"}
  • POST端点的实现: 此端点接收POST请求。它会检查用户、消息及图片的有效性。如果都不符合要求,它会简单地向CloudAPI返回一个状态消息。如果用户未经过身份验证,它会引发一个带有401状态码的HTTPException异常。
  • 处理图片和消息: 对于接收到的图片,我们进行简单的打印输出作为将来处理的占位符。然后会异步处理该消息以避免阻塞主应用程序线程。然后调用message_service.respond_and_send_message函数根据LLM-Agent工作流处理消息。

使用线程池的Webhook解释: WhatsApp会一直重新发送Webhook,直到接收到200响应,因此使用线程池来确保消息处理不会阻塞Webhook回复。

6 消息传递服务

在我们先前定义端点的表示层中,我们使用了一些需要接下来定义的 message_service 函数。具体来说,我们需要实现处理和转录音频数据、用户认证,以及最终调用代理并返回响应的功能。我们将把这些功能放在 domain/message_service.py 文件中。在生产环境中,随着应用程序的增长,我建议将它们进一步拆分为比如 transcription_service.pyauth_service.pymessage_service.py

在本节的多个函数中,我们将向 Meta API "https://graph.facebook.com/..." 发出请求。在所有这些请求中,我们需要包含授权头,使用我们在 步骤 1.3 中创建的 WHATSAPP_API_KEY 作为 bearer token。我通常将 API 密钥和令牌存放在 .env 文件中,并通过 Python 的 dotenv 库来读取它们。我们还会用到你的 OPENAI_API_KEY,可以通过同样的 .env 文件来存取。

不过为了简单点,我们就在message_service.py脚本的开头这样定义和初始化它们:

   import os    
   import json    
   import requests    
   from typing import BinaryIO  

   WHATSAPP_API_KEY = "YOUR_ACCESS_TOKEN"  
   llm = OpenAI(api_key="YOUR_OPENAI_API_KEY")

将“YOUR_ACCESS_TOKEN”替换为你在步骤1.3中创建的实际访问令牌值。

6.1 处理和转录音频文件

处理来自 WhatsApp webhook 的语音记录并不像看起来那样简单。首先,重要的是要知道,传入的 webhook 只告诉我们数据类型和一个对象 ID,因此它不包含二进制音频文件,所以我们需要通过其他方式获取音频文件。首先,我们得利用 Meta 的 Graph API 下载音频文件。为了下载收到的音频,我们需要依次发起两次请求。第一次,我们发送一个带有 object_id 的 GET 请求来获取下载 URL。然后我们会用这个下载 URL 发起第二次 GET 请求。

    def download_file_from_facebook(file_id: str, file_type: str, mime_type: str) -> str | None:    
        # 第一次 GET 请求来获取下载 URL    
        url = f"https://graph.facebook.com/v19.0/{file_id}"    
        headers = {"Authorization": f"Bearer {WHATSAPP_API_KEY}"}    
        response = requests.get(url, headers=headers)  
        if response.status_code == 200:    
                download_url = response.json().get('url')    
                # 第二次 GET 请求来下载文件    
                response = requests.get(download_url, headers=headers)    
                if response.status_code == 200:  
                    # 从 mime_type 中提取文件扩展    
                    file_extension = mime_type.split('/')[-1].split(';')[0]  
                    # 创建带有扩展名的 file_path  
                    file_path = f"{file_id}.{file_extension}"    
                    with open(file_path, 'wb') as file:    
                        file.write(response.content)    
                    if file_type == "image" or file_type == "audio":    
                        return file_path    
                raise ValueError(f"无法下载文件。状态码:{response.status_code}");    
            raise ValueError(f"无法获取下载 URL。状态码:{response.status_code}");

在这里,我们主要通过对象ID和文件扩展名获取下载URL并将文件下载到文件系统中。如果出现问题,我们会抛出一个ValueError,表明错误发生在哪个部分。

接下来,我们只需定义一个函数来使用名为Whisper的工具转录音频二进制。

    def transcribe_audio_file(audio_file: BinaryIO) -> str:    
        if not audio_file:    
            return "未提供任何音频文件"    
        try:    
            转录 = llm.audio.transcriptions.create(    
                file=audio_file,    
                model="whisper-1",    
                response_format="text"    
            )    
            return 转录    
        except Exception as e:    
            raise ValueError("音频转录过程中出错") from e

最后一步,让我们将下载和转录部分结合起来:

def 转写音频(audio: Audio) -> str:    
    file_path = 下载脸书文件(audio.id, "audio", audio.mime_type)    
    with open(file_path, 'rb') as audio_binary:    
        转写 = 转写音频文件(audio_binary)    
    try:    
        os.remove(file_path)    
    except Exception as e:    
        print(f"无法删除文件: {e}")    
    return 转录
6.2 用户验证

在使用Meta提供的测试电话号码时,我们必须预先定义我们的聊天机器人可以向哪些号码发送消息。我不太确定并且尚未测试过,是否任何号码都无法向我们的聊天机器人发送消息。不过无论如何,一旦我们切换到自定义号码,我们就不希望任何人能直接控制我们的代理聊天机器人。因此我们需要一个方法来验证用户身份。首先,我们需要考虑将用户信息存储在哪里。我们有几个选择来实现这一点。我们可以使用PostgreSQL之类的数据库,或者使用Firestore之类的非关系型数据库。我们也可以在文件系统中的一个JSON文件或.env文件中预先设定用户。在这个教程中,我选择最简单的方法,直接在认证函数中的列表里硬编码用户信息。

列表项的结构遵循步骤 5.1中定义的 User 模型。因此,一个用户包括 ID、名字、姓氏和电话号码。我们目前还没有在代理工作流程中实现角色系统。但在大多数涉及不同用户的情况中,例如在小型企业助手的例子中,不同的用户会有不同的权限和访问权限。目前,我们只是将 "default" 作为占位符角色。

    def 通过手机号验证用户身份(电话号码: str) -> User | None:    
        允许的用户列表 = [    
            {"id": 1, "电话": "+1234567890", "first_name": "John", "last_name": "Doe", "role": "default"},    
            {"id": 2, "电话": "+0987654321", "first_name": "Jane", "last_name": "Smith", "role": "default"}    
        ]      
        for 用户 in 允许的用户列表:    
            if 用户["电话"] == 电话号码:    
                return User(**用户)    
        return None

所以只需验证手机号码是否在我们的allowed_users列表中,如果在列表中,则返回该用户。否则返回None。查看我们在步骤5.3中的端点,会发现用户为None时,我们就会抛出一个错误,以防止未经授权的消息进一步处理。

6.3 发送消息.

在我们实际调用代理之前,最后一个帮助函数是发送WhatsApp消息的函数。因为WhatsApp API有一些特定于Meta的逻辑,我在该函数中加入了两种模式。

基本上,你不可以发送自定义消息来开始和用户的对话。这意味着如果用户先向聊天机器人发消息开始对话,你可以用单独的一条文本消息回复。如果你希望聊天机器人开始对话的话,你只能用经过批准的模板,比如“你好,世界”模板。

也值得一提的是,当谈到元对话时,一旦对话开始,会有一个24小时的对话时间,在此期间你可以给对方发送消息。计费的是这24小时的对话时间,而不是每条单独的消息。根据对话类型,比如营销或支持,计费方式会更加复杂。

你也可以在Meta获得批准一个模板。我目前还没这样做过,所以为了测试能否从我们的后端给用户发送消息,我用了“Hello World”模板。如果你有自定义的已批准模板,你也可以用这个功能把这些模板发给用户。

所以,要发送消息,我们发送一个POST请求,并定义载荷内容,里面包含文本正文或模板。

    def send_whatsapp_message(to, message, template=True):    
        url = f"https://graph.facebook.com/v18.0/289534840903017/messages"    
        headers = {    
            "Authorization": f"Bearer " + WHATSAPP_API_KEY,    
            "Content-Type": "application/json"    
        }    
        if not template:    
            data = {    
                "messaging_product": "whatsapp",    
                "preview_url": False,    
                "recipient_type": "individual",    
                "to": to,    
                "type": "text",    
                "text": {    
                    "body": message    
                }    
            }    
        else:    
            data = {    
                "messaging_product": "whatsapp",    
                "to": to,    
                "type": "template",    
                "template": {    
                    "name": "hello_world",    
                    "language": {    
                        "code": "en_US"    
                    }    
                }    
            }    

        response = requests.post(url, headers=headers, data=json.dumps(data))    
        # 返回响应的JSON数据
        return response.json()
6.4 请求我们的代理

最后,我们可以集成我们之前示例中的智能代理。在这个阶段,你也可以集成自己的自定义代理,例如 Langchain 的 AgentExecutor,或者 Langgraph 的 AgentWorkflow 等等。

所以我们将在每条传入的消息上调用的主要函数是“该函数respond_and_send_message”,它接收user_message字符串并将其作为输入传递给我们的代理工作流。

    # app/domain/message_service.py  
    import json    
    import requests  
    from app.domain.agents.routing_agent import RoutingAgent    
    from app.schema import User    

    # 类型: 函数 # 功能: 根据用户消息和用户信息回复并发送消息
    def 响应并发送消息(user_message: str, user: User):    
        agent = RoutingAgent()    
        response = agent.run(用户消息, 用户.ID)    
        send_whatsapp_message(用户.phone, response, template=False)

在调用我们的代理之后,我们得到一个想要回复的消息,然后使用send_whatsapp_message函数发送给用户。

现在你应该能够向测试号码发送消息,并获得回复。备注:在使用WhatsApp测试号码时,你需要在你的Meta API应用中注册被允许向你的机器人发送消息的手机号码。

通过按照本指南,你已经朝着创建一个强大的 LLM 聊天机器人,与 WhatsApp 无缝对接,迈出了重要一步。这不仅是为了实现实时商业自动化通信,更是为了未来构建更高级的人工智能驱动的流程打下坚实基础。

我们做了哪些事情:
我们做了什么: 修正后的版本直接采用更自然的表达“我们做了哪些事情:”,同时保持了冒号的使用,以保持与原文的一致性。
  • WhatsApp 云 API 集成:
    我们已成功地将 Meta 的云 API 集成到 WhatsApp 中,并为 WhatsApp 构建了 webhook 来处理实时消息。
  • FastAPI 后端开发:
    搭建了用于验证 webhook、处理传入消息及异步响应的端点。
  • 多媒体及身份验证支持:
    还启用了音频转录、用户身份验证等特性,以实现更加个性化的互动体验。虽然图片处理功能尚未完全实现,但我们已经为图片处理预留了接口。
接下来会是什么?

在接下来的部分里,我承诺会尽快推出 🙏,我会将实现迁移至LangGraph。我将让代理能够自动创建数据库表等,并添加更多工具等。这会让代理更灵活。也欢迎大家提反馈和建议,告诉我你们觉得还应该加什么功能!

最后来总结一下:

将 WhatsApp 的覆盖面和易用性与大型语言模型结合起来对企业和个人用例来说都是一项重大胜利。无论你是为了个人助手还是全面的商业工具,本指南都为你提供了实现目标的路线。不断打磨、改进,并突破界限——这只是你能构建的内容的开始。

祝你编程快乐!🚀

你可以在这里找到全部代码:Github项目

链接:https://github.com/elokus/WhatsappAgent

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP