---
title: "使用 Langchain 和開源 Llama AI 在 Next.js 打造 AI Bot API Part 4 - AI產品推薦 API"
description: "整合 Qdrant 向量資料庫與 Langchain AI Agent，實作具備結構化篩選條件的電商產品推薦 API，並說明提升查詢精準度的多種策略。"
canonical_url: "https://blog.markkulab.net/post/ai-bot-api-parts-4"
author: "Mark Ku"
author_url: "https://blog.markkulab.net/author/mark-ku"
site: "Mark Ku's Blog"
date_published: "2024-10-16 19:00:35 +0800"
category: "AI"
tags: ["llama", "ai", "langchain", "qdrant", "node", "recommendation"]
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 4 - AI產品推薦 API

## 前言
上一篇，談到[透過向量資料庫，我們讓AI 擁有自己的知識庫](https://blog.markkulab.net/ai-bot-api-parts-3/)，這篇想要做產品推薦的API。

## 首先，我先透過程式，插入大量測試資料到我的向量資料庫，內容包含了
* 非結構性資料 - 貓狗、水餃的文字說明。
* 結構性資料 - 電腦、筆電、電腦週邊配備，將其規格屬性也寫入到向量資料庫。

### 接著， 回到向量資料庫(qdrant)的Dashboard > 點選 Visuallize
```
http://localhost:6333/dashboard
```
![Qdrant Dashbaord ](https://blog.markkulab.net/content/markku/posts/ai-bot-api-parts-4/images/qdrant-dashbaord-1.png)

### 點選Run，之後，就能看見向量資料庫所裡資料，像資料宇宙般程現。
![Data universe](https://blog.markkulab.net/content/markku/posts/ai-bot-api-parts-4/images/qdrant-dashbaord-2.png)

### 接著選取資料圓點後，點選OPEN GRAPH 按鈕，就能展開被選取資料的關係圖，並將滑鼠滑動到連接的線上，就能得知，資料與資料間的相似度。
![Graph](https://blog.markkulab.net/content/markku/posts/ai-bot-api-parts-4/images/qdrant-dashbaord-3.png)

發現有趣事，居然動物和水餃相似度居然有 0.8，動物和HP筆電的相似度是0.06，難不成因為水餃是用動物做的嗎? XD   

### 提昇向量資料庫的搜尋精準度
有趣的是真實的世界裡，水餃和動物，並不太相關，資料間的相似度是模型透過特定演算法得出，且看起來是不能修改，因此，必須透過其他方式解決查詢精準度的問題，以下是我整理出可行的方式 :
 
#### 1. 寫入向量資料庫時，加入結構化的資料，並利用篩選條件去查詢
```
 qdrantClient.filter({
    collection_name: 'products_collection',
    filter: {
        must: [
            { key: 'price', range: { gte: 50, lte: 150 } }, // 價格範圍查詢
            { key: 'category', match: { value: '筆記型電腦' } } // 分類查詢
        ]
    },
    limit: 5
});
```
### 2. 自定義相似度函數，增減語義權重

```
...
  async function adjustSimilarity(results) {
    for (let result of results) {
      if (result.payload.productName === "動物") {
        result.score *= 0.5;  // 調整「動物」的相似度
      }
    }
    return results;
  }

  // 進行相似度搜尋
  const query = "水餃";
  const topK = 10;
  const results = await client.search(query, {
    vector: queryEmbedding,
    limit: topK,
  });

  // 調整特定結果中的相似度
  const adjustedResults = await adjustSimilarity(results);

  // 打印調整後的結果
  console.log(adjustedResults);
... 
```
### 3. 從向量資料庫拿回來的結果集，再交給AI決定.
```
...
        const searchResults = await qdrantClient.search(collectionName, {
            vector: vector,
            limit: 5, // 查詢最相似的 5 條結果
        });
		
        const faqContext = searchResults
            .map((result: any) => `Q: ${result.payload.question}\nA: ${result.payload.answer} score: ${result.score}`)
            .join('\n\n');

        // 初始化 ChatOllama
        const model = new ChatOllama({
            model: 'llama3.2',
            temperature: 0,
            maxRetries: 2,
            baseUrl: 'http://localhost:11434',
        });

        // 使用 ChatOllama 根據用戶問題進行進一步分析
        const response = await model.invoke(
            `Here is a list of FAQs and their answers:\n\n${faqContext}\n\nBased on the question "${text}", which answer is the most relevant? Please choose the best one.`
        );
...
```

## 範例程式碼部份
### 首先，先撰寫插入大量產品資料 (/api/lang-chain/insert-products-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; price: number; rating: number; reviews: number };
}

// 定義產品數據
const products = [
    {
        ProductID: 1,
        ProductName: 'Dell XPS 15',
        Category: '筆記型電腦',
        SalesUnits: 120,
        Revenue: 240000,
        Price: 2000,
        Rating: 4.5,
        Reviews: 300,
        SaleDate: '1/1/2024',
        LastRestockDate: '1/15/2024',
        StockLevel: 50,
        Supplier: 'Dell Inc.',
        URL: 'https://www.dell.com/XPS15',
    },
    {
        ProductID: 2,
        ProductName: 'Logitech MX Master 3',
        Category: '配件',
        SalesUnits: 200,
        Revenue: 180000,
        Price: 900,
        Rating: 4.8,
        Reviews: 500,
        SaleDate: '1/2/2024',
        LastRestockDate: '1/12/2024',
        StockLevel: 150,
        Supplier: 'Logitech',
        URL: 'https://www.logitech.com/MX3',
    },
    {
        ProductID: 3,
        ProductName: 'HP Spectre x360',
        Category: '筆記型電腦',
        SalesUnits: 180,
        Revenue: 270000,
        Price: 1500,
        Rating: 4.7,
        Reviews: 450,
        SaleDate: '1/3/2024',
        LastRestockDate: '1/14/2024',
        StockLevel: 80,
        Supplier: 'HP',
        URL: 'https://www.hp.com/SpectreX360',
    },
    {
        ProductID: 4,
        ProductName: 'Lenovo ThinkPad X1',
        Category: '筆記型電腦',
        SalesUnits: 130,
        Revenue: 195000,
        Price: 1500,
        Rating: 4.6,
        Reviews: 400,
        SaleDate: '1/4/2024',
        LastRestockDate: '1/13/2024',
        StockLevel: 70,
        Supplier: 'Lenovo',
        URL: 'https://www.lenovo.com/ThinkPadX1',
    },
    {
        ProductID: 5,
        ProductName: 'Apple MacBook Pro',
        Category: '筆記型電腦',
        SalesUnits: 250,
        Revenue: 500000,
        Price: 2000,
        Rating: 4.8,
        Reviews: 600,
        SaleDate: '1/5/2024',
        LastRestockDate: '1/10/2024',
        StockLevel: 100,
        Supplier: 'Apple',
        URL: 'https://www.apple.com/MacBookPro',
    },
    {
        ProductID: 6,
        ProductName: 'Asus ROG Strix',
        Category: '合式機',
        SalesUnits: 90,
        Revenue: 180000,
        Price: 2000,
        Rating: 4.5,
        Reviews: 350,
        SaleDate: '1/6/2024',
        LastRestockDate: '1/6/2024',
        StockLevel: 40,
        Supplier: 'Asus',
        URL: 'https://www.asus.com/ROGStrix',
    },
    {
        ProductID: 7,
        ProductName: 'Acer Predator Helios',
        Category: '筆記型電腦',
        SalesUnits: 75,
        Revenue: 150000,
        Price: 2000,
        Rating: 4.4,
        Reviews: 320,
        SaleDate: '1/7/2024',
        LastRestockDate: '1/9/2024',
        StockLevel: 40,
        Supplier: 'Acer',
        URL: 'https://www.acer.com/PredatorHelios',
    },
    {
        ProductID: 8,
        ProductName: 'Dell UltraSharp',
        Category: '顯示器',
        SalesUnits: 300,
        Revenue: 210000,
        Price: 700,
        Rating: 4.5,
        Reviews: 500,
        SaleDate: '1/8/2024',
        LastRestockDate: '1/20/2024',
        StockLevel: 200,
        Supplier: 'Dell Inc.',
        URL: 'https://www.dell.com/UltraSharp',
    },
    {
        ProductID: 9,
        ProductName: 'Logitech G Pro X',
        Category: '鍵盤',
        SalesUnits: 150,
        Revenue: 300000,
        Price: 200,
        Rating: 4.7,
        Reviews: 450,
        SaleDate: '1/9/2024',
        LastRestockDate: '1/12/2024',
        StockLevel: 50,
        Supplier: 'Logitech',
        URL: 'https://www.logitech.com/GProX',
    },
    {
        ProductID: 10,
        ProductName: 'HP Omen',
        Category: '筆記型電腦',
        SalesUnits: 100,
        Revenue: 150000,
        Price: 1500,
        Rating: 4.5,
        Reviews: 370,
        SaleDate: '1/10/2024',
        LastRestockDate: '1/11/2024',
        StockLevel: 60,
        Supplier: 'HP',
        URL: 'https://www.hp.com/Omen',
    },
];

// 定義 Next.js API 處理器 
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
    try {
        // 遍歷產品，將每個產品插入 Qdrant
        for (const product of products) {
            const text = `${product.ProductName} - ${product.Category}`;

            // 生成文本的嵌入向量
            const vector: number[] = await embeddings.embedQuery(text);

            // 定義要插入的點
            const points: VectorPoint[] = [
                {
                    id: Date.now(), // 使用當前時間作為唯一 ID
                    vector: vector, // 插入生成的向量
                    payload: {
                        text, // 存放文本數據
                        price: product.Price,
                        rating: product.Rating,
                        reviews: product.Reviews,
                    },
                },
            ];

            // 檢查集合是否已存在，若不存在則創建
            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 });
    }
}

```
API 執行
```
curl -X GET http://localhost:3001/api/lang-chain/insert-products-vector-database 
```

### 接著，撰寫查詢產品資料的API (/api/lang-chain/query-products-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, category } = req.body;

        // 檢查是否有文本內容
        if (!text) {
            return res.status(400).json({ message: '文本內容為必填項' });
        }

        // 生成文本的嵌入向量
        const vector: number[] = await embeddings.embedQuery(text);

        // 構建搜索參數
        const searchParams: any = {
            vector: vector,
            limit: 5, // 查詢的最大結果數量
        };

        // 如果提供了 category，則添加過濾條件
        if (category) {
            searchParams.filter = {
                must: [
                    {
                        key: 'category',
                        match: { value: category }, // 動態應用類別過濾
                    },
                ],
            };
        }

        // 使用生成的向量在 Qdrant 中查詢相似的向量
        const searchResults = await qdrantClient.search(collectionName, searchParams);

        // 格式化查詢結果，返回產品詳細信息
        const formattedResults = searchResults.map((result: any) => {
            const { payload, score } = result;

            return {
                productName: payload.text, // 使用生成時的產品名稱
                price: payload.price, // 產品價格
                rating: payload.rating, // 產品評分
                reviews: payload.reviews, // 產品評論數量
                similarityScore: score, // 相似度分數
            };
        });

        res.status(200).json({ results: formattedResults });
    } catch (error) {
        console.error('查詢向量時發生錯誤:', error);
        res.status(500).json({ message: '查詢向量時發生錯誤', error: error });
    }
}
```
### 執行API
```
curl -X POST http://localhost:3001/api/lang-chain/query-products-vector-database \
  -H "Content-Type: application/json" \
  -d '{
  "text": "幫我推薦一台筆電",
  "category": "筆記型電腦"
  }'
```

## 加查詢條件後，執行的結果，就變的相當的精準
```
{
  "results": [
    {
      "productName": "Acer Predator Helios - 筆記型電腦",
      "price": 2000,
      "rating": 4.4,
      "reviews": 320,
      "similarityScore": 0.7771975
    },
    {
      "productName": "HP Spectre x360 - 筆記型電腦",
      "price": 1500,
      "rating": 4.7,
      "reviews": 450,
      "similarityScore": 0.7680249
    },
    {
      "productName": "Dell XPS 15 - 筆記型電腦",
      "price": 2000,
      "rating": 4.5,
      "reviews": 300,
      "similarityScore": 0.7658051
    },
    {
      "productName": "HP Omen - 筆記型電腦",
      "price": 1500,
      "rating": 4.5,
      "reviews": 370,
      "similarityScore": 0.7594989
    },
    {
      "productName": "Apple MacBook Pro - 筆記型電腦",
      "price": 2000,
      "rating": 4.8,
      "reviews": 600,
      "similarityScore": 0.73970205
    }
  ]
}
```
## 心得
透過前面幾篇的實驗，因此我們得出，如何用Langchain 打造更精準的產品推薦的AI代理機器人。  

* 代理 (Agent)   
使用者輸入 > AI解析用戶的意圖，依據動態路由，AI決定採用什麼工具 > 執行自訂工具(產品推薦) >  回傳結果給最終用戶。  

* 客製產品推薦工具 (Custom Tool)  
產品推薦工具的實作，則可以讓AI依據使用者輸入，解析成產品分類、產品價錢、品牌的查詢條件，最後查詢向量資料庫。

## 此系列相關文章
* [使用 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-4)

授權條款： [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) — 第一時間收到新文章通知，無垃圾信、隨時可取消訂閱。
