使用 AI 模型產生內容

生成式 AI 的核心是 AI 模型。目前,生成模型最顯著的兩個例子是大型語言模型 (LLM) 和圖像生成模型。這些模型會接收輸入內容,也就是稱為「提示」的內容 (通常是文字、圖片或兩者組合),並將其輸出為文字、圖片,甚至是音訊或影片。

這些模型的輸出內容令人驚訝地逼真:LLM 產生的文字看起來就像是人類所寫,而圖像生成模型則能產生與真實相片或人類創作藝術品極為相似的圖像。

此外,大型語言模型已證明可執行超出簡單文字生成工作以外的任務:

  • 編寫電腦程式
  • 規劃完成大型任務所需的子任務
  • 整理未整理的資料
  • 從文字集合中瞭解及擷取資訊資料
  • 根據活動的文字說明,執行自動化活動

您可以選擇多家供應商提供的多種型號。每個模型都有各自的優缺點,某個模型可能擅長某項工作,但在其他工作上表現不佳。應用程式在使用生成式 AI 時,通常會根據手邊工作使用多種不同的模型。

身為應用程式開發人員,您通常不會直接與生成式 AI 模型互動,而是透過可用於網路 API 的服務互動。雖然這些服務通常提供類似的功能,但都透過不同的 API 提供,且這些 API 不相容。如果您想使用多個模型服務,就必須使用各自的專屬 SDK,但這些 SDK 可能彼此不相容。而且如果您想從一個模型升級至最新且功能最強大的模型,可能就必須重新建構該整合。

Genkit 提供單一介面,可抽象化存取任何生成式 AI 模型服務的詳細資料,並提供多個預先建構的實作項目,解決這項挑戰。以 Genkit 為基礎建構 AI 應用程式,可簡化首次生成式 AI 呼叫的程序,並讓您輕鬆結合多個模型,或在新的模型推出時交換模型。

事前準備

如果您想執行本頁的程式碼範例,請先完成「開始使用」指南中的步驟。所有範例都假設您已將 Genkit 設為專案中的依附元件。

Genkit 支援的模型

Genkit 的設計非常靈活,可用於任何生成式 AI 模型服務。其核心程式庫會定義處理模型的通用介面,而模型外掛程式會定義處理特定模型及其 API 的實作詳細資料。

Genkit 團隊會維護外掛程式,以便使用 Vertex AI、Google 生成式 AI 和 Ollama 提供的模型:

  • Gemini 系列 LLM,透過 Google Cloud Vertex AI 外掛程式
  • 透過 Google AI 外掛程式使用 Gemini 系列大型語言模型
  • 透過 Google Cloud Vertex AI 使用 Imagen2 和 Imagen3 圖片生成模型
  • 透過 Google Cloud Vertex AI 的模型花園,使用 Anthropic 的 Claude 3 系列 LLM
  • 透過 Ollama 外掛程式使用 Gemma 2、Llama 3 和其他許多開放式模型 (您必須自行代管 Ollama 伺服器)

此外,還有幾個社群支援的外掛程式,可提供這些模型的介面:

如要進一步瞭解,請在 npmjs.org 上搜尋標示為 genkit-model 的套件

載入及設定模型外掛程式

您必須先載入並設定模型外掛程式,才能使用 Genkit 開始產生內容。如果您是從「開始使用」指南前來,表示您已完成這項操作。否則,請參閱入門指南或個別外掛程式的說明文件,並按照其中的步驟操作,再繼續進行。

generate() 方法

在 Genkit 中,您與生成式 AI 模型互動的主要介面是 generate() 方法。

最簡單的 generate() 呼叫會指定要使用的模型和文字提示:

import { gemini15Flash, googleAI } from '@genkit-ai/googleai';
import { genkit } from 'genkit';

const ai = genkit({
  plugins: [googleAI()],
  model: gemini15Flash,
});

(async () => {
  const { text } = await ai.generate(
    'Invent a menu item for a pirate themed restaurant.'
  );
  console.log(text);
})();

執行這個簡短範例時,系統會先列印一些偵錯資訊,接著是 generate() 呼叫的輸出內容,通常會是 Markdown 文字,如下例所示:

## The Blackheart's Bounty

**A hearty stew of slow-cooked beef, spiced with rum and molasses, served in a
hollowed-out cannonball with a side of crusty bread and a dollop of tangy
pineapple salsa.**

**Description:** This dish is a tribute to the hearty meals enjoyed by pirates
on the high seas. The beef is tender and flavorful, infused with the warm spices
of rum and molasses. The pineapple salsa adds a touch of sweetness and acidity,
balancing the richness of the stew. The cannonball serving vessel adds a fun and
thematic touch, making this dish a perfect choice for any pirate-themed
adventure.

再次執行指令碼,您會看到不同的輸出結果。

上述程式碼範例會將產生要求傳送至預設模型,也就是您在設定 Genkit 執行個體時指定的模型。

您也可以為單一 generate() 呼叫指定模型:

const { text } = await ai.generate({
  model: gemini15Pro,
  prompt: 'Invent a menu item for a pirate themed restaurant.',
});

本範例使用模型外掛程式匯出的模型參照。另一個選項是使用字串 ID 指定模型:

const { text } = await ai.generate({
  model: 'googleai/gemini-1.5-pro-latest',
  prompt: 'Invent a menu item for a pirate themed restaurant.',
});

模型字串 ID 的格式為 providerid/modelid,其中提供者 ID (在本例中為 googleai) 會識別外掛程式,而模型 ID 則是特定版本模型的外掛程式專屬字串 ID。

某些模型外掛程式 (例如 Ollama 外掛程式) 可提供數十種不同模型的存取權,因此不會匯出個別模型參照。在這種情況下,您只能使用字串 ID 將模型指定給 generate()

這些範例也說明瞭一個重要重點:當您使用 generate() 呼叫生成式 AI 模型時,只要將不同的值傳遞至模型參數,即可變更要使用的模型。使用 generate() 而非原生模型 SDK,您就能更靈活地在應用程式中使用多個不同模型,並在日後變更模型。

到目前為止,您只看到最簡單的 generate() 呼叫範例。不過,generate() 也提供介面,可與生成式模型進行更進階的互動,您將在後續章節中看到相關說明。

系統提示

部分模型支援提供系統提示,可讓模型瞭解如何回應使用者的訊息。您可以使用系統提示,指定要讓模型採用的角色、回應的語調、回應格式等等。

如果您使用的模型支援系統提示,可以使用 system 參數提供提示:

const { text } = await ai.generate({
  system: 'You are a food industry marketing consultant.',
  prompt: 'Invent a menu item for a pirate themed restaurant.',
});

模型參數

generate() 函式會採用 config 參數,您可以透過該參數指定可選設定,控制模型產生內容的方式:

const { text } = await ai.generate({
  prompt: 'Invent a menu item for a pirate themed restaurant.',
  config: {
    maxOutputTokens: 400,
    stopSequences: ['<end>', '<fin>'],
    temperature: 1.2,
    topP: 0.4,
    topK: 50,
  },
});

支援的確切參數取決於個別模型和模型 API。不過,前述範例中的參數幾乎適用於所有模型。以下是這些參數的說明:

控制輸出長度的參數

maxOutputTokens

LLM 會以稱為「符記」的單位運作。符記通常會對應至特定字元序列,但不一定如此。當您將提示傳遞至模型時,模型會先將提示字串轉為符記,再將符記轉為符記序列。接著,LLM 會根據已切割的輸入內容產生符記序列。最後,符記序列會轉換回文字,也就是輸出內容。

輸出符記數量上限參數只會設定使用 LLM 產生符記的數量上限。每個模型都可能使用不同的分詞器,但一般來說,單一英文單字可視為由 2 到 4 個字元組成。

如先前所述,部分符記可能無法對應至字元序列。舉例來說,通常會有一個符記可指出序列的結尾:當 LLM 產生這個符記時,就會停止產生更多符記。因此,LLM 產生的符記可能會少於上限,因為它會產生「stop」符記。

stopSequences

您可以使用這個參數設定符記或符記序列,當系統產生這些符記或符記序列時,就會表示 LLM 輸出內容結束。這裡使用的正確值通常取決於模型的訓練方式,通常由模型外掛程式設定。不過,如果您已提示模型產生其他停止序列,可以在這裡指定。

請注意,您指定的是字元序列,而非符記。在大多數情況下,您會指定字元序列,讓模型的分析器將其對應至單一符號。

控制「廣告素材」的參數

溫度前 P 個前 K 個參數會共同控制您希望模型採取的「創意」程度。以下簡要說明這些參數的含義,但更重要的是:這些參數可用於調整 LLM 輸出的字元。這些值的最佳值取決於您的目標和偏好設定,而且可能只有透過實驗才能找出。

溫度

大型語言模型 (LLM) 基本上是符號預測機器,對於特定的符記序列 (例如提示),LLM 會針對詞彙中的每個符記,預測符記在序列中出現的可能性。溫度是一種縮放因子,用於將這些預測值除以 0 和 1 之間的概率,以便正規化。

低溫度值 (介於 0.0 和 1.0 之間) 會放大符號之間的可能性差異,導致模型更不可能產生已評估為不太可能的符號。這通常會被視為不太有創意的輸出內容。雖然 0.0 在技術上並非有效值,但許多模型都將其視為模型應以確定性行為運作,並只考慮單一最可能的符記。

高溫度值 (大於 1.0) 會壓縮符號之間的可能性差異,導致模型更有可能產生先前評估為不太可能的符號。這通常被視為更具創意的輸出內容。某些模型 API 會設有溫度上限,通常為 2.0。

topP

Top-p 是介於 0.0 和 1.0 之間的值,可透過指定符記的累積機率,控制模型要考量的可能符記數量。舉例來說,如果值為 1.0,表示系統會考慮所有可能的符記 (但仍會考量每個符記的機率)。值為 0.4 表示只考慮機率總和為 0.4 的機率最高的符記,並排除其他符記。

topK

Top-k 是整數值,同樣可控制模型要考量的可能符號數量,但這次是透過明確指定符號的最大數量。如果指定的值為 1,表示模型應以確定性運作。

嘗試使用模型參數

您可以使用開發人員 UI 實驗這些參數對不同模型和提示組合產生的輸出內容有何影響。使用 genkit start 指令啟動開發人員 UI,系統就會自動載入專案中所配置外掛程式定義的所有模型。您可以快速嘗試不同的提示和設定值,而不必在程式碼中重複進行這些變更。

結構化輸出內容

將生成式 AI 用於應用程式中的元件時,您通常會希望輸出格式為純文字以外的格式。即使您只是要產生內容來向使用者顯示,也可以利用結構化輸出內容,讓使用者更容易看見。不過,如果是更進階的生成式 AI 應用程式 (例如以程式輔助方式使用模型輸出內容,或將一個模型的輸出內容提供給另一個模型),就必須使用結構化輸出內容。

在 Genkit 中,您可以在呼叫 generate() 時指定結構定義,從模型要求結構化輸出內容:

import { z } from 'genkit'; // Import Zod, which is re-exported by Genkit.
const MenuItemSchema = z.object({
  name: z.string(),
  description: z.string(),
  calories: z.number(),
  allergens: z.array(z.string()),
});

const { output } = await ai.generate({
  prompt: 'Invent a menu item for a pirate themed restaurant.',
  output: { schema: MenuItemSchema },
});

模型輸出結構定義會使用 Zod 程式庫指定。除了結構定義語言之外,Zod 也提供執行階段類型檢查功能,可彌補靜態 TypeScript 類型與生成式 AI 模型不可預測輸出內容之間的差距。使用 Zod 時,您編寫的程式碼可依賴以下事實:成功的產生呼叫一律會傳回符合 TypeScript 類型的輸出內容。

generate() 中指定結構定義時,Genkit 會在幕後執行以下幾項操作:

  • 在提示中加入有關所需輸出格式的額外指引。這也會產生副作用,向模型指定您要產生的確切內容 (例如,不僅建議選單項目,還要產生說明、過敏原清單等)。
  • 將模型輸出內容剖析為 JavaScript 物件。
  • 驗證輸出內容是否符合結構定義。

如要從成功的產生呼叫取得結構化輸出內容,請使用回應物件的 output 屬性:

if (output) {
  const { name, description, calories, allergens } = output;
}

處理錯誤

請注意,在前述範例中,output 屬性可以是 null。當模型無法產生符合結構定義的輸出內容時,就可能發生這種情況。處理這類錯誤的最佳策略取決於您的實際用途,但以下提供一些一般提示:

  • 嘗試其他模型。如要成功產生結構化輸出內容,模型必須能夠以 JSON 格式產生輸出內容。最強大的 LLM (例如 Gemini 和 Claude) 具有足夠的多功能可執行這項操作;不過,較小的模型 (例如您在 Ollama 中使用的部分本機模型) 可能無法可靠地產生結構化輸出內容,除非經過特別訓練才能做到。

  • 善用 Zod 的強制功能:您可以在結構定義中指定 Zod 應嘗試將不符合規範的類型強制轉換為結構定義指定的類型。如果您的結構定義包含字串以外的原始類型,使用 Zod 強制轉換功能可減少 generate() 失敗的次數。以下版本的 MenuItemSchema 會使用類型強制轉換,自動修正模型產生卡路里資訊時,以字串而非數字的情況:

    const MenuItemSchema = z.object({
      name: z.string(),
      description: z.string(),
      calories: z.coerce.number(),
      allergens: z.array(z.string()),
    });
    
  • 重試 generate() 呼叫。如果您選擇的模型很少發生無法產生符合規範的輸出結果,您可以將錯誤視為網路錯誤,然後使用某種遞增的輪詢策略重新嘗試要求。

串流

產生大量文字時,您可以透過串流方式呈現產生的輸出內容,為使用者提供更優質的體驗。在大多數 LLM 即時通訊應用程式中,您可以看到串流運作情形的常見例子:使用者可以讀取模型對其訊息的回應,並且在回應產生時即時顯示,這有助於提升應用程式的回應速度,並強化使用者與智慧對象對話的錯覺。

在 Genkit 中,您可以使用 generateStream() 方法串流輸出內容。其語法與 generate() 方法類似:

const { response, stream } = await ai.generateStream(
  'Suggest a complete menu for a pirate themed restaurant.'
);

回應物件含有 stream 屬性,可用於在產生時,逐一檢視要求的串流輸出內容:

for await (const chunk of stream) {
  console.log(chunk.text);
}

您也可以取得要求的完整輸出內容,就像使用非串流要求一樣:

const completeText = (await response).text;

串流也適用於結構化輸出內容:

const MenuSchema = z.object({
  starters: z.array(MenuItemSchema),
  mains: z.array(MenuItemSchema),
  desserts: z.array(MenuItemSchema),
});

const { response, stream } = await ai.generateStream({
  prompt: 'Suggest a complete menu for a pirate themed restaurant.',
  output: { schema: MenuSchema },
});

for await (const chunk of stream) {
  // `output` is an object representing the entire output so far.
  console.log(chunk.output);
}

// Get the completed output.
const { output } = await response;

串流結構化輸出內容的運作方式與串流文字稍有不同:回應區塊的 output 屬性是從目前產生的區塊累積資料所建構的物件,而非代表單一區塊的物件 (單一區塊可能無法單獨有效)。從某種意義來說,每個結構化輸出區塊都會取代先前的區塊

舉例來說,以下是前一個範例的前五個輸出內容:

null

{ starters: [ {} ] }

{
  starters: [ { name: "Captain's Treasure Chest", description: 'A' } ]
}

{
  starters: [
    {
      name: "Captain's Treasure Chest",
      description: 'A mix of spiced nuts, olives, and marinated cheese served in a treasure chest.',
      calories: 350
    }
  ]
}

{
  starters: [
    {
      name: "Captain's Treasure Chest",
      description: 'A mix of spiced nuts, olives, and marinated cheese served in a treasure chest.',
      calories: 350,
      allergens: [Array]
    },
    { name: 'Shipwreck Salad', description: 'Fresh' }
  ]
}

多模態輸入

您目前看到的範例都使用文字字串做為模型提示。雖然這仍是提示生成式 AI 模型最常見的方式,但許多模型也能接受其他媒體做為提示。媒體提示最常與文字提示搭配使用,可指示模型對媒體執行某些作業,例如為圖片加上說明文字或轉錄錄音檔。

接受媒體輸入內容的能力和可使用的媒體類型,完全取決於模型及其 API。舉例來說,Gemini 1.5 系列模型可接受圖片、影片和音訊做為提示。

如要向支援媒體提示的模型提供媒體提示,請傳遞包含媒體部分和文字部分的陣列,而非將簡單的文字提示傳遞至 generate

const { text } = await ai.generate([
  { media: { url: 'https://example.com/photo.jpg' } },
  { text: 'Compose a poem about this image.' },
]);

在上述範例中,您使用可公開存取的 HTTPS 網址指定圖片。您也可以將媒體資料編碼為資料網址,直接傳遞媒體資料。例如:

import { readFile } from 'node:fs/promises';
const b64Data = await readFile('photo.jpg', { encoding: 'base64url' });
const dataUrl = `data:image/jpeg;base64,${b64Data}`;

const { text } = await ai.generate([
  { media: { url: dataUrl } },
  { text: 'Compose a poem about this image.' },
]);

所有支援媒體輸入的模型都支援資料網址和 HTTPS 網址。部分模型外掛程式會新增其他媒體來源的支援功能。舉例來說,Vertex AI 外掛程式也允許使用 Cloud Storage (gs://) 網址。

產生媒體

目前為止,本頁大部分的範例都處理使用 LLM 產生文字的情況。不過,Genkit 也可以搭配圖像生成模型使用。使用 generate() 與圖片產生模型的方式,與使用 LLM 類似。舉例來說,如要透過 Vertex AI 使用 Imagen2 模型生成圖片,請按照下列步驟操作:

  1. Genkit 會使用 data: 網址,做為產生媒體的標準輸出格式。這是標準格式,許多程式庫都可以處理。本範例使用 jsdom 中的 data-urls 套件:

    npm i --save data-urls
    npm i --save-dev @types/data-urls
  2. 如要產生圖片並儲存至檔案,請呼叫 generate(),指定圖片產生模型和輸出格式的媒體類型:

    import { imagen3Fast, vertexAI } from '@genkit-ai/vertexai';
    import parseDataURL from 'data-urls';
    import { genkit } from 'genkit';
    
    import { writeFile } from 'node:fs/promises';
    
    const ai = genkit({
      plugins: [vertexAI({ location: 'us-central1' })],
    });
    
    (async () => {
      const { media } = await ai.generate({
        model: imagen3Fast,
        prompt: 'photo of a meal fit for a pirate',
        output: { format: 'media' },
      });
    
      if (media === null) throw new Error('No media generated.');
    
      const data = parseDataURL(media.url);
      if (data === null) throw new Error('Invalid "data:" URL.');
    
      await writeFile(`output.${data.mimeType.subtype}`, data.body);
    })();
    

後續步驟

進一步瞭解 Genkit

  • 應用程式開發人員影響生成式 AI 模型輸出內容的主要方式,就是透過提示。請參閱「提示管理」一文,瞭解 Genki 如何協助您開發有效的提示,並在程式碼庫中管理提示。
  • 雖然 generate() 是每個生成式 AI 應用程式的核心,但實際應用程式通常需要在叫用生成式 AI 模型前後進行額外作業。為反映這項趨勢,Genkit 引入了「流程」概念,其定義與函式類似,但會新增可觀察性和簡化部署等額外功能。詳情請參閱「定義工作流程」。

進階 LLM 用法

  • 提升 LLM 功能的方式之一,就是提供一長串提示,讓系統可以向您索取更多資訊,或要求您執行某些動作。這就是所謂的「工具呼叫」或「函式呼叫」。經過訓練可支援這項功能的模型,可透過特殊格式的回應來回應提示,這會向呼叫應用程式指出,應執行某些動作,並將結果傳回 LLM 以及原始提示。Genkit 提供程式庫函式,可自動產生提示,並在呼叫工具實作時產生呼叫-回應迴圈元素。詳情請參閱「呼叫工具」。
  • 檢索增強生成 (RAG) 是一種技術,可將特定領域的資訊引入模型輸出內容。方法是在提示中插入相關資訊,然後再傳遞至語言模型。完整的 RAG 導入作業需要整合多項技術:文字嵌入生成模型、向量資料庫和大型語言模型。請參閱「檢索增強生成 (RAG)」一文,瞭解 Genkit 如何簡化協調這些不同元素的程序。

測試模型輸出

身為軟體工程師,您可能習慣使用確定性系統,也就是相同的輸入內容一律會產生相同的輸出內容。不過,由於 AI 模型是概率性的,輸出結果可能會因輸入內容的細微差異、模型的訓練資料,甚至是溫度等參數刻意引入的隨機性而有所不同。

Genkit 的評估工具會使用各種策略,以結構化方式評估 LLM 回應的品質。詳情請參閱「評估」頁面。