Yapay zeka iş akışlarını tanımlama

Uygulamanızın yapay zeka özelliklerinin temelinde üretken model istekleri vardır ancak kullanıcı girişini alıp modele iletmeniz ve model çıktısını kullanıcıya göstermeniz pek mümkün değildir. Genellikle model çağrısına eşlik etmesi gereken ön ve son işleme adımları vardır. Örneğin:

  • Model çağrısıyla gönderilecek bağlamsal bilgileri alma.
  • Örneğin, bir sohbet uygulamasında kullanıcının mevcut oturumunun geçmişini alma
  • Kullanıcı girişini başka bir modele aktarmaya uygun olacak şekilde yeniden biçimlendirmek için bir model kullanmak.
  • Kullanıcıya sunmadan önce modelin çıktısının "güvenliğini" değerlendirme
  • Birkaç modelin çıkışını birleştirme.

Yapay zeka ile ilgili görevlerin başarılı olması için bu iş akışının her adımı birlikte çalışmalıdır.

Genkit'te bu sıkı bağlantılı mantığı akış adı verilen bir yapı kullanarak temsil edersiniz. Akışlar, normal Go kodu kullanılarak işlevler gibi yazılır ancak yapay zeka özelliklerinin geliştirilmesini kolaylaştırmak için ek özellikler ekler:

  • Tür güvenliği: Hem statik hem de çalışma zamanında tür kontrolü sağlayan giriş ve çıkış şemaları.
  • Geliştirici kullanıcı arayüzüyle entegrasyon: Geliştirici kullanıcı arayüzünü kullanarak akışları uygulama kodunuzdan bağımsız olarak hata ayıklayın. Geliştirici kullanıcı arayüzünde akışları çalıştırabilir ve akışın her adımı için izlemeleri görüntüleyebilirsiniz.
  • Basitleştirilmiş dağıtım: Bir web uygulamasını barındırabilen herhangi bir platformu kullanarak akışları doğrudan web API uç noktaları olarak dağıtın.

Genkit'in akışları hafif ve göze çarpmayan bir yapıdadır ve uygulamanızı belirli bir soyutlamaya uymaya zorlamaz. Akıştaki tüm mantık standart Go ile yazılır ve akıştaki kodun akış bilincine sahip olması gerekmez.

Akışları tanımlama ve çağırma

En basit haliyle akış, bir işlevi sarmalayan bir yapıdır. Aşağıdaki örnekte, GenerateData() işlevini çağıran bir işlev sarmalanmıştır:

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

genkit.Generate() çağrılarınızı bu şekilde sarmalayarak bazı işlevler eklersiniz: Bu işlem, akışı Genkit CLI'den ve geliştirici kullanıcı arayüzünden çalıştırmanıza olanak tanır ve dağıtım ve gözlemlenebilirlik dahil olmak üzere Genkit'in çeşitli özellikleri için bir şarttır (bu konular sonraki bölümlerde ele alınmıştır).

Giriş ve çıkış şemaları

Genkit akışlarının, model API'sini doğrudan çağırmaya kıyasla en önemli avantajlarından biri hem girişlerin hem de çıkışların tür güvenliğidir. Akışları tanımlarken, genkit.Generate() çağrısının çıkış şemasını tanımladığınıza benzer şekilde şemalar tanımlayabilirsiniz. Ancak genkit.Generate()'ten farklı olarak bir giriş şeması da belirtebilirsiniz.

Son örneğin daha ayrıntılı bir versiyonunu aşağıda bulabilirsiniz. Bu örnekte, giriş olarak bir dize alan ve çıkış olarak bir nesne veren bir akış tanımlanmaktadır:

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

Bir akış şemasının, akış içindeki genkit.Generate() çağrılarının şemasıyla eşleşmesi gerekmediğini unutmayın (aslında bir akış genkit.Generate() çağrısı bile içermeyebilir). Aşağıda, genkit.Generate()'e bir şema aktaran ancak akışın döndürdüğü basit bir dizeyi biçimlendirmek için yapılandırılmış çıkışı kullanan örneğin bir varyasyonu verilmiştir.

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

Arama akışları

Tanımladığınız bir akışı Go kodunuzdan çağırabilirsiniz:

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

Akışa ait bağımsız değişken, giriş şemasına uygun olmalıdır.

Bir çıkış şeması tanımladıysanız akış yanıtı buna uygun olur. Örneğin, çıkış şemasını MenuItem olarak ayarlarsanız akış çıkışı, özelliklerini içerir:

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

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

Akış akışları

Akışlar, genkit.Generate()'ın yayın arayüzüne benzer bir arayüz kullanarak yayını destekler. Akış, akışınız çok fazla çıkış oluşturduğunda kullanışlıdır. Çünkü çıkışı, oluşturulduğu sırada kullanıcıya sunabilirsiniz. Bu da uygulamanızın algılanan yanıt verebilirliğini artırır. Bilinen bir örnek olarak, sohbet tabanlı LLM arayüzleri genellikle yanıtlarını oluşturuldukları sırada kullanıcıya aktarır.

Akışları destekleyen bir akış örneğini aşağıda görebilirsiniz:

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

StreamCallback[string] içindeki string türü, akışınızın aktardığı değer türünü belirtir. Bu, akıştaki tüm çıkışın türü olan döndürme türüyle (bu örnekte Menu) aynı olmak zorunda değildir.

Bu örnekte, akış tarafından aktarılan değerler doğrudan akış içindeki genkit.Generate() çağrısı tarafından aktarılan değerlerle birleştirilir. Bu genellikle geçerli olsa da gerekli değildir: Geri çağırma işlevini, akışınız için yararlı olduğu kadar sık kullanarak akışa değer gönderebilirsiniz.

Görüşme aktarma akışları

Akış akışları, menuSuggestionFlow.Run(ctx, "bistro") ile akış dışı akışlar gibi çalıştırılabilir veya akış şeklinde yayınlanabilir:

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

Akışları komut satırından çalıştırma

Genkit CLI aracını kullanarak akışları komut satırından çalıştırabilirsiniz:

genkit flow:run menuSuggestionFlow '"French"'

Akış akışları için -s işaretini ekleyerek akış çıkışını konsola yazdırabilirsiniz:

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

Bir akışı komut satırından çalıştırmak, akışı test etmek veya gerektiğinde anlık olarak görevleri gerçekleştiren akışları çalıştırmak için (ör. bir dokümanı vektör veritabanınıza aktaran bir akışı çalıştırmak) kullanışlıdır.

Akışlarda hata ayıklama

Yapay zeka mantığını bir akış içinde kapsamanın avantajlarından biri, Genkit geliştirici kullanıcı arayüzünü kullanarak akışı uygulamanızdan bağımsız olarak test edip hata ayıklayabilmenizdir.

Geliştirici kullanıcı arayüzü, mantık tamamlanmış olsa bile Go uygulamasının çalışmaya devam etmesini gerektirir. Yeni başlıyorsanız ve Genkit daha kapsamlı bir uygulamanın parçası değilse uygulamanın kapanmasını önlemek ve kullanıcı arayüzünde inceleyebilmek için main() dosyasının son satırına select {} ekleyin.

Geliştirici kullanıcı arayüzünü başlatmak için proje dizininizden aşağıdaki komutu çalıştırın:

genkit start -- go run .

Geliştirici kullanıcı arayüzünün Çalıştır sekmesinden projenizde tanımlanan akışlardan herhangi birini çalıştırabilirsiniz:

Akış çalıştırıcısının ekran görüntüsü

Bir akışı çalıştırdıktan sonra İzleyiciyi görüntüle'yi tıklayarak veya İncele sekmesine giderek akışı çağırma işleminin izini inceleyebilirsiniz.

Akışları dağıtma

Akışlarınızı doğrudan web API uç noktaları olarak dağıtabilir ve uygulama istemcilerinizden çağırmaya hazır hale getirebilirsiniz. Dağıtım, diğer çeşitli sayfalarda ayrıntılı olarak ele alınmıştır ancak bu bölümde dağıtım seçeneklerinize kısaca göz atabilirsiniz.

net/http Sunucu

Cloud Run gibi herhangi bir Go barındırma platformunu kullanarak bir akış dağıtmak için DefineFlow() kullanarak akışınızı tanımlayın ve sağlanan akış işleyiciyle bir net/http sunucusu başlatın:

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(), yerel geliştirmeyi kolaylaştırmak için kesinti sinyallerini yakalama da dahil olmak üzere sunucuyu başlatan ve yaşam döngüsünü yöneten isteğe bağlı bir yardımcı işlevdir. Ancak kendi yönteminizi kullanabilirsiniz.

Kod tabanınızda tanımlanan tüm akışları yayınlamak için ListFlows()'ü kullanabilirsiniz:

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

Bir akış uç noktasını POST isteğiyle aşağıdaki gibi çağırabilirsiniz:

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

Diğer sunucu çerçeveleri

Akışlarınızı dağıtmak için diğer sunucu çerçevelerini de kullanabilirsiniz. Örneğin, Gin'i yalnızca birkaç satırla kullanabilirsiniz:

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"))

Belirli platformlara dağıtma hakkında bilgi edinmek için Cloud Run ile Genkit başlıklı makaleyi inceleyin.