使用 AI 模型生成内容

生成式 AI 的核心是 AI 模型。目前,生成式模型的两个最显著的示例是大语言模型 (LLM) 和图片生成模型。这些模型接受输入(称为提示,通常是文本、图片或两者的组合),并据此生成文本、图片,甚至音频或视频等输出。

这些模型的输出效果令人惊讶地逼真:LLM 生成的文本看起来就像是人类撰写的,而图片生成模型可以生成与人类创作的真实照片或艺术品非常接近的图片。

此外,LLM 已被证明能够胜任除简单文本生成之外的任务:

  • 编写计算机程序
  • 规划完成更大任务所需的子任务
  • 整理未整理的数据
  • 从文本语料库中理解和提取信息数据
  • 根据活动的文字说明跟踪和执行自动化活动

您可以从多家不同的提供商处获取许多模型。每种模型都有自己的优缺点,一种模型可能擅长某项任务,但在其他任务上表现不佳。利用生成式 AI 的应用通常可以通过使用多个不同的模型获益,具体取决于手头的任务。

作为应用开发者,您通常不会直接与生成式 AI 模型交互,而是通过可用作 Web API 的服务进行交互。虽然这些服务通常具有类似的功能,但它们都通过不同的不兼容 API 提供这些功能。如果您想使用多个模型服务,则必须使用各自的专有 SDK,这些 SDK 可能彼此不兼容。如果您想从一种模型升级到最新且功能最强大的模型,可能就必须重新构建该集成。

Genkit 通过提供一个接口来解决此问题,该接口可抽象出访问可能的任何生成式 AI 模型服务的详细信息,并且已经提供了多个预构建实现。围绕 Genkit 构建 AI 赋能的应用可简化首次进行生成式 AI 调用的流程,并让您同样轻松地组合多个模型,或随着新模型的出现将一个模型换成另一个模型。

准备工作

如果您想运行本页中的代码示例,请先完成开始使用指南中的步骤。所有示例都假设您已在项目中将 Genkit 安装为依赖项。

Genkit 支持的模型

Genkit 的设计非常灵活,可以使用几乎任何生成式 AI 模型服务。其核心库定义了用于处理模型的通用接口,而模型插件定义了用于处理特定模型及其 API 的实现细节。

Genkit 团队维护了一些插件,可用于处理 Vertex AI、Google 生成式 AI 和 Ollama 提供的模型:

  • 通过 Google Cloud Vertex AI 插件使用 Gemini 系列 LLM
  • 通过 Google AI 插件使用 Gemini 系列 LLM
  • 通过 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.',
});

此示例使用模型插件导出的模型引用。另一种方法是使用字符串标识符指定模型:

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

模型字符串标识符类似于 providerid/modelid,其中提供方 ID(在本例中为 googleai)用于标识插件,而模型 ID 是特定版本模型的插件专用字符串标识符。

某些模型插件(例如 Ollama 插件)可提供对数十种不同模型的访问权限,因此不会导出单个模型引用。在这些情况下,您只能使用字符串标识符向 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 生成的令牌数量可能少于上限,因为它生成了“停止”令牌。

stopSequences

您可以使用此参数设置令牌或令牌序列,以便在生成时指示 LLM 输出的结束。此处要使用的正确值通常取决于模型的训练方式,通常由模型插件设置。不过,如果您已提示模型生成其他停止序列,则可以在此处指定该序列。

请注意,您要指定的是字符序列,而不是字符串本身。在大多数情况下,您将指定一个字符序列,该序列会映射到模型的词解析器的单个令牌。

用于控制“广告素材”的参数

temperaturetop-ptop-k 参数共同控制您希望模型的“创意性”程度。下面简要介绍了这些参数的含义,但更重要的是:这些参数用于调整 LLM 输出的性质。这些参数的最佳值取决于您的目标和偏好,可能只有通过实验才能找到。

temperature

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 表示模型应以确定性方式运行。

对模型参数进行实验

您可以使用开发者界面,试验这些参数对不同模型和提示组合生成的输出的影响。使用 genkit start 命令启动开发者界面,它会自动加载项目中配置的插件定义的所有模型。您可以快速尝试不同的提示和配置值,而无需反复在代码中进行这些更改。

结构化输出

在应用中将生成式 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,您可以编写代码,并依赖于以下事实:成功的 generate 调用始终会返回符合 TypeScript 类型的输出。

generate() 中指定架构后,Genkit 会在后台执行多项操作:

  • 在问题中添加有关所需输出格式的其他指导。 这还有一个副作用,即向模型指定您确切想要生成的内容(例如,不仅建议菜单项,还生成说明、过敏原列表等)。
  • 将模型输出解析为 JavaScript 对象。
  • 验证输出是否符合架构。

如需从成功的 generate 调用获取结构化输出,请使用响应对象的 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 模型输出的主要方式是通过提示。请参阅提示管理,了解 Genkit 如何帮助您开发有效的提示并在代码库中管理这些提示。
  • 虽然 generate() 是每个由生成式 AI 驱动的应用的核心,但现实世界的应用通常需要在调用生成式 AI 模型之前和之后执行额外的工作。为此,Genkit 引入了流程的概念,其定义与函数类似,但添加了可观察性和简化部署等额外功能。如需了解详情,请参阅定义工作流

高级 LLM 用法

  • 增强 LLM 能力的一种方法是,向其提供一系列方法,以便其向您请求更多信息或请求您执行某些操作。这称为“工具调用”或“函数调用”。经过训练以支持此功能的模型可以使用采用特殊格式的回答来响应提示,这会指示调用应用应执行某些操作,并将结果连同原始提示一起发送回 LLM。Genkit 提供了一些库函数,可自动生成提示,并自动执行调用实现的调用-响应循环元素。如需了解详情,请参阅工具调用
  • 检索增强生成 (RAG) 是一种技术,用于将特定领域的信息引入到模型的输出中。为此,系统会先将相关信息插入问题中,然后再将其传递给语言模型。若要实现完整的 RAG,您需要将多种技术结合起来:文本嵌入生成模型、向量数据库和大语言模型。如需了解 Genkit 如何简化协调这些各种元素的过程,请参阅检索增强生成 (RAG)

测试模型输出

作为软件工程师,您可能习惯了确定性系统,在这种系统中,相同的输入始终会产生相同的输出。不过,由于 AI 模型是概率性的,因此输出可能会因输入中的细微差异、模型的训练数据,甚至温度等参数故意引入的随机性而异。

Genkit 的评估器采用结构化方式,使用多种策略来评估 LLM 回答的质量。如需了解详情,请参阅评估页面。