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

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

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

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

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

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

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

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

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

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

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

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

جریان‌های جریانی را می‌توان مانند جریان‌های غیر استریم با 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 .

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

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

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

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

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

سرور 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"}'

سایر چارچوب های سرور

همچنین می توانید از سایر فریم ورک های سرور برای استقرار جریان های خود استفاده کنید. به عنوان مثال، می توانید از جین فقط با چند خط استفاده کنید:

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 مراجعه کنید.