Vercel AI SDK (Part 1)

文字生成與分類

eric

最後更新時間

2025年2月3日

文章列表

概述

本文詳細記錄了 Vercel Workshop 中關於 AI 應用開發的實作內容,並無創新。講者是 Nico Albanese,他是 AI SDK 的開發人員。會寫這篇文章是因為我認為講者循序漸進的教學方式很值得我學習,所以想記錄下來供我自己未來參考。同時也能讓更多人因此獲益。該工作坊主要運用 AI SDK 與 Next.js 兩大核心技術,內容架構分為三個重要面向:

  1. AI SDK 的基礎應用與實作
  2. AI SDK 與 Next.js 框架的整合方式
  3. AI Chatbot 的實作

本篇為這次工作坊的第一部分,我們將探討 Vercel AI SDK 的基礎應用。內容包含專案建置和透過大型語言模型(LLM)在文字資料中取得特定資訊分類

專案建置

你可以使用以下指令將指定的 Git 儲存庫 Clone 到本地端

git clone https://github.com/nicoalbanese/build-an-ai-app-starter-dec-24.git
cd build-an-ai-app-starter-dec-24

你可以使用你偏好的套件管理器來安裝相依套件

bun install

你可以使用以下指令來複製 .env.example 並將其重新命名為 .env

.env.example .env

打開 .env 檔案,然後新增或修改 OPENAI_API_KEY 變數,例如:

OPENAI_API_KEY="sk-*****-*************"

為了確認是否有正確加入 OpenAI 的 API key,我們可以透過下面指令來檢查。但請小心,這會直接顯示你的 API Key 在終端機,建議只在本機測試時使用:

bun tsx env-check.ts

如果成功設置,你會在終端機中看到你的 OpenAI API Key 被輸出。

如果成功了,我們就可以開始使用 Vercel AI SDK 了!☺️

文字生成

從 AI SDK 引入 generateText,然後調用它吧!

app/(1-extraction)/extraction.ts
import "dotenv/config";
import fs from "fs";
import { generateText } from "ai"; 
 
// import essay
const essay = fs.readFileSync("app/(1-extraction)/essay.txt", "utf-8");
 
async function main() {
  const result = await generateText({}); 
}
 
main().catch(console.error);

引入 OpenAI provider,然後選擇你要使用的模型

app/(1-extraction)/extraction.ts
import "dotenv/config";
import fs from "fs";
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai"; 
 
// import essay
const essay = fs.readFileSync("app/(1-extraction)/essay.txt", "utf-8");
 
async function main() {
  const result = await generateText({
    model: openai("gpt-4o-mini"), 
  });
}
 
main().catch(console.error);

傳入一個提示詞,這次我們要提取文章中提到的所有名字

app/(1-extraction)/extraction.ts
import "dotenv/config";
import fs from "fs";
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";
 
// import essay
const essay = fs.readFileSync("app/(1-extraction)/essay.txt", "utf-8");
 
async function main() {
  const result = await generateText({
    model: openai("gpt-4o-mini"),
    prompt: "Extract all the names mentioned in this essay." + "\n\n" + essay, 
  });
}
 
main().catch(console.error);

現在,讓我們把生成的文本輸出到 console 看看

app/(1-extraction)/extraction.ts
import "dotenv/config";
import fs from "fs";
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";
 
// import essay
const essay = fs.readFileSync("app/(1-extraction)/essay.txt", "utf-8");
 
async function main() {
  const result = await generateText({
    model: openai("gpt-4o-mini"),
    prompt: "Extract all the names mentioned in this essay." + "\n\n" + essay,
  });
 
  console.log(result.text); 
}
 
main().catch(console.error);

運行腳本

bun extraction

試著改變提示詞,問一些不同的問題,比如:「這篇文章的關鍵觀點是什麼?用 50 字來描述。」

app/(1-extraction)/extraction.ts
import "dotenv/config";
import fs from "fs";
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";
 
// import essay
const essay = fs.readFileSync("app/(1-extraction)/essay.txt", "utf-8");
 
async function main() {
  const result = await generateText({
    model: openai("gpt-4o-mini"),
    prompt:
      "What is the key takeaway of this piece in 50 words?" + "\n\n" + essay, 
  });
 
  console.log(result.text);
}
 
main().catch(console.error);

試試使用不同的模型,看看回應會有什麼變化。

app/(1-extraction)/extraction.ts
import "dotenv/config";
import fs from "fs";
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";
 
// import essay
const essay = fs.readFileSync("app/(1-extraction)/essay.txt", "utf-8");
 
async function main() {
  const result = await generateText({
    model: openai("gpt-4o"), 
    prompt:
      "What is the key takeaway of this piece in 50 words?" + "\n\n" + essay,
  });
 
  console.log(result.text);
}
 
main().catch(console.error);

繼續試著變換提示詞,問不同的問題,看看模型是如何回應的。你也可以嘗試不同的模型,看看回應有什麼不同。

分類文字描述

在這個專案中,我們要將一堆支援請求傳遞給模型,並請它將這些請求分類到不同的類別中。

打開 app/(2-classification)/classification.ts,你應該會看到以下的程式碼:

app/(2-classification)/classification.ts
import "dotenv/config";
import supportRequests from "./support_requests.json";
 
async function main() {
  console.log(supportRequests.slice(0, 2));
}
 
main().catch(console.error);

像之前一樣導入並調用 generateText。提示模型對支援請求(issues)進行分類(請求以 JSON 物件的形式傳遞進來)。

app/(2-classification)/classification.ts
import "dotenv/config";
import supportRequests from "./support_requests.json";
import { generateText } from "ai"; 
import { openai } from "@ai-sdk/openai"; 
 
async function main() {

  const result = await generateText({
    model: openai("gpt-4o-mini"), 

    prompt:
      "Classify the following support requests. The categories are (billing, product issues, enterprise sales, account issues, product feedback).\n\n" +
      JSON.stringify(supportRequests), 
  }); 
  console.log(result.text); 
}
 
main().catch(console.error);

運行腳本

bun classification

Response:

Here are the classified support requests:

1. **Account Issues**: "I'm having trouble logging into my account. Can you please assist?" (id: 1)
2. **Product Issues**: "The export feature isn't working correctly. Is there a known issue?" (id: 2)
3. **Enterprise Sales**: "Can you provide more information about your enterprise pricing plans?" (id: 4)
4. **Account Issues**: "I'm having trouble cancelling my account. Please can you help?" (id: 5)
5. **Product Issues**: "The dashboard is not displaying real-time data. How can I fix this?" (id: 6)
6. **Product Feedback**: "Do you offer technical support for self-hosted installations?" (id: 7)

(Note: The request about API integration (id: 3) was not classified as it does not fit neatly into the provided categories.)

Notice how the model is able to classify the support requests into the correct categories. This is great, but generating a big plain text chunk isn't super useful. Notice, the model also generates extraneous information like "Here are the classified support requests:" and "(Note: The request about API integration (id: 3) was not classified as it does not fit neatly into the provided categories.)". We can solve this with the generateObject function.

使用 generateObject 取代 generateText

app/(2-classification)/classification.ts
import "dotenv/config";
import { generateObject } from "ai"; 
import { openai } from "@ai-sdk/openai";
import supportRequests from "./support_requests.json";
 
async function main() {

  const result = await generateObject({
    model: openai("gpt-4o-mini"),
    prompt:
      "Classify the following support requests. The categories are (billing, product issues, enterprise sales, account issues, product feedback).\n\n" +
      JSON.stringify(supportRequests),
  });
  console.log(result.object); 
}
 
main().catch(console.error);

定義輸出的 schema,並將輸出模式設為「array」。

app/(2-classification)/classification.ts
import "dotenv/config";
import { generateObject } from "ai";
import { openai } from "@ai-sdk/openai";
import supportRequests from "./support_requests.json";
import { z } from "zod";
 
async function main() {
  const result = await generateObject({
    model: openai("gpt-4o-mini"),
    prompt:
      "Classify the following support requests.\n\n" +
      JSON.stringify(supportRequests),

    schema: z.object({
      request: z.string(),
      category: z.enum([
        "billing",
        "product_issues",
        "enterprise_sales",
        "account_issues",
        "product_feedback",
      ]),
    }),
  });
  console.log(result.object);
}
 
main().catch(console.error);

將輸出模式設為「array」,這樣模型會生成符合我們定義 schema 的物件陣列。先定義輸出的 schema,然後設定輸出模式為「array」。

app/(2-classification)/classification.ts
import "dotenv/config";
import { generateObject } from "ai";
import { openai } from "@ai-sdk/openai";
import supportRequests from "./support_requests.json";
import { z } from "zod";
 
async function main() {
  const result = await generateObject({
    model: openai("gpt-4o-mini"),
    prompt:
      "Classify the following support requests.\n\n" +
      JSON.stringify(supportRequests),
    schema: z.object({
      request: z.string(),
      category: z.enum([
        "billing",
        "product_issues",
        "enterprise_sales",
        "account_issues",
        "product_feedback",
      ]),
    }),
    output: "array", 
  });
  console.log(result.object);
}
 
main().catch(console.error);

現在,輸出會完全符合我們指定的格式,變得更精確且可控了。

[
  {
    request:
      "I'm having trouble logging into my account. Can you please assist?",
    category: "account_issues",
  },
  {
    request:
      "The export feature isn't working correctly. Is there a known issue?",
    category: "product_issues",
  },
  {
    request: "I need help integrating your API with our existing system.",
    category: "product_issues",
  },
  {
    request:
      "Can you provide more information about your enterprise pricing plans?",
    category: "enterprise_sales",
  },
  {
    request: "I'm having trouble cancelling my account. Please can you help?",
    category: "account_issues",
  },
  {
    request:
      "The dashboard is not displaying real-time data. How can I fix this?",
    category: "product_issues",
  },
  {
    request: "Do you offer technical support for self-hosted installations?",
    category: "product_issues",
  },
];

更新 schema,讓模型評估請求的緊急程度。

app/(2-classification)/classification.ts
import "dotenv/config";
import { generateObject } from "ai";
import { openai } from "@ai-sdk/openai";
import supportRequests from "./support_requests.json";
import { z } from "zod";
 
async function main() {
  const result = await generateObject({
    model: openai("gpt-4o-mini"),
    prompt:
      "Classify the following support requests.\n\n" +
      JSON.stringify(supportRequests),
    schema: z.object({
      request: z.string(),
      category: z.enum([
        "billing",
        "product_issues",
        "enterprise_sales",
        "account_issues",
        "product_feedback",
      ]),
      urgency: z.enum(["low", "medium", "high"]), 
    }),
    output: "array",
  });
  console.log(result.object);
}
 
main().catch(console.error);

這真的很強大!許多模型都是多語言的,所以我們可以傳入不同語言的支援請求,模型仍然能夠正確分類。這是一個很好的例子,展示了如何利用 AI 自動化一項原本非常耗時的工作。

更新匯入內容以處理多語言的支援請求,並在 schema 中新增一個 language 欄位。

app/(2-classification)/classification.ts
import "dotenv/config";
import { generateObject } from "ai";
import { openai } from "@ai-sdk/openai";
import supportRequests from "./support_requests_multilanguage.json"; 
import { z } from "zod";
 
async function main() {
  const result = await generateObject({
    model: openai("gpt-4o-mini"),
    prompt:
      "Classify the following support requests.\n\n" +
      JSON.stringify(supportRequests),
    schema: z.object({
      request: z.string(),
      category: z.enum([
        "billing",
        "product_issues",
        "enterprise_sales",
        "account_issues",
        "product_feedback",
      ]),
      urgency: z.enum(["low", "medium", "high"]),
      language: z.string(), 
    }),
    output: "array",
  });
  console.log(result.object);
}
 
main().catch(console.error);

注意

模型雖然能正確識別語言,但它回傳的是國家代碼,而不是完整的語言名稱。

但我們可以使用 describe 方法,提供更多細節,讓模型更精確地生成你想要的結果。

app/(2-classification)/classification.ts
import "dotenv/config";
import { generateObject } from "ai";
import { openai } from "@ai-sdk/openai";
import supportRequests from "./support_requests_multilanguage.json";
import { z } from "zod";
 
async function main() {
  const result = await generateObject({
    model: openai("gpt-4o-mini"),
    prompt:
      "Classify the following support requests.\n\n" +
      JSON.stringify(supportRequests),
    schema: z.object({
      request: z.string(),
      category: z.enum([
        "billing",
        "product_issues",
        "enterprise_sales",
        "account_issues",
        "product_feedback",
      ]),
      urgency: z.enum(["low", "medium", "high"]),

      language: z
        .string()
        .describe(
          "The language the support request is in. eg. English, Spanish etc.",
        ),
    }),
    output: "array",
  });
  console.log(result.object);
}
 
main().catch(console.error);

這篇文章一步一步教大家做了文字生成和支援請求分類的實作。但 Vercel AI SDK 有很多其他的方法,可以幫助你做更多的事情。繼續閱讀這篇文章以獲得更多進階的實作應用。