大数跨境
0
0

如何构建一个能与数据库对话的人工智能:一段 RAG 之旅

如何构建一个能与数据库对话的人工智能:一段 RAG 之旅 数据驱动智能
2025-11-24
9
导读:使用检索增强生成技术将自然语言转换为 SQL 查询。
使用检索增强生成技术将自然语言转换为 SQL 查询
想象一下,你用简单的语言向数据库提问:“显示所有售价低于 100 万的三居室房屋”,然后就能得到自然流畅的对话式回答。无需任何 SQL 知识,也无需编写复杂的查询语句。只需一个简单的问题,剩下的就交给 AI 来处理。
这不是科幻小说。这是检索增强生成(RAG),接下来我将向你展示我是如何构建它的。
一 概述:为什么是 RAG?
作为一名开发者,我一直对人类思维方式和数据库工作方式之间的鸿沟感到沮丧。我们思考问题的方式是提出问题:“平均价格是多少?”或者“哪个经纪人的房源最多?”而数据库则需要精确的 SQL 语法,这大多数人都不了解。
传统的聊天机器人在这里帮不上忙。它们接受的是通用知识训练,而不是针对您特定数据库的训练。它们或许能自信地告诉您一些关于房地产的总体情况,但它们根本不知道您的数据库中目前实际有哪些房产。
这就是 RAG 改变一切的地方。
RAG结合了两种强大的技术:
  • 数据检索:从数据库中提取真实数据
  • 生成:利用人工智能自然地理解问题并格式化答案
结果如何?一款真正了解你的数据并能实时回答相关问题的AI助手。
二 架构:构建模块
在深入代码之前,让我先带您了解一下架构。每个 RAG 系统都需要以下组件:
1. 前端:用户交互的地方
我选择Streamlit作为前端框架,因为它能让我在几分钟内构建一个美观的聊天界面。用户可以输入问题、查看回复并保存对话记录——所有操作都无需编写复杂的 JavaScript 代码。
2. 后端:API层
FastAPI为后端提供支持。它速度快、界面现代,并且能够自动生成 API 文档。这种职责分离的设计意味着我以后可以更换前端,或者构建使用相同 API 的移动应用。
3. RAG 管道:奇迹发生的地方
这是系统的核心。它基于LangChain构建,协调三个关键步骤:
  • 问题理解:LLM 分析自然语言问题
  • SQL生成:LLM 将问题转换为 SQL 查询
  • 答案格式:LLM 获取原始数据库结果并将其格式化为自然语言。
4. 数据库:数据来源
为了简化操作,我使用了SQLite,但该架构适用于任何 SQL 数据库。该系统包含一个房地产数据库,其中包含房产、经纪人和客户信息——非常适合用来演示该概念。
三 过程:一步一步构建
让我带你了解我是如何构建每个组件的,并分享我在此过程中获得的经验和见解。
第一步:教人工智能编写 SQL
第一个挑战是让LLM生成正确的SQL查询语句。这比听起来要复杂得多。人工智能需要:
了解数据库模式
将自然语言转换为 SQL 语法
处理特殊情况(结果为空、问题无效)
避免 SQL 注入漏洞
以下是我构建的方式:
sql_prompt = ChatPromptTemplate.from_messages([ 
    ( "system" , """您是房地产数据库的 SQL 查询专家。


数据库模式:

{schema}

编写 SQL 查询时:

1. 使用 SQLite 的正确 SQL 语法

2. 仅查询存在的表和列

3. 对于文本搜索,请使用带有 % 通配符的 LIKE 语句

4. 仅返回 SQL 查询语句,不返回其他任何内容

5. 不要包含 Markdown 代码块或反引号

6. 如果数据库无法回答该问题,则返回 "NO_QUERY"


请编写一个 SQL 查询语句来回答用户关于房地产数据库的问题。""" ) 

    ( "human" , "{input}" ) 

])
关键要点:在提示中提供数据库模式至关重要。如果没有数据库模式,LLM 只能靠猜测。有了数据库模式,LLM 才能生成准确的查询。
步骤二:执行和验证
一旦我们有了 SQL 查询语句,就需要安全地执行它。LangChain 的SQLDatabase封装器很好地处理了这一点:
db = SQLDatabase(engine, include_tables=[ "properties" , "agents" , "clients" ]) 
result = db.run(sql_query)
但如果查询失败了呢?如果查询没有返回任何结果呢?这时错误处理就显得至关重要了。我学会了在执行查询之前始终对其进行验证,并提供清晰易懂的错误信息。
步骤三:使结果更易于理解
原始 SQL 查询结果如下所示:
[(1, '橡树街123号' , '房子' , 3, 250000), (2, '松树大道456号' , '房子' , 3, 280000)]
不太方便用户使用,对吧?最后一步再次使用LLM来格式化这些结果:
format_prompt = ChatPromptTemplate.from_messages([ 
    ( "system" , """您是一位乐于助人的助手,能够用自然语言解释数据库查询结果。

    请根据 SQL 查询结果提供清晰简洁的答案。

    如果结果为空或未显示任何数据,请解释未找到匹配的记录。""" ), 

    ( "human" , "问题:{question}\n\nSQL 查询:{sql_query}\n\n查询结果:{result}\n\n请用自然语言回答:" ) 

])
现在相同的数据变成了:
“我找到了两套三居室的房子:一套是橡树街 123 号,售价 98 万;另一套是松树大道 456 号,售价 92 万。”
好多了。
四 全栈:连接一切
构建 RAG 流水线仅仅是开始。我还需要将其封装成一个完整的应用程序。
API层
FastAPI 让这一切变得简单。一个端点即可处理所有查询:
@app.post( "/api/query" , response_model=QueryResponse ) 
async  def  query ( request: QueryRequest ): 
    """使用自然语言查询数据库。""" 

if  not request.question or  not request.question.strip(): 
raise HTTPException(status_code= 400 , detail= "问题不能为空" ) 

try : 
        answer = query_database(request.question) 

return QueryResponse(answer=answer) 
except Exception as e: 
raise HTTPException(status_code= 500 , detail= f"错误:{ str (e)} " )
为什么这很重要:通过将 RAG 管道公开为 API,我可以从任何前端(Web、移动端,甚至是命令行工具)使用它。
前端体验
Streamlit前端提供了一个感觉自然的聊天界面:
如果 prompt := st.chat_input ( "询问有关房地产数据库的问题..." ): 
    st.session_state.messages.append ( {"role": "user" , "content" : prompt}) 


with st.chat_message ("assistant"): with st.spinner

         ( "思考中..." ):             
answer = query_backend (prompt)             st.markdown ( answer)

用户会看到自己的问题、加载指示器,然后是自然语言的答案。感觉就像在和一位知识渊博的助手聊天。
五 经验教训
参与这个项目让我学到了很多宝贵的经验:
1. 提示工程是一门艺术
提示语的质量直接决定了结果的质量。我花了几个小时来完善提示语,测试各种极端情况,并不断迭代。措辞上的细微改动就能显著提高准确率。
提示:务必在提示中包含示例。向学习导师展示优秀的成果是什么样的。
2. 错误处理不容妥协
LLM(大语言模型)功能强大但难以预测。它们可能会生成无效的 SQL、返回意外的格式,或者以各种意想不到的方式出错。因此,强大的错误处理机制至关重要,并非可有可无。
我学会了:
  • 验证所有输入
  • 捕获 SQL 执行错误
  • 提供有用的错误信息
  • 记录调试失败日志
3. 模式感知至关重要
LLM 需要了解你的数据库结构。我会在每次提示中传递数据库模式,这能显著提高查询准确率。如果没有数据库模式,LLM 基本上只能靠猜测。
4. 职责分离带来回报
通过将前端、后端和 RAG 流水线分离,我可以:
  • 分别测试每个组件
  • 轻松替换实现方式
  • 分别缩放不同部分
  • 在多个前端中复用 API
这种模块化设计在我需要进行修改时节省了无数时间。
六 实际应用
这不仅仅是一个测试项目。RAG 在现实世界中有着广泛的应用:
  • 商业智能:非技术用户无需学习 SQL 即可查询数据库。销售团队可以询问“我们本季度的收入是多少?”并立即获得答案。
  • 客户支持:客服人员可以快速查找信息:“显示客户 X 过去一周的所有工单。”
  • 数据探索:分析师可以以对话的方式探索数据集:“价格和面积之间有什么相关性?”
  • 内部工具:任何团队都可以根据自身特定的数据需求构建自定义助手。
七 技术栈:为什么选择这些方案
让我解释一下我选择每项技术的理由:
  • LangChain:它提供的抽象层让构建 RAG 流水线变得易于管理。如果没有它,我得写几百行样板代码。
  • OpenAI GPT-4o-mini:经济高效且速度快。对于 SQL 生成,您不需要功能最强大的模型,而需要的是一个可靠且价格合理的模型。
  • FastAPI:一款现代化的Python Web框架,具备自动生成API文档的功能。非常适合快速构建可用于生产环境的API。
  • Streamlit:数据应用快速原型开发工具。我只用了一个下午就搭建好了整个前端。
  • SQLite:零配置,非常适合学习和开发。只需稍作修改,同一段代码即可用于 PostgreSQL 或 MySQL。
八 挑战与解决方案
建造过程并非一帆风顺。以下是我解决这些挑战的方法:
挑战 1:SQL 注入
问题:LLM 生成的 SQL 可能存在恶意。
解决方案:我使用参数化查询,并在执行前验证所有生成的 SQL。LangChain 的SQLDatabase封装器在这里发挥了作用。
挑战二:处理空结果
问题:当查询没有返回任何数据时会发生什么?
解决方案:格式化提示明确处理了这种情况,指示 LLM 说明未找到匹配的记录。
挑战3:复杂问题
问题:有些问题需要多个查询或聚合。
解决方案:我在提示中提供了清晰的示例,展示了如何处理聚合、连接和复杂筛选。
挑战四:成本管理
问题:每个查询都会发出多个 LLM API 调用(SQL 生成 + 格式化)。
解决方案:我使用 GPT-4o-mini,它性价比很高。对于生产环境,我会为频繁查询添加缓存。
九 下一步:扩展系统
这个项目仅仅是个开始。以下是我正在探索的方向:
  • 实时响应:无需等待完整答案,即可在生成结果的同时实时显示结果。这能使界面响应更加迅速。
  • 多轮对话:记住之前问题的上下文。“公寓怎么样?”这个问题应该让对方明白你仍然在谈论房产。
  • 查询说明:向用户展示生成的 SQL 查询语句。这有助于建立信任并帮助用户学习。
  • 可视化:根据查询结果自动生成图表和图形。
  • 多数据库支持:连接多个数据库,并允许用户跨数据库进行查询。
  • 微调:专门训练用于 SQL 生成的自定义模型,提高准确性并降低成本。
从更宏观的角度来看:为什么这很重要
RAG 代表着我们与数据交互方式的根本性转变。我们正在从:
  • 复杂查询语言自然语言
  • 技术障碍无障碍界面
  • 静态报告动态对话
这不仅仅关乎便利,更关乎数据访问的民主化。当任何人都能提问并获得答案时,我们就为团队如何利用信息开启了新的可能性。
技术已经成熟,工具也已就绪。问题不在于 RAG 是否会成为主流,而在于它能以多快的速度成为主流。
小结:构建未来,从每一次查询做起
开发这款 RAG 应用是一段难忘的旅程。我学习了提示系统设计、数据库设计、API 架构和用户体验等方面的知识。更重要的是,我亲眼见证了人工智能如何弥合人类问题和机器数据之间的鸿沟。
代码是开源的。需要的朋友转发加点赞向作者索取代码链接。
数据交互的未来在于对话式交互。而这一切都始于像这样的项目。


往期推荐

IT项目经理应该如何推动数据治理项目?

面向人工智能企业的5种数据产品采用分发模式

50个我们都在用但没人真正理解的流行语

真正理解人工智能的三个简单原则

高管必须了解哪些内容才能构建数据和人工智能驱动型组织

谈谈实体关系图 (ERD) 与数据血缘关系

一文读懂大数据时代的数据格式特征:CSV、JSON、Parquet、Avro、ORC

为什么数据团队难以完成既定工作:如何克服数据团队语境切换障碍


【声明】内容源于网络
0
0
数据驱动智能
专注数据治理、数字化转型、数据资产、数据要素等方面的实践分享。
内容 1046
粉丝 0
数据驱动智能 专注数据治理、数字化转型、数据资产、数据要素等方面的实践分享。
总阅读2.2k
粉丝0
内容1.0k