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

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

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

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

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

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

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

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

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

अपने generate() कॉल को इस तरह रैप करने से, कुछ फ़ंक्शन जुड़ जाते हैं: इससे, Genkit CLI और डेवलपर यूज़र इंटरफ़ेस (यूआई) से फ़्लो को चलाया जा सकता है. साथ ही, यह Genkit की कई सुविधाओं के लिए ज़रूरी है. इनमें डिप्लॉयमेंट और ऑब्ज़र्वेबिलिटी (बाद के सेक्शन में इन विषयों पर चर्चा की गई है) शामिल हैं.

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

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

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

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

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

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

कॉलिंग फ़्लो

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

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

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

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

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

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

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

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

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

    return {
      theme: restaurantTheme,
      menuItem: (await response.response).text,
    };
  }
);
  • streamSchema विकल्प से यह पता चलता है कि आपका फ़्लो किस तरह की वैल्यू स्ट्रीम करता है. यह ज़रूरी नहीं है कि यह outputSchema जैसा ही हो, जो फ़्लो के पूरे आउटपुट का टाइप होता है.
  • streamingCallback एक कॉलबैक फ़ंक्शन है, जो streamSchema के ज़रिए तय किए गए टाइप का एक पैरामीटर लेता है. जब भी आपके फ़्लो में डेटा उपलब्ध हो, तो इस फ़ंक्शन को कॉल करके डेटा को आउटपुट स्ट्रीम में भेजें. ध्यान दें कि streamingCallback सिर्फ़ तब तय किया जाता है, जब आपके फ़्लो को कॉल करने वाले व्यक्ति ने स्ट्रीमिंग आउटपुट का अनुरोध किया हो. इसलिए, आपको इसे कॉल करने से पहले यह देखना होगा कि इसे तय किया गया है या नहीं.

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

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

स्ट्रीमिंग फ़्लो को भी कॉल किया जा सकता है. हालांकि, ये तुरंत एक प्रॉमिस के बजाय जवाब ऑब्जेक्ट दिखाते हैं:

const response = menuSuggestionStreamingFlow('Danube');

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

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

स्ट्रीमिंग फ़्लो की तरह ही, आपको इस फ़्लो का पूरा आउटपुट भी मिल सकता है:

const output = await response.output;

ध्यान दें कि किसी फ़्लो का स्ट्रीमिंग आउटपुट, पूरे आउटपुट जैसा नहीं हो सकता. स्ट्रीमिंग आउटपुट streamSchema के मुताबिक होता है, जबकि पूरा आउटपुट outputSchema के मुताबिक होता है.

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

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

genkit flow:run menuSuggestionFlow '"French"'

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

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

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

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

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

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

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

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

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

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

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

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

इस फ़्लो को चलाने पर, ट्रेस व्यूअर आपको हर जनरेशन के अनुरोध के बारे में जानकारी दिखाता है. इसमें, उसके आउटपुट की जानकारी भी शामिल होती है:

ट्रेस इंस्पेक्टर का स्क्रीनशॉट

फ़्लो के चरण

पिछले उदाहरण में, आपने देखा कि हर generate() कॉल, ट्रेस व्यूअर में एक अलग चरण के तौर पर दिखता है. Genkit की हर बुनियादी कार्रवाई, फ़्लो के अलग-अलग चरणों के तौर पर दिखती है:

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

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

उदाहरण के लिए, यहां दो चरणों वाला एक फ़्लो दिया गया है: पहला चरण, किसी तय नहीं किए गए तरीके का इस्तेमाल करके मेन्यू को वापस लाता है और दूसरे चरण में, 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;
  }
);

डेटा वापस पाने के चरण को run() कॉल में रैप किया गया है. इसलिए, इसे ट्रैक व्यूअर में चरण के तौर पर शामिल किया गया है:

ट्रेस इंस्पेक्टर में साफ़ तौर पर बताए गए चरण का स्क्रीनशॉट

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

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

Firebase के लिए Cloud Functions

Firebase के लिए Cloud Functions की मदद से फ़्लो डिप्लॉय करने के लिए, firebase प्लग इन का इस्तेमाल करें. अपनी फ़्लो परिभाषाओं में, defineFlow को onFlow से बदलें और 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) => {
    // ...
  }
);

ज़्यादा जानकारी के लिए, ये पेज देखें:

Express.js

Cloud Run जैसे किसी भी Node.js होस्टिंग प्लैटफ़ॉर्म का इस्तेमाल करके फ़्लो डिप्लॉय करने के लिए, defineFlow() का इस्तेमाल करके अपने फ़्लो तय करें. इसके बाद, startFlowServer() को कॉल करें:

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

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

डिफ़ॉल्ट रूप से, startFlowServer आपके कोडबेस में एचटीटीपी एंडपॉइंट (उदाहरण के लिए, http://localhost:3400/menuSuggestionFlow) के तौर पर तय किए गए सभी फ़्लो दिखाएगा. किसी फ़्लो को पीओएसटी अनुरोध के साथ इस तरह कॉल किया जा सकता है:

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

ज़रूरत पड़ने पर, फ़्लो सर्वर को पसंद के मुताबिक बनाया जा सकता है, ताकि फ़्लो की खास सूची दिखाई जा सके. इसके लिए, यहां दिया गया तरीका अपनाएं. आपके पास कस्टम पोर्ट तय करने का विकल्प भी है. अगर पोर्ट सेट है, तो यह PORT एनवायरमेंट वैरिएबल का इस्तेमाल करेगा. इसके अलावा, सीओआरएस सेटिंग भी तय की जा सकती हैं.

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: '*',
  },
});

किसी खास प्लैटफ़ॉर्म पर डिप्लॉय करने के बारे में जानने के लिए, Cloud Run की मदद से डिप्लॉय करना और किसी भी Node.js प्लैटफ़ॉर्म पर फ़्लो डिप्लॉय करना लेख पढ़ें.