تحديد سير عمل الذكاء الاصطناعي

إنّ طلبات النماذج التوليدية هي جوهر ميزات الذكاء الاصطناعي في تطبيقك، ولكن من النادر أن تتمكّن من معالجة إدخال المستخدم وإرساله إلى النموذج وعرض مخرجات النموذج مجددًا على المستخدم. عادةً ما تكون هناك خطوات ما قبل المعالجة وما بعد المعالجة يجب أن تكون مصحوبة باستدعاء النموذج. على سبيل المثال:

  • يتم استرداد المعلومات السياقية لإرسالها مع طلب النموذج.
  • استرداد سجلّ جلسة المستخدم الحالية، على سبيل المثال في أحد تطبيقات المحادثة
  • استخدام نموذج واحد لإعادة تنسيق إدخال المستخدم بطريقة مناسبة لتمرير البيانات إلى نموذج آخر
  • تقييم "أمان" مخرجات النموذج قبل عرضها على العميل
  • الجمع بين نتائج عدة نماذج

يجب أن تعمل كل خطوة من خطوات سير العمل هذا معًا لكي تنجح أي مهمة مرتبطة بالذكاء الاصطناعي.

في 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.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(). يكون البث مفيدًا عندما يُنشئ مسارك كمية كبيرة من النتائج، لأنّه يمكنك عرض النتائج للمستخدم أثناء إنشائها، ما يُحسِّن من سرعة استجابة تطبيقك. وإليك مثالاً مألوفًا: غالبًا ما تبث واجهات LLM المستندة إلى المحادثات ردودها على العميل أثناء إنشائها.

في ما يلي مثال على عملية تتيح البث:

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

يحدِّد نوع string في StreamCallback[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 .

من علامة التبويب تشغيل في واجهة مستخدم المطوّر، يمكنك تنفيذ أيّ من عمليات التنقّل المحدّدة في مشروعك:

لقطة شاشة لتطبيق Flow runner

بعد تشغيل مسار، يمكنك فحص تتبع لطلب تنفيذ المسار من خلال النقر على عرض التتبُّع أو الاطّلاع على علامة التبويب فحص.

نشر المسارات

يمكنك نشر مسارات الإحالات الناجحة مباشرةً كنقاط نهاية لواجهة برمجة تطبيقات الويب، وتكون جاهزة للاتّصال بها من عملاء تطبيقك. تتم مناقشة عملية النشر بالتفصيل في عدة صفحات أخرى، ولكنّ هذا القسم يقدّم نظرة عامة موجزة على خيارات النشر.

net/http الخادم

لنشر عملية باستخدام أيّ منصة استضافة Go، مثل Cloud Run، حدِّد عمليتك باستخدام 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))

يمكنك استدعاء نقطة نهاية مسار باستخدام طلب POST على النحو التالي:

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

للحصول على معلومات عن عمليات النشر على منصات معيّنة، يُرجى الاطّلاع على مقالة Genkit مع Cloud Run.