# 使用 OpenAI 构建 RAG 和 Text2SQL AI 应用

## 概述

AI 目前是一个前沿领域，吸引了许多开发者渴望学习。然而，学习过程中常见的一些障碍包括：

- 缺乏基础的 AI 知识；
- 底层基础设施过于复杂；
- 缺乏 AI 应用程序的全面、循序渐进的文档。

为了解决这些问题，我们开发了这个实验室，您将学习：

- 如何使用 **TiDB Serverless** 作为 AI 应用程序的数据存储层，以加速您的 AI 开发过程。
- 什么是 Embedding 以及它的功能；
- 如何在获取 Embedding 向量后加以利用；
- 如何在处理大量数据时使用 RAG 进行数据检索；
- 如何制作一个有用的 Text2SQL AI 应用程序来探索数据集。

> **注意：**
>
> 在进入此实验室之前，您需要注册一个付费的 OpenAI 账户并获取 `OPENAI_API_KEY`。您可以从此 [OpenAI 的文档](https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key) 中找到获取密钥的指南。

### 任务

- **RAG**

  - 将您的 embedding 向量和其他数据保存到 TiDB Serverless 集群。
  - 启动一个 Streamlit 网页来测试您的 RAG 应用程序的性能。
  - 阅读 RAG 演示的代码。

- **Text2SQL**

  - 使用 TiDB Serverless 集群作为 SQL 运行器来更正 LLM。
  - 启动一个 Streamlit 网页以使用您的 Text2SQL 应用程序。
  - 阅读 Text2SQL 的代码。

### 时长

- 本实验应大约需要 1 小时完成。

> [!提示]
> 一旦实验环境部署完成，点击底部栏的 **下一步** 开始 **模块 1**。

## 模块 1：准备运行 Python 代码

### 步骤 1. 打开终端

- 在浏览器中打开你自己的 [VS Code](${LAB:SECURITY_ACCESS_PROXY_URL}?next=${LAB:"/vscode/?folder=/root/tidb-lab-ai"|urlencode})。

- 在弹出的窗口中点击 `Yes, I trust the authors`（是的，我信任作者）。

  ![90%](https://lab-static.pingcap.com/quick-demo/vscode-trust.jpg)

- 点击左上角的 `≡`，选择 `Terminal`，然后点击 `New Terminal` 打开一个新的终端窗口。

  ![90%](https://public-pingcap-innovation.s3.us-west-2.amazonaws.com/demo-405/405-open-terminal-0307.jpg)

### 步骤 2. 安装 Python 依赖

安装 Python 依赖。

```bash
pip install -r requirements.txt
```

```output
Collecting aiohappyeyeballs==2.6.1 (from -r requirements.txt (line 1))
  Downloading aiohappyeyeballs-2.6.1-py3-none-any.whl.metadata (5.9 kB)
Collecting aiohttp==3.11.16 (from -r requirements.txt (line 2))
  Downloading aiohttp-3.11.16-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (7.7 kB)
Collecting aiosignal==1.3.2 (from -r requirements.txt (line 3))
  Downloading aiosignal-1.3.2-py2.py3-none-any.whl.metadata (3.8 kB)
...
[notice] A new release of pip is available: 24.0 -> 25.1
[notice] To update, run: pip install --upgrade pip
```

### 步骤 3. 创建 .env 文件

- 在 `openai` 文件夹中创建 `.env`。

  ![90%](https://public-pingcap-innovation.s3.us-west-2.amazonaws.com/demo-409/409-new-env-0427.jpg)

- 向 `.env` 文件中添加以下内容并保存。请将 `<Your OpenAI API Key>` 占位符更改为您的实际 OpenAI API Key。

  ```properties
  OPENAI_API_KEY=<Your OpenAI API Key>

  TIDB_HOST=${LAB:SERVERLESS_CLUSTER_HOST}
  TIDB_PORT=${LAB:SERVERLESS_CLUSTER_PORT}
  TIDB_USERNAME='${LAB:SERVERLESS_CLUSTER_USERNAME}'
  TIDB_PASSWORD='${LAB:SERVERLESS_CLUSTER_PASSWORD}'
  TIDB_DATABASE=${LAB:SERVERLESS_CLUSTER_DATABASE_NAME}
  ```

## 模块 2：运行 RAG 演示

准备好之后，我们可以运行 RAG 演示来连接到 TiDB Cloud Serverless 集群，保存您的 Embedding，检索相关数据，并生成答案。

### 步骤 1. 运行 Streamlit 服务

  然后使用 `streamlit` 命令行工具运行此脚本：

  ```bash
  streamlit run openai/rag.py --server.port 8501
  ```

  ```output
  Collecting usage statistics. To deactivate, set browser.gatherUsageStats to false.


  You can now view your Streamlit app in your browser.

  Local URL: http://localhost:8501
  Network URL: http://172.17.0.2:8501
  External URL: http://${LAB:HOST_MONITOR1_PUBLIC_IP}:8501
  ```

  ![90%](https://public-pingcap-innovation.s3.us-west-2.amazonaws.com/demo-409/409-rag-streamlit-start-0427.jpg)

### 步骤 2. 使用 RAG 演示网页

- 打开您的 [RAG 示例网页](http://${LAB:HOST_MONITOR1_PUBLIC_IP}:8501/)。

  ![90%](https://public-pingcap-innovation.s3.us-west-2.amazonaws.com/demo-409/409-rag-frontend.jpg)

- 打开名为 `All Chunks in the Database` 的文件夹，以查看当前 TiDB Serverless 集群中的数据。由于我们尚未保存任何数据，所以它应该是空的。

  ![90%](https://public-pingcap-innovation.s3.us-west-2.amazonaws.com/demo-409/409-rag-start-data.jpg)

- 点击 `Save Data` 以保存我们为此演示准备的文本数据。您可以在下一步中看到原始数据，它是一个字符串列表。
- 保存数据后，您会看到 TiDB Serverless 集群中的数据显示在 `All Chunks in the Database` 文件夹中。我们有 3 列，分别是 `id`，`text` 和 `text_vec`。

  ![90%](https://public-pingcap-innovation.s3.us-west-2.amazonaws.com/demo-409/409-rag-saved-data.jpg)

- 如果您只想检索上下文，可以点击 `Retrieval Only` 选项。然后输入您的问题。
- 假设我想问一个问题：

  ```text
  What is TiDB?
  ```

- 点击 `Send` 按钮开始检索。

  ![90%](https://public-pingcap-innovation.s3.us-west-2.amazonaws.com/demo-409/409-rag-retrieve.jpg)

- 如您所见，`TiDB is a distributed SQL database with HTAP capabilities.` 的相似度得分最高。

> **提示：**
>
> 您可以按下按钮并拖动列的顶部以更改表格的顺序。
> 您还可以将数据下载为 CSV 表格。
> ![70%](https://public-pingcap-innovation.s3.us-west-2.amazonaws.com/demo-409/409-rag-tips-download-0427.jpg)

- 最后，让我们尝试让 RAG 给我们答案。
- 在左上角将 `Retrieval Limit` 改为 `1`，这意味着我们只会检索与您输入的问题最相似的文本。

  ![90%](https://public-pingcap-innovation.s3.us-west-2.amazonaws.com/demo-409/409-rag-retrival-limit-1.jpg)

- 将选项更改为 `RAG Q&A`，然后点击 `Send`。
- 这次，演示将给我们答案而不是材料列表。

  ![90%](https://public-pingcap-innovation.s3.us-west-2.amazonaws.com/demo-409/409-rag-answer-0427.jpg)

### 步骤 3. RAG 演示代码阅读

让我们回到 [VS Code](http://${LAB:HOST_MONITOR1_PUBLIC_IP}:3000/?folder=/root/tidb-lab-ai) 并点击代码文件 `openai/rag.py`。

1. 使用 `dotenv.load_dotenv()` 从 `.env` 加载参数后，下一步是我们的提示模板。可以看到我们将使用这个模板来插入 `{context}` 和 `{question}`，然后让 LLM 根据上下文给出答案。

  ```python
  # RAG prompt template
  RAG_PROMPT_TEMPLATE = """Answer the question based on the following reference information.

  Reference Information:
  {context}

  Question: {question}

  Please answer:"""
  ```

2. 我们将连接到 TiDB Serverless 集群。实验室 VM 启动时我们已经设置了这些环境。如果您需要在 TiDB 实验室之外使用这段代码，您可能需要在 [TiDB Cloud](https://tidbcloud.com/clusters) 中找到这些参数，并手动设置。

  ```python
  db = TiDBClient.connect(
      host=os.getenv("TIDB_HOST"),
      port=int(os.getenv("TIDB_PORT")),
      username=os.getenv("TIDB_USERNAME"),
      password=os.getenv("TIDB_PASSWORD"),
      database=os.getenv("TIDB_DATABASE"),
      enable_ssl=True,
  )
  ```

3. 我们定义了 embedding 模型和 LLM 的名称。然后用这个 embedding 模型定义一个 `Chunk` 表。这是因为向量索引的长度是固定的，我们必须在创建表之前决定要使用的模型。

  ```python
  text_embed = EmbeddingFunction("openai/text-embedding-3-small")
  llm_model = "openai/gpt-4o"


  # Define the Chunk table
  table_name = "chunks"
  class Chunk(TableModel, table=True):
      __tablename__ = table_name
      __table_args__ = {"extend_existing": True}

      id: int = Field(primary_key=True)
      text: str = Field()
      text_vec: Optional[Any] = text_embed.VectorField(
          source_field="text",
      )
  ```

4. 我们将示例 chunks 写为字符串列表。在实际应用中，您可能需要制定策略来分隔整个文档，这个过程在 RAG 场景中称为“chunking”。

  ```python
  sample_chunks = [
      "Llamas are camelids known for their soft fur and use as pack animals.",
      "Python's GIL ensures only one thread executes bytecode at a time.",
      "TiDB is a distributed SQL database with HTAP capabilities.",
      "Einstein's theory of relativity revolutionized modern physics.",
      "The Great Wall of China stretches over 13,000 miles.",
      "Ollama enables local deployment of large language models.",
      "HTTP/3 uses QUIC protocol for improved web performance.",
      "Kubernetes orchestrates containerized applications across clusters.",
      "Blockchain technology enables decentralized transaction systems.",
      "GPT-4 demonstrates remarkable few-shot learning capabilities.",
      "Machine learning algorithms improve with more training data.",
      "Quantum computing uses qubits instead of traditional bits.",
      "Neural networks are inspired by the human brain's structure.",
      "Docker containers package applications with their dependencies.",
      "Cloud computing provides on-demand computing resources.",
      "Artificial intelligence aims to mimic human cognitive functions.",
      "Cybersecurity protects systems from digital attacks.",
      "Big data analytics extracts insights from large datasets.",
      "Internet of Things connects everyday objects to the internet.",
      "Augmented reality overlays digital content on the real world.",
  ]
  ```

5. 我们尝试打开名为 `chunks` 的表。如果数据库中尚不存在该表（这将是首次运行时的情况），我们会根据之前定义的 `Chunk` 模式创建它。这确保了表已准备好用于存储和查询数据。

  ```python
  table = db.open_table(table_name)
  if table is None:
      table = db.create_table(schema=Chunk)
  ```

6. 下一部分使用 Streamlit 设置用户界面。我们将网页的标题设置为 `🔍 RAG Demo` 并添加子标题“数据库操作”。

  ```python
  st.title("🔍 RAG Demo")

  st.subheader("Database Operations")
  ```

7. 我们为数据库操作按钮创建两个列。“重置”按钮单击后，会截断表（删除所有数据）并重新运行 Streamlit 应用以刷新视图。警告信息显示在其下方。“Save Data” 按钮会在当前表为空时插入 `sample_chunks`，然后重新运行应用。保存 `text` 到表中时，`pytidb` 会调用嵌入模型向量化您的 `text` 数据。这是由表字段定义 `text_vec: Optional[Any] = text_embed.VectorField(source_field="text")` 确定的。

  ```python
  left, right = st.columns(2)

  if left.button("Reset", type="primary"):
      left.write("Resetting...")
      table.truncate()
      st.rerun()

  left.markdown("This option <span style='color: red;'>will delete all data</span></h3>", unsafe_allow_html=True)

  if right.button("Save Data"):
      right.write("Saving...")
      # insert sample chunks
      if table.rows() == 0:
          chunks = [Chunk(text=text) for text in sample_chunks]
          table.bulk_insert(chunks)
          st.rerun()
  ```

8. 创建一个标记为 `📁 All Chunks in the Database` 的扩展部分。它从 `table` 中查询所有行，将数据转换为 Pandas DataFrame，并在扩展中显示。如果表为空，则显示信息消息。

  ```python
  with st.expander("📁 All Chunks in the Database", expanded=False):
      chunks = table.query()
      if chunks:
          data = [{'id': chunk.id, 'text': chunk.text, 'text_vec': chunk.text_vec} for chunk in chunks]
          df = pd.DataFrame(data)
          st.dataframe(df)
      else:
          st.info("No data found in database.")
  ```

9. 我们添加指导性文本并设置用户输入元素。单选按钮让用户选择操作模式（`Retrieval Only` 或 `RAG Q&A`）。侧边栏的滑块让用户调整检索的 chunk 数量（`Retrieval Limit`）。最后，一个文本输入框提示用户输入他们的问题。

  ```python
  st.write(
      "Enter your question, and the system will retrieve relevant knowledge and generate an answer"
  )
  mode = st.radio("Select Mode:", ["Retrieval Only", "RAG Q&A"])

  query_limit = st.sidebar.slider("Retrieval Limit", min_value=1, max_value=20, value=5)
  query = st.text_input("Enter your question:", "")
  ```

10. 这一块为处理用户点击 "Send" 按钮且输入了问题后的逻辑。在处理过程中会显示一个加载动画。核心操作是 `table.search(query).limit(query_limit)`，它在 `text_vec` 列上根据用户的查询进行向量相似度搜索，并检索出最相似的 `query_limit` 条记录。

```python
if st.button("Send") and query:
    with st.spinner("Processing..."):
        # Retrieve relevant chunks
        res = table.search(query).limit(query_limit)
```

11. 根据选择的 `mode`，应用程序要么直接展示检索结果，要么继续执行 RAG（检索增强生成）流程。

- 如果选择了 `Retrieval Only`（仅检索）：会直接将检索到的块（转换成 Pandas DataFrame）展示出来。
- 如果选择了 `RAG Q&A`：

  - 从检索结果中提取 `text` 内容；
  - 通过拼接提取的文本，构建 `context`；
  - 使用生成的 `context` 和用户的 `question` 填充 `RAG_PROMPT_TEMPLATE`；
  - 调用 LLM（使用 `litellm.completion`）发送构建好的提示词以获取答案；
  - 使用自定义 CSS 样式展示 LLM 的回复；
  - 并包含一个可展开的区域（"📚 Retrieved Knowledge"），显示用于生成答案的检索块。

如果初步搜索 (`res`) 没有返回任何结果，会显示一条信息提示。

```python
        if res:
            if mode == "Retrieval Only":
                st.write("### Retrieval Results:")
                st.dataframe(res.to_pandas())
            else:
                text = [chunk.text for chunk in res.to_rows()]

                # Build RAG prompt
                context = "\n".join(text)
                prompt = RAG_PROMPT_TEMPLATE.format(context=context, question=query)

                # Call LLM to generate answer
                response = completion(
                    model=llm_model,
                    messages=[{"content": prompt, "role": "user"}],
                )

                st.markdown(f"### 🤖 {llm_model}")
                st.markdown(
                    """
                <style>
                .llm-response {
                    background: rgba(255, 255, 255, 0.05);
                    padding: 25px;
                    border-radius: 15px;
                    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
                    border: 1px solid rgba(255, 255, 255, 0.1);
                    margin: 15px 0;
                    font-size: 1.1em;
                    line-height: 1.6;
                    color: #e1e4e8;
                }
                .llm-response:hover {
                    background: rgba(255, 255, 255, 0.08);
                    box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3);
                    transition: all 0.3s ease;
                }
                </style>
                """,
                    unsafe_allow_html=True,
                )

                # show the response
                st.markdown(
                    f'<div class="llm-response">{response.choices[0].message.content}</div>',
                    unsafe_allow_html=True,
                )

                with st.expander("📚 Retrieved Knowledge", expanded=False):
                    st.dataframe(res.to_pandas())
        else:
            st.info("No relevant information found")
```

## 模块 3：运行 Text2SQL 示例

接下来的示例，我们将探索另一种 AI 应用。它使用 TiDB Cloud Serverless 集群作为 SQL 校验器。流程如下：

1. 从你的查询生成 SQL
2. 在 TiDB Serverless 集群上执行
3. 获取 TiDB Serverless 集群的反馈或错误信息
4. 自动修正，直到得到正确结果

### 第一步：运行 Streamlit 服务

打开一个新的终端：

![90%](https://public-pingcap-innovation.s3.us-west-2.amazonaws.com/demo-409/409-text2sql-new-terminal-0427.jpg)

然后使用 `streamlit` 命令行工具运行脚本：

```bash
streamlit run openai/text2sql.py --server.port 8502
```

```output
Collecting usage statistics. To deactivate, set browser.gatherUsageStats to false.

You can now view your Streamlit app in your browser.

Local URL: http://localhost:8502
Network URL: http://172.17.0.2:8502
External URL: http://${LAB:HOST_MONITOR1_PUBLIC_IP}:8502
```

### 第二步：使用 Text2SQL 示例网页

- 打开你的 [Text2SQL 示例网页](http://${LAB:HOST_MONITOR1_PUBLIC_IP}:8502/)。

  ![90%](https://public-pingcap-innovation.s3.us-west-2.amazonaws.com/demo-409/409-text2sql-frontend-0427.jpg)

- 然后，可以在网页上提出一个问题，例如：

  ```text
  How many tables in the database?
  ```

- 按下 <kbd>Enter</kbd> 键，系统会生成 SQL 并在 TiDB Serverless 集群上运行。

  ![90%](https://public-pingcap-innovation.s3.us-west-2.amazonaws.com/demo-409/409-text2sql-query-0427.jpg)

- 你也可以试另一个问题，比如：

  ```text
  What is the table name in the database?
  ```

  ![90%](https://public-pingcap-innovation.s3.us-west-2.amazonaws.com/demo-409/409-text2sql-query-2-0427.jpg)

- 这是因为在模块 2 中，我们创建了 `chunks` 表。
- 你可以根据这个数据集自由尝试想问的问题。

### 第三步：阅读 Text2SQL 示例代码

接下来回到 [VS Code](http://${LAB:HOST_MONITOR1_PUBLIC_IP}:3000/?folder=/root/tidb-lab-ai)，点击 `openai/text2sql.py` 文件。

1. 首先，我们从 `.env` 文件中加载环境变量，其中包含敏感信息，如 API 密钥和数据库凭证。

```python
dotenv.load_dotenv()
```

2. 定义一个 Pydantic 模型 `QuestionSQLResponse`。它规定了我们期望从 OpenAI API 返回的结构，包括原始问题、生成的 SQL 以及初步的 Markdown 表达。使用 Pydantic 确保 API 响应符合要求。

```python
class QuestionSQLResponse(BaseModel):
    question: str
    sql: str
    markdown: str
```

3. 设置 Streamlit 应用页面的标题。

```python
st.markdown("## 📖 Text2SQL")
```

4. 使用加载的环境变量连接 TiDB Serverless 数据库，同时初始化 OpenAI 客户端。

```python
db = TiDBClient.connect(
    host=os.getenv("TIDB_HOST"),
    port=int(os.getenv("TIDB_PORT")),
    username=os.getenv("TIDB_USERNAME"),
    password=os.getenv("TIDB_PASSWORD"),
    database=os.getenv("TIDB_DATABASE"),
    enable_ssl=True,
)
oai = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
```

5. 初始化 Streamlit 的会话状态。`st.session_state` 是一个类似字典的对象，在单次会话中能保持状态。我们创建两个空列表 `generated` 和 `past`，分别保存助手的回答和用户的输入，形成聊天历史。

```python
for item in ["generated", "past"]:
    if item not in st.session_state:
        st.session_state[item] = []
```

6. 获取当前数据库中所有表的建表语句（`SHOW CREATE TABLE`）。这些表定义信息是生成正确 SQL 查询的重要上下文。

```python
table_definitions = []
current_database = db._db_engine.url.database
for table_name in db.table_names():
    table_definitions.append(db.query(f"SHOW CREATE TABLE `{table_name}`").to_rows()[0])
```

7. 定义 `on_submit` 函数，当用户输入问题时触发。

```python
def on_submit():
    user_input = st.session_state.user_input
    if user_input:
        # ...（LLM 调用与处理）...
```

8. 在 `on_submit` 中，如果用户有输入：

- 使用 `gpt-4o-mini` 调用 OpenAI API（`oai.beta.chat.completions.parse`）；
- 提供系统提示词，指示模型扮演资深 DBA，撰写符合 MySQL 规范的 SQL；
- 传递表定义和数据库名作为上下文；
- 指定返回格式为 `QuestionSQLResponse`。

```python
    response = (
        oai.beta.chat.completions.parse(
            model="gpt-4o-mini",
            messages=[
                {
                    "role": "system",
                    "content": f"""
                    You are a very senior database administrator who can write SQL very well,
                    please write MySQL SQL to answer user question,
                    Use backticks to quote table names and column names,
                    here are some table definitions in database,
                    the database name is {current_database}

"""
                    + "
".join("|".join(t) for t in table_definitions),
                },
                {"role": "user", "content": f"Question: {user_input}
"},
            ],
            response_format=QuestionSQLResponse,
        )
        .choices[0]
        .message.parsed
    )
    st.session_state.past.append(user_input)
```

9. 安全检查，防止生成危险 SQL。如果生成的 SQL 包含 `INSERT` 或 `UPDATE`，则提示错误并停止执行。

```python
    if "insert" in response.sql.lower() or "update" in response.sql.lower():
        st.error(
            "The generated SQL is not a SELECT statement, please check it carefully before running it."
        )
        st.stop()
```

10. 执行生成的 SQL 查询。

- 使用 `try...except` 捕获查询异常；
- 成功后，提取结果并格式化成字符串；
- 再次调用 OpenAI API，将问题、SQL、初始 Markdown 和实际结果整合为最终的 Markdown 格式回答；
- 将最终回答存入 `st.session_state.generated`；
- 如果执行失败，记录错误信息。

```python
    # Execute the SQL query and set the result
    answer = None
    try:
        rows = db.query(response.sql).to_rows()
        sql_result = "
".join(str(row) for row in rows)

        answer = (
            oai.chat.completions.create(
                model="gpt-4o-mini",
                messages=[
                    {
                        "role": "system",
                        "content": "You are a markdown formatter, format the user input to markdown, format the data row into markdown tables.",
                    },
                    {
                        "role": "user",
                        "content": f"""
                    Question: {response.question}


                    SQL: {response.sql}


                    Markdown: {response.markdown}


                    Result: {sql_result}""",
                    },
                ],
            )
            .choices[0]
            .message.content
        )
        st.session_state.generated.append(answer)
    except Exception as e:
        st.session_state.generated.append(f"Error: {e}")
```

11. 设置聊天界面元素。

- 添加小标题 "User Query"；
- 使用 `st.container` 分组输入框和聊天记录；
- 创建聊天输入控件 (`st.chat_input`)，绑定到 `user_input`；
- 创建空占位元素 (`st.empty()`) 用于动态渲染聊天记录。

```python
st.markdown("##### User Query")
with st.container():
    st.chat_input(
        "Input your question, e.g. how many tables in the database?",
        key="user_input",
        on_submit=on_submit,
    )

    chat_placeholder = st.empty()
```

12. 渲染聊天历史记录到 `chat_placeholder` 中。

- 以最新消息优先的顺序，遍历 `generated`（助手）和 `past`（用户）内容；
- 使用 `st.chat_message("user")` 和 `st.chat_message("assistant")` 渲染对应样式的对话框。

```python
    with chat_placeholder.container():
        for i in range(len(st.session_state["generated"]) - 1, -1, -1):
            with st.chat_message("user"):
                st.write(st.session_state["past"][i])
            with st.chat_message("assistant"):
                st.write(st.session_state["generated"][i])
```

## 结论

在本实验中，你学习了如何使用 Python、TiDB Serverless 和 OpenAI 构建两种类型的 AI 应用：

- **检索增强生成（RAG）：** 学会了如何使用 `pytidb` 连接 TiDB Serverless，将文本数据及其向量嵌入存储，基于向量相似度进行检索，并用大型语言模型（如 `gpt-4o`）根据检索上下文生成答案。你还搭建了基于 Streamlit 的交互界面。
- **自然语言到 SQL（Text-to-SQL）：** 探索了如何使用大型语言模型（`gpt-4o-mini`）将自然语言问题转换为 SQL 查询，如何提供 TiDB 表结构作为上下文辅助生成准确 SQL，并执行查询与结果格式化，同样通过 Streamlit 进行展示。

你实际动手体验了数据库操作、向量检索和大模型结合的开发过程。

接下来你可以：

- 进一步探索本实验的源代码 [tidb-samples/tidb-lab-ai](https://github.com/tidb-samples/tidb-lab-ai)。
- 在 [Github 仓库](https://github.com/pingcap/pytidb) 了解更多 `pytidb` 的特性。
- 在 [https://tidbcloud.com](https://tidbcloud.com) 获取自己的 TiDB Cloud Serverless 集群。
- 在 [tidb-samples](https://github.com/tidb-samples) Github 组织查看更多示例项目。