एआई वर्कफ़्लो तय करना

जनरेटिव मॉडल के अनुरोध, आपके ऐप्लिकेशन की एआई सुविधाओं में सबसे अहम होते हैं. हालांकि, ऐसा बहुत कम होता है कि आप बस उपयोगकर्ता का इनपुट ले सकें, उसे मॉडल पर पास कर सकें, और मॉडल आउटपुट को उपयोगकर्ता को वापस दिखा सकें. आम तौर पर, प्रोसेसिंग से पहले और बाद में कुछ चरण होते हैं, जिन्हें मॉडल कॉल के साथ पूरा करना ज़रूरी है. उदाहरण के लिए:

  • मॉडल कॉल के साथ भेजने के लिए, कॉन्टेक्स्ट के हिसाब से जानकारी हासिल करना.
  • उपयोगकर्ता के मौजूदा सेशन का इतिहास वापस पाना, जैसे कि चैट ऐप्लिकेशन में.
  • एक मॉडल का इस्तेमाल करके, उपयोगकर्ता के इनपुट को फिर से फ़ॉर्मैट करना, ताकि वह दूसरे मॉडल को पास कर सके.
  • उपयोगकर्ता को मॉडल के आउटपुट को दिखाने से पहले, उसकी "सुरक्षा" का आकलन करना.
  • कई मॉडल के आउटपुट को मिलाना.

एआई से जुड़े किसी भी टास्क को सफल बनाने के लिए, इस वर्कफ़्लो के हर चरण को एक साथ मिलकर काम करना होगा.

Genkit में, एक-दूसरे से जुड़े इस लॉजिक को दिखाने के लिए, फ़्लो नाम के कंस्ट्रक्शन का इस्तेमाल किया जाता है. फ़्लो को सामान्य Go कोड का इस्तेमाल करके, फ़ंक्शन की तरह ही लिखा जाता है. हालांकि, इनमें ऐसी अतिरिक्त सुविधाएं भी मिलती हैं जिनसे एआई सुविधाओं के डेवलपमेंट को आसान बनाया जा सकता है:

  • टाइप सेफ़्टी: इनपुट और आउटपुट स्कीमा में स्टैटिक और रनटाइम, दोनों टाइप की जांच की सुविधा मिलती है.
  • डेवलपर यूज़र इंटरफ़ेस (यूआई) के साथ इंटिग्रेशन: डेवलपर यूज़र इंटरफ़ेस (यूआई) का इस्तेमाल करके, आपके ऐप्लिकेशन कोड से अलग फ़्लो को डीबग करें. डेवलपर यूज़र इंटरफ़ेस (यूआई) में, आपके पास फ़्लो के हर चरण के लिए फ़्लो चलाने और ट्रेस देखने का विकल्प होता है.
  • आसान डिप्लॉयमेंट: वेब ऐप्लिकेशन को होस्ट करने वाले किसी भी प्लैटफ़ॉर्म का इस्तेमाल करके, सीधे वेब एपीआई एंडपॉइंट के तौर पर फ़्लो को डिप्लॉय करें.

Genkit के फ़्लो हल्के और बिना रुकावट वाले होते हैं. साथ ही, आपके ऐप्लिकेशन को किसी खास ऐब्स्ट्रैक्ट के हिसाब से बनाने के लिए मजबूर नहीं करते हैं. फ़्लो के सभी लॉजिक, स्टैंडर्ड Go में लिखे जाते हैं. साथ ही, फ़्लो के अंदर मौजूद कोड का फ़्लो-अवेयर होना ज़रूरी नहीं है.

फ़्लो तय करना और कॉल करना

अपने सबसे आसान रूप में, फ़्लो सिर्फ़ फ़ंक्शन को रैप करता है. इस उदाहरण में, एक फ़ंक्शन को शामिल किया गया है जो Generate() को कॉल करता है:

menuSuggestionFlow := genkit.DefineFlow(g, "menuSuggestionFlow",
    func(ctx context.Context, theme string) (string, error) {
        resp, err := genkit.Generate(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() कॉल को इस तरह से रैप करके, कुछ फ़ंक्शन जोड़े जा सकते हैं: ऐसा करने से, Genkit CLI और डेवलपर यूज़र इंटरफ़ेस (यूआई) से फ़्लो चलाया जा सकता है. साथ ही, Genkit के डिप्लॉयमेंट और निगरानी के साथ-साथ कई सुविधाओं का इस्तेमाल करने की ज़रूरत होती है (बाद के सेक्शन में इन विषयों पर चर्चा की जाएगी).

इनपुट और आउटपुट स्कीमा

सीधे मॉडल एपीआई का इस्तेमाल करने की तुलना में, Genkit के फ़्लो का सबसे बड़ा फ़ायदा यह है कि इनपुट और आउटपुट दोनों की सुरक्षा एक-एक तरह की होती है. फ़्लो तय करते समय, स्कीमा को उसी तरह परिभाषित किया जा सकता है जैसे genkit.Generate() कॉल के लिए आउटपुट स्कीमा को तय किया जाता है. हालांकि, genkit.Generate() के उलट, इनपुट स्कीमा भी बताया जा सकता है.

यहां पिछले उदाहरण को बेहतर बनाने का तरीका दिया गया है. इसमें ऐसे फ़्लो के बारे में बताया गया है जो इनपुट के तौर पर स्ट्रिंग को लेता है और किसी ऑब्जेक्ट को आउटपुट करता है:

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

ध्यान दें कि फ़्लो के स्कीमा का, फ़्लो में genkit.Generate() कॉल के स्कीमा के साथ अलाइन होना ज़रूरी नहीं है (असल में, हो सकता है कि फ़्लो में genkit.Generate() कॉल शामिल न हों). यह उदाहरण का वह वैरिएंट है जो genkit.GenerateData() को कॉल करता है. हालांकि, वह एक सामान्य स्ट्रिंग को फ़ॉर्मैट करने के लिए स्ट्रक्चर्ड आउटपुट का इस्तेमाल करता है, जिसे फ़्लो देता है. ध्यान दें कि हम MenuItem को टाइप पैरामीटर के तौर पर कैसे पास करते हैं. यह, WithOutputType() विकल्प को पास करने और रिस्पॉन्स में उस टाइप की वैल्यू पाने के बराबर है.

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

कॉलिंग फ़्लो

फ़्लो तय करने के बाद, उसे अपने Go कोड से कॉल किया जा सकता है:

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

फ़्लो का आर्ग्युमेंट, इनपुट स्कीमा के मुताबिक होना चाहिए.

अगर आपने आउटपुट स्कीमा तय किया है, तो फ़्लो रिस्पॉन्स उसके मुताबिक होगा. उदाहरण के लिए, अगर आउटपुट स्कीमा को MenuItem पर सेट किया जाता है, तो फ़्लो आउटपुट में अपनी प्रॉपर्टी शामिल होंगी:

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

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

स्ट्रीमिंग फ़्लो

फ़्लो के ज़रिए स्ट्रीमिंग की सुविधा मिलती है. इसके लिए वह genkit.Generate() के स्ट्रीमिंग इंटरफ़ेस से मिलते-जुलते इंटरफ़ेस का इस्तेमाल करता है. स्ट्रीमिंग तब मददगार होती है, जब आपका फ़्लो ज़्यादा आउटपुट जनरेट करता है. इसकी वजह यह है कि आप आउटपुट जनरेट होते ही उसे उपयोगकर्ता को दिखा सकते हैं. इससे आपके ऐप्लिकेशन का रिस्पॉन्स बेहतर होता है. चैट पर आधारित एलएलएम इंटरफ़ेस अक्सर लोगों को उनके जवाब जनरेट होते ही उन्हें स्ट्रीम करते हैं.

यहां स्ट्रीमिंग की सुविधा देने वाले फ़्लो का उदाहरण दिया गया है:

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] में मौजूद string टाइप से, आपके फ़्लो स्ट्रीम की वैल्यू के बारे में पता चलता है. ज़रूरी नहीं है कि इसका टाइप और रिटर्न टाइप एक ही हों, जो कि फ़्लो के पूरे आउटपुट का टाइप है (इस उदाहरण में Menu).

इस उदाहरण में, फ़्लो से स्ट्रीम की गई वैल्यू, फ़्लो में genkit.Generate() कॉल से स्ट्रीम की गई वैल्यू के साथ सीधे तौर पर मिल जाती हैं. हालांकि, अक्सर ऐसा होता है, लेकिन ज़रूरी नहीं है कि आप कॉलबैक का इस्तेमाल करके, स्ट्रीम में वैल्यू आउटपुट करें. ऐसा उतनी बार किया जा सकता है जितनी बार आपके फ़्लो के लिए मददगार हो.

स्ट्रीमिंग फ़्लो

स्ट्रीमिंग फ़्लो को menuSuggestionFlow.Run(ctx, "bistro") के साथ नॉन-स्ट्रीमिंग फ़्लो की तरह चलाया जा सकता है या उन्हें स्ट्रीम किया जा सकता है:

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

कमांड लाइन से चलने वाला फ़्लो

Genkit CLI टूल का इस्तेमाल करके, कमांड लाइन से फ़्लो चलाया जा सकता है:

genkit flow:run menuSuggestionFlow '"French"'

स्ट्रीमिंग फ़्लो के लिए, -s फ़्लैग जोड़कर स्ट्रीमिंग आउटपुट को कंसोल पर प्रिंट किया जा सकता है:

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

कमांड लाइन से फ़्लो को चलाने से, फ़्लो की जांच करने में मदद मिलती है. इसके अलावा, ऐसे रनिंग फ़्लो के लिए मददगार है जो ऐड-हॉक के आधार पर ज़रूरी टास्क पूरे करते हैं. उदाहरण के लिए, ऐसा फ़्लो चलाना जो आपके वेक्टर डेटाबेस में दस्तावेज़ का डेटा डालता हो.

फ़्लो डीबग करना

एक फ़्लो में एआई लॉजिक को इनकैप्सुलेट करने का एक फ़ायदा यह भी है कि इसमें Genkit डेवलपर के यूज़र इंटरफ़ेस (यूआई) का इस्तेमाल करके, फ़्लो की जांच और उसे डीबग करने के लिए अलग से अपने ऐप्लिकेशन का इस्तेमाल किया जा सकता है.

डेवलपर यूज़र इंटरफ़ेस (यूआई), इस बात पर निर्भर करता है कि Go ऐप्लिकेशन लगातार चलता रहेगा, भले ही लॉजिक पूरा हो गया हो. अगर आपने अभी शुरुआत की है और Genkit किसी बड़े ऐप्लिकेशन का हिस्सा नहीं है, तो ऐप्लिकेशन को बंद होने से रोकने के लिए, select {} को main() की आखिरी लाइन के तौर पर जोड़ें, ताकि आप यूज़र इंटरफ़ेस (यूआई) में उसकी जांच कर सकें.

डेवलपर यूज़र इंटरफ़ेस (यूआई) शुरू करने के लिए, अपनी प्रोजेक्ट डायरेक्ट्री से नीचे दिया गया कमांड चलाएं:

genkit start -- go run .

डेवलपर यूज़र इंटरफ़ेस (यूआई) के Run टैब से, अपने प्रोजेक्ट में तय किए गए किसी भी फ़्लो को चलाया जा सकता है:

फ़्लो रनर का स्क्रीनशॉट

फ़्लो चलाने के बाद, ट्रेस देखें पर क्लिक करके या जांच करें टैब में जाकर, फ़्लो शुरू करने के ट्रेस की जांच की जा सकती है.

फ़्लो डिप्लॉय करना

अपने फ़्लो को सीधे वेब एपीआई एंडपॉइंट के तौर पर डिप्लॉय किया जा सकता है. ऐसा करने पर, आपको अपने ऐप्लिकेशन क्लाइंट से कॉल किया जा सकता है. कई अन्य पेजों पर डिप्लॉयमेंट के बारे में विस्तार से बताया गया है, लेकिन इस सेक्शन में डिप्लॉयमेंट के विकल्पों की खास जानकारी दी गई है.

net/http सर्वर

Cloud Run जैसे किसी भी Go होस्टिंग प्लैटफ़ॉर्म का इस्तेमाल करके फ़्लो डिप्लॉय करने के लिए, DefineFlow() का इस्तेमाल करके अपना फ़्लो तय करें और दिए गए फ़्लो हैंडलर के साथ net/http सर्वर शुरू करें:

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() एक वैकल्पिक हेल्पर फ़ंक्शन है, जो सर्वर को शुरू करता है और उसके लाइफ़साइकल को मैनेज करता है. इसमें स्थानीय डेवलपमेंट को आसान बनाने के लिए रुकावट के सिग्नल कैप्चर करना शामिल है. हालांकि, आपके पास अपना तरीका इस्तेमाल करने का भी विकल्प है.

अपने कोड बेस में बताए गए सभी फ़्लो लागू करने के लिए, 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))

पोस्ट अनुरोध के साथ फ़्लो एंडपॉइंट को इस तरह कॉल किया जा सकता है:

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

अन्य सर्वर फ़्रेमवर्क

अपने फ़्लो को डिप्लॉय करने के लिए, दूसरे सर्वर फ़्रेमवर्क का भी इस्तेमाल किया जा सकता है. उदाहरण के लिए, Gin का इस्तेमाल सिर्फ़ कुछ लाइनों से किया जा सकता है:

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

खास प्लैटफ़ॉर्म पर डिप्लॉयमेंट के बारे में जानकारी पाने के लिए, Cloud Run के साथ Genkit देखें.