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

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

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

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

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

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

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

फ़्लो तय करना और उन्हें कॉल करना

सबसे आसान तरीके से, फ़्लो सिर्फ़ किसी फ़ंक्शन को रैप करता है. यहां दिए गए उदाहरण में, 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
    })

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

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 सीएलआई टूल का इस्तेमाल करके, कमांड लाइन से फ़्लो चलाए जा सकते हैं:

genkit flow:run menuSuggestionFlow '"French"'

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

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

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

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

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

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

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

genkit start -- go 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 देखें.