”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > 如何用 Python 行构建无服务器 AI 聊天机器人

如何用 Python 行构建无服务器 AI 聊天机器人

发布于2024-11-02
浏览:620

How to build a serverless AI chatbot in < lines of Python

?想要用

本指南向您展示如何使用 DBOS 和 LangChain 构建一个由 LLM 驱动的交互式聊天机器人,并将其无服务器部署到 DBOS Cloud。

您可以在这里看到聊天机器人。

除了聊天之外,该机器人还会显示您的请求所消耗的 CPU 时间和挂钟时间。
当您聊天时,您很快就会注意到,虽然您的请求可能需要很长时间,但它们消耗的 CPU 很少。
那是因为他们大部分时间都在闲置等待 LLM 的回复。
这一差距解释了为什么 DBOS 的 AI 工作负载成本效益比其他无服务器平台高 50 倍,因为 DBOS 仅按您实际消耗的 CPU 时间计费,而其他平台则按总请求持续时间计费。

所有源代码均可在 GitHub 上获取。

导入并初始化应用程序

让我们从导入和初始化 DBOS 开始。
我们还将设置 FastAPI 来服务 HTTP 请求。

import os
import threading
import time
from collections import deque

import psutil
from dbos import DBOS
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from langchain_core.messages import HumanMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_openai import ChatOpenAI
from langgraph.checkpoint.postgres import PostgresSaver
from langgraph.graph import START, MessagesState, StateGraph
from psycopg_pool import ConnectionPool
from pydantic import BaseModel

from .schema import chat_history

app = FastAPI()
dbos = DBOS(fastapi=app)

设置LangChain

接下来,让我们设置Langchain。
我们将使用 Langchain 使用 OpenAI 的 gpt-3.5-turbo 模型来回复每条聊天消息。
我们将配置 LangChain 将消息历史记录存储在 Postgres 中,以便它在应用程序重新启动后仍然存在。

为了好玩,我们还可以指示我们的聊天机器人像海盗一样说话。

def create_langchain():
    # We use gpt-3.5-turbo as our model.
    model = ChatOpenAI(model="gpt-3.5-turbo")

    # This prompt instructs the model how to act. We'll tell it to talk like a pirate!
    prompt = ChatPromptTemplate.from_messages(
        [
            (
                "system",
                "You talk like a pirate. Answer all questions to the best of your ability.",
            ),
            MessagesPlaceholder(variable_name="messages"),
        ]
    )

    # This function tells LangChain to invoke our model with our prompt.
    def call_model(state: MessagesState):
        chain = prompt | model
        response = chain.invoke(state)
        return {"messages": response}

    # Create a checkpointer LangChain can use to store message history in Postgres.
    db = DBOS.config["database"]
    connection_string = f"postgresql://{db['username']}:{db['password']}@{db['hostname']}:{db['port']}/{db['app_db_name']}"
    pool = ConnectionPool(connection_string)
    checkpointer = PostgresSaver(pool)

    # Finally, construct and return the graph LangChain uses to respond to each message.
    # This chatbot uses a simple one-node graph that just calls the model.
    graph = StateGraph(state_schema=MessagesState)
    graph.add_node("model", call_model)
    graph.add_edge(START, "model")
    return graph.compile(checkpointer=checkpointer)


chain = create_langchain()

处理聊天

现在我们来聊聊吧!
我们将首先编写处理每个聊天请求的端点。

此端点是一个 DBOS 工作流程,包含三个步骤:

  1. 将传入的聊天消息存储在 Postgres 中。
  2. 使用LangChain查询LLM回复聊天消息。
  3. 将响应存储在 Postgres 中。

它还将每个请求的总持续时间记录在内存缓冲区中。

class ChatSchema(BaseModel):
    message: str
    username: str


@app.post("/chat")
@DBOS.workflow()
def chat_workflow(chat: ChatSchema):
    start_time = time.time()
    insert_chat(chat.username, chat.message, True)
    response = query_model(chat.message, chat.username)
    insert_chat(chat.username, response, False)
    elapsed_time = time.time() - start_time
    wallclock_times_buffer.append((time.time(), elapsed_time))
    return {"content": response, "isUser": True}

接下来,我们来编写实际查询LangChain以获取每条新消息的函数。
它使用您的用户名作为线程 ID,因此不同的用户可以有不同的对话线程。

我们用 @DBOS.step() 注释此函数,将其标记为聊天工作流程中的一个步骤。

@DBOS.step()
def query_model(message: str, username: str) -> str:
    config = {"configurable": {"thread_id": username}}
    input_messages = [HumanMessage(message)]
    output = chain.invoke({"messages": input_messages}, config)
    return output["messages"][-1].content

我们还需要一个历史记录端点,用于从数据库中检索特定用户的所有过去的聊天记录。

当我们启动聊天机器人时会调用此函数,以便它可以显示您的聊天历史记录。

@app.get("/history/{username}")
def history_endpoint(username: str):
    return get_chats(username)

然后,让我们使用 SQLAlchemy 编写将聊天记录写入数据库和从数据库读取聊天记录的函数。
我们用@DBOS.transaction()注释这些函数来访问DBOS的托管数据库连接。

@DBOS.transaction()
def insert_chat(username: str, content: str, is_user: bool):
    DBOS.sql_session.execute(
        chat_history.insert().values(
            username=username, content=content, is_user=is_user
        )
    )


@DBOS.transaction()
def get_chats(username: str):
    stmt = (
        chat_history.select()
        .where(chat_history.c.username == username)
        .order_by(chat_history.c.created_at.asc())
    )
    result = DBOS.sql_session.execute(stmt)
    return [{"content": row.content, "isUser": row.is_user} for row in result]

此外,让我们使用 FastAPI 从 HTML 文件提供应用程序的前端。
在生产中,我们建议主要将 DBOS 用于后端,并将前端部署在其他地方。

@app.get("/")
def frontend():
    with open(os.path.join("html", "app.html")) as file:
        html = file.read()
    return HTMLResponse(html)

跟踪应用程序使用情况

最后,让我们编写一些代码来跟踪您的请求消耗的 CPU 时间和挂钟时间,以便我们可以在应用程序的 UI 中显示这些指标。
此代码在后台线程中每秒运行一次。

我们使用 psutil 跟踪该进程的 CPU 消耗。
我们通过记录每个请求的端到端持续时间来跟踪挂钟时间。

当您第一次启动应用程序时,您会注意到 HTTP 服务器有一些少量的剩余 CPU 消耗。
然而,当您开始聊天时,您很快就会发现每次聊天仅消耗约 10 毫秒的 CPU 时间,但消耗 1-2 秒的挂钟时间。
这一差距解释了为什么 DBOS 的 AI 工作负载比其他无服务器平台便宜 50 倍,因为 DBOS 仅按您实际消耗的 CPU 时间计费,而其他平台按总请求持续时间计费。

last_cpu_time_ms = 0
cpu_times_buffer = deque()
wallclock_times_buffer = deque()


def update_cpu_usage():
    while True:
        time.sleep(1)
        global last_cpu_time_ms
        # Every second, record CPU time consumed by this process
        # in the last second.
        process = psutil.Process()
        cpu_times = process.cpu_times()
        cpu_time = cpu_times.system   cpu_times.user
        time_consumed = cpu_time - last_cpu_time_ms
        if last_cpu_time_ms > 0:
            cpu_times_buffer.append((time.time(), time_consumed))
        last_cpu_time_ms = cpu_time
        # We only track usage in the last minute, so
        # pop measurements more than 60 seconds old.
        for buf in [cpu_times_buffer, wallclock_times_buffer]:
            while buf and time.time() - buf[0][0] > 60:
                buf.popleft()


threading.Thread(target=update_cpu_usage).start()


@app.get("/times")
def times_endpoint():
    return {
        "cpu_time": sum([t for _, t in cpu_times_buffer]),
        "wall_clock_time": sum([t for _, t in wallclock_times_buffer]),
    }

自己尝试一下!

创建 OpenAI 帐户

要运行此应用程序,您需要一个 OpenAI 开发者帐户。
在此处获取 API 密钥并在此处为您的帐户设置付款方式。
该机器人使用 gpt-3.5-turbo 进行文本生成。
确保您有一些积分 (~&dollar;1) 来使用它。

将您的 API 密钥设置为环境变量:

export OPENAI_API_KEY=

部署到云端

要将此应用程序部署到 DBOS Cloud,请首先安装 DBOS Cloud CLI(需要 Node):

npm i -g @dbos-inc/dbos-cloud

然后克隆 dbos-demo-apps 存储库并部署:

git clone https://github.com/dbos-inc/dbos-demo-apps.git
cd python/chatbot
dbos-cloud app deploy

此命令输出一个 URL — 访问它即可查看您的聊天机器人!
您还可以访问DBOS云控制台查看应用程序的状态和日志。

本地运行

首先,克隆并进入dbos-demo-apps存储库:

git clone https://github.com/dbos-inc/dbos-demo-apps.git
cd python/chatbot

然后创建虚拟环境:

python3 -m venv .venv
source .venv/bin/activate

DBOS 需要 Postgres 数据库。
如果您还没有,您可以使用 Docker 启动一个:

export PGPASSWORD=dbos
python3 start_postgres_docker.py

然后在虚拟环境中运行应用程序:

pip install -r requirements.txt
dbos migrate
dbos start

访问 http://localhost:8000 查看您的聊天机器人!

下一步

了解 DBOS 如何使您的应用程序更具可扩展性和弹性:

  • 使用持久执行来编写防崩溃工作流程。
  • 使用队列优雅地管理 API 速率限制。
  • 使用计划的工作流程定期运行您的函数。
  • 想了解可以使用 DBOS 构建什么?探索其他示例应用程序。
版本声明 本文转载于:https://dev.to/dbos/how-to-build-a-serverless-ai-chatbot-in-100-lines-of-python-2m2?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • 机器学习中的 C++:逃离 Python 和 GIL
    机器学习中的 C++:逃离 Python 和 GIL
    介绍 当 Python 的全局解释器锁 (GIL) 成为需要高并发或原始性能的机器学习应用程序的瓶颈时,C 提供了一个引人注目的替代方案。这篇博文探讨了如何利用 C 语言进行 ML,重点关注性能、并发性以及与 Python 的集成。 阅读完整的博客! ...
    编程 发布于2024-11-08
  • 如何在 PHP 中将 UTF-8 字符转换为 ISO-8859-1 并返回?
    如何在 PHP 中将 UTF-8 字符转换为 ISO-8859-1 并返回?
    将 UTF-8 字符转换为 ISO-88591 并返回 PHP当使用使用不同编码的多个脚本时,需要在字符集之间进行转换。其中一种转换涉及将 UTF-8 字符转换为 ISO-88591,反之亦然。尽管存在 utf_encode() 和 _decode(),但将 UTF-8 直接转换为 ISO-8859...
    编程 发布于2024-11-08
  • 以下是一些标题选项,使用问题格式,重点关注文章中提出的挑战和解决方案:

选项 1(直接且简洁):
* 如何避免 Angul 中的模板标签冲突
    以下是一些标题选项,使用问题格式,重点关注文章中提出的挑战和解决方案: 选项 1(直接且简洁): * 如何避免 Angul 中的模板标签冲突
    为 AngularJS 和 Django 定制模板标签由于模板标签冲突,将 AngularJS 与 Django 集成可能会带来挑战,两者都使用{{}}。为了克服这个问题,需要调整 AngularJS 或 Django 的模板标签语法。AngularJS 模板标签定制:在 AngularJS 1.0...
    编程 发布于2024-11-08
  • 每个开发人员都应该了解的高级 JavaScript 概念
    每个开发人员都应该了解的高级 JavaScript 概念
    JavaScript 是许多开发人员日常使用的语言,但其生态系统中存在许多隐藏的瑰宝,即使是经验丰富的开发人员也可能不熟悉。本文探讨了一些鲜为人知的 JavaScript 概念,它们可以显着提高您的编程技能。我们将介绍 代理、符号、生成器等概念,通过示例演示每个概念并解决问题以说明其强大功能。 最...
    编程 发布于2024-11-08
  • 直接用mysqli_函数替换mysql_函数会带来挑战吗?
    直接用mysqli_函数替换mysql_函数会带来挑战吗?
    盲目用 mysqli_ 替换 mysql_ 函数会导致问题吗?将代码库更新到 PHP 7 需要将已弃用的 mysql_ 函数替换为 mysqli_ 对应函数。然而,一个常见的误解是您可以直接进行全面替换。答案:不,事情没那么简单虽然函数名称可能会出现类似地,与 mysql_ 相比,mysqli_ 函...
    编程 发布于2024-11-08
  • 为什么 `malloc()` 在 C++ 中会导致“无效转换”错误?
    为什么 `malloc()` 在 C++ 中会导致“无效转换”错误?
    Malloc 分配问题:了解“无效转换”错误提供的代码在尝试使用 malloc 分配内存时引入了一个常见问题( )。该错误源于将 malloc() 的返回值直接分配给 char 指针而没有进行正确的转换。malloc() 函数在堆中保留一块内存并返回一个通用的 void 指针。但是,代码将此指针分配...
    编程 发布于2024-11-08
  • 如何在 Zend Framework 中确定客户端的时区?
    如何在 Zend Framework 中确定客户端的时区?
    客户端时区确定确定客户端时区对于时间敏感的应用程序至关重要。这个问题探讨了如何在 Zend Framework 中获取此信息。以秒偏移量形式检索时区获取时区的首选方法是作为距 UTC 的秒数。例如,俄罗斯莫斯科将返回 36060,而英国伦敦将返回 0。建议的解决方案建议的解决方案涉及利用jQuery...
    编程 发布于2024-11-08
  • 如何使用 React 构建通知功能
    如何使用 React 构建通知功能
    Hello everyone ?? In today's tutorial, we'll guide you through building a real-time notifications feature using SuperViz, a powerful platform for rea...
    编程 发布于2024-11-08
  • 了解命令式编程和声明式编程之间的区别
    了解命令式编程和声明式编程之间的区别
    当我刚开始学习React时,我的老师说:“JavaScript是命令式编程,而React是声明式编程。”然而,一开始这对我来说不太有意义。因此,我决定将其分解以更好地理解其中的区别。 将命令式和声明式编程与披萨进行比较? 为了更容易理解,让我们比较一下这两种烹饪方法。 ...
    编程 发布于2024-11-08
  • 如何使用 JPA 和 Hibernate 以 UTC 格式存储日期/时间和时间戳?
    如何使用 JPA 和 Hibernate 以 UTC 格式存储日期/时间和时间戳?
    使用 JPA 和 Hibernate 以 UTC 格式存储日期/时间和时间戳在 Java Persistence API (JPA) 和 Hibernate 中,管理日期/时间不同时区的时间戳值可能是一个挑战。为了确保 UTC(协调世界时)时间的一致存储和检索,正确配置框架至关重要。考虑提供的带注释...
    编程 发布于2024-11-08
  • java.lang.RuntimeException 和 java.lang.Exception 之间的主要区别是什么?
    java.lang.RuntimeException 和 java.lang.Exception 之间的主要区别是什么?
    揭示 java.lang.RuntimeException 和 java.lang.Exception 的独特本质在 Java 异常领域内,两个经常遇到的异常类出现:java.lang.RuntimeException 和 java.lang.Exception。为了有效地理解异常处理的复杂性,剖析...
    编程 发布于2024-11-08
  • 为什么嵌入框阴影在透明背景的图像上消失?
    为什么嵌入框阴影在透明背景的图像上消失?
    了解图像上的插入框阴影问题在网页设计中,使用插入框阴影在元素内创建深度和尺寸是一种常见技术。然而,在处理包含图像的容器时,事情并不总是那么简单。当嵌入框阴影似乎在嵌入图像上消失时,就会出现问题。隐形阴影的情况考虑原始问题中提供的示例:body { background-color: #00000...
    编程 发布于2024-11-08
  • 如何在 ReactJS 中维护悬停状态:解决事件注册问题
    如何在 ReactJS 中维护悬停状态:解决事件注册问题
    在 ReactJS 中维护悬停状态:解决事件注册问题使用内联样式时,您会遇到 ReactJS 中悬停和活动事件的问题,因为 onMouseEnter 和 onMouseLeave 方法被证明是不可靠的。要解决此问题,请考虑利用这些事件之一处理程序:onMouseDownonMouseEnteronM...
    编程 发布于2024-11-08
  • 如何在 JavaScript 中准确检查 Null 值和空字符串?
    如何在 JavaScript 中准确检查 Null 值和空字符串?
    检查 JavaScript 中的 Null 值在 JavaScript 中,确定值是否为 null 有时会令人困惑。为了提供更深入的理解,本文将深入研究在 JavaScript 上下文中检测空值的细节。检查空值提供的代码片段旨在检查跨多个变量的 null 值:if (pass == null || ...
    编程 发布于2024-11-08
  • PHP 4 快速部署
    PHP 4 快速部署
    Servbay 已成为高效配置开发环境的领先工具。在本指南中,我们将引导您完成快速、安全地部署 PHP 8.1 的过程,展示 Servbay 对简化部署的承诺。 先决条件 确保您的计算机上安装了 Servbay。您可以从 Servbay 官方网站轻松下载。安装过程人性化;只需按照安装...
    编程 发布于2024-11-08

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3