تعریف گردش کار هوش مصنوعی

هسته اصلی ویژگی‌های هوش مصنوعی برنامه شما درخواست‌های مدل تولیدی است، اما به ندرت پیش می‌آید که بتوانید ورودی کاربر را دریافت کنید، آن را به مدل ارسال کنید و خروجی مدل را به کاربر نمایش دهید. معمولاً مراحل پیش و پس از پردازش وجود دارد که باید با فراخوانی مدل همراه باشد. به عنوان مثال:

  • بازیابی اطلاعات متنی برای ارسال با تماس مدل
  • بازیابی تاریخچه جلسه فعلی کاربر، به عنوان مثال در یک برنامه چت
  • استفاده از یک مدل برای فرمت مجدد ورودی کاربر به روشی که برای انتقال به مدل دیگر مناسب باشد
  • ارزیابی "ایمنی" خروجی یک مدل قبل از ارائه آن به کاربر
  • ترکیب خروجی چندین مدل

هر مرحله از این گردش کار باید برای موفقیت هر کار مرتبط با هوش مصنوعی با هم کار کند.

در Genkit، شما این منطق مرتبط را با استفاده از ساختاری به نام جریان نشان می‌دهید. جریان‌ها دقیقاً مانند توابع با استفاده از کدهای TypeScript معمولی نوشته می‌شوند، اما قابلیت‌های اضافی را برای سهولت توسعه ویژگی‌های هوش مصنوعی اضافه می‌کنند:

  • ایمنی نوع : طرحواره‌های ورودی و خروجی که با استفاده از Zod تعریف شده‌اند، که بررسی نوع استاتیک و زمان اجرا را فراهم می‌کند.
  • یکپارچه سازی با رابط کاربری توسعه دهنده : اشکال زدایی مستقل از کد برنامه شما با استفاده از رابط کاربری توسعه دهنده جریان می یابد. در رابط کاربری توسعه‌دهنده، می‌توانید جریان‌ها را اجرا کنید و ردیابی‌ها را برای هر مرحله از جریان مشاهده کنید.
  • استقرار ساده : جریان ها را مستقیماً به عنوان نقاط پایانی API وب، با استفاده از توابع ابری برای Firebase یا هر پلتفرمی که می تواند یک برنامه وب را میزبانی کند، استقرار دهید.

برخلاف ویژگی‌های مشابه در سایر فریم‌ورک‌ها، جریان‌های 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 و از UI توسعه دهنده اجرا کنید و برای چندین ویژگی Genkit، از جمله استقرار و مشاهده پذیری (بخش های بعدی) لازم است. در مورد این موضوعات بحث کنید).

طرحواره های ورودی و خروجی

یکی از مهمترین مزایای جریان های Genkit نسبت به فراخوانی مستقیم API مدل، ایمنی نوع ورودی و خروجی است. هنگام تعریف جریان ها، می توانید طرحواره ها را با استفاده از 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');

جریان جریان دارد

Flow ها با استفاده از رابطی شبیه به رابط جریان generate() از جریان پشتیبانی می کنند. پخش جریانی زمانی مفید است که جریان شما مقدار زیادی خروجی تولید می کند، زیرا می توانید خروجی را در حین تولید به کاربر ارائه دهید، که پاسخگویی درک شده برنامه شما را بهبود می بخشد. به عنوان یک مثال آشنا، رابط های LLM مبتنی بر چت اغلب پاسخ های خود را در حین تولید به کاربر ارسال می کنند.

در اینجا نمونه ای از جریانی است که از جریان پشتیبانی می کند:

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() در داخل جریان جفت می شوند. اگرچه اغلب این‌طور است، اما لازم نیست اینطور باشد: می‌توانید مقادیری را با استفاده از callback هر چند وقت یکبار برای جریان شما مفید است، به جریان خروجی بدهید.

فراخوانی جریان‌های جریان

جریان‌های جریانی نیز قابل فراخوانی هستند، اما آنها بلافاصله به جای یک وعده، یک شی پاسخ را برمی‌گردانند:

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 آزمایش و اشکال زدایی کنید.

برای شروع UI توسعه دهنده، دستورات زیر را از دایرکتوری پروژه خود اجرا کنید:

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

از تب Run در رابط توسعه دهنده، می توانید هر یک از جریان های تعریف شده در پروژه خود را اجرا کنید:

اسکرین شات از Flow runner

پس از اجرای یک جریان، می‌توانید با کلیک روی View trace یا نگاه کردن به برگه Inspect ، ردی از فراخوان جریان را بررسی کنید.

در نمایشگر ردیابی، می توانید جزئیات مربوط به اجرای کل جریان و همچنین جزئیات هر یک از مراحل جداگانه در جریان را مشاهده کنید. به عنوان مثال، جریان زیر را در نظر بگیرید که شامل چندین درخواست تولید است:

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() پیچیده شده است، به عنوان یک مرحله در نمایشگر ردیابی گنجانده شده است:

اسکرین شات یک مرحله به صراحت تعریف شده در بازرس ردیابی

استقرار جریان ها

می‌توانید جریان‌های خود را مستقیماً به‌عنوان نقاط پایانی API وب مستقر کنید، تا بتوانید از مشتریان برنامه خود تماس بگیرید. استقرار به طور مفصل در چندین صفحه دیگر مورد بحث قرار گرفته است، اما این بخش مروری کوتاه بر گزینه های استقرار شما ارائه می دهد.

توابع ابری برای Firebase

برای استقرار جریان‌ها با توابع Cloud برای Firebase، از افزونه 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

برای استقرار جریان‌ها با استفاده از هر پلتفرم میزبانی Node.js، مانند Cloud Run، جریان‌های خود را با استفاده از defineFlow() تعریف کنید و سپس startFlowServer() فراخوانی کنید:

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

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

به‌طور پیش‌فرض، startFlowServer تمام جریان‌های تعریف‌شده در پایگاه کد شما را به‌عنوان نقاط پایانی HTTP ارائه می‌کند (به عنوان مثال، http://localhost:3400/menuSuggestionFlow ). می توانید یک جریان را با درخواست POST به صورت زیر فراخوانی کنید:

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

در صورت نیاز، می‌توانید سرور flows را سفارشی کنید تا لیست خاصی از جریان‌ها را ارائه کند، همانطور که در زیر نشان داده شده است. همچنین می توانید یک پورت سفارشی را مشخص کنید (اگر تنظیم شود از متغیر محیطی PORT استفاده می کند) یا تنظیمات CORS را مشخص کنید.

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

برای اطلاعات در مورد استقرار در پلتفرم‌های خاص، به Deploy with Cloud Run و Deploy flows به هر پلتفرم Node.js مراجعه کنید.