Les requêtes de modèle génératif constituent le cœur des fonctionnalités d'IA de votre application. Toutefois, il est rare que vous puissiez simplement prendre l'entrée utilisateur, la transmettre au modèle et afficher la sortie du modèle à l'utilisateur. En général, des étapes de prétraitement et de posttraitement doivent accompagner l'appel du modèle. Exemple :
- Récupération d'informations contextuelles à envoyer avec l'appel du modèle.
- Récupérer l'historique de la session actuelle de l'utilisateur, par exemple dans une application de chat.
- Utilisation d'un modèle pour reformater l'entrée utilisateur de manière à pouvoir la transmettre à un autre modèle.
- Évaluer la "sécurité" de la sortie d'un modèle avant de la présenter à l'utilisateur.
- Combiner la sortie de plusieurs modèles
Chaque étape de ce workflow doit fonctionner ensemble pour que toute tâche liée à l'IA aboutisse.
Dans Genkit, vous représentez cette logique étroitement liée à l'aide d'une construction appelée "flux". Les flux sont écrits comme des fonctions, à l'aide de code Go ordinaire, mais ils ajoutent des fonctionnalités supplémentaires destinées à faciliter le développement de fonctionnalités d'IA:
- Sûreté du typage: schémas d'entrée et de sortie, qui fournissent une vérification de type statique et d'exécution.
- Intégration à l'UI du développeur: déboguez les flux indépendamment du code de votre application à l'aide de l'UI du développeur. Dans l'UI du développeur, vous pouvez exécuter des flux et afficher des traces pour chaque étape du flux.
- Déploiement simplifié: déployez des flux directement en tant que points de terminaison d'API Web, à l'aide de n'importe quelle plate-forme pouvant héberger une application Web.
Les flux de Genkit sont légers et discrets, et ne forcent pas votre application à se conformer à une abstraction spécifique. Toute la logique du flux est écrite en Go standard, et le code d'un flux n'a pas besoin d'être compatible avec le flux.
Définir et appeler des flux
Dans sa forme la plus simple, un flux encapsule simplement une fonction. L'exemple suivant encapsule une fonction qui appelle 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
})
En encapsulant vos appels genkit.Generate()
de cette manière, vous ajoutez des fonctionnalités : cela vous permet d'exécuter le flux à partir de la CLI Genkit et de l'interface utilisateur du développeur, et est obligatoire pour plusieurs fonctionnalités de Genkit, y compris le déploiement et l'observabilité (ces sujets sont abordés dans les sections suivantes).
Schémas d'entrée et de sortie
L'un des principaux avantages des flux Genkit par rapport à l'appel direct d'une API de modèle est la sûreté du typage des entrées et des sorties. Lorsque vous définissez des flux, vous pouvez définir des schémas, de la même manière que vous définissez le schéma de sortie d'un appel genkit.Generate()
. Toutefois, contrairement à genkit.Generate()
, vous pouvez également spécifier un schéma d'entrée.
Voici un affinement du dernier exemple, qui définit un flux qui prend une chaîne en entrée et produit un objet:
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),
)
})
Notez que le schéma d'un flux n'a pas nécessairement besoin de correspondre au schéma des appels genkit.Generate()
dans le flux (en fait, un flux peut même ne pas contenir d'appels genkit.Generate()
). Voici une variante de l'exemple qui transmet un schéma à genkit.Generate()
, mais qui utilise la sortie structurée pour mettre en forme une chaîne simple, que le flux renvoie.
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
})
Flux d'appels
Une fois que vous avez défini un flux, vous pouvez l'appeler à partir de votre code Go:
item, err := menuSuggestionFlow.Run(ctx, "bistro")
L'argument du flux doit être conforme au schéma d'entrée.
Si vous avez défini un schéma de sortie, la réponse du flux s'y conformera. Par exemple, si vous définissez le schéma de sortie sur MenuItem
, la sortie du flux contiendra ses propriétés:
item, err := menuSuggestionFlow.Run(ctx, "bistro")
if err != nil {
log.Fatal(err)
}
log.Println(item.DishName)
log.Println(item.Description)
Flux en streaming
Les flux sont compatibles avec le streaming à l'aide d'une interface semblable à celle de genkit.Generate()
. Le streaming est utile lorsque votre flux génère une grande quantité de sortie, car vous pouvez présenter la sortie à l'utilisateur pendant qu'elle est générée, ce qui améliore la réactivité perçue de votre application. Par exemple, les interfaces LLM basées sur le chat transmettent souvent leurs réponses à l'utilisateur pendant qu'elles sont générées.
Voici un exemple de flux compatible avec le streaming:
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
})
Le type string
dans StreamCallback[string]
spécifie le type de valeurs que votre flux lit. Il ne doit pas nécessairement s'agir du même type que le type de retour, qui est le type de la sortie complète du flux (Menu
dans cet exemple).
Dans cet exemple, les valeurs diffusées par le flux sont directement associées aux valeurs diffusées par l'appel genkit.Generate()
dans le flux.
Bien que ce soit souvent le cas, cela n'est pas obligatoire: vous pouvez envoyer des valeurs au flux à l'aide du rappel aussi souvent que nécessaire pour votre flux.
Flux de streaming d'appels
Les flux en streaming peuvent être exécutés comme des flux non en streaming avec menuSuggestionFlow.Run(ctx, "bistro")
ou être diffusés en streaming:
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)
}
}
Exécuter des flux à partir de la ligne de commande
Vous pouvez exécuter des flux à partir de la ligne de commande à l'aide de l'outil de ligne de commande Genkit:
genkit flow:run menuSuggestionFlow '"French"'
Pour les flux de streaming, vous pouvez imprimer la sortie de streaming dans la console en ajoutant l'option -s
:
genkit flow:run menuSuggestionFlow '"French"' -s
Exécuter un flux à partir de la ligne de commande est utile pour tester un flux ou pour exécuter des flux qui effectuent des tâches nécessaires de manière ponctuelle, par exemple pour exécuter un flux qui ingère un document dans votre base de données de vecteurs.
Flux de débogage
L'un des avantages de l'encapsulation de la logique d'IA dans un flux est que vous pouvez tester et déboguer le flux indépendamment de votre application à l'aide de l'interface utilisateur du développeur Genkit.
Pour démarrer l'UI du développeur, exécutez la commande suivante à partir du répertoire de votre projet:
genkit start -- go run .
Dans l'onglet Run (Exécuter) de l'UI du développeur, vous pouvez exécuter n'importe lequel des flux définis dans votre projet:
Après avoir exécuté un flux, vous pouvez inspecter une trace de l'appel du flux en cliquant sur Afficher la trace ou en consultant l'onglet Inspecter.
Déployer des flux
Vous pouvez déployer vos flux directement en tant que points de terminaison d'API Web, prêts à être appelés à partir des clients de votre application. Le déploiement est abordé en détail sur plusieurs autres pages, mais cette section fournit un bref aperçu de vos options de déploiement.
Serveur net/http
Pour déployer un flux à l'aide de n'importe quelle plate-forme d'hébergement Go, telle que Cloud Run, définissez votre flux à l'aide de DefineFlow()
et démarrez un serveur net/http
avec le gestionnaire de flux fourni:
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()
est une fonction d'assistance facultative qui démarre le serveur et gère son cycle de vie, y compris la capture des signaux d'interruption pour faciliter le développement local. Vous pouvez toutefois utiliser votre propre méthode.
Pour diffuser tous les flux définis dans votre codebase, vous pouvez utiliser 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))
Vous pouvez appeler un point de terminaison de flux avec une requête POST comme suit:
curl -X POST "http://localhost:3400/menuSuggestionFlow" \
-H "Content-Type: application/json" -d '{"data": "banana"}'
Autres frameworks de serveur
Vous pouvez également utiliser d'autres frameworks de serveur pour déployer vos flux. Par exemple, vous pouvez utiliser Gin en quelques lignes seulement:
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"))
Pour en savoir plus sur le déploiement sur des plates-formes spécifiques, consultez la section Genkit avec Cloud Run.