Menentukan alur kerja AI

Inti dari fitur AI aplikasi Anda adalah permintaan model generatif, tetapi jarang sekali Anda dapat mengambil input pengguna, meneruskannya ke model, dan menampilkan output model kembali kepada pengguna. Biasanya, ada langkah pra- dan pasca-pemrosesan yang harus menyertai panggilan model. Contoh:

  • Mengambil informasi kontekstual untuk dikirim dengan panggilan model
  • Mengambil histori sesi pengguna saat ini, misalnya di aplikasi chat
  • Menggunakan satu model untuk memformat ulang input pengguna dengan cara yang sesuai untuk diteruskan ke model lain
  • Mengevaluasi "keamanan" output model sebelum menampilkannya kepada pengguna
  • Menggabungkan output dari beberapa model

Setiap langkah alur kerja ini harus bekerja sama agar tugas terkait AI dapat berhasil.

Di Genkit, Anda mewakili logika yang terikat erat ini menggunakan konstruksi yang disebut alur. Alur ditulis seperti fungsi, menggunakan kode TypeScript biasa, tetapi menambahkan kemampuan tambahan yang dimaksudkan untuk memudahkan pengembangan fitur AI:

  • Keamanan jenis: Skema input dan output yang ditentukan menggunakan Zod, yang menyediakan pemeriksaan jenis statis dan runtime
  • Integrasi dengan UI developer: Debug alur secara terpisah dari kode aplikasi Anda menggunakan UI developer. Di UI developer, Anda dapat menjalankan flow dan melihat trace untuk setiap langkah flow.
  • Deployment yang disederhanakan: Men-deploy alur secara langsung sebagai endpoint API web, menggunakan Cloud Functions for Firebase atau platform apa pun yang dapat menghosting aplikasi web.

Tidak seperti fitur serupa di framework lain, alur Genkit bersifat ringan dan tidak mengganggu, serta tidak memaksa aplikasi Anda untuk mematuhi abstraksi tertentu. Semua logika alur ditulis dalam TypeScript standar, dan kode di dalam alur tidak perlu mengetahui alur.

Menentukan dan memanggil flow

Dalam bentuk yang paling sederhana, flow hanya menggabungkan fungsi. Contoh berikut menggabungkan fungsi yang memanggil 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;
  }
);

Cukup dengan menggabungkan panggilan generate() seperti ini, Anda akan menambahkan beberapa fungsi: dengan melakukannya, Anda dapat menjalankan alur dari Genkit CLI dan dari UI developer, dan merupakan persyaratan untuk beberapa fitur Genkit, termasuk deployment dan kemampuan observasi (bagian selanjutnya membahas topik ini).

Skema input dan output

Salah satu keuntungan terpenting yang dimiliki flow Genkit dibandingkan memanggil API model secara langsung adalah keamanan jenis input dan output. Saat menentukan flow, Anda dapat menentukan skema untuknya menggunakan Zod, dengan cara yang sama seperti menentukan skema output panggilan generate(); Namun, tidak seperti generate(), Anda juga dapat menentukan skema input.

Berikut adalah peningkatan dari contoh terakhir, yang menentukan alur yang menggunakan string sebagai input dan menghasilkan objek:

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;
  }
);

Perhatikan bahwa skema alur tidak harus selaras dengan skema panggilan generate() dalam alur (bahkan, alur mungkin tidak berisi panggilan generate()). Berikut adalah variasi contoh yang meneruskan skema ke generate(), tetapi menggunakan output terstruktur untuk memformat string sederhana, yang ditampilkan oleh flow.

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}`;
  }
);

Melakukan panggilan ke alur

Setelah menentukan alur, Anda dapat memanggilnya dari kode Node.js:

const { text } = await menuSuggestionFlow('bistro');

Argumen ke flow harus sesuai dengan skema input, jika Anda menentukannya.

Jika Anda menentukan skema output, respons alur akan sesuai dengan skema tersebut. Misalnya, jika Anda menetapkan skema output ke MenuItemSchema, output alur akan berisi propertinya:

const { dishname, description } =
  await menuSuggestionFlowWithSchema('bistro');

Alur streaming

Flow mendukung streaming menggunakan antarmuka yang mirip dengan antarmuka streaming generate(). Streaming berguna saat alur Anda menghasilkan output dalam jumlah besar, karena Anda dapat menampilkan output kepada pengguna saat dihasilkan, yang meningkatkan responsivitas aplikasi yang dirasakan. Sebagai contoh yang sudah dikenal, antarmuka LLM berbasis chat sering kali melakukan streaming responsnya kepada pengguna saat dibuat.

Berikut adalah contoh flow yang mendukung streaming:

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.
        // @ts-ignore
        streamingCallback(chunk.text());
      }
    }

    return {
      theme: restaurantTheme,
      menuItem: (await response.response).text,
    };
  }
);
  • Opsi streamSchema menentukan jenis nilai yang di-streaming alur Anda. Jenis ini tidak harus sama dengan jenis outputSchema, yang merupakan jenis output lengkap alur.
  • streamingCallback adalah fungsi callback yang menggunakan satu parameter, dari jenis yang ditentukan oleh streamSchema. Setiap kali data tersedia dalam alur Anda, kirim data ke aliran output dengan memanggil fungsi ini. Perhatikan bahwa streamingCallback hanya ditentukan jika pemanggil alur Anda meminta output streaming, sehingga Anda perlu memeriksa apakah streamingCallback ditentukan sebelum memanggilnya.

Pada contoh di atas, nilai yang di-streaming oleh flow secara langsung disambungkan ke nilai yang di-streaming oleh panggilan generate() di dalam flow. Meskipun sering kali demikian, hal ini tidak harus terjadi: Anda dapat menghasilkan nilai ke streaming menggunakan callback sesering yang berguna untuk alur Anda.

Memanggil flow streaming

Alur streaming juga dapat dipanggil, tetapi langsung menampilkan objek respons, bukan promise:

const response = menuSuggestionStreamingFlow('Danube');

Objek respons memiliki properti streaming, yang dapat Anda gunakan untuk melakukan iterasi pada output streaming alur saat dihasilkan:

for await (const chunk of response.stream) {
  console.log('chunk', chunk);
}

Anda juga bisa mendapatkan output lengkap alur, seperti yang dapat Anda lakukan dengan alur non-streaming:

const output = await response.output;

Perhatikan bahwa output streaming alur mungkin bukan jenis yang sama dengan output lengkap; output streaming sesuai dengan streamSchema, sedangkan output lengkap sesuai dengan outputSchema.

Menjalankan alur dari command line

Anda dapat menjalankan flow dari command line menggunakan alat Genkit CLI:

genkit flow:run menuSuggestionFlow '"French"'

Untuk alur streaming, Anda dapat mencetak output streaming ke konsol dengan menambahkan flag -s:

genkit flow:run menuSuggestionFlow '"French"' -s

Menjalankan alur dari command line berguna untuk menguji alur, atau untuk menjalankan alur yang melakukan tugas yang diperlukan secara ad hoc—misalnya, untuk menjalankan alur yang menyerap dokumen ke dalam database vektor Anda.

Men-debug flow

Salah satu keuntungan dari mengenkapsulasi logika AI dalam alur adalah Anda dapat menguji dan men-debug alur secara independen dari aplikasi menggunakan UI developer Genkit.

Untuk memulai UI developer, jalankan perintah berikut dari direktori project Anda:

genkit start -- tsx --watch src/your-code.ts

Dari tab Run di UI developer, Anda dapat menjalankan alur apa pun yang ditentukan dalam project Anda:

Screenshot Runner alur

Setelah menjalankan flow, Anda dapat memeriksa rekaman aktivitas pemanggilan flow dengan mengklik Lihat rekaman aktivitas atau melihat di tab Periksa.

Di penampil rekaman aktivitas, Anda dapat melihat detail tentang eksekusi seluruh alur, serta detail untuk setiap langkah dalam alur. Misalnya, pertimbangkan alur berikut, yang berisi beberapa permintaan pembuatan:

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;
  }
);

Saat Anda menjalankan alur ini, penampil rekaman aktivitas akan menampilkan detail tentang setiap permintaan pembuatan, termasuk outputnya:

Screenshot trace inspector

Langkah-langkah alur

Pada contoh terakhir, Anda melihat bahwa setiap panggilan generate() muncul sebagai langkah terpisah di penampil rekaman aktivitas. Setiap tindakan mendasar Genkit muncul sebagai langkah terpisah dari alur:

  • generate()
  • Chat.send()
  • embed()
  • index()
  • retrieve()

Jika ingin menyertakan kode selain yang di atas dalam rekaman aktivitas, Anda dapat melakukannya dengan menggabungkan kode dalam panggilan run(). Anda dapat melakukannya untuk panggilan ke library pihak ketiga yang tidak mendukung Genkit, atau untuk bagian kode kritis.

Misalnya, berikut adalah alur dengan dua langkah: langkah pertama mengambil menu menggunakan beberapa metode yang tidak ditentukan, dan langkah kedua menyertakan menu sebagai konteks untuk panggilan 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;
  }
);

Karena langkah pengambilan digabungkan dalam panggilan run(), langkah ini disertakan sebagai langkah di penampil rekaman aktivitas:

Screenshot langkah yang ditentukan secara eksplisit di Trace Inspector

Men-deploy flow

Anda dapat men-deploy alur secara langsung sebagai endpoint API web, yang siap Anda panggil dari klien aplikasi. Deployment dibahas secara mendetail di beberapa halaman lain, tetapi bagian ini memberikan ringkasan singkat tentang opsi deployment Anda.

Cloud Functions for Firebase

Untuk men-deploy alur dengan Cloud Functions for Firebase, gunakan plugin firebase. Dalam definisi alur, ganti defineFlow dengan onFlow dan sertakan 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) => {
    // ...
  }
);

Untuk informasi lebih lanjut, lihat halaman berikut:

Express.js

Untuk men-deploy flow menggunakan platform hosting Node.js, seperti Cloud Run, tentukan flow Anda menggunakan defineFlow(), lalu panggil startFlowServer():

export const menuSuggestionFlow = ai.defineFlow(
  {
    name: 'menuSuggestionFlow',
  },
  async (restaurantTheme) => {
    // ...
  }
);

ai.startFlowServer({
  flows: [menuSuggestionFlow],
});

Secara default, startFlowServer akan menayangkan semua alur yang ditentukan dalam codebase Anda sebagai endpoint HTTP (misalnya, http://localhost:3400/menuSuggestionFlow). Anda dapat memanggil alur dengan permintaan POST sebagai berikut:

curl -X POST "http://localhost:3400/menuSuggestionFlow" \
  -H "Content-Type: application/json"  -d '{"data": "banana"}'

Jika perlu, Anda dapat menyesuaikan server alur untuk menayangkan daftar alur tertentu, seperti yang ditunjukkan di bawah. Anda juga dapat menentukan port kustom (port akan menggunakan variabel lingkungan PORT jika ditetapkan) atau menentukan setelan 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: '*',
  },
});

Untuk informasi tentang cara men-deploy ke platform tertentu, lihat Men-deploy dengan Cloud Run dan Men-deploy alur ke platform Node.js mana pun.