---
title: "使用 Langchain 和開源 Llama AI 在 Next.js 打造 AI Bot API Part 3 - 加入向量資料庫，讓AI擁有額外的腦袋"
description: "說明向量資料庫原理，並示範如何用 Ollama Embedding 搭配 Qdrant，讓 AI Agent 擁有自定義知識庫，提升查詢精準度。"
canonical_url: "https://blog.markkulab.net/post/ai-bot-api-parts-3"
author: "Mark Ku"
author_url: "https://blog.markkulab.net/author/mark-ku"
site: "Mark Ku's Blog"
date_published: "2024-10-15 01:20:35 +0800"
category: "AI"
tags: ["llama", "ai", "langchain", "qdrant", "node", "vector-database"]
language: "zh-TW"
license: "CC BY 4.0"
license_url: "https://creativecommons.org/licenses/by/4.0/"
attribution: "轉載或引用請註明作者並附上原文連結"
---

# 使用 Langchain 和開源 Llama AI 在 Next.js 打造 AI Bot API Part 3 - 加入向量資料庫，讓AI擁有額外的腦袋

## 時空背景
因為想要打造產品推薦機器人，但發現自己訓練技術門檻及訓練成本太高，且訓練完後回答有一定層度的隨機性，因此打算用 AI Agent 技術，搭配別人己訓練好的大型語言模型。

為了避免 AI 胡亂生成，因此，此章節我們要使用向量資料庫，讓AI擁有其他方面的知識，並可以提前告知告知大型語言模式，不知道就不要任意生成。

### 首先，我們得先了解什麼是向量資料庫?
向量資料庫其實聽起來很抽向，向量資料庫主要用於存多維度的資料，由大型語言模型將數據其向量化，儲放在向量資料庫，就能依據資料與資料間的距離及其他權重，就能夠推算出相似度。  

簡單來說，向量資料庫會把類似的資料，比如"狗"和"動物"，放得很近，這樣當你搜尋一張狗的圖片時，系統能快速找到跟它相似的圖片或資訊。

![向量資料庫的可視圖表](https://blog.markkulab.net/content/markku/posts/ai-bot-api-parts-3/images/visua-diagrams-in-vector-database.png)

## 為什麼需要向量化 ? 
我們都知道計算數學運算及圖型運算是GPU 的強項，而向量化是把數據轉換成數字向量的過程，這樣能提高計算速度、方便機器學習處理，也讓不同類型的數據（如文字、圖片）變得可以比較，因此特性，所以其實非常適合推薦及圖型搜尋系統。

## 向量資料的示意圖

```
* 向量A: 
[3.2, 4.1, 5.7, 8.9, 1.0]
* 向量B: 
[1.5, 2.8, 9.3, 0.4, 6.5]
* 向量C: 
[7.1, 0.3, 4.8, 5.5, 2.2]
```
P.S. 每個數字代表特定的類別，使用一個預定義的字典來進行映射，但也有可能是範圍的映射。ex: 狗:1 , 貓:2

## 向量化的資料包含什麼 ?
在一大串向量數值中，資料內容包含了關鍵特徵、維度、距離度量、權重分配、上下文的關聯。

### Langchin 如何操作 向量資料庫
取得各種資料來源(Source) > 加載到應用程式(Load)  > 轉換 (Transform)，將資料進行清理、格式化、分割切割，擷取成比較小的段落  > 向量化(Embed) > 儲存到向量資料庫 (Store) > 取回相似的資料(Retrieve)

## 首先，我們先學習用程式將文字向量化 (Embedding)
### 安裝套件
```
npm i @langchain/ollama @langchain/core
```
### 首先，先在 NextJS 中撰寫 Embedding API (/api/lang-chain/ollama-embedding)
```
import { OllamaEmbeddings } from '@langchain/ollama';
import { MemoryVectorStore } from 'langchain/vectorstores/memory';
import { NextApiRequest, NextApiResponse } from 'next';

// 定義 Next.js API 處理器，處理 API 請求
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
    try {
        const { text } = req.body;

        // 檢查是否有文字內容
        if (!text) {
            return res.status(400).json({ message: '文字內容為必填項' });
        }

        // 初始化 OllamaEmbeddings，設定模型和基礎 URL
        const embeddings = new OllamaEmbeddings({
            model: 'llama3.2', // 預設模型
            baseUrl: 'http://localhost:11434', // 預設的 API 基礎 URL
        });

        // 使用 OllamaEmbeddings 將文字轉換為向量並存入 MemoryVectorStore 中
        const vectorstore = await MemoryVectorStore.fromDocuments([{ pageContent: text, metadata: {} }], embeddings);

        // 將向量儲存庫作為檢索器，並設置返回單一文件
        const retriever = vectorstore.asRetriever(1);

        // 檢索最相似的文字，根據給定的查詢進行檢索
        const retrievedDocuments = await retriever.invoke('What is LangChain?');

        // 將文字（text）轉換為向量的方法。
        const singleVector = await embeddings.embedQuery(text);

        // 返回檢索結果
        res.status(200).json({ retrievedDocuments, singleVector });
    } catch (error) {
        // 捕捉錯誤並返回 500 狀態碼
        console.error('Error handling request:', error);
        res.status(500).json({ error: '處理請求時發生錯誤' });
    }
}
```
使用方法
```
curl -X POST http://localhost:3001/api/lang-chain/ollama-embedding \
  -H "Content-Type: application/json" \
  -d '{"text": "LangChain is the framework for building context-aware reasoning applications"}'
```
## 接著，安裝及用程式操作向量資料庫
### 什麼是 [qdrant](https://qdrant.tech/)
Qdrant 是一個非常熱門的向量數據庫，用於儲存和檢索高維度向量資料，適合做相似度查詢，例如推薦系統和文字、以圖搜尋，且擁有簡單易用的 RESTful API 和可視化儀表板來管理和查看向量數據。

### 啟動 qdrant 向量資料庫容器
```
docker run -d --name qdrant-container -p 6333:6333 --restart=always qdrant-container qdrant/qdrant 
```
### 訪問 Dashboard
```
http://localhost:6333/dashboard
```
![Qdrant dashboard](https://blog.markkulab.net/content/markku/posts/ai-bot-api-parts-3/images/qdrant-dashboard.png)
![Qdrant dashboard](https://blog.markkulab.net/content/markku/posts/ai-bot-api-parts-3/images/qdrant-dashboard-2.png)

### 也支援 Restful api 操作向量資料庫
```
##  測試健康度
curl http://127.0.0.1:6333/healthz

## 取得所有的集合
curl -X GET "http://localhost:6333/collections"

```
### 安裝相關套件
```
npm install @qdrant/js-client-rest --save
```
### 接著，先寫一個NextJS API ，使用 Llama 去 Embedding 新增資料到 Collection  (/api/lang-chain/insert-vector-database)
```
import { OllamaEmbeddings } from '@langchain/ollama';
import { QdrantClient } from '@qdrant/js-client-rest'; // 引入 Qdrant 客戶端
import { NextApiRequest, NextApiResponse } from 'next';

// 初始化 Qdrant 客戶端
const qdrantClient = new QdrantClient({ url: 'http://localhost:6333' });

// 初始化 OllamaEmbeddings
const embeddings = new OllamaEmbeddings({
    model: 'llama3.2',
    baseUrl: 'http://localhost:11434',
});

// 定義集合名稱
const collectionName = 'product_vectors';

// 定義向量點的界面
interface VectorPoint {
    id: number | string;
    vector: number[];
    payload: { text: string };
}

// 定義 Next.js API 處理器
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
    try {
        const { text } = req.body;

        // 檢查是否有文字內容;
        if (!text) {
            return res.status(400).json({ message: '文字內容為必填項' });
        }

        // 生成文字的嵌入向量
        const vector: number[] = await embeddings.embedQuery(text);

        // 定義要插入的點
        const points: VectorPoint[] = [
            {
                id: Date.now(), // 使用當前時間作為唯一 ID
                vector: vector, // 插入生成的向量
                payload: { text }, // 存放文字數據
            },
        ];

        // 檢查集合是否已存在，若不存在則創建
        try {
            const collectionExists = await qdrantClient.getCollection(collectionName);

            if (!collectionExists) {
                await qdrantClient.createCollection(collectionName, {
                    vectors: {
                        size: 1000, // 向量大小
                        distance: 'Cosine', // 距離度量使用 Cosine
                    },
                });
            }
        } catch (error) {
            console.log(`集合 ${collectionName} 不存在，正在創建...`);
            await qdrantClient.createCollection(collectionName, {
                vectors: {
                    size: vector.length,
                    distance: 'Cosine',
                },
            });
        }

        // 將向量插入 Qdrant
        await qdrantClient.upsert(collectionName, { points });

        res.status(200).json({ message: '向量已成功插入 Qdrant' });
    } catch (error) {
        console.error('插入向量時發生錯誤:', error);
        res.status(500).json({ message: '插入向量時發生錯誤', error: error });
    }
}

```
使用方法
```
# Dumpling
curl -X POST http://localhost:3001/api/lang-chain/insert-vector-database \
  -H "Content-Type: application/json" \
  -d '{"text": "Dumpling: A small dough pocket, often filled with meat, vegetables, or other ingredients, commonly boiled or steamed. Dumplings, such as Chinese \"shui jiao,\" are a popular dish in many cultures and can be served with various dipping sauces."}'

```

### 再寫一個NextJS API ，透過 Llama 去把相量資料庫，相似的東西取回來 (/api/lang-chain/query-vector-database)
```
import { OllamaEmbeddings } from '@langchain/ollama';
import { QdrantClient } from '@qdrant/js-client-rest'; // 引入 Qdrant 客戶端
import { NextApiRequest, NextApiResponse } from 'next';

// 初始化 qdrant 客戶端
const qdrantClient = new QdrantClient({ url: 'http://localhost:6333' });

// 初始化 OllamaEmbeddings
const embeddings = new OllamaEmbeddings({
    model: 'llama3.2',
    baseUrl: 'http://localhost:11434',
});

// 定義集合名稱
const collectionName = 'product_vectors';

// 定義 Next.js API 查詢處理器
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
    try {
        const { text } = req.body;

        // 檢查是否有文字內容
        if (!text) {
            return res.status(400).json({ message: '文字內容為必填項' });
        }

        // 生成文字的嵌入向量
        const vector: number[] = await embeddings.embedQuery(text);

        // 使用生成的向量在 Qdrant 中查詢相似的向量
        const searchResults = await qdrantClient.search(collectionName, {
            vector: vector,
            limit: 5, // 查詢的最大結果數量
        });

        res.status(200).json({ results: searchResults });
    } catch (error) {
        console.error('查詢向量時發生錯誤:', error);
        res.status(500).json({ message: '查詢向量時發生錯誤', error: error });
    }
}
```
使用方法
```
curl -X POST http://localhost:3001/api/lang-chain/query-vector-database \
  -H "Content-Type: application/json" \
  -d '{"text": "What is LangChain"}'
```

回傳的結果
```
{
  "results": [
    {
      "id": 1728983487057,
      "version": 0,
      "score": 0.41997233, => 相似度
      "payload": {
        "text": "LangChain is the framework for building context-aware reasoning applications" => 回傳的資料
      }
    }
  ]
}
```

## 進階查詢應用
### 插入結構
向量資料庫可以存放結構性資料及非結構資料，依據結構化資料，可以使查詢會更為精準
#### 插入非結構性資料
```
qdrantClient.upsert({
    collection_name: 'your_collection_name',
    points: [
        {
            id: 1, // 唯一識別符
            vector: vector, // 向量資料
            payload: { text: "your_original_text" } // 非結構化資料作為 payload 插入
        }
    ]
});
```
#### 插入結構性資料
```
 qdrantClient.upsert({
    collection_name: 'products_collection',
    points: [
        {
            id: productData.id, // 唯一識別符
            vector: productData.features, // 插入特徵向量
            payload: { // 結構化資料作為 payload 插入
                name: productData.name, // 結構化資料
                price: productData.price, // 結構化資料
                rating: productData.rating, // 結構化資料
                category: productData.category // 結構化資料
            }
        }
    ]
});
```
### 進階查詢參數
向量資料庫可以存放結構性資料舉非結構性資料，所以也能透過特定的屬性去查詢
![結構性資料](https://blog.markkulab.net/content/markku/posts/ai-bot-api-parts-3/images/advance-visua-diagrams-in-vector-database.png)

```
const searchResults = await qdrantClient.search(collectionName, {
    vector: [0.5, 0.2, ...], // 用於查詢的向量
    limit: 5, // 限制返回 5 筆結果
    filter: {
        must: [
            {
                key: 'price',
                range: {
                    gte: 1000, // 價格至少 1000
                    lte: 2000  // 價格最多 2000
                }
            },
        ]
    },
    hnsw_ef: 200, // 調整查詢效率
    with_payload: true, // 返回結果中的 payload 資料
    score_threshold: 0.7, // 只返回相似度高於 0.7 的結果
});

```

## 最近使用 AI 的心得
最近蠻有感的，AI 出現確實拿走了一些基礎工作，透過用ＡI，工程師可以做更多的事，也提昇學習東西效率;隨著各種知識學習門檻變得簡單，很多知識，可以透過和AI 聊天就學會，人們獲取知識也更容易，我們花更多時間擷取知識、洞察、規劃、驗證及下決策。

## 參考資料
* [Qdrant 向量資料庫基本練習](https://blog.darkthread.net/blog/qdrant-w-cs/)
* [使用Qdrant向量資料庫實作語意相似度比對](https://studyhost.blogspot.com/2024/04/qdrant.html)
* [使用Nodejs和Langchain开发大模型](https://blog.csdn.net/Aweii__/article/details/140316743?ops_request_misc=%257B%2522request%255Fid%2522%253A%25229492A1C1-E398-41F8-8F92-1D5B5E1E6D78%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=9492A1C1-E398-41F8-8F92-1D5B5E1E6D78&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-1-140316743-null-null.142^v100^pc_search_result_base5&utm_term=%E4%BD%BF%E7%94%A8Nodejs%E5%92%8CLangchain%E5%BC%80%E5%8F%91%E5%A4%A7%E6%A8%A1%E5%9E%8B&spm=1018.2226.3001.4187)
* [qdran官方網站](https://qdrant.tech/)

## 此系列相關文章
* [使用 Langchain 和開源 Llama AI 在 Next.js 打造 AI Bot API Part 1 - 從了解 lanchain 開始](https://blog.markkulab.net/ai-bot-api-parts-1/)
* [使用 Langchain 和開源 Llama AI 在 Next.js 打造 AI Bot API Part 2 - 打造 AI 具有記憶功能的 AI Agent](https://blog.markkulab.net/ai-bot-api-parts-2/)
* [使用 Langchain 和開源 Llama AI 在 Next.js 打造 AI Bot API Part 3 - 加入向量資料庫，讓AI擁有額外的腦袋](https://blog.markkulab.net/ai-bot-api-parts-3/)
* [使用 Langchain 和開源 Llama AI 在 Next.js 打造 AI Bot API Part 4 - AI產品推薦 API](https://blog.markkulab.net/ai-bot-api-parts-4/)

---

## 關於本文與作者

本文出自 [Mark Ku's Blog](https://blog.markkulab.net/post/ai-bot-api-parts-3)

授權條款： [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) — 轉載或引用請註明作者並附上原文連結

### 關於作者

**[Mark Ku](https://blog.markkulab.net/author/mark-ku)** — Software Solution Provider

- 10+ 年資深軟體工程師，現為 AI 應用 Builder
- 專注大型平台架構設計，從北美電商到AI SaaS訂閱收費系統
- 結合 AI Agent 與自動化，打造高效可演進的產品技術基礎

### 作者開發的免費工具

以下工具皆可免費使用：

- [免費 PDF 簽名工具](https://blog.markkulab.net/tools/pdf-sign): 線上 PDF 簽名工具，瀏覽器內完成手繪、打字、上傳簽名，可拖曳放置、縮放、下載。所有處理都在你的裝置完成，檔案不會上傳。
- [VS Code Refactory](https://blog.markkulab.net/tools/refactory): Refactory 是一款 VS Code 重構擴充套件：34 個重構動作、37 條 code smell 檢查、Code Health 儀表板、18 種語言、534 支測試。懂你的專案慣例：介面放哪、DI 註冊寫在哪、'use client' 該不該加；還會用 git 修改頻率 × 複雜度排出「該先修哪個檔案」，並一鍵把壞味道交給你自己電腦上的 Claude Code 修。免費使用，原始碼不離開你的機器。
- [DB-Kit 資料庫管理工具](https://blog.markkulab.net/tools/db-kit): DB-Kit 是一個用 Tauri + Rust + React 打造的輕量跨平台資料庫管理工具，用單一一致的介面同時管理 MySQL、MariaDB、PostgreSQL、SQL Server、Oracle、SQLite、MongoDB、Redis、Kafka、Elasticsearch 與 RabbitMQ 十一種資料來源：連線密碼以 OS keychain 加密、SSH Tunnel、完整 CRUD、視覺化查詢建構器、多結果集同時顯示、跨連線資料傳輸與比對同步、Excel / CSV 匯入匯出、執行計畫視覺化、ER 圖、排程備份、SQL 壓力測試（p50～p99 延遲百分位）、15 條規則的 SQL 審查、Kafka 訊息瀏覽與監控告警；繁中 / 英文雙語介面，內建 AI 助手（自然語言生成 SQL、AI 審查與調校建議）與命令列工具 dbk。免費開源（MIT），提供 Windows / macOS / Linux 安裝檔。
- [VS Code Super Mermaid](https://blog.markkulab.net/tools/super-mermaid): Super Mermaid 是一款 VS Code 擴充套件：開箱即用的漂亮 Mermaid 圖表，自動上色、即時預覽、滑鼠平移縮放、PNG / SVG 高解析匯出，內建 21 種範本與多種主題。免費開源（MIT）。
- [React Super Mermaid](https://blog.markkulab.net/tools/react-super-mermaid): react-super-mermaid 是一個開源 React 元件庫：一行 <MermaidViewer> 即可渲染漂亮的 Mermaid 圖表，內建 colorful / sketch 主題、平移縮放、圖內搜尋、SVG / PNG 高解析匯出。輕量、SSR 安全、完整 TypeScript 型別。免費開源（MIT）。
- [Jira / Confluence Super Mermaid](https://blog.markkulab.net/tools/jira-super-mermaid): Atlassian Forge app：在 Jira issue 與 Confluence 內文直接寫 Mermaid 語法，畫流程圖、時序圖、狀態機與甘特圖。11 種圖表、SVG / PNG 匯出、明暗主題、完整中日韓文字支援。取得 Runs on Atlassian 資格：圖表存在你自己的站台，app 不呼叫任何第三方服務。免費，即將上架 Atlassian Marketplace。
- [Mermaid 線上預覽](https://blog.markkulab.net/tools/mermaid-preview): 在瀏覽器裡寫 Mermaid、即時看圖，整張圖表壓進網址就能分享。免註冊、不上傳伺服器，相容 mermaid.live 的分享連結。
- [React Intl Phone Number](https://blog.markkulab.net/tools/react-intl-phone-number): react-intl-phone-number 是一個開源 React 元件：framework-agnostic、不依賴 antd，提供 E.164 進出、可搜尋國旗 / 國碼下拉、可配置驗證等級（strict / mobile-strict / loose）、可主題化 CSS 與 i18n，電話邏輯由 google-libphonenumber 驅動。輕量、完整 TypeScript 型別。免費開源（MIT）。
- [Uptime Kuma Cluster](https://blog.markkulab.net/tools/uptime-kuma-cluster): 把單機版 Uptime Kuma 改造成高可用叢集：OpenResty + Lua 智慧負載平衡、MariaDB 共享狀態、健康檢查與自動 Failover，附叢集管理 REST API，一行 Docker Compose 啟動。免費開源（MIT）。
- [特教專案](https://blog.markkulab.net/education): 為特殊教育學生製作的學習教材

### 每日 Podcast

- [科技新鮮事](https://blog.markkulab.net/category/tech-news): 每日精選 AI 與科技趨勢，透過語音摘要快速掌握最新技術動態，涵蓋 AI 應用、軟體架構、DevOps 與工程實戰。 — RSS: https://blog.markkulab.net/feed.xml
- [AI股市蝦聊](https://blog.markkulab.net/category/ai-stock-chat): 每個交易日用 AI 分析台股盤勢，以雙人對話聊當天的盤中觀察與隔日預測。 — RSS: https://blog.markkulab.net/ai-stock-chat/feed.xml

### 電子報

[訂閱電子報](https://blog.markkulab.net/subscribe) — 第一時間收到新文章通知，無垃圾信、隨時可取消訂閱。
