使用 Python、HTMX 和 LLM 生成的 SQL 查询实现实时数据可视化
显示器的实时更新
中文:
欢迎阅读本文,本篇简要介绍了 Web 应用实时更新的主题,这对于增强用户交互性和响应速度至关重要。
大纲
实时数据可视化
中文:
实时数据在现代 Web 开发中发挥着至关重要的作用,因为它能够提供即时信息,从而增强用户体验并实现动态、交互式的 Web 应用。以下是一些关键点:
- 用户体验 🧑💻:实时数据可以显著改善用户体验。
- 实时分析 📊:实时分析使企业能够根据当前数据做出即时决策。
- 协作工具 🤝:实时数据对于 Google Docs 等协作工具至关重要,多个用户可以同时编辑同一个文档。
- 通知 🔔:实时数据支持即时通知。
- 实时更新 📡:在新闻或金融应用中,实时数据提供有关当前事件或股票价格的实时更新,让用户随时了解最新情况。
- 物联网 (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 被广泛应用于各种应用中,包括:
- **Web 开发**:Python 的可读性和简洁性,加上 Django 和 Flask 等强大的框架,使其成为 Web 开发的热门选择。
- **数据分析与可视化**:Pandas、NumPy 和 Matplotlib 等库使 Python 成为数据分析和可视化的强大工具。
- **机器学习与 AI**:Python 是机器学习领域的领先语言之一,拥有 TensorFlow、PyTorch 和 Scikit-learn 等库。
- **脚本编写与自动化**:Python 的简洁性使其成为编写脚本和自动化任务的绝佳语言。
**优点**:
- **可读性**:Python 的语法设计为可读且直观,这使其成为初学者的绝佳语言。
- **多功能性**:Python 用于许多开发领域,从 Web 应用到数据分析。
- **强大的社区**:Python 拥有庞大且活跃的用户和开发者社区,他们为海量的开源库和框架做出了贡献。
**缺点**:
- **速度**:Python 是一种解释型语言,可能比 C 或 Java 等编译型语言慢。
- **移动开发**:虽然可以用 Python 开发移动应用,但不如使用 Swift 或 Java 等专门为移动开发设计的语言那样直接。
- **内存消耗**:与其他语言相比,Python 的灵活性可能导致更高的内存消耗。
FastAPI
中文:
**概述**:FastAPI 是一个现代、快速(高性能)的 Web 框架,用于基于标准 Python 类型提示构建 Python 3.6+ API。它的设计旨在让入门变得快速简单,并具备扩展到复杂应用的能力。它已成为最流行的 Python Web 应用框架之一。
**用例**:FastAPI 被用于各种应用中,包括:
- **Web 开发**:FastAPI 的简洁性和自动交互式 API 文档使其成为构建 Web 应用和服务的绝佳选择。
- **API 开发**:FastAPI 可用于构建带有自动交互式 API 文档的 RESTful API。
- **微服务**:FastAPI 轻量级和模块化的设计使其非常适合微服务架构。
**优点**:
- **简洁性**:FastAPI 使用简单,易于上手。它不需要任何特定的项目或代码布局,因此很容易从小规模开始并逐步扩展。
- **灵活性**:FastAPI 可用于构建各种 Web 应用,从简单的单页应用到复杂的数据库驱动网站。
- **可扩展性**:FastAPI 可以通过“插件”进行扩展,从而为应用增加功能。有用于表单验证、上传处理、各种开放认证技术以及几种常见框架相关工具的插件。
**缺点**:
- **缺乏约定**:FastAPI 将许多决策和实现细节留给开发者,这在构建大型应用或团队协作时可能会导致问题。
- **可扩展性**:虽然 FastAPI 非常适合小型应用,但对于大型、复杂的应用可能不太适用。
- **异步支持**:FastAPI 的请求处理是异步的。对于需要处理大量并发连接的应用,Node.js 等其他框架可能是更好的选择。
HTMX
中文:
HTMX 是一个现代工具,允许你直接在 HTML 中访问 AJAX、WebSockets、服务器发送事件和其他动态行为,而无需编写任何 JavaScript。对于想要以较低复杂度创建交互式网页的开发者来说,这是一个强大的工具。
**背景与用例**: HTMX 适用于开发者希望保持服务器渲染 HTML 的简洁性,但又需要单页应用 (SPA) 通常具备的交互性的场景。它非常适合为网页添加实时更新、懒加载和无限滚动等功能。
**优点**:
- **简洁性**:HTMX 让事情变得简单。你不需要编写 JavaScript,只需 HTML。
- **轻量级**:它是一个小型库,因此不会增加太多页面加载时间。
- **兼容性**:它适用于任何可以提供 HTML 的后端,使其具有高度的通用性。
**缺点**:
- **社区有限**:作为一个相对较新的工具,围绕 HTMX 的社区仍在成长中。这可能使寻找特定问题的解决方案更具挑战性。
- **学习曲线**:虽然 HTMX 简化了动态 Web 开发的许多方面,但仍然存在学习曲线,特别是对于习惯了重 JavaScript 的 SPA 的开发者而言。
- **并非完全替代品**:对于具有繁重客户端逻辑的复杂应用,传统的 JavaScript 或框架可能仍然是必要的。HTMX 最适合用于增强服务器渲染页面的交互功能。
简化 Web 交互:HTMX 如何缓解 JavaScript 的复杂性
JavaScript 虽然功能强大,但有时会在 Web 开发中引入复杂性和挑战。HTMX 旨在缓解其中的一些痛点。
- **简洁性**:JavaScript,特别是在与现代框架一起使用时,可能会变得复杂且难以管理。相比之下,HTMX 允许你直接在 HTML 中添加动态行为,从而减少了对复杂 JavaScript 代码的需求。
- **缩短加载时间**:JavaScript 文件(特别是对于大型应用)可能很大,并影响页面加载时间。作为一个轻量级库,HTMX 将此问题降至最低。
- **易于学习**:JavaScript 的学习曲线陡峭,特别是对于初学者。HTMX 通过允许开发者使用熟悉的 HTML 语法来简化这一点。
- **兼容性**:JavaScript 有时会在不同浏览器之间出现兼容性问题。HTMX 适用于任何可以提供 HTML 的后端,使其具有高度的通用性,且不易出现兼容性问题。
- **服务器端渲染**:对于重 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 监听这些事件。服务器随后可以在有新数据可用时随时向客户端发送事件。
**优点**:
- **简洁性**:SSE 比 WebSockets 更易于实现,因为它不需要全双工连接。
- **效率**:SSE 比长轮询更高效,因为它不需要客户端不断检查新数据。
- **内置重连**:如果连接丢失,SSE 会自动尝试重新连接。
**缺点**:
- **单向通信**:SSE 仅支持从服务器到客户端的单向通信。如果需要双向通信,WebSockets 等其他技术可能更合适。
- **浏览器支持有限**:并非所有浏览器都支持 SSE。例如,Internet Explorer 不支持 SSE。
- **开销**:每个 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)中文:
**用例**:当处理不想一次性全部存储在内存中的大数据集时,生成器特别有用。在处理极大甚至无限序列时,它也非常有用。
**优点**:
- **内存效率**:生成器是优化内存的绝佳方式。由于它们一次生成一个项目,因此不需要将所有内容加载到内存中。
- **惰性求值**:生成器是惰性的,这意味着它们即时生成值。这种惰性可以显著提高大数据集的性能。
- **代码整洁**:生成器有助于编写整洁且可读的代码。
**缺点**:
- **一次性使用**:生成器只能迭代一次。遍历完值后,就不能再次迭代它们了。
- **复杂性**:生成器可能会使代码变得更复杂,对初学者来说更难理解。
SQLAlchemy
中文:
SQLAlchemy 是一个用于 Python 的 SQL 工具包和对象关系映射 (ORM) 系统。它提供了一整套众所周知的企业级持久化模式,专为高效、高性能的数据库访问而设计。
**概述**:SQLAlchemy 引入于 2005 年,是一个用于在 Python 中处理 SQL 数据库的综合库。它包括高级 ORM、低级直接 SQL 访问等。
**用例**:SQLAlchemy 被用于各种应用中,以:
- **与数据库交互**:SQLAlchemy 提供了一致且统一的 API 来与不同的数据库系统交互。
- **数据映射**:SQLAlchemy 的 ORM 允许用户将 Python 类映射到数据库表,从而提供了一种更直观的数据库交互方式。
- **数据分析**:SQLAlchemy 可以与 Pandas 等库一起用于数据分析任务。
**优点**:
- **多功能性**:SQLAlchemy 支持广泛的 SQL 数据库,而不仅仅是 SQLite。
- **效率**:SQLAlchemy 的 ORM 和表达式语言实现了高效的数据库操作。
- **成熟度**:作为一个成熟的库,SQLAlchemy 拥有强大的支持和庞大的社区。
**缺点**:
- **复杂性**:SQLAlchemy 广泛的功能和灵活性使其学习起来可能很复杂,特别是对于初学者。
- **性能**:虽然 SQLAlchemy 的 ORM 使数据库操作更方便,但与原始 SQL 相比,有时会导致性能变慢。
- **开销**: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 页面,例如显示用户信息、生成报告或渲染实时更新。
**优点**:
- **灵活且富有表现力**:Jinja2 提供了丰富的功能集,包括条件语句、循环、过滤器和宏,使你能够轻松创建复杂的模板。
- **关注点分离**:通过将表示逻辑与业务逻辑分离,Jinja2 促进了整洁的代码架构和可维护性。
- **可扩展性**:Jinja2 允许你定义自定义过滤器、函数和标签,从而让你完全控制模板渲染过程。
- **与 Python 集成**:由于 Jinja2 是用 Python 编写的,它与 Python 代码无缝集成,使得从后端向模板传递数据变得容易。
**缺点**:
- **学习曲线**:Jinja2 有自己的语法和概念,因此理解和有效使用它需要一定的学习曲线。
- **前端交互有限**: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="数据的结束日期")参考资料
HTMX
服务器发送事件 (SSE)
Python
SQLAlchemy
Jinja2
Bokeh

