アプリの AI 機能のコアは生成モデルのリクエストですが、ユーザー入力を取得してモデルに渡し、モデルの出力をユーザーに表示するだけというケースはほとんどありません。通常、モデル呼び出しには前処理と後処理の手順が必要です。例:
- モデル呼び出しとともに送信するコンテキスト情報を取得する
- ユーザーの現在のセッションの履歴の取得(チャットアプリなど)
- 1 つのモデルを使用して、別のモデルに渡すのに適した方法でユーザー入力を再フォーマットする
- モデルの出力をユーザーに提示する前に、その出力の「安全性」を評価する
- 複数のモデルの出力を組み合わせる
AI 関連のタスクを成功させるには、このワークフローのすべてのステップが連携して機能する必要があります。
Genkit では、この緊密にリンクされたロジックをフローという構造を使用して表します。フローの関数は、通常の TypeScript コードを使用して作成されますが、AI 機能の開発を容易にするための追加機能が追加されています。
- 型安全性: Zod を使用して定義された入出力スキーマ。静的型チェックとランタイム型チェックの両方を提供します。
- デベロッパー UI との統合: デベロッパー UI を使用して、アプリケーション コードとは別にフローをデバッグします。デベロッパー UI では、フローを実行し、フローの各ステップのトレースを表示できます。
- 簡素化されたデプロイ: Cloud Functions for Firebase またはウェブアプリをホストできる任意のプラットフォームを使用して、フローをウェブ API エンドポイントとして直接デプロイします。
他のフレームワークの同様の機能とは異なり、Genkit のフローは軽量で目立たず、アプリに特定の抽象化を強制しません。フロー内のすべてのロジックは標準の TypeScript で記述され、フロー内のコードはフロー対応である必要はありません。
フローの定義と呼び出し
最も単純な形式のフローは、関数をラップしただけのものです。次の例では、generate()
を呼び出す関数をラップしています。
export const menuSuggestionFlow = ai.defineFlow(
{
name: 'menuSuggestionFlow',
},
async (restaurantTheme) => {
const { text } = await ai.generate({
model: gemini15Flash,
prompt: `Invent a menu item for a ${restaurantTheme} themed restaurant.`,
});
return text;
}
);
このように generate()
呼び出しをラップするだけで、いくつかの機能が追加されます。これにより、Genkit CLI とデベロッパー UI からフローを実行できます。また、デプロイとオブザーバビリティなど、Genkit のいくつかの機能の要件でもあります(これらのトピックについては後のセクションで説明します)。
入力スキーマと出力スキーマ
Genkit フローがモデル API を直接呼び出すよりも優れている点の 1 つは、入力と出力の両方の型安全性が確保される点です。フローを定義するときに、generate()
呼び出しの出力スキーマを定義する場合と同様に、Zod を使用してフローのスキーマを定義できます。ただし、generate()
とは異なり、入力スキーマも指定できます。
次の例は、最後の例を改良したもので、文字列を入力として受け取り、オブジェクトを出力するフローを定義しています。
const MenuItemSchema = z.object({
dishname: z.string(),
description: z.string(),
});
export const menuSuggestionFlowWithSchema = ai.defineFlow(
{
name: 'menuSuggestionFlow',
inputSchema: z.string(),
outputSchema: MenuItemSchema,
},
async (restaurantTheme) => {
const { output } = await ai.generate({
model: gemini15Flash,
prompt: `Invent a menu item for a ${restaurantTheme} themed restaurant.`,
output: { schema: MenuItemSchema },
});
if (output == null) {
throw new Error("Response doesn't satisfy schema.");
}
return output;
}
);
フローのスキーマは、フロー内の generate()
呼び出しのスキーマと一致している必要はありません(実際、フローには generate()
呼び出しが含まれていないこともあります)。次の例は、スキーマを generate()
に渡しますが、構造化出力を使用して、フローが返す単純な文字列をフォーマットします。
export const menuSuggestionFlowMarkdown = ai.defineFlow(
{
name: 'menuSuggestionFlow',
inputSchema: z.string(),
outputSchema: z.string(),
},
async (restaurantTheme) => {
const { output } = await ai.generate({
model: gemini15Flash,
prompt: `Invent a menu item for a ${restaurantTheme} themed restaurant.`,
output: { schema: MenuItemSchema },
});
if (output == null) {
throw new Error("Response doesn't satisfy schema.");
}
return `**${output.dishname}**: ${output.description}`;
}
);
通話フロー
フローを定義したら、Node.js コードから呼び出すことができます。
const { text } = await menuSuggestionFlow('bistro');
フローへの引数は、入力スキーマ(定義されている場合)に準拠している必要があります。
出力スキーマを定義した場合、フロー レスポンスはそれに準拠します。たとえば、出力スキーマを MenuItemSchema
に設定すると、フロー出力にはそのプロパティが含まれます。
const { dishname, description } =
await menuSuggestionFlowWithSchema('bistro');
ストリーミング フロー
フローは、generate()
のストリーミング インターフェースに似たインターフェースを使用してストリーミングをサポートしています。ストリーミングは、フローで大量の出力が生成される場合に便利です。生成された出力をユーザーに提示できるため、アプリの応答性が向上します。よくある例として、チャットベースの LLM インターフェースでは、生成されたレスポンスをユーザーにストリーミングすることがよくあります。
ストリーミングをサポートするフローの例を次に示します。
export const menuSuggestionStreamingFlow = ai.defineStreamingFlow(
{
name: 'menuSuggestionFlow',
inputSchema: z.string(),
streamSchema: z.string(),
outputSchema: z.object({ theme: z.string(), menuItem: z.string() }),
},
async (restaurantTheme, streamingCallback) => {
const response = await ai.generateStream({
model: gemini15Flash,
prompt: `Invent a menu item for a ${restaurantTheme} themed restaurant.`,
});
if (streamingCallback) {
for await (const chunk of response.stream) {
// Here, you could process the chunk in some way before sending it to
// the output stream via streamingCallback(). In this example, we output
// the text of the chunk, unmodified.
streamingCallback(chunk.text);
}
}
return {
theme: restaurantTheme,
menuItem: (await response.response).text,
};
}
);
streamSchema
オプションは、フロー ストリームの値の型を指定します。これは、フロー全体の出力のタイプであるoutputSchema
と同じタイプである必要はありません。streamingCallback
は、streamSchema
で指定された型の単一のパラメータを受け入れるコールバック関数です。フロー内でデータが利用可能になったら、この関数を呼び出してデータを出力ストリームに送信します。streamingCallback
は、フローの呼び出し元がストリーミング出力をリクエストした場合にのみ定義されるため、呼び出す前に定義されていることを確認する必要があります。
上記の例では、フローによってストリーミングされる値は、フロー内の generate()
呼び出しによってストリーミングされる値に直接結合されています。多くの場合、これは事実ですが、必ずしもそうである必要はありません。フローにとって有用な頻度で、コールバックを使用してストリームに値を出力できます。
通話ストリーミング フロー
ストリーミング フローも呼び出し可能ですが、プロミスではなくレスポンス オブジェクトをすぐに返します。
const response = menuSuggestionStreamingFlow('Danube');
レスポンス オブジェクトにはストリーム プロパティがあります。このプロパティを使用すると、生成されたフローのストリーミング出力を反復処理できます。
for await (const chunk of response.stream) {
console.log('chunk', chunk);
}
ストリーミング以外のフローと同様に、フロー全体の出力を取得することもできます。
const output = await response.output;
フローのストリーミング出力は、完全な出力と同じタイプではない場合があります。ストリーミング出力は streamSchema
に準拠しますが、完全な出力は outputSchema
に準拠します。
コマンドラインからのフローの実行
フローは、Genkit CLI ツールを使用してコマンドラインから実行できます。
genkit flow:run menuSuggestionFlow '"French"'
ストリーミング フローの場合は、-s
フラグを追加してストリーミング出力をコンソールに出力できます。
genkit flow:run menuSuggestionFlow '"French"' -s
コマンドラインからフローを実行すると、フローのテストや、アドホックに必要なタスクを実行するフローの実行に役立ちます。たとえば、ドキュメントをベクトル データベースに取り込むフローを実行できます。
フローのデバッグ
AI ロジックをフロー内にカプセル化する利点の一つは、Genkit デベロッパー UI を使用して、アプリから独立してフローをテストおよびデバッグできることです。
デベロッパー UI を起動するには、プロジェクト ディレクトリから次のコマンドを実行します。
genkit start -- tsx --watch src/your-code.ts
デベロッパー UI の [実行] タブから、プロジェクトで定義した任意のフローを実行できます。
フローを実行したら、[トレース表示] をクリックするか、[検査] タブで、フローの呼び出しのトレースを確認できます。
トレースビューアでは、フロー全体の実行に関する詳細と、フロー内の個々のステップの詳細を確認できます。たとえば、次のフローには複数の生成リクエストが含まれています。
const PrixFixeMenuSchema = z.object({
starter: z.string(),
soup: z.string(),
main: z.string(),
dessert: z.string(),
});
export const complexMenuSuggestionFlow = ai.defineFlow(
{
name: 'complexMenuSuggestionFlow',
inputSchema: z.string(),
outputSchema: PrixFixeMenuSchema,
},
async (theme: string): Promise<z.infer<typeof PrixFixeMenuSchema>> => {
const chat = ai.chat({ model: gemini15Flash });
await chat.send('What makes a good prix fixe menu?');
await chat.send(
'What are some ingredients, seasonings, and cooking techniques that ' +
`would work for a ${theme} themed menu?`
);
const { output } = await chat.send({
prompt:
`Based on our discussion, invent a prix fixe menu for a ${theme} ` +
'themed restaurant.',
output: {
schema: PrixFixeMenuSchema,
},
});
if (!output) {
throw new Error('No data generated.');
}
return output;
}
);
このフローを実行すると、トレース ビューアに、出力を含む各生成リクエストの詳細が表示されます。
フローステップ
前回の例では、各 generate()
呼び出しがトレース ビューアに個別のステップとして表示されました。Genkit の基本的なアクションは、フローの個別のステップとして表示されます。
generate()
Chat.send()
embed()
index()
retrieve()
上記以外のコードをトレース内に含める場合は、コードを run()
呼び出しでラップします。これは、Genkit に対応していないサードパーティ ライブラリの呼び出しや、コードの重要なセクションに対して行うことができます。
たとえば、次の 2 ステップのフローがあります。最初のステップでは、指定されていないメソッドを使用してメニューを取得し、2 番目のステップでは、generate()
呼び出しのコンテキストとしてメニューを含めます。
export const menuQuestionFlow = ai.defineFlow(
{
name: 'menuQuestionFlow',
inputSchema: z.string(),
outputSchema: z.string(),
},
async (input: string): Promise<string> => {
const menu = await run('retrieve-daily-menu', async (): Promise<string> => {
// Retrieve today's menu. (This could be a database access or simply
// fetching the menu from your website.)
// ...
return menu;
});
const { text } = await ai.generate({
model: gemini15Flash,
system: "Help the user answer questions about today's menu.",
prompt: input,
docs: [{ content: [{ text: menu }] }],
});
return text;
}
);
取得ステップは run()
呼び出しでラップされているため、トレース ビューアにステップとして含まれます。
フローをデプロイする
フローは、ウェブ API エンドポイントとして直接デプロイして、アプリ クライアントから呼び出すことができます。デプロイについては、他のいくつかのページで詳しく説明していますが、このセクションではデプロイ オプションの概要について説明します。
Cloud Functions for Firebase
Cloud Functions for Firebase でフローをデプロイするには、firebase
プラグインを使用します。フロー定義で、defineFlow
を onFlow
に置き換え、authPolicy
を含めます。
import { firebaseAuth } from '@genkit-ai/firebase/auth';
import { onFlow } from '@genkit-ai/firebase/functions';
export const menuSuggestion = onFlow(
ai,
{
name: 'menuSuggestionFlow',
authPolicy: firebaseAuth((user) => {
if (!user.email_verified) {
throw new Error('Verified email required to run flow');
}
}),
},
async (restaurantTheme) => {
// ...
}
);
詳しくは次のページをご覧ください。
Express.js
Cloud Run などの Node.js ホスティング プラットフォームを使用してフローをデプロイするには、defineFlow()
を使用してフローを定義し、startFlowServer()
を呼び出します。
export const menuSuggestionFlow = ai.defineFlow(
{
name: 'menuSuggestionFlow',
},
async (restaurantTheme) => {
// ...
}
);
ai.startFlowServer({
flows: [menuSuggestionFlow],
});
デフォルトでは、startFlowServer
はコードベースで HTTP エンドポイントとして定義されているすべてのフロー(http://localhost:3400/menuSuggestionFlow
など)を提供します。POST リクエストを使用してフローを呼び出す方法は次のとおりです。
curl -X POST "http://localhost:3400/menuSuggestionFlow" \
-H "Content-Type: application/json" -d '{"data": "banana"}'
必要に応じて、次のように、特定のフローリストを提供するフロー サーバーをカスタマイズできます。カスタム ポートを指定することもできます(PORT 環境変数が設定されている場合は、その環境変数を使用します)。また、CORS 設定を指定することもできます。
export const flowA = ai.defineFlow({ name: 'flowA' }, async (subject) => {
// ...
});
export const flowB = ai.defineFlow({ name: 'flowB' }, async (subject) => {
// ...
});
ai.startFlowServer({
flows: [flowB],
port: 4567,
cors: {
origin: '*',
},
});
特定のプラットフォームにデプロイする方法については、Cloud Run でデプロイすると任意の Node.js プラットフォームにフローをデプロイするをご覧ください。