Ubuntu TechHive
realtime-data-visualizations-with-python-htmx-and-llm-generated-sql-queries.md
使用 Python、HTMX 和 LLM 生成的 SQL 查询实现实时数据可视化
article.细节

使用 Python、HTMX 和 LLM 生成的 SQL 查询实现实时数据可视化

reading.进展 5 分钟阅读数

使用 Python、HTMX 和 LLM 生成的 SQL 查询实现实时数据可视化的说明

使用 Python、HTMX 和 LLM 生成的 SQL 查询实现实时数据可视化

显示器的实时更新

中文:

欢迎阅读本文,本篇简要介绍了 Web 应用实时更新的主题,这对于增强用户交互性和响应速度至关重要。

实时数据可视化

中文:

实时数据在现代 Web 开发中发挥着至关重要的作用,因为它能够提供即时信息,从而增强用户体验并实现动态、交互式的 Web 应用。以下是一些关键点:

  1. 用户体验 🧑‍💻:实时数据可以显著改善用户体验。
  2. 实时分析 📊:实时分析使企业能够根据当前数据做出即时决策。
  3. 协作工具 🤝:实时数据对于 Google Docs 等协作工具至关重要,多个用户可以同时编辑同一个文档。
  4. 通知 🔔:实时数据支持即时通知。
  5. 实时更新 📡:在新闻或金融应用中,实时数据提供有关当前事件或股票价格的实时更新,让用户随时了解最新情况。
  6. 物联网 (IoT) 设备 🌐:物联网设备严重依赖实时数据来有效运行。例如,智能恒温器需要了解实时温度,以调节房屋内的供暖或制冷。

架构

中文:
  • **FastAPI 后端**:FastAPI 是一个现代、快速(高性能)的 Web 框架,用于基于标准 Python 类型提示构建 Python 3.6+ API。它的设计旨在让入门变得快速简单,并具备扩展到复杂应用的能力。在此设置中,FastAPI 用于处理所有后端操作,包括提供网页服务、处理请求以及与数据库交互。
  • **PostgreSQL 作为数据库系统**:PostgreSQL 是一个功能强大的开源对象关系数据库系统。它以其稳健性、功能性和标准合规性而闻名。在此设置中,PostgreSQL 用于存储和检索应用数据。
  • **SQLAlchemy 用于数据库操作**:SQLAlchemy 是一个用于 Python 的 SQL 工具包和对象关系映射 (ORM) 系统。它提供了一整套众所周知的企业级持久化模式,专为高效、高性能的数据库访问而设计。在此设置中,SQLAlchemy 用于以 Pythonic 的方式与 PostgreSQL 数据库进行交互。
  • **SSE 用于向前端发送更新**:服务器发送事件 (SSE) 是一项标准,允许 Web 服务器通过 HTTP 向客户端推送更新。在此设置中,SSE 用于将实时更新从服务器(Flask 应用)发送到客户端(Web 浏览器)。
  • **HTMX 用于以最少的 JavaScript 处理前端更新**:HTMX 是一个现代的“HTML 优先、JavaScript 次之”的库,用于构建 AJAX 驱动的 Web 应用。它允许你直接在 HTML 中使用属性来访问 AJAX、CSS 过渡、WebSockets 和服务器发送事件,从而利用超文本的简洁性和强大功能构建现代用户界面。在此设置中,HTMX 用于处理服务器发送的更新并动态更新 HTML 内容。
  • **Jinja2 模板用于渲染带有 Python 数据的初始 HTML**:Jinja2 是一个现代且对设计人员友好的 Python 模板语言。它用于创建可以利用 Python 数据进行渲染的 HTML 模板。在此设置中,Jinja2 用于渲染网页的初始 HTML,并填充来自 Flask 应用的数据。

插图

graph LR
  A[FastAPI] -->|获取数据| B[PostgreSQL]
  A -->|发送更新| C[SSE]
  B -->|数据库操作| D[SQLAlchemy]
  C -->|更新前端| E[HTMX]
  A -->|渲染初始 HTML| F[Jinja2]

Python 🐍

中文:

Python 是一种由 Guido van Rossum 创建并于 1991 年首次发布的高级解释型编程语言。Python 的设计哲学强调代码的可读性,其显著特点是使用缩进。它支持多种编程范式,包括结构化(特别是过程式)、面向对象和函数式编程。

**用例**:Python 被广泛应用于各种应用中,包括:

  1. **Web 开发**:Python 的可读性和简洁性,加上 Django 和 Flask 等强大的框架,使其成为 Web 开发的热门选择。
  2. **数据分析与可视化**:Pandas、NumPy 和 Matplotlib 等库使 Python 成为数据分析和可视化的强大工具。
  3. **机器学习与 AI**:Python 是机器学习领域的领先语言之一,拥有 TensorFlow、PyTorch 和 Scikit-learn 等库。
  4. **脚本编写与自动化**:Python 的简洁性使其成为编写脚本和自动化任务的绝佳语言。

**优点**:

  1. **可读性**:Python 的语法设计为可读且直观,这使其成为初学者的绝佳语言。
  2. **多功能性**:Python 用于许多开发领域,从 Web 应用到数据分析。
  3. **强大的社区**:Python 拥有庞大且活跃的用户和开发者社区,他们为海量的开源库和框架做出了贡献。

**缺点**:

  1. **速度**:Python 是一种解释型语言,可能比 C 或 Java 等编译型语言慢。
  2. **移动开发**:虽然可以用 Python 开发移动应用,但不如使用 Swift 或 Java 等专门为移动开发设计的语言那样直接。
  3. **内存消耗**:与其他语言相比,Python 的灵活性可能导致更高的内存消耗。

FastAPI

中文:

**概述**:FastAPI 是一个现代、快速(高性能)的 Web 框架,用于基于标准 Python 类型提示构建 Python 3.6+ API。它的设计旨在让入门变得快速简单,并具备扩展到复杂应用的能力。它已成为最流行的 Python Web 应用框架之一。

**用例**:FastAPI 被用于各种应用中,包括:

  1. **Web 开发**:FastAPI 的简洁性和自动交互式 API 文档使其成为构建 Web 应用和服务的绝佳选择。
  2. **API 开发**:FastAPI 可用于构建带有自动交互式 API 文档的 RESTful API。
  3. **微服务**:FastAPI 轻量级和模块化的设计使其非常适合微服务架构。

**优点**:

  1. **简洁性**:FastAPI 使用简单,易于上手。它不需要任何特定的项目或代码布局,因此很容易从小规模开始并逐步扩展。
  2. **灵活性**:FastAPI 可用于构建各种 Web 应用,从简单的单页应用到复杂的数据库驱动网站。
  3. **可扩展性**:FastAPI 可以通过“插件”进行扩展,从而为应用增加功能。有用于表单验证、上传处理、各种开放认证技术以及几种常见框架相关工具的插件。

**缺点**:

  1. **缺乏约定**:FastAPI 将许多决策和实现细节留给开发者,这在构建大型应用或团队协作时可能会导致问题。
  2. **可扩展性**:虽然 FastAPI 非常适合小型应用,但对于大型、复杂的应用可能不太适用。
  3. **异步支持**:FastAPI 的请求处理是异步的。对于需要处理大量并发连接的应用,Node.js 等其他框架可能是更好的选择。

HTMX

中文:

HTMX 是一个现代工具,允许你直接在 HTML 中访问 AJAX、WebSockets、服务器发送事件和其他动态行为,而无需编写任何 JavaScript。对于想要以较低复杂度创建交互式网页的开发者来说,这是一个强大的工具。

**背景与用例**: HTMX 适用于开发者希望保持服务器渲染 HTML 的简洁性,但又需要单页应用 (SPA) 通常具备的交互性的场景。它非常适合为网页添加实时更新、懒加载和无限滚动等功能。

**优点**:

  1. **简洁性**:HTMX 让事情变得简单。你不需要编写 JavaScript,只需 HTML。
  2. **轻量级**:它是一个小型库,因此不会增加太多页面加载时间。
  3. **兼容性**:它适用于任何可以提供 HTML 的后端,使其具有高度的通用性。

**缺点**:

  1. **社区有限**:作为一个相对较新的工具,围绕 HTMX 的社区仍在成长中。这可能使寻找特定问题的解决方案更具挑战性。
  2. **学习曲线**:虽然 HTMX 简化了动态 Web 开发的许多方面,但仍然存在学习曲线,特别是对于习惯了重 JavaScript 的 SPA 的开发者而言。
  3. **并非完全替代品**:对于具有繁重客户端逻辑的复杂应用,传统的 JavaScript 或框架可能仍然是必要的。HTMX 最适合用于增强服务器渲染页面的交互功能。

简化 Web 交互:HTMX 如何缓解 JavaScript 的复杂性

JavaScript 虽然功能强大,但有时会在 Web 开发中引入复杂性和挑战。HTMX 旨在缓解其中的一些痛点。

  1. **简洁性**:JavaScript,特别是在与现代框架一起使用时,可能会变得复杂且难以管理。相比之下,HTMX 允许你直接在 HTML 中添加动态行为,从而减少了对复杂 JavaScript 代码的需求。
  2. **缩短加载时间**:JavaScript 文件(特别是对于大型应用)可能很大,并影响页面加载时间。作为一个轻量级库,HTMX 将此问题降至最低。
  3. **易于学习**:JavaScript 的学习曲线陡峭,特别是对于初学者。HTMX 通过允许开发者使用熟悉的 HTML 语法来简化这一点。
  4. **兼容性**:JavaScript 有时会在不同浏览器之间出现兼容性问题。HTMX 适用于任何可以提供 HTML 的后端,使其具有高度的通用性,且不易出现兼容性问题。
  5. **服务器端渲染**:对于重 JavaScript 的应用,大部分渲染逻辑被移动到客户端,这可能会消耗大量资源。HTMX 允许服务器端渲染,这可能更高效且更易于管理。

然而,需要注意的是,虽然 HTMX 可以缓解与 JavaScript 相关的一些痛点,但它并不是一个完全的替代品。对于具有繁重客户端逻辑的复杂应用,JavaScript 或 JavaScript 框架可能仍然是必要的。HTMX 最适合用于增强服务器渲染页面的交互功能。

服务器发送事件 (SSE)

中文:

服务器发送事件 (SSE) 是一项标准,允许 Web 服务器向客户端推送实时更新。这项技术是 HTML5 规范的一部分,旨在通过允许服务器在有新数据可用时随时向客户端发送数据,来增强传统的请求-响应模型。

中文:

服务器发送事件 (SSE) 是一项标准,允许 Web 服务器向客户端推送实时更新。这项技术是 HTML5 规范的一部分,旨在通过允许服务器在有新数据可用时随时向客户端发送数据,来增强传统的请求-响应模型。

插图

sequenceDiagram
    participant Client
    participant Server
    Client->>Server: GET /stream
    Note over Client: 标头: Accept: text/event-stream
    Note over Server: 建立流连接
    loop 流式传输数据
        Server-->>Client: data: {"event": "message", "data": "JSON 有效载荷"}
        Note over Server: 标头: Content-Type: text/event-stream
    end
    Note over Client: 处理每个接收到的事件
中文:

**背景**:在 SSE 之前,开发者通常使用长轮询或 WebSockets 来实现实时通信。然而,这些方法可能很复杂且资源密集。SSE 作为一种更简单、更高效的单向实时通信(从服务器到客户端)替代方案被引入。

**实现**:要实现 SSE,服务器会发送一个 MIME 类型为 `text/event-stream` 的响应。客户端使用 JavaScript 中的 `EventSource` API 监听这些事件。服务器随后可以在有新数据可用时随时向客户端发送事件。

**优点**:

  1. **简洁性**:SSE 比 WebSockets 更易于实现,因为它不需要全双工连接。
  2. **效率**:SSE 比长轮询更高效,因为它不需要客户端不断检查新数据。
  3. **内置重连**:如果连接丢失,SSE 会自动尝试重新连接。

**缺点**:

  1. **单向通信**:SSE 仅支持从服务器到客户端的单向通信。如果需要双向通信,WebSockets 等其他技术可能更合适。
  2. **浏览器支持有限**:并非所有浏览器都支持 SSE。例如,Internet Explorer 不支持 SSE。
  3. **开销**:每个 SSE 连接都需要一个单独的 HTTP 连接,如果连接数很多,这会增加开销。

更多信息请参阅 [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events)

Python 生成器

中文:

Python 生成器 是 Python 中的一种特殊函数,允许你以快速、简单且整洁的方式创建迭代器。它们是作为 Python 增强提案 (PEP) 255 的一部分引入的。

**背景**:在 Python 中,迭代器是一个可以进行迭代(循环)的对象。它是一个每次返回一个元素数据的对象。生成器提供了一种实现迭代器协议的便捷方式。

**实现**:Python 生成器作为函数实现,但它不使用 `return` 语句返回数值,而是使用 `yield`。当调用生成器函数时,它会返回一个生成器对象,甚至不会开始执行函数。当第一次调用 `next()` 时,函数开始执行,直到到达 `yield` 语句,该语句返回生成的值。此时函数执行暂停,并将控制权交还给调用者。

中文:

Python 生成器 是 Python 中的一种特殊函数,允许你以快速、简单且整洁的方式创建迭代器。它们是作为 Python 增强提案 (PEP) 255 的一部分引入的。

**背景**:在 Python 中,迭代器是一个可以进行迭代(循环)的对象。它是一个每次返回一个元素数据的对象。生成器提供了一种实现迭代器协议的便捷方式。

**实现**:Python 生成器作为函数实现,但它不使用 `return` 语句返回数值,而是使用 `yield`。当调用生成器函数时,它会返回一个生成器对象,甚至不会开始执行函数。当第一次调用 `next()` 时,函数开始执行,直到到达 `yield` 语句,该语句返回生成的值。此时函数执行暂停,并将控制权交还给调用者。

def count_up_to(n):
  count = 1
  while count <= n:
    yield count
    count += 1

# 创建一个生成器
counter = count_up_to(5)

# 使用生成器
for num in counter:
  print(num)
中文:

**用例**:当处理不想一次性全部存储在内存中的大数据集时,生成器特别有用。在处理极大甚至无限序列时,它也非常有用。

**优点**:

  1. **内存效率**:生成器是优化内存的绝佳方式。由于它们一次生成一个项目,因此不需要将所有内容加载到内存中。
  2. **惰性求值**:生成器是惰性的,这意味着它们即时生成值。这种惰性可以显著提高大数据集的性能。
  3. **代码整洁**:生成器有助于编写整洁且可读的代码。

**缺点**:

  1. **一次性使用**:生成器只能迭代一次。遍历完值后,就不能再次迭代它们了。
  2. **复杂性**:生成器可能会使代码变得更复杂,对初学者来说更难理解。

SQLAlchemy

中文:

SQLAlchemy 是一个用于 Python 的 SQL 工具包和对象关系映射 (ORM) 系统。它提供了一整套众所周知的企业级持久化模式,专为高效、高性能的数据库访问而设计。

**概述**:SQLAlchemy 引入于 2005 年,是一个用于在 Python 中处理 SQL 数据库的综合库。它包括高级 ORM、低级直接 SQL 访问等。

**用例**:SQLAlchemy 被用于各种应用中,以:

  1. **与数据库交互**:SQLAlchemy 提供了一致且统一的 API 来与不同的数据库系统交互。
  2. **数据映射**:SQLAlchemy 的 ORM 允许用户将 Python 类映射到数据库表,从而提供了一种更直观的数据库交互方式。
  3. **数据分析**:SQLAlchemy 可以与 Pandas 等库一起用于数据分析任务。

**优点**:

  1. **多功能性**:SQLAlchemy 支持广泛的 SQL 数据库,而不仅仅是 SQLite。
  2. **效率**:SQLAlchemy 的 ORM 和表达式语言实现了高效的数据库操作。
  3. **成熟度**:作为一个成熟的库,SQLAlchemy 拥有强大的支持和庞大的社区。

**缺点**:

  1. **复杂性**:SQLAlchemy 广泛的功能和灵活性使其学习起来可能很复杂,特别是对于初学者。
  2. **性能**:虽然 SQLAlchemy 的 ORM 使数据库操作更方便,但与原始 SQL 相比,有时会导致性能变慢。
  3. **开销**:SQLAlchemy 提供的抽象引入了一些开销,对于需要最高性能的应用来说可能并不理想。
class EnergyDataTable(Base):
    __tablename__ = "energy_data"
    id = Column(Integer, primary_key=True, autoincrement=True)
    period = Column(DateTime, nullable=False)
    respondent = Column(String, nullable=True)
    respondent_name = Column(String, nullable=True)
    type = Column(String, nullable=True)
    type_name = Column(String, nullable=True)
    value = Column(Float, nullable=True)
    value_units = Column(String, nullable=True)

    __table_args__ = (
        UniqueConstraint(
            "period", "respondent", "type", name="uix_period_respondent_type"
        ),
    )

    def __repr__(self):
        return f"{self.id}, period={self.period}, respondent={self.respondent}, respondent_name={self.respondent_name}, type={self.type}, type_name={self.type_name}, value={self.value}, value_units={self.value_units})>"

Jinja2

中文:

Jinja2 是一个强大的 Python 模板引擎,允许你生成动态 HTML、XML 或其他标记格式。它提供了一种灵活且高效的方式来在服务器端渲染数据,并为 Web 应用生成动态内容。

Jinja2 受 Django 模板引擎的启发,被广泛用于 Flask 和 Django 等框架的 Web 开发中。它将表示逻辑与业务逻辑分离,使维护和更新应用前端变得更加容易。

**用例**:Jinja2 常用于需要在服务器端渲染动态内容的场景。它特别适用于生成带有动态数据的 HTML 页面,例如显示用户信息、生成报告或渲染实时更新。

**优点**:

  1. **灵活且富有表现力**:Jinja2 提供了丰富的功能集,包括条件语句、循环、过滤器和宏,使你能够轻松创建复杂的模板。
  2. **关注点分离**:通过将表示逻辑与业务逻辑分离,Jinja2 促进了整洁的代码架构和可维护性。
  3. **可扩展性**:Jinja2 允许你定义自定义过滤器、函数和标签,从而让你完全控制模板渲染过程。
  4. **与 Python 集成**:由于 Jinja2 是用 Python 编写的,它与 Python 代码无缝集成,使得从后端向模板传递数据变得容易。

**缺点**:

  1. **学习曲线**:Jinja2 有自己的语法和概念,因此理解和有效使用它需要一定的学习曲线。
  2. **前端交互有限**:Jinja2 主要侧重于服务器端渲染,因此对于需要频繁更新而无需重新加载页面的高度交互式前端组件,它可能不是最佳选择。在这种情况下,React 或 Vue.js 等 JavaScript 框架可能更合适。
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
    <div class="container-fluid">
        <a class="navbar-brand" href="{{ url_for('index') }}">
            <i class="bi bi-lightning-charge-fill me-2">i>能源仪表盘
        a>
        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
                aria-controls="navbarNav" aria-expanded="false" aria-label="切换导航">
            <span class="navbar-toggler-icon">span>
        button>
        <div class="collapse navbar-collapse" id="navbarNav">
            <ul class="navbar-nav ml-auto">
                <li class="nav-item">
                    <a class="nav-link" href="{{ url_for('index') }}">首页a>
                li>
                <li class="nav-item">
                    <a class="nav-link" href="{{ url_for('instruct') }}">指令 (使用 LLM Energy)a>
                li>
            ul>
        div>
    div>
nav>

如何使用 LLM 生成 SQL 查询?

中文:

首先,让我们从 SQLAlchemy 获取能源数据表的 DDL 脚本

def get_energy_data_schema() -> str:
    """
    获取数据库架构
    """
    schema_ddl = CreateTable(EnergyDataTable.__table__).compile(engine)
    log.info(schema_ddl)
    return schema_ddl
中文:

然后,我们继续利用通过 Instructor 配置的 LLM 的函数调用能力,为我们提供 SQL 查询:

def gen_select_query(
    ai_client: Instructor, schema, parametre: str, model=LLMModel.GPT4_Omni
) -> SqlSelectQuery:
    system_msg = f"""
    根据以下表架构发出有效的 SQL 语句:
    '''sql
    {schema}
    '''
    """
    log.info(f"system_msg: {system_msg}")
    log.info(f"parametre: {parametre}")
    query = ai_client.chat.completions.create(
        model=model,
        response_model=SqlSelectQuery,
        messages=[
            {"role": "system", "content": system_msg},
            {"role": "user", "content": parametre},
        ],
    )
    return query
中文:

使用以下 Pydantic 结构:

class SqlSelectQuery(BaseModel):
    select_stmt: str = Field(..., description="查询的 select 语句")
    explain_stmt: str = Field(..., description="查询的 explain 语句")
    start_date: str = Field(..., description="数据的开始日期")
    end_date: str = Field(..., description="数据的结束日期")