定義 AI 工作流程

應用程式 AI 功能的核心是生成式模型要求,但您很少會直接擷取使用者輸入內容、將其傳遞給模型,然後將模型輸出內容顯示給使用者。通常,模型呼叫必須搭配預處理和後處理步驟。例如:

  • 擷取要透過模型呼叫傳送的背景資訊。
  • 擷取使用者目前工作階段的記錄,例如在即時通訊應用程式中。
  • 使用一個模型,以適合傳遞至其他模型的方式重新格式化使用者輸入內容。
  • 在向使用者顯示模型輸出內容前,先評估內容的「安全性」。
  • 結合多個模型的輸出內容。

這個工作流程的每個步驟都必須相互配合,才能順利完成任何與 AI 相關的任務。

在 Genkit 中,您可以使用名為「流程」的結構來表示這種緊密連結的邏輯。流程的編寫方式與函式相同,使用一般 Go 程式碼,但會加入額外功能,方便開發 AI 功能:

  • 型別安全性:輸入和輸出結構定義,可提供靜態和執行階段型別檢查。
  • 與開發人員 UI 整合:使用開發人員 UI 時,偵錯流程與應用程式程式碼無關。您可以在開發人員 UI 中執行流程,並查看流程的每個步驟追蹤記錄。
  • 簡化部署程序:使用任何可代管網頁應用程式的平台,直接將流程部署為 Web 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 多項功能的必要條件,包括部署和可觀察性 (後續章節會討論這些主題)。

輸入和輸出結構定義

相較於直接呼叫模型 API,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)

串流流程

Flows 支援使用類似 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
    })

StreamCallback[string] 中的 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

透過指令列執行流程,可用於測試流程,或執行流程以執行所需的臨時任務,例如執行將文件取入向量資料庫的流程。

偵錯流程

在流程中封裝 AI 邏輯的好處之一,就是您可以使用 Genkit 開發人員 UI 獨立測試及偵錯流程,不必透過應用程式。

開發人員 UI 會依賴 Go 應用程式持續執行,即使邏輯已完成也一樣。如果您是初次使用 Genkit,且 Genkit 並非較廣泛應用程式的一部分,請將 select {} 新增為 main() 的最後一行,以免應用程式關閉,讓您可以在 UI 中檢查。

如要啟動開發人員 UI,請在專案目錄中執行下列指令:

genkit start -- go run .

您可以透過開發人員 UI 的「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"}'

其他伺服器架構

您也可以使用其他伺服器架構來部署流程。舉例來說,您只需幾行程式碼即可使用 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"))

如要瞭解如何部署至特定平台,請參閱「搭配 Cloud Run 使用 Genkit」。