Första intrycket av Genkit Go
Genkit är ett öppet ramverk för att bygga fullstack-applikationer som drivs av AI, utvecklat och använt i produktion av Google.
Bakgrund
I början av september presenterade Google den stabila och produktionsklara Genkit Go 1.0.
Genkit har sedan tidigare funnits för JavaScript/TypeScript, men finns nu även i en stabil version för Go.
(En variant för Python är under utveckling och finns i dagsläget som en förhandsversion)
Vad du behöver installera
Du behöver ha installerat Go 1.24+
på din dator samt en valfri textredigerare.
Om du även vill köra en helt lokal modell behöver du även Ollama
Om du använder Homebrew så bör du kunna göra:
$ brew install go neovim ollama
Komma igång
Dokumentationen för att komma igång med Genkit är föredömligt bra och tydlig, i de fall där den är uppdaterad för Go.
Notera: Vissa sektioner har dock ännu inte skrivits om (tutorials, authorization, monitorering, osv) men det kommer förhoppningsvis att förbättras med tiden då mer funktionalitet portas över.
Installation av genkit
Nu installerar vi CLI-verktyget genkit
(detta ger framförallt tillgång till genkit ui:start
) via;
$ curl -sL cli.genkit.dev | bash
Notera: Om du inte vill delta i Googles insamling av analysvärden så kan du:
$ genkit config set analyticsOptOut true
Installation av Genkit Go ✨
Först behöver vi initialisera vår Go-modul i en ny mapp;
$ mkdir genkit-with-ollama && cd genkit-with-ollama
$ go mod init genkit-with-ollama
Genkit Go hämtas sedan ner med hjälp av go get
;
$ go get github.com/google/genkit/go@latest
Detta bör nu resultera i en go.mod
likt detta;
module genkit-with-ollama
go 1.25.1
require github.com/firebase/genkit/go v1.0.2
Uppdatera beroenden i go.mod
och checksummor i go.sum
via go mod tidy
.
Val av lokal AI-modell
Då vi ämnar använda oss av Ollama för att köra en lokal AI-modell så behöver vi ladda ner en lämplig sådan.
Man bör antagligen välja en Ollama-modell taggad som tools
.
Sådana modeller hittar du under https://ollama.com/search?c=tools
Notera: Du kommer med största säkerhet vilja jämföra ett antal olika modeller innan du hittar den som lämpar sig bäst för dina syften, och hårdvara.
Efter att ha testat lite olika modeller så landade jag i att använda llama3-groq-tool-use:8b i det här exemplet.
Med hjälp av ollama pull
hämtar du ner denna modell;
$ ollama pull llama3-groq-tool-use:8b
Starta utvecklingsgränssnittet för Genkit
I genkit-with-ollama/
kör du följande;
$ genkit ui:start
Starting...
Genkit Developer UI started at: http://localhost:4000
To stop the UI, run `genkit ui:stop`.
Set env variable `GENKIT_ENV` to `dev` and start
your app code to interact with it in the UI.
Nu bör du kunna gå till http://localhost:4000 och se webbgränssnittet för Genkit,
med ett meddelande som säger Waiting to connect to Genkit runtime...
Ett första Genkit-flöde för att testa att webbgränssnittet fungerar
För att verifiera att webbgränssnittet hittar vår applikation, så kan vi skriva ett litet program likt detta;
Notera: Här använder vi oss inte av någon AI-modell, utan funktionen konstruerar bara en sträng.
minimal.go
package main
import (
"context"
"fmt"
"log"
"net/http"
"github.com/firebase/genkit/go/genkit"
"github.com/firebase/genkit/go/plugins/server"
)
func main() {
log.Fatal(run(context.Background(), "127.0.0.1:3400"))
}
func run(ctx context.Context, addr string) error {
g := genkit.Init(ctx)
genkit.DefineFlow(g, "greetingFlow",
func(ctx context.Context, name string) (string, error) {
return fmt.Sprintf("Hello, %s!", name), nil
},
)
mux := http.NewServeMux()
for _, a := range genkit.ListFlows(g) {
mux.HandleFunc("POST /"+a.Name(), genkit.Handler(a))
}
return server.Start(ctx, addr, mux)
}
$ GENKIT_ENV=dev go run minimal.go
Nu bör vi se följande i webbläsaren:
Och om du skickar "Peter"
som input JSON till flödet bör du få ett resultat (och trace) likt detta;
Du kan naturligtvis även använda dig av cURL och jq
$ curl -s -d $(jq -n -c '{data: "Peter"}') http://127.0.0.1:3400/greetingFlow | jq
{
"result": "Hello, Peter!"
}
Om allt fungerade som det skulle så är vi nu redo att skriva kod där Genkit kommunicerar med Ollama!
Första AI-flödet med Genkit och Ollama
Exemplet i dokumentationen är en receptgenerator men jag tänkte att vi
kunde göra något lite roligare;
En karaktärsgenerator för rollspel.
Ett flöde som genererar rollspelskaraktärer
main.go
package main
import (
"cmp"
"context"
"fmt"
"log"
"math/rand"
"net/http"
"os"
"strings"
"github.com/firebase/genkit/go/ai"
"github.com/firebase/genkit/go/genkit"
"github.com/firebase/genkit/go/plugins/ollama"
"github.com/firebase/genkit/go/plugins/server"
)
const modelName = "llama3-groq-tool-use:8b"
func main() {
addr := "127.0.0.1:3400"
log.Fatal(run(context.Background(), addr))
}
func run(ctx context.Context, addr string) error {
o := newOllamaPlugin()
g := genkit.Init(ctx,
genkit.WithPlugins(o),
genkit.WithDefaultModel("ollama/"+modelName),
)
o.DefineModel(g, ollama.ModelDefinition{
Name: modelName,
Type: "llm",
}, nil)
genkit.DefineFlow(g, "rpgCharacterGeneratorFlow",
func(ctx context.Context, input *RPGCharacterInput) (*RPGCharacter, error) {
c := &RPGCharacter{
Race: randomRace(input.Race),
Class: randomClass(input.Class),
}
{ // Generate the attributes of the character
out, _, err := genkit.GenerateData[RPGCharacterAttributes](ctx, g,
ai.WithPrompt(fmt.Sprintf(
`Create attribute scores in the inclusive range 1-20
for a Role Playing Game character, for inspiration:
Class: %s Race: %s`,
c.Class, c.Race,
)),
)
if err != nil {
return nil, fmt.Errorf("failed to generate attributes: %w", err)
}
c.RPGCharacterAttributes = *out
}
{ // Generate the name of the character
out, _, err := genkit.GenerateData[RPGCharacterDetails](ctx, g,
ai.WithPrompt(fmt.Sprintf(
`The name and age of a Role Playing Game character with:
Race: %q
Class: %q
Attributes: %+v
`,
c.Race, c.Class, c.RPGCharacterAttributes,
)),
)
if err != nil {
return nil, fmt.Errorf("failed to generate details: %w", err)
}
c.RPGCharacterDetails = *out
}
{ // Generate some extra information about the character
out, _, err := genkit.GenerateData[RPGCharacterExtra](ctx, g,
ai.WithPrompt(fmt.Sprintf(
`Extra information about a Role Playing Game character,
for inspiration: %s %s named %s and its age is %d
with the attributes %+v
`,
c.Class, c.Race, c.Name, c.Age, c.RPGCharacterAttributes,
)),
)
if err != nil {
return nil, fmt.Errorf("failed to generate extra: %w", err)
}
c.RPGCharacterExtra = *out
}
return c, nil
},
)
mux := setupMux(g)
log.Println("Starting server on http://" + addr)
log.Println("Available workflows:")
for _, a := range genkit.ListFlows(g) {
log.Println(" POST http://" + addr + "/" + a.Name())
}
return server.Start(ctx, addr, mux)
}
func setupMux(g *genkit.Genkit) *http.ServeMux {
mux := http.NewServeMux()
for _, a := range genkit.ListFlows(g) {
mux.HandleFunc("POST /"+a.Name(), genkit.Handler(a))
}
return mux
}
func newOllamaPlugin() *ollama.Ollama {
return &ollama.Ollama{ServerAddress: ollamaServerAddress()}
}
func ollamaServerAddress() string {
ollamaAddr := cmp.Or(os.Getenv("OLLAMA_HOST"), "127.0.0.1:11434")
if !strings.Contains(ollamaAddr, ":") {
ollamaAddr += ":11434"
}
return fmt.Sprintf("http://%s", ollamaAddr)
}
func randomRace(races ...string) string {
if len(races) == 0 ||
len(races) == 1 && races[0] == "" {
races = []string{
"Human",
"Elf",
"Dwarf",
"Halfling",
"Gnome",
"Half-Elf",
"Half-Orc",
"Tiefling",
}
}
return races[rand.Intn(len(races))]
}
func randomClass(classes ...string) string {
if len(classes) == 0 ||
len(classes) == 1 && classes[0] == "" {
classes = []string{
"Fighter",
"Rogue",
"Wizard",
"Cleric",
"Ranger",
"Bard",
"Paladin",
"Sorcerer",
}
}
return classes[rand.Intn(len(classes))]
}
type RPGCharacterInput struct {
Race string `json:"race,omitempty" jsonschema:"description=Race of the RPG character"`
Class string `json:"class,omitempty" jsonschema:"description=Class of the RPG character"`
}
type RPGCharacter struct {
Race string `json:"race"`
Class string `json:"class"`
RPGCharacterAttributes `json:"attributes"`
RPGCharacterDetails `json:"details"`
RPGCharacterExtra `json:"extra"`
}
type RPGCharacterAttributes struct {
STR int `json:"STR" jsonschema:"description=Physical power; affects melee attack rolls, carrying capacity, and physical feats."`
DEX int `json:"DEX" jsonschema:"description=Agility, reflexes, and balance; affects ranged attacks, Armor Class (AC), and skills like Stealth."`
CON int `json:"CON" jsonschema:"description=Endurance and health; affects hit points (HP) and resistance to fatigue or poison."`
INT int `json:"INT" jsonschema:"description=Reasoning, memory, and knowledge; affects spellcasting for Wizards and skills like Arcana or Investigation."`
WIS int `json:"WIS" jsonschema:"description=Perception, insight, and willpower; affects spellcasting for Clerics and Druids, and skills like Perception or Survival."`
CHA int `json:"CHA" jsonschema:"description=Force of personality; affects social interactions and spellcasting for Bards, Sorcerers, and Warlocks."`
}
type RPGCharacterDetails struct {
Name string `json:"name" jsonschema:"description=Name of the RPG character"`
Age int `json:"age" jsonschema:"description=Age of the RPG character (max age per race: Human=90 Elf=750 Dwarf=350 Halfling=250 Gnome=400 Half-Elf=180 Half-Orc=75 Tiefling=100)"`
}
type RPGCharacterExtra struct {
Background string `json:"background" jsonschema:"description=Background of the character"`
Personality string `json:"personality" jsonschema:"description=Personality of the character"`
Appearance string `json:"appearance" jsonschema:"description=Appearance of the character"`
}
$ GENKIT_ENV=dev go run main.go
Nu bör du kunna generera en karaktär;
$ curl -sd '{"data":{"race":"Elf"}}' http://127.0.0.1:3400/rpgCharacterGeneratorFlow | jq
{
"result": {
"race": "Elf",
"class": "Cleric",
"attributes": {
"STR": 14,
"DEX": 16,
"CON": 10,
"INT": 12,
"WIS": 18,
"CHA": 15
},
"details": {
"name": "Eira Moonwhisper",
"age": 550
},
"extra": {
"background": "Born in the mystical realm of the Elven kingdom, Eira was raised in harmony with nature and the whispers of ancient magic.",
"personality": "Eira is compassionate and empathetic, often seen healing the wounded or offering guidance. Her wisdom is unmatched, sought out by many for her insight into the workings of the universe.",
"appearance": "Eira stands tall at 6 feet, with long silver hair that falls like a river down her back. Her eyes shimmer like moonlight on a quiet lake, and her skin has an ethereal glow."
}
}
}
Notera: Tyvärr är det inte helt problemfritt att använda en relativt liten lokal modell på detta sätt.
Vanliga fel är att modellen inte lyckas svara med ett välformaterat svar, eller att svaret inte ens är JSON.
Att använda en större modell “i molnet” fungerar förhoppningsvis något bättre.
Vad Genkit Go anbelangar behöver man då bara byta ut vilket plugin man använder (via genkit.WithPlugins
)
Om du exempelvis vill använda dig av Google: &googlegenai.GoogleAI{APIKey: "YOUR_API_KEY"}
Som synes är det är inte heller jättesnabbt att kalla på en sådan modell flera gånger.
(Jag använder mig av en Apple M1
samt ett Nvidia GeForce RTX 4070
när jag kör lokala modeller)
/ Peter
När denna artikel skrevs var den aktuella versionen av Go 1.25.1
och Genkit Go 1.0.2