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

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

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

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

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

  • أمان الكتابة: مخطّطات الإدخال والمخرجات توفّر إمكانية التحقّق من نوعَي بيئة التشغيل والثابتة.
  • الدمج مع واجهة مستخدم المطوّر: تتدفق عملية تصحيح الأخطاء بشكل مستقل عن رمز التطبيق باستخدام واجهة مستخدم المطوّر. في واجهة مستخدم المطور، يمكنك تشغيل التدفقات وعرض الآثار لكل خطوة من خطوات التدفق.
  • النشر المبسّط: يمكنك نشر التدفقات مباشرةً كنقاط نهاية لواجهة برمجة تطبيقات الويب، باستخدام أي نظام أساسي يستضيف تطبيق ويب.

وتتميز تدفقات 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 كمَعلمة type، فإنّها تُعادِل تمرير الخيار 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
    })

يحدد النوع 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 .

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

لقطة شاشة لمخطط التدفق

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

مسارات النشر

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

خادم في 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 with Cloud Run.