」工欲善其事,必先利其器。「—孔子《論語.錄靈公》
首頁 > 程式設計 > 如何用 Python 行建構無伺服器 AI 聊天機器人

如何用 Python 行建構無伺服器 AI 聊天機器人

發佈於2024-11-02
瀏覽:164

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如有侵犯,請洽study_golang@163 .com刪除
最新教學 更多>
  • 為什麼應該避免在 JSX Props 中使用箭頭函數或綁定?
    為什麼應該避免在 JSX Props 中使用箭頭函數或綁定?
    為什麼在JSX Props 中使用箭頭函數或Bind 是禁忌使用React 時,避免使用箭頭函數或Bind 非常重要在JSX屬性中綁定。這種做法可能會導致效能問題和不正確的行為。 效能問題在 JSX props 中使用箭頭函數或綁定會強制在每次渲染時重新建立這些函數。這意味著:舊函數被丟棄,觸發垃圾...
    程式設計 發佈於2024-11-08
  • 自動模式的 CSS 主題選擇器 [教學]
    自動模式的 CSS 主題選擇器 [教學]
    This tutorial shows you how to create a theme selector in Svelte, enabling multiple theme options for your website. It also includes an automatic them...
    程式設計 發佈於2024-11-08
  • 了解 Java 中的靜態實用方法
    了解 Java 中的靜態實用方法
    在现代软件开发中,非常重视干净、可重用和有效的编码。 Java 中的一项功能对实现这一目标大有帮助,称为静态实用方法。本文将探讨什么是静态实用方法、它们的好处、常见用例以及有效实现这些方法的最佳实践。 什么是静态实用方法? 静态实用方法是属于类的方法,而不是属于类的实例。这些方法是使...
    程式設計 發佈於2024-11-08
  • ## 如何在 JavaScript 中限制函數執行:自訂解決方案與函式庫解決方案
    ## 如何在 JavaScript 中限制函數執行:自訂解決方案與函式庫解決方案
    透過自訂實作實作 JavaScript 中的簡單節流使用 JavaScript 時,控制函數執行速率至關重要。節流函數限制函數呼叫的頻率,防止繁重的處理或重複的使用者操作。 在這篇文章中,我們提出了一個簡單的自訂節流函數來實現此目的,而不依賴 Lodash 或 Underscore 等外部函式庫。 ...
    程式設計 發佈於2024-11-08
  • 了解 WebSocket:React 開發人員綜合指南
    了解 WebSocket:React 開發人員綜合指南
    Understanding WebSockets: A Comprehensive Guide for React Developers In today’s world of modern web applications, real-time communication is ...
    程式設計 發佈於2024-11-08
  • 如何在 macOS 上安裝並啟用 Imagick for PHP
    如何在 macOS 上安裝並啟用 Imagick for PHP
    如果您在 macOS 上工作並且需要安裝 Imagick for PHP 8.3,則可能會遇到預設安裝較舊版本 PHP(例如 PHP 8.0)的問題。在這篇文章中,我將引導您完成確保 Imagick 已安裝並針對 PHP 8.3 正確配置的步驟。 步驟 1:透過 Homebrew ...
    程式設計 發佈於2024-11-08
  • 如何使用 JavaScript 為物件陣列新增附加屬性?
    如何使用 JavaScript 為物件陣列新增附加屬性?
    擴展具有附加屬性的物件陣列程式設計中普遍存在的任務涉及使用附加屬性來增強現有物件陣列。為了說明這個概念,請考慮包含兩個元素的物件陣列:Object {Results:Array[2]} Results:Array[2] [0-1] 0:Object id=1 name: &quo...
    程式設計 發佈於2024-11-08
  • 如何解決 CSS 中可變字體的文字筆畫問題?
    如何解決 CSS 中可變字體的文字筆畫問題?
    文本描邊難題:解決CSS 相容性問題使用-webkit-text-lines 創建引人注目的文本效果是網頁設計師的一項基本技術。但是,當將此屬性與可變字體一起使用時,可能會出現意外的筆劃行為。這種不一致不僅限於 Chrome,而是不同瀏覽器中更普遍的問題。 問題的癥結:可變字體和筆畫衝突可變字體具有...
    程式設計 發佈於2024-11-08
  • C++ 中的私有虛擬方法:平衡封裝與重寫
    C++ 中的私有虛擬方法:平衡封裝與重寫
    了解 C 中私有虛擬方法的好處 在物件導向程式設計中,私有方法封裝實作細節並限制其在一個班級。然而,在 C 中,虛函數提供後期綁定並允許物件的多態行為。透過結合這些概念,私有虛擬方法提供了獨特的優勢。 考慮以下用法,其中 HTMLDocument 繼承自多個基底類別:class HTMLDocume...
    程式設計 發佈於2024-11-08
  • 齋浦爾資料科學研究所:傳統與科技的邂逅
    齋浦爾資料科學研究所:傳統與科技的邂逅
    斋浦尔,粉红之城,长期以来一直是一座拥有丰富文化遗产、雄伟宫殿和充满活力的传统的城市,但这座城市的另一个特征是教育和技术进步。这是通过斋浦尔的几个数据科学研究所推出的,通过这些机构引导学生和专业人士进入快速变化的技术世界。 这些机构融合了传统与创新,在培养这座城市的未来科技人才方面发挥着重要作用。在...
    程式設計 發佈於2024-11-08
  • 如何根據多個條件過濾 JavaScript 物件數組?
    如何根據多個條件過濾 JavaScript 物件數組?
    基於多個條件過濾JavaScript中的數組問題陳述給定一個對象數組和一個過濾器對象,目標是過濾和根據篩選器中指定的多個條件簡化陣列。但是,當過濾器包含多個屬性時,會出現一個特定問題。 建議的解決方案考慮以下程式碼段:function filterUsers(users, filter) { v...
    程式設計 發佈於2024-11-08
  • 理解 Laravel 11 中 pluck() 和 select() 之間的差異
    理解 Laravel 11 中 pluck() 和 select() 之間的差異
    Laravel 是最受歡迎的 PHP 架構之一,提供了一系列強大的資料操作方法。其中,pluck() 和 select() 在處理集合時經常使用。儘管它們看起來相似,但它們的目的卻不同。在本文中,我們將探討這兩種方法之間的差異,解釋何時使用每種方法,並提供實際的編碼範例來示範它們在 Laravel ...
    程式設計 發佈於2024-11-08
  • 什麼是 Cloudflare? Web 效能與安全性公司概述
    什麼是 Cloudflare? Web 效能與安全性公司概述
    在快節奏的數位世界中,網站的速度、安全性和可靠性對於企業和使用者都至關重要。 Cloudflare 已成為確保網站平穩、安全和高效運作的基石。但 Cloudflare 到底是什麼?為什麼它成為網站所有者如此重要的工具?讓我們深入了解它的作用和產品。 Cloudflare 簡介 Cl...
    程式設計 發佈於2024-11-08
  • 如何最佳化 MySQL 索引效能以加快查詢速度?
    如何最佳化 MySQL 索引效能以加快查詢速度?
    優化MySQL索引效能要有效檢查MySQL索引的效能,可以使用以下查詢:EXPLAIN EXTENDED SELECT col1, col2, col3, COUNT(1) FROM table_name WHERE col1 = val GROUP BY col1 ORDER BY col...
    程式設計 發佈於2024-11-08
  • 如何在 PHP 中將資料加入文件?
    如何在 PHP 中將資料加入文件?
    PHP 中的檔案追加與前置 在 PHP 中使用「a」(append ) 模式。然而,寫入文件的開頭需要更細緻的方法。 在所描述的場景中,「r」模式(讀寫)允許添加數據,但會覆蓋先前的內容。為了避免這種限制,需要更複雜的技術。 使用 file_put_contents() 的解決方案該解決方案涉及將 ...
    程式設計 發佈於2024-11-08

免責聲明: 提供的所有資源部分來自互聯網,如果有侵犯您的版權或其他權益,請說明詳細緣由並提供版權或權益證明然後發到郵箱:[email protected] 我們會在第一時間內為您處理。

Copyright© 2022 湘ICP备2022001581号-3