Menentukan alur kerja AI

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

  • Mengambil informasi kontekstual yang akan 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 agar dapat diteruskan ke model lain.
  • Mengevaluasi "keamanan" output model sebelum menampilkannya kepada pengguna.
  • Menggabungkan output dari beberapa model.

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

Di Genkit, Anda merepresentasikan logika yang terikat erat ini menggunakan konstruksi yang disebut flow. Flow ditulis seperti fungsi menggunakan kode Go biasa, tetapi menambahkan kemampuan lain yang dimaksudkan untuk memudahkan pengembangan fitur AI:

  • Keamanan jenis: Skema input dan output, yang menyediakan pemeriksaan jenis statis dan runtime.
  • Integrasi dengan UI developer: Men-debug flow secara terpisah dari kode aplikasi menggunakan UI developer. Di UI developer, Anda dapat menjalankan flow dan melihat trace untuk setiap langkah flow.
  • Deployment yang disederhanakan: Men-deploy flow langsung sebagai endpoint API web, menggunakan platform yang dapat menghosting aplikasi web.

Flow Genkit bersifat ringan dan tidak mengganggu, serta tidak memaksa aplikasi Anda untuk mematuhi abstraksi tertentu. Semua logika flow ditulis dalam Go standar, dan kode di dalam flow tidak perlu mengetahui flow itu sendiri.

Menentukan dan memanggil flow

Dalam bentuk yang paling sederhana, flow hanya menggabungkan fungsi. Contoh berikut menggabungkan fungsi yang memanggil GenerateData():

menuSuggestionFlow := genkit.DefineFlow(g, "menuSuggestionFlow",
    func(ctx context.Context, theme string) (string, error) {
        resp, err := genkit.GenerateData(ctx, g,
            ai.WithPrompt("Invent a menu item for a %s themed restaurant.", theme),
        )
        if err != nil {
            return "", err
        }

        return resp.Text(), nil
    })

Cukup dengan menggabungkan panggilan genkit.Generate() seperti ini, Anda akan menambahkan beberapa fungsi: Dengan melakukannya, Anda dapat menjalankan flow dari Genkit CLI dan dari UI developer, serta merupakan persyaratan untuk beberapa fitur Genkit, termasuk deployment dan kemampuan observasi (topik ini akan dibahas di bagian selanjutnya).

Skema input dan output

Salah satu keunggulan terpenting yang dimiliki flow Genkit selain memanggil API model secara langsung adalah keamanan jenis input dan output. Saat menentukan flow, Anda dapat menentukan skema, dengan cara yang sama seperti menentukan skema output panggilan genkit.Generate(). Namun, tidak seperti genkit.Generate(), Anda juga dapat menentukan skema input.

Berikut adalah penyempurnaan dari contoh terakhir, yang menentukan flow menggunakan string sebagai input dan menghasilkan sebuah objek:

type MenuItem struct {
    Name        string `json:"name"`
    Description string `json:"description"`
}

menuSuggestionFlow := genkit.DefineFlow(g, "menuSuggestionFlow",
    func(ctx context.Context, theme string) (MenuItem, error) {
        return genkit.GenerateData[MenuItem](ctx, g,
            ai.WithPrompt("Invent a menu item for a %s themed restaurant.", theme),
        )
    })

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

type MenuItem struct {
    Name        string `json:"name"`
    Description string `json:"description"`
}

menuSuggestionMarkdownFlow := genkit.DefineFlow(g, "menuSuggestionMarkdownFlow",
    func(ctx context.Context, theme string) (string, error) {
        item, _, err := genkit.GenerateData[MenuItem](ctx, g,
            ai.WithPrompt("Invent a menu item for a %s themed restaurant.", theme),
        )
        if err != nil {
            return "", err
        }

        return fmt.Sprintf("**%s**: %s", item.Name, item.Description), nil
    })

Memanggil flow

Setelah menentukan flow, Anda dapat memanggilnya dari kode Go:

item, err := menuSuggestionFlow.Run(ctx, "bistro")

Argumen ke flow harus sesuai dengan skema input.

Jika Anda menentukan skema output, respons flow akan sesuai dengan skema tersebut. Misalnya, jika Anda menetapkan skema output ke MenuItem, output flow akan berisi properti dari skema tersebut:

item, err := menuSuggestionFlow.Run(ctx, "bistro")
if err != nil {
    log.Fatal(err)
}

log.Println(item.DishName)
log.Println(item.Description)

Flow streaming

Flow mendukung streaming menggunakan antarmuka yang mirip dengan antarmuka streaming genkit.Generate(). Streaming berguna saat flow menghasilkan output dalam jumlah besar, karena Anda dapat menampilkan output kepada pengguna saat output tersebut dihasilkan, dan meningkatkan responsivitas aplikasi Anda. Sebagai contoh umum, antarmuka LLM berbasis chat sering kali melakukan streaming respons kepada pengguna saat dihasilkan.

Berikut adalah contoh flow yang mendukung streaming:

type Menu struct {
    Theme  string     `json:"theme"`
    Items  []MenuItem `json:"items"`
}

type MenuItem struct {
    Name        string `json:"name"`
    Description string `json:"description"`
}

menuSuggestionFlow := genkit.DefineStreamingFlow(g, "menuSuggestionFlow",
    func(ctx context.Context, theme string, callback core.StreamCallback[string]) (Menu, error) {
        item, _, err := genkit.GenerateData[MenuItem](ctx, g,
            ai.WithPrompt("Invent a menu item for a %s themed restaurant.", theme),
            ai.WithStreaming(func(ctx context.Context, chunk *ai.ModelResponseChunk) error {
                // Here, you could process the chunk in some way before sending it to
                // the output stream using StreamCallback. In this example, we output
                // the text of the chunk, unmodified.
                return callback(ctx, chunk.Text())
            }),
        )
        if err != nil {
            return nil, err
        }

        return Menu{
            Theme: theme,
            Items: []MenuItem{item},
        }, nil
    })

Jenis string di StreamCallback[string] menentukan jenis nilai yang di-streaming oleh flow Anda. Jenis ini tidak harus sama dengan jenis nilai yang ditampilkan, yaitu jenis output lengkap flow (Menu dalam contoh ini).

Dalam contoh ini, nilai yang di-streaming oleh flow secara langsung disambungkan ke nilai yang di-streaming oleh panggilan genkit.Generate() di dalam flow. Meskipun sering terjadi, namun tidak harus demikian: Anda dapat menghasilkan nilai streaming menggunakan callback sesuai dengan kebutuhan flow Anda.

Memanggil flow streaming

Flow streaming dapat dijalankan seperti flow non-streaming dengan menuSuggestionFlow.Run(ctx, "bistro") atau dapat di-streaming:

streamCh, err := menuSuggestionFlow.Stream(ctx, "bistro")
if err != nil {
    log.Fatal(err)
}

for result := range streamCh {
    if result.Err != nil {
        log.Fatal("Stream error: %v", result.Err)
    }
    if result.Done {
        log.Printf("Menu with %s theme:\n", result.Output.Theme)
        for item := range result.Output.Items {
            log.Println(" - %s: %s", item.Name, item.Description)
        }
    } else {
        log.Println("Stream chunk:", result.Stream)
    }
}

Menjalankan flow dari command line

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

genkit flow:run menuSuggestionFlow '"French"'

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

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

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

Proses debug flow

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

UI developer mengandalkan aplikasi Go yang terus berjalan, meskipun logika telah selesai. Jika Anda baru memulai dan Genkit bukan bagian dari aplikasi yang lebih luas, tambahkan select {} sebagai baris terakhir main() untuk mencegah aplikasi dimatikan sehingga Anda dapat memeriksanya di UI.

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

genkit start -- go run .

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

Screenshot Runner flow

Setelah menjalankan flow, Anda dapat memeriksa trace pemanggilan flow dengan mengklik View trace atau melihat tab Inspect.

Men-deploy flow

Anda dapat men-deploy flow 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.

Server net/http

Untuk men-deploy flow menggunakan platform hosting Go, seperti Cloud Run, tentukan flow Anda menggunakan DefineFlow() dan mulai server net/http dengan pengendali flow yang disediakan:

import (
    "context"
    "log"
    "net/http"

    "github.com/firebase/genkit/go/genkit"
    "github.com/firebase/genkit/go/plugins/googlegenai"
    "github.com/firebase/genkit/go/plugins/server"
)

func main() {
    ctx := context.Background()

    g, err := genkit.Init(ctx, genkit.WithPlugins(&googlegenai.GoogleAI{}))
    if err != nil {
      log.Fatal(err)
    }

    menuSuggestionFlow := genkit.DefineFlow(g, "menuSuggestionFlow",
        func(ctx context.Context, theme string) (MenuItem, error) {
            // Flow implementation...
        })

    mux := http.NewServeMux()
    mux.HandleFunc("POST /menuSuggestionFlow", genkit.Handler(menuSuggestionFlow))
    log.Fatal(server.Start(ctx, "127.0.0.1:3400", mux))
}

server.Start() adalah fungsi bantuan opsional yang memulai server dan mengelola siklus prosesnya, termasuk menangkap sinyal gangguan untuk memudahkan pengembangan lokal, tetapi Anda dapat menggunakan metode Anda sendiri.

Untuk menayangkan semua flow yang ditentukan dalam codebase, Anda dapat menggunakan ListFlows():

mux := http.NewServeMux()
for _, flow := range genkit.ListFlows(g) {
    mux.HandleFunc("POST /"+flow.Name(), genkit.Handler(flow))
}
log.Fatal(server.Start(ctx, "127.0.0.1:3400", mux))

Anda dapat memanggil endpoint flow dengan permintaan POST sebagai berikut:

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

Framework server lainnya

Anda juga dapat menggunakan framework server lain untuk men-deploy flow. Misalnya, Anda dapat menggunakan Gin hanya dengan beberapa baris:

router := gin.Default()
for _, flow := range genkit.ListFlows(g) {
    router.POST("/"+flow.Name(), func(c *gin.Context) {
        genkit.Handler(flow)(c.Writer, c.Request)
    })
}
log.Fatal(router.Run(":3400"))

Untuk informasi terkait deployment ke platform tertentu, lihat Genkit dengan Cloud Run.