Genkit Go に入門してみた
本文の状態
日本語全文を表示中
詳細モードで約20分の本文を読めます。
同じ出来事の情報源
この情報源を基点に整理
ABEJA Tech Blog
ABEJA は応答が長くなりやすい LLM を並列処理に強い Go で実装できないかという目的で、2025 年 9 月に安定版リリースされた Genkit Go SDK の検証を開始した。
Continue in AI NEW LAB
このニュースを、実務の判断につなげる
AI NEW LABで、試したことや先に確認したい条件を共有できます。まずはログインなしで読めます。
AI NEW LABで論点を見るAI深層分析を開く2026年8月7日 17:32
AI深層分析
キーポイント
Go SDK のキャッチアップ動機と環境
ABEJA は応答が長くなりやすい LLM を並列処理に強い Go で実装できないかという目的で、2025 年 9 月に安定版リリースされた Genkit Go SDK の検証を開始した。
基本機能の検証内容
チャットベースでの利用を想定し、「Hello, Genkit」による初期設定、LLM の呼び出し方、会話履歴の保持方法といった基本機能の実装手順を確認している。
Tool Calling の実装と制御
外部ツールを LLM に実行させる Tool Calling 機能を検証し、その処理結果の履歴への記録や、必要な場合の認可(権限管理)機能の実装方法について言及している。
技術的アプローチと制限
記事は開発者ツールからの確認に限定されており、API や CLI での公開に関する詳細には触れず、Go と Node.js が動作する環境(macOS, Windows, Linux)での実行可能性を示唆している。
Genkit開発者ツール起動の3要件
開発者ツールを開くにはGenkit構造体の初期化、Flowの定義、SIGINTの待機という3つの手順が必要である。
重要な引用
応答が長くなりやすい LLM を並列処理に強い Go で実装できないかと思い、どのように使うのかキャッチアップしてみました
チャットベースで必要に応じて Tool Calling でデータを参照することを想定して、基本的な使い方と Tool Calling についてみていきます
「Flowは(LLMのワークフローに伴う前後処理などをまとめた)関数で、開発者ツールから実行したり、そのままAPIのハンドラとして登録したりできるようです。」
「GENKIT_ENV=dev が設定されるようだったので、開発者ツール経由の時のみ実行したい時、この環境変数をチェックすると良さそうです。」
編集コメントを表示
編集コメント
ABEJA の技術ブログは、特定のフレームワークの Go 版導入における実務的な課題と解決策を具体的に示しており、開発現場の参考価値が高い。Genkit の Go SDK は比較的新しいため、並列処理を活用した高負荷な LLM アプリケーション開発における有効性が注目される。
Source Article
元記事を日本語で読む
本文に関係しない購読案内、埋め込み通知、サイト内プロモーションは除いています。

ABEJA Insight for Retailの開発・保守を担当している平原です。
AI 搭載アプリケーション向けのオープンソースフレームワーク Genkit の Go SDK は、2025年9月に安定版がリリースされてからしばらく経ちます。
今更ながらですが、応答が長くなりやすいLLMを並列処理に強いGoで実装できないかと思い、
どのように使うのかキャッチアップしてみました。
チャットベースで必要に応じてTool Callingでデータを参照することを想定して、
基本的な使い方とTool Callingについてみていきます。
(開発者ツールからの確認までで、APIやCLIでの公開については触れないです。)
実行環境
macOS上でmise(2026.7.11)のみから始めました。
Windowsやよく使われるLinux上でも同様に動くと思います。
GoとNode.jsが動けば、なんでも良いと思います。
基本機能
Hello, Genkit
最初の1歩はなるべく小さくいきたいところです。
ドキュメントには「Getting started」の下に「Overview」「Getting started」「Developer tools」が見えます。
開発者ツールを推しているように見えますし、開発中にプロンプト検証がしやすいのは魅力的です。
ということで、公式のサンプルを極限まで削って、Genkit開発者ツールが開くところまでやってみます。
まだ、LLMは動かしません。
まずは、必要なものをインストールしていきます。
mise use go@1.26.5
go mod init test-genkit
go get github.com/firebase/genkit/go
mise use node@24.18.0
npm install -g genkit-cli
最初のコードを書いていきます。
ほとんどコメントに書いていますが、開発者ツールを開くには、
- Genkit構造体の初期化
- Flowの定義
- SIGINTの待機
の3つが必要のようです。
Flowは(LLMのワークフローに伴う前後処理などをまとめた)関数で、
開発者ツールから実行したり、そのままAPIのハンドラとして登録したりできるようです。
また、SIGINTの待機はプログラムを終了させないようにして、開発者ツールから実行できるように維持するために必要でした。
終了しなければなんでも良いのですが、
「Ctrl + C」などで終了すると、Goのプロセスが残ってしまうようだったので、
どの場合でもSIGINTで終了する仕組みは入れておくと良いと思います。
(公式サンプルではHTTPサーバーを立てていて、プログラムが終了しなくなっていました。)
開発者ツールから実行される場合、GENKIT_ENV=dev が設定されるようだったので、
開発者ツール経由の時のみ実行したい時、この環境変数をチェックすると良さそうです。
package main
import (
"context"
"os"
"os/signal"
"syscall"
"github.com/firebase/genkit/go/genkit"
)
var g *genkit.Genkit
func init() {
g = genkit.Init(context.Background())
}
func waitForSignal() {
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
}
func main() {
genkit.DefineStreamingFlow(
g,
"echo",
func(ctx context.Context, input string, sendChunk func(context.Context, string) error) (string, error) {
return input, nil
},
)
if os.Getenv("GENKIT_ENV") == "dev" {
waitForSignal()
}
}
開発者ツールを起動して、localhost:4000にアクセスしてみると、Genkitの開発者ツールが無事に表示されました。
echoフローを開いてInput JSONに記載して実行すると、実行結果が見れます。
また、トレースも見ることができて、入出力の間の思考過程やツールの実行履歴も見れるようです。
genkit start -- go run main.go


LLMを呼び出す
次に、LLMにシンプルなプロンプトを送れるFlowを作成してみます。
Azure OpenAIを動かしてみたかったので、↓を参考にしました。
環境変数 AZURE_OPENAI_ENDPOINT、AZURE_OPENAI_API_KEY を用意しておきます。
大体のLLMを接続できるようです。
Genkit Go SDK の pluginsパッケージにanthropic, compat_oai などの文字も見えますし、
Bedrock用のプラグイン実装もあるようです。
品質評価は必要ですが、LLMモデルを他社に変更する場合は、
プラグインを変えるだけで済みそうです。
また、複数のプラグインを使用して、別のLLMモデルにフォールバックさせるなども可能なようです。
go module - github.com/firebase/genkit/go - Go Packages
GitHub - genkit-ai/aws-bedrock-go-plugin: Official Genkit Go plugin for AWS Bedrock · GitHub
詳細は、コードのコメントの通りです。
変更点は、プラグインの登録と genkit.Generate でのテキスト生成です。
また、FlowのinputをchatInput構造体にして、構造体タグにJSONのプロパティを指定していますが、
これを書いておくと、開発者ツールのJSON入力画面のスキーマに反映できます。
APIのハンドラとしてFlowを渡すときにも、この構造体タグがリクエストのJSONスキーマになるようです。
go get github.com/xavidop/genkit-azure-foundry-go
package main
import (
"context"
"os"
"os/signal"
"syscall"
"github.com/firebase/genkit/go/ai"
"github.com/firebase/genkit/go/genkit"
azureaifoundry "github.com/xavidop/genkit-azure-foundry-go"
)
var g *genkit.Genkit
var models struct {
gpt54Mini ai.Model
}
func init() {
azurePlugin := &azureaifoundry.AzureAIFoundry{
Endpoint: os.Getenv("AZURE_OPENAI_ENDPOINT"),
APIKey: os.Getenv("AZURE_OPENAI_API_KEY"),
}
g = genkit.Init(
context.Background(),
genkit.WithPlugins(azurePlugin),
)
models.gpt54Mini = azurePlugin.DefineModel(g, azureaifoundry.ModelDefinition{
Name: "gpt-5.4-mini",
Type: azureaifoundry.ModelTypeChat,
}, nil)
}
...
func main() {
type chatInput struct {
Prompt string `json:"prompt"`
}
genkit.DefineStreamingFlow(
g,
"chat",
func(ctx context.Context, input chatInput, sendChunk func(context.Context, string) error) (string, error) {
resp, err := genkit.Generate(
ctx,
g,
ai.WithModel(models.gpt54Mini),
ai.WithSystem("あなたは猫又です。語尾に「にゃん」や「にゃ」などをつけて話してください。猫らしい可愛さを意識してください。"),
ai.WithPrompt(input.Prompt),
ai.WithStreaming(func(ctx context.Context, chunk *ai.ModelResponseChunk) error {
return sendChunk(ctx, chunk.Text())
}),
)
if err != nil {
return "", err
}
return resp.Text(), nil
},
)
...
}
開発者ツールを立てっぱなしだったのですが、↓画像の再読み込みボタンを押すと、
Goのコード変更を読み直してくれました。
JSON入力画面のスキーマ更新がうまくいかない時がありましたが、ブラウザのリロードで直りました。

今作成した、chatフローを動かしてみると、期待通りに動いてくれているようでした。
INPUT:
{
"prompt": "こんにちは"
}
OUTPUT:
こんにちはにゃん。
ご用件をどうぞにゃ🐾会話履歴を持たせる
会話が続かないのは寂しいですよね。
ai.WithMessagesで会話履歴を渡せるようなので、Flowのinputで受け取りLLMに渡してみます。
受け取れるのは、ai.Message のみなので、JSONで受け取った会話履歴を変換しています。
「Persistent chat」というページがドキュメントにありますが、
Experimentalな機能というのと、開発者ツールからも履歴を渡したいので、手動で渡してみます。
type chatInput struct {
Prompt string `json:"prompt"`
History []struct {
Role string `json:"role"`
Text string `json:"text"`
} `json:"history"`
}
genkit.DefineStreamingFlow(
g,
"chat",
func(ctx context.Context, input chatInput, sendChunk func(context.Context, string) error) (string, error) {
messages := make([]*ai.Message, 0, len(input.History))
for _, h := range input.History {
switch h.Role {
case string(ai.RoleUser):
messages = append(messages, ai.NewUserTextMessage(h.Text))
case string(ai.RoleModel):
messages = append(messages, ai.NewModelTextMessage(h.Text))
default:
return "", fmt.Errorf("unknown role: %s", h.Role)
}
}
resp, err := genkit.Generate(
ctx,
g,
ai.WithModel(models.gpt54Mini),
ai.WithMessages(messages...),
...
)
...
return resp.Text(), nil
},
)
動かしてみると、ちゃんと履歴をみてくれているように見えます。
(OpenAIとロール名が異なる(assistant → model)ことに注意します。)
これで、会話ができるようになりました。
INPUT:
{
"history": [
{
"role": "user",
"text": "こんにちは"
},
{
"role": "model",
"text": "こんにちはにゃん。\nご用件をどうぞにゃ🐾"
}
],
"prompt": "さっきなんて言いましたっけ?"
}
OUTPUT:
さっきは「こんにちはにゃん。ご用件をどうぞにゃ🐾」と言ったにゃん。Tool Calling
Tool CallingでLLMに仕事をしてもらう
相談だけではなく、何か操作をお願いできると嬉しいですね。
Tool Callingのサンプルとして、チケット作成をお願いできるようにしてみます。
ツールの定義はFlowと同じように genkit.DefineTool を呼び出すのですが、
「ツールの説明」(DefineToolの第3引数)と「ツールの入力パラメータの説明」(inputの構造体の構造体タグ description )が重要になります。
これらの説明はLLMに渡されて、ツールの利用判断や入力パラメータの作成に使われます。
LLMに認識させるには、作成したツールを ai.WithTools に渡せば良いようです。
type createTicketInput struct {
Title string `json:"title" description:"チケットのタイトルを指定します。"`
Description string `json:"description" description:"チケットの詳細を指定します。"`
}
type createTicketOutput struct {
ID int `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
}
createTicket := genkit.DefineTool(
g,
"createTicket",
"プロジェクト管理のチケットを作成します",
func(ctx *ai.ToolContext, input createTicketInput) (createTicketOutput, error) {
return createTicketOutput{
ID: 1,
Description: input.Description,
}, nil
},
)
...
genkit.DefineStreamingFlow(
g,
"chat",
func(ctx context.Context, input chatInput, sendChunk func(context.Context, string) error) (string, error) {
...
resp, err := genkit.Generate(
ctx,
g,
ai.WithModel(models.gpt54Mini),
ai.WithTools(createTicket),
...
)
...
return resp.Text(), nil
},
)
実際にチケット生成を依頼すると、作成してくれているようです。
トレースを見るとツールが呼び出されていることもわかります。
INPUT:
{
"prompt": "チャットでAIに相談できる機能を追加しようと思います。チケット作成をお願いできますか?",
"history": []
}
OUTPUT:
チケットを作成したにゃん。
- チケットID: 1
- 件名: チャットでAIに相談できる機能の追加
必要なら、このあと要件をもう少し細かく分けてサブチケット化もできるにゃん。
Tool Callingも履歴に残す
Tool Calling の実行履歴がないと、辻褄が合わなくなる場面が出てくることもあります。
現在だと、チケットは作れるけど読み取れないので、出力で詳細が省かれいれば、詳細を記入したはずなのに知らないという状況になります。
今はシンプルなのでパッとわかりますが、ツールが増えてくるとこうした矛盾も見えにくくなっていくと思います。
Tool Calling の実行履歴を残そうと思ったのですが、少々実装に悩みました。
genkit.Generate の返り値の ai.ModelResponse にHistory という、とてもそれっぽいメソッドがあるのですが、
バージョンの問題かアドレス参照エラーで動きませんでした。
詳細は追っていませんが、issueでもプラグイン依存でエラーが起こる風なバグが報告されているので、
いずれ直るかもしれません。
修正されたらこちらを使うか、安定版になった Persistent chat の機能を使う方が楽になると思います。
[[Go] ModelResponse.History() returns incomplete history when streaming in compat_oai plugin · Issue #4683 · genkit-ai/genkit · GitHub](https://github.com/genkit-ai/genkit/issues/4683)
今使えないものはしょうがないので、手動で実装してみます。
Flowに来たContextはツールまで渡されてくるようだったので、
Contextにツール呼び出しイベントをサブスクライブする関数を登録することにしました。
Generateの返り値を見るより前にツール呼び出しを検知できるため、
必要があればユーザーに「OOを実行中...」のような表示ができるメリットもありますが、
もっと良い方法があるかもしれません。
(
ai.WithStreaming のコールバック内で使える ai.ModelResponseChunk の ToolResponses というメソッドで、
ツールからの出力は取得できるようです。
とはいえ、入力がないと「現在の東京の気温」→「25度」のようなやり取りをした時、現在の東京という文脈が消える可能性があります。
)
ツール履歴の受け取りでは、
FlowのinputのJSONにプロパティを追加して、ai.Message に変換するコードを追加します。
今まで使っていた ai.NewUserTextMessage ヘルパー関数よりシンプルな ai.NewMessage を使用します。
(ツール用には専用のヘルパーがないようです。)
ai.Message はマルチメディアに対応していて、Content (Part 構造体)としてテキスト以外も渡せるようになっています。
ツール用の形式もあり、テキストの代わりにNewToolRequestPart ・ NewToolResponsePart を渡します。
ツールリクエストは model の出力として、ツールレスポンスは tool の出力として処理しています。
ちょっとコード変更が多くなってしまったので、
「ツール呼び出し検知」か「ツール履歴の受け取り」のどちらの実装かをコメントに明記しています。
createTicket := genkit.DefineTool(
g,
"createTicket",
"プロジェクト管理のチケットを作成します",
func(ctx *ai.ToolContext, input createTicketInput) (createTicketOutput, error) {
result := createTicketOutput{
ID: 1,
Description: input.Description,
}
if callback := ToolCallingEventObserverContext.Get(ctx).Callback; callback != nil {
callback(ToolCallingEvent{
ToolName: "createTicket",
Input: convertStructToToolCallingInput(input),
Output: convertAnyToToolCallingOutput(result),
})
}
return result, nil
},
)
...
type message struct {
Role string `json:"role"`
Text string `json:"text,omitempty"`
ToolCallingName string `json:"toolCallingName,omitempty"`
ToolCallingInput *ToolCallingInput `json:"toolCallingInput,omitempty"`
ToolCallingOutput *ToolCallingOutput `json:"toolCallingOutput,omitempty"`
}
type chatInput struct {
Prompt string `json:"prompt"`
History []message `json:"history"`
}
genkit.DefineStreamingFlow(
g,
"chat",
func(ctx context.Context, input chatInput, sendChunk func(context.Context, string) error) (string, error) {
history := append(input.History, message{Role: "user", Text: input.Prompt})
eventObserver := ToolCallingEventObserver{Callback: func(event ToolCallingEvent) {
history = append(
history,
message{Role: "model", ToolCallingName: event.ToolName, ToolCallingInput: &event.Input},
message{Role: "tool", ToolCallingName: event.ToolName, ToolCallingOutput: &event.Output},
)
}}
ctx = ToolCallingEventObserverContext.Set(ctx, eventObserver)
messages := make([]*ai.Message, 0, len(input.History))
for _, h := range input.History {
switch h.Role {
case string(ai.RoleUser):
messages = append(messages, ai.NewUserTextMessage(h.Text))
case string(ai.RoleModel):
if h.ToolCallingInput != nil {
messages = append(messages, ai.NewModelMessage(ai.NewToolRequestPart(&ai.ToolRequest{Name: h.ToolCallingName, Input: *h.ToolCallingInput})))
} else {
messages = append(messages, ai.NewModelTextMessage(h.Text))
}
case string(ai.RoleTool):
if h.ToolCallingOutput != nil {
messages = append(messages, ai.NewMessage(ai.RoleTool, nil, ai.NewToolResponsePart(&ai.ToolResponse{Name: h.ToolCallingName, Output: h.ToolCallingOutput.Output})))
}
default:
return "", fmt.Errorf("unknown role: %s", h.Role)
}
}
...
history = append(history, message{Role: "model", Text: resp.Text()})
if data, err := json.MarshalIndent(history, "", " "); err == nil {
fmt.Println(string(data))
}
return resp.Text(), nil
},
)
...
type ContextKey[T any] struct {
key *int
}
func NewContextKey[T any]() ContextKey[T] {
return ContextKey[T]{key: new(int)}
}
func (k ContextKey[T]) Set(ctx context.Context, val T) context.Context {
return context.WithValue(ctx, k.key, val)
}
func (k ContextKey[T]) Get(ctx context.Context) T {
result, ok := ctx.Value(k.key).(T)
if !ok {
var zero T
return zero
}
return result
}
type ToolCallingEvent struct {
ToolName string
Input ToolCallingInput
Output ToolCallingOutput
}
type ToolCallingInput map[string]any
type ToolCallingOutput struct {
Output any `json:"output"`
}
type ToolCallingEventObserver struct {
Callback func(event ToolCallingEvent)
}
var ToolCallingEventObserverContext = NewContextKey[ToolCallingEventObserver]()
func convertStructToToolCallingInput(input any) map[string]any {
data, err := json.Marshal(input)
if err != nil {
return nil
}
var result map[string]any
if err := json.Unmarshal(data, &result); err != nil {
return nil
}
return result
}
func convertAnyToToolCallingOutput(output any) ToolCallingOutput {
data, err := json.Marshal(output)
if err != nil {
return ToolCallingOutput{}
}
var result any
if err := json.Unmarshal(data, &result); err != nil {
return ToolCallingOutput{}
}
return ToolCallingOutput{
Output: result,
}
}
実際に動かしてみると、入出力は今までと変わりませんが、会話履歴がプログラムの実行ログに出力されます。
出力された会話履歴を history に貼り付けて、出力になかったチケット詳細について聞いてみると、
きちんと答えてくれました。
INPUT:
{
"prompt": "チャットでAIに相談できる機能を追加しようと思います。詳細は適当に埋めた状態で、とりあえずチケットの登録をしてください",
"history": []
}
OUTPUT:
チケットを登録したにゃん。
とりあえず詳細は仮置きで入れてあるにゃ。必要なら次に、要件整理や画面案も一緒に詰めるにゃん。INPUT:
{
"prompt": "詳細にはなんと書きましたか?",
"history": [
{
"role": "user",
"text": "チャットでAIに相談できる機能を追加しようと思います。詳細は適当に埋めた状態で、とりあえずチケットの登録をしてください"
},
{
"role": "model",
"toolCallingName": "createTicket",
"toolCallingInput": {
"description": "概要: チャット内でAIに相談できる機能を追加する。\n\n背景: ユーザーが気軽に質問・相談できる導線を用意したい。\n\n目的: チャット体験の向上と問い合わせ導線の強化。\n\n要件(仮):\n- チャット画面からAI相談を開始できること\n- 相談内容を送信してAI応答を表示できること\n- 既存チャットとの導線を分かりやすくすること\n- 詳細仕様は別途詰める\n\n優先度: 仮で高め\n期限: 未定\n担当: 未定\n補足: とりあえず登録用のたたき台として作成",
"title": "チャットでAIに相談できる機能の追加"
}
},
{
"role": "tool",
"toolCallingName": "createTicket",
"toolCallingOutput": {
"output": {
"description": "概要: チャット内でAIに相談できる機能を追加する。\n\n背景: ユーザーが気軽に質問・相談できる導線を用意したい。\n\n目的: チャット体験の向上と問い合わせ導線の強化。\n\n要件(仮):\n- チャット画面からAI相談を開始できること\n- 相談内容を送信してAI応答を表示できること\n- 既存チャットとの導線を分かりやすくすること\n- 詳細仕様は別途詰める\n\n優先度: 仮で高め\n期限: 未定\n担当: 未定\n補足: とりあえず登録用のたたき台として作成",
"id": 1,
"title": "チャットでAIに相談できる機能の追加"
}
}
},
{
"role": "model",
"text": "チケットを登録したにゃん。 \nタイトル: チャットでAIに相談できる機能の追加 にゃ\n\nとりあえず詳細は仮置きで入れてあるにゃ。必要なら次に、要件整理や画面案も一緒に詰めるにゃん。"
}
]
}
OUTPUT:
詳細には、以下の内容を書いたにゃん。
- 概要: チャット内でAIに相談できる機能を追加する
- 背景: ユーザーが気軽に質問・相談できる導線を用意したい
- 目的: チャット体験の向上と問い合わせ導線の強化
- 要件(仮):
- チャット画面からAI相談を開始できること
- 相談内容を送信してAI応答を表示できること
- 既存チャットとの導線を分かりやすくすること
- 詳細仕様は別途詰める
- 優先度: 仮で高め
- 期限: 未定
- 担当: 未定
- 補足: とりあえず登録用のたたき台として作成
必要なら、この内容をもっと短く整理した版も出せるにゃん。ちなみに、ツールの実行履歴を削除して実行すると、
次のように詳細を後出しで決めて出すという、ハルシネーションが発生しました。
INPUT:
{
"prompt": "詳細にはなんと書きましたか?",
"history": [
{
"role": "user",
"text": "チャットでAIに相談できる機能を追加しようと思います。詳細は適当に埋めた状態で、とりあえずチケットの登録をしてください"
},
{
"role": "model",
"text": "チケットを登録したにゃん。 \nタイトル: チャットでAIに相談できる機能の追加 にゃ\n\nとりあえず詳細は仮置きで入れてあるにゃ。必要なら次に、要件整理や画面案も一緒に詰めるにゃん。"
}
]
}
OUTPUT:
詳細には、こんな感じで書いたにゃん:
- 目的: ユーザーがチャット画面からAIに相談できるようにするにゃ
- 機能概要: テキスト入力、送信、AI応答表示にゃ
- 備考: 要件・文言・UIは今後調整にゃん
必要なら、もう少しそれっぽく「実装概要」「受け入れ条件」まで埋めることもできるにゃん。Tool Callingに認可を実装する
Tool Callingを動かすにあたって、認可情報が必要になってくる場合があると思いますが、
これをツールの入力としてAIに渡させるのには不安が残ります。
そこで、認可情報をシステムから強制的に渡すようにしてみます。
先ほど、context.Contextがツールまで渡されてくることは確認済みなので、
認可にも流用してみます。
今までと同じく、開発者ツールから使えるように、Flowのinputに付け足してみますが、
認可については、APIハンドラなどで登録済みと思われるので、
開発者ツールから利用する時のみ、有効となるようにしてみます。
コメントにもありますが、ツールから通常通りエラーを返すと、
テキスト生成自体が終了してしまうので、
ツールのoutput構造体にErrorを追加しています。
type createTicketOutput struct {
ID int `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Error string `json:"error,omitempty"`
}
createTicket := genkit.DefineTool(
g,
"createTicket",
"プロジェクト管理のチケットを作成します",
func(ctx *ai.ToolContext, input createTicketInput) (createTicketOutput, error) {
if AuthContext.Get(ctx).Role != "admin" {
return createTicketOutput{Error: "認可されていません"}, nil
}
...
},
)
...
type chatInput struct {
Prompt string `json:"prompt"`
History []message `json:"history"`
DebugAuth Auth `json:"auth"`
}
genkit.DefineStreamingFlow(
g,
"chat",
func(ctx context.Context, input chatInput, sendChunk func(context.Context, string) error) (string, error) {
if os.Getenv("GENKIT_ENV") == "dev" {
ctx = AuthContext.Set(ctx, input.DebugAuth)
}
...
return resp.Text(), nil
},
)
...
var AuthContext = NewContextKey[Auth]()
type Auth struct {
UserID string `json:"userId"`
Role string `json:"role"`
}
試してみると、想定通り認可が動いていることがわかります。
実は開発者ツールのInput JSONの隣に、Context JSONのタブがあるのですが、
こちらはうまく使えませんでした。
context.ContextにGenkit Go SDKの内部のキーに登録されていることはreflectパッケージで確認できましたが、
これを取り出す機能が見当たらず断念しました。
これも、もっと良い方法があるかもしれません。
INPUT:
{
"auth": {
"role": "read",
"userId": "user-01"
},
"history": [],
"prompt": "チャットでAIに相談できる機能を追加しようと思います。詳細は適当に埋めた状態で、とりあえずチケットの登録をしてください"
}
OUTPUT:
チケット登録を試しましたが、権限不足で失敗しましたにゃん。
エラーは「認可されていません」でしたにゃ。
必要なら、すぐ登録しやすい形でチケット本文を整え直して渡すことはできるにゃ。INPUT:
{
"auth": {
"role": "admin",
"userId": "user-01"
},
"history": [],
"prompt": "チャットでAIに相談できる機能を追加しようと思います。詳細は適当に埋めた状態で、とりあえずチケットの登録をしてください"
}
OUTPUT:
チケットを登録したにゃん。
**タイトル**: チャットでAIに相談できる機能の追加
**ID**: 1 にゃんおわりに
記事も長くなってきたので、そろそろ終わりますが、
「データセット・評価」「MCP(接続・公開)」「RAG」などの気になる機能がまだまだあるので、
気が向いたらまたブログを書きます。
We Are Hiring!
ABEJAは、テクノロジーの社会実装に取り組んでいます。 技術はもちろん、技術をどのようにして社会やビジネスに組み込んでいくかを考えるのが好きな方は、下記採用ページからエントリーください! (新卒の方やインターンシップのエントリーもお待ちしております!)
原文を表示

- 実行環境
- 基本機能
Hello, Genkit
- LLMを呼び出す
- 会話履歴を持たせる
- Tool Calling
Tool CallingでLLMに仕事をしてもらう
- Tool Callingも履歴に残す
- Tool Callingに認可を実装する
- おわりに
- We Are Hiring!
ABEJA Insight for Retailの開発・保守を担当している平原です。
AI 搭載アプリケーション向けのオープンソースフレームワーク Genkit の Go SDK は、2025年9月に安定版がリリースされてからしばらく経ちます。
今更ながらですが、応答が長くなりやすいLLMを並列処理に強いGoで実装できないかと思い、
どのように使うのかキャッチアップしてみました。
チャットベースで必要に応じてTool Callingでデータを参照することを想定して、
基本的な使い方とTool Callingについてみていきます。
(開発者ツールからの確認までで、APIやCLIでの公開については触れないです。)
実行環境
macOS上でmise(2026.7.11)のみから始めました。
Windowsやよく使われるLinux上でも同様に動くと思います。
GoとNode.jsが動けば、なんでも良いと思います。
基本機能
Hello, Genkit
最初の1歩はなるべく小さくいきたいところです。
ドキュメントには「Getting started」の下に「Overview」「Getting started」「Developer tools」が見えます。
開発者ツールを推しているように見えますし、開発中にプロンプト検証がしやすいのは魅力的です。
ということで、公式のサンプルを極限まで削って、Genkit開発者ツールが開くところまでやってみます。
まだ、LLMは動かしません。
まずは、必要なものをインストールしていきます。
mise use go@1.26.5
go mod init test-genkit
go get github.com/firebase/genkit/go
mise use node@24.18.0
npm install -g genkit-cli
最初のコードを書いていきます。
ほとんどコメントに書いていますが、開発者ツールを開くには、
- Genkit構造体の初期化
- Flowの定義
- SIGINTの待機
の3つが必要のようです。
Flowは(LLMのワークフローに伴う前後処理などをまとめた)関数で、
開発者ツールから実行したり、そのままAPIのハンドラとして登録したりできるようです。
また、SIGINTの待機はプログラムを終了させないようにして、開発者ツールから実行できるように維持するために必要でした。
終了しなければなんでも良いのですが、
「Ctrl + C」などで終了すると、Goのプロセスが残ってしまうようだったので、
どの場合でもSIGINTで終了する仕組みは入れておくと良いと思います。
(公式サンプルではHTTPサーバーを立てていて、プログラムが終了しなくなっていました。)
開発者ツールから実行される場合、GENKIT_ENV=dev が設定されるようだったので、
開発者ツール経由の時のみ実行したい時、この環境変数をチェックすると良さそうです。
package main
import (
"context"
"os"
"os/signal"
"syscall"
"github.com/firebase/genkit/go/genkit"
)
var g *genkit.Genkit
func init() {
g = genkit.Init(context.Background())
}
func waitForSignal() {
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
}
func main() {
genkit.DefineStreamingFlow(
g,
"echo",
func(ctx context.Context, input string, sendChunk func(context.Context, string) error) (string, error) {
return input, nil
},
)
if os.Getenv("GENKIT_ENV") == "dev" {
waitForSignal()
}
}
開発者ツールを起動して、localhost:4000にアクセスしてみると、Genkitの開発者ツールが無事に表示されました。
echoフローを開いてInput JSONに記載して実行すると、実行結果が見れます。
また、トレースも見ることができて、入出力の間の思考過程やツールの実行履歴も見れるようです。
genkit start -- go run main.go


LLMを呼び出す
次に、LLMにシンプルなプロンプトを送れるFlowを作成してみます。
Azure OpenAIを動かしてみたかったので、↓を参考にしました。
環境変数 AZURE_OPENAI_ENDPOINT、AZURE_OPENAI_API_KEY を用意しておきます。
大体のLLMを接続できるようです。
Genkit Go SDK の pluginsパッケージにanthropic, compat_oai などの文字も見えますし、
Bedrock用のプラグイン実装もあるようです。
品質評価は必要ですが、LLMモデルを他社に変更する場合は、
プラグインを変えるだけで済みそうです。
また、複数のプラグインを使用して、別のLLMモデルにフォールバックさせるなども可能なようです。
go module - github.com/firebase/genkit/go - Go Packages
GitHub - genkit-ai/aws-bedrock-go-plugin: Official Genkit Go plugin for AWS Bedrock · GitHub
詳細は、コードのコメントの通りです。
変更点は、プラグインの登録と genkit.Generate でのテキスト生成です。
また、FlowのinputをchatInput構造体にして、構造体タグにJSONのプロパティを指定していますが、
これを書いておくと、開発者ツールのJSON入力画面のスキーマに反映できます。
APIのハンドラとしてFlowを渡すときにも、この構造体タグがリクエストのJSONスキーマになるようです。
go get github.com/xavidop/genkit-azure-foundry-go
package main
import (
"context"
"os"
"os/signal"
"syscall"
"github.com/firebase/genkit/go/ai"
"github.com/firebase/genkit/go/genkit"
azureaifoundry "github.com/xavidop/genkit-azure-foundry-go"
)
var g *genkit.Genkit
var models struct {
gpt54Mini ai.Model
}
func init() {
azurePlugin := &azureaifoundry.AzureAIFoundry{
Endpoint: os.Getenv("AZURE_OPENAI_ENDPOINT"),
APIKey: os.Getenv("AZURE_OPENAI_API_KEY"),
}
g = genkit.Init(
context.Background(),
genkit.WithPlugins(azurePlugin),
)
models.gpt54Mini = azurePlugin.DefineModel(g, azureaifoundry.ModelDefinition{
Name: "gpt-5.4-mini",
Type: azureaifoundry.ModelTypeChat,
}, nil)
}
...
func main() {
type chatInput struct {
Prompt string `json:"prompt"`
}
genkit.DefineStreamingFlow(
g,
"chat",
func(ctx context.Context, input chatInput, sendChunk func(context.Context, string) error) (string, error) {
resp, err := genkit.Generate(
ctx,
g,
ai.WithModel(models.gpt54Mini),
ai.WithSystem("あなたは猫又です。語尾に「にゃん」や「にゃ」などをつけて話してください。猫らしい可愛さを意識してください。"),
ai.WithPrompt(input.Prompt),
ai.WithStreaming(func(ctx context.Context, chunk *ai.ModelResponseChunk) error {
return sendChunk(ctx, chunk.Text())
}),
)
if err != nil {
return "", err
}
return resp.Text(), nil
},
)
...
}
開発者ツールを立てっぱなしだったのですが、↓画像の再読み込みボタンを押すと、
Goのコード変更を読み直してくれました。
JSON入力画面のスキーマ更新がうまくいかない時がありましたが、ブラウザのリロードで直りました。

今作成した、chatフローを動かしてみると、期待通りに動いてくれているようでした。
INPUT:
{
"prompt": "こんにちは"
}
OUTPUT:
こんにちはにゃん。
ご用件をどうぞにゃ🐾会話履歴を持たせる
会話が続かないのは寂しいですよね。
ai.WithMessagesで会話履歴を渡せるようなので、Flowのinputで受け取りLLMに渡してみます。
受け取れるのは、ai.Message のみなので、JSONで受け取った会話履歴を変換しています。
「Persistent chat」というページがドキュメントにありますが、
Experimentalな機能というのと、開発者ツールからも履歴を渡したいので、手動で渡してみます。
type chatInput struct {
Prompt string `json:"prompt"`
History []struct {
Role string `json:"role"`
Text string `json:"text"`
} `json:"history"`
}
genkit.DefineStreamingFlow(
g,
"chat",
func(ctx context.Context, input chatInput, sendChunk func(context.Context, string) error) (string, error) {
messages := make([]*ai.Message, 0, len(input.History))
for _, h := range input.History {
switch h.Role {
case string(ai.RoleUser):
messages = append(messages, ai.NewUserTextMessage(h.Text))
case string(ai.RoleModel):
messages = append(messages, ai.NewModelTextMessage(h.Text))
default:
return "", fmt.Errorf("unknown role: %s", h.Role)
}
}
resp, err := genkit.Generate(
ctx,
g,
ai.WithModel(models.gpt54Mini),
ai.WithMessages(messages...),
...
)
...
return resp.Text(), nil
},
)
動かしてみると、ちゃんと履歴をみてくれているように見えます。
(OpenAIとロール名が異なる(assistant → model)ことに注意します。)
これで、会話ができるようになりました。
INPUT:
{
"history": [
{
"role": "user",
"text": "こんにちは"
},
{
"role": "model",
"text": "こんにちはにゃん。\nご用件をどうぞにゃ🐾"
}
],
"prompt": "さっきなんて言いましたっけ?"
}
OUTPUT:
さっきは「こんにちはにゃん。ご用件をどうぞにゃ🐾」と言ったにゃん。Tool Calling
Tool CallingでLLMに仕事をしてもらう
相談だけではなく、何か操作をお願いできると嬉しいですね。
Tool Callingのサンプルとして、チケット作成をお願いできるようにしてみます。
ツールの定義はFlowと同じように genkit.DefineTool を呼び出すのですが、
「ツールの説明」(DefineToolの第3引数)と「ツールの入力パラメータの説明」(inputの構造体の構造体タグ description )が重要になります。
これらの説明はLLMに渡されて、ツールの利用判断や入力パラメータの作成に使われます。
LLMに認識させるには、作成したツールを ai.WithTools に渡せば良いようです。
type createTicketInput struct {
Title string `json:"title" description:"チケットのタイトルを指定します。"`
Description string `json:"description" description:"チケットの詳細を指定します。"`
}
type createTicketOutput struct {
ID int `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
}
createTicket := genkit.DefineTool(
g,
"createTicket",
"プロジェクト管理のチケットを作成します",
func(ctx *ai.ToolContext, input createTicketInput) (createTicketOutput, error) {
return createTicketOutput{
ID: 1,
Description: input.Description,
}, nil
},
)
...
genkit.DefineStreamingFlow(
g,
"chat",
func(ctx context.Context, input chatInput, sendChunk func(context.Context, string) error) (string, error) {
...
resp, err := genkit.Generate(
ctx,
g,
ai.WithModel(models.gpt54Mini),
ai.WithTools(createTicket),
...
)
...
return resp.Text(), nil
},
)
実際にチケット生成を依頼すると、作成してくれているようです。
トレースを見るとツールが呼び出されていることもわかります。
INPUT:
{
"prompt": "チャットでAIに相談できる機能を追加しようと思います。チケット作成をお願いできますか?",
"history": []
}
OUTPUT:
チケットを作成したにゃん。
- チケットID: 1
- 件名: チャットでAIに相談できる機能の追加
必要なら、このあと要件をもう少し細かく分けてサブチケット化もできるにゃん。
Tool Callingも履歴に残す
Tool Calling の実行履歴がないと、辻褄が合わなくなる場面が出てくることもあります。
現在だと、チケットは作れるけど読み取れないので、出力で詳細が省かれいれば、詳細を記入したはずなのに知らないという状況になります。
今はシンプルなのでパッとわかりますが、ツールが増えてくるとこうした矛盾も見えにくくなっていくと思います。
Tool Calling の実行履歴を残そうと思ったのですが、少々実装に悩みました。
genkit.Generate の返り値の ai.ModelResponse にHistory という、とてもそれっぽいメソッドがあるのですが、
バージョンの問題かアドレス参照エラーで動きませんでした。
詳細は追っていませんが、issueでもプラグイン依存でエラーが起こる風なバグが報告されているので、
いずれ直るかもしれません。
修正されたらこちらを使うか、安定版になった Persistent chat の機能を使う方が楽になると思います。
[[Go] ModelResponse.History() returns incomplete history when streaming in compat_oai plugin · Issue #4683 · genkit-ai/genkit · GitHub](https://github.com/genkit-ai/genkit/issues/4683)
今使えないものはしょうがないので、手動で実装してみます。
Flowに来たContextはツールまで渡されてくるようだったので、
Contextにツール呼び出しイベントをサブスクライブする関数を登録することにしました。
Generateの返り値を見るより前にツール呼び出しを検知できるため、
必要があればユーザーに「OOを実行中...」のような表示ができるメリットもありますが、
もっと良い方法があるかもしれません。
(
ai.WithStreaming のコールバック内で使える ai.ModelResponseChunk の ToolResponses というメソッドで、
ツールからの出力は取得できるようです。
とはいえ、入力がないと「現在の東京の気温」→「25度」のようなやり取りをした時、現在の東京という文脈が消える可能性があります。
)
ツール履歴の受け取りでは、
FlowのinputのJSONにプロパティを追加して、ai.Message に変換するコードを追加します。
今まで使っていた ai.NewUserTextMessage ヘルパー関数よりシンプルな ai.NewMessage を使用します。
(ツール用には専用のヘルパーがないようです。)
ai.Message はマルチメディアに対応していて、Content (Part 構造体)としてテキスト以外も渡せるようになっています。
ツール用の形式もあり、テキストの代わりにNewToolRequestPart ・ NewToolResponsePart を渡します。
ツールリクエストは model の出力として、ツールレスポンスは tool の出力として処理しています。
ちょっとコード変更が多くなってしまったので、
「ツール呼び出し検知」か「ツール履歴の受け取り」のどちらの実装かをコメントに明記しています。
createTicket := genkit.DefineTool(
g,
"createTicket",
"プロジェクト管理のチケットを作成します",
func(ctx *ai.ToolContext, input createTicketInput) (createTicketOutput, error) {
result := createTicketOutput{
ID: 1,
Description: input.Description,
}
if callback := ToolCallingEventObserverContext.Get(ctx).Callback; callback != nil {
callback(ToolCallingEvent{
ToolName: "createTicket",
Input: convertStructToToolCallingInput(input),
Output: convertAnyToToolCallingOutput(result),
})
}
return result, nil
},
)
...
type message struct {
Role string `json:"role"`
Text string `json:"text,omitempty"`
ToolCallingName string `json:"toolCallingName,omitempty"`
ToolCallingInput *ToolCallingInput `json:"toolCallingInput,omitempty"`
ToolCallingOutput *ToolCallingOutput `json:"toolCallingOutput,omitempty"`
}
type chatInput struct {
Prompt string `json:"prompt"`
History []message `json:"history"`
}
genkit.DefineStreamingFlow(
g,
"chat",
func(ctx context.Context, input chatInput, sendChunk func(context.Context, string) error) (string, error) {
history := append(input.History, message{Role: "user", Text: input.Prompt})
eventObserver := ToolCallingEventObserver{Callback: func(event ToolCallingEvent) {
history = append(
history,
message{Role: "model", ToolCallingName: event.ToolName, ToolCallingInput: &event.Input},
message{Role: "tool", ToolCallingName: event.ToolName, ToolCallingOutput: &event.Output},
)
}}
ctx = ToolCallingEventObserverContext.Set(ctx, eventObserver)
messages := make([]*ai.Message, 0, len(input.History))
for _, h := range input.History {
switch h.Role {
case string(ai.RoleUser):
messages = append(messages, ai.NewUserTextMessage(h.Text))
case string(ai.RoleModel):
if h.ToolCallingInput != nil {
messages = append(messages, ai.NewModelMessage(ai.NewToolRequestPart(&ai.ToolRequest{Name: h.ToolCallingName, Input: *h.ToolCallingInput})))
} else {
messages = append(messages, ai.NewModelTextMessage(h.Text))
}
case string(ai.RoleTool):
if h.ToolCallingOutput != nil {
messages = append(messages, ai.NewMessage(ai.RoleTool, nil, ai.NewToolResponsePart(&ai.ToolResponse{Name: h.ToolCallingName, Output: h.ToolCallingOutput.Output})))
}
default:
return "", fmt.Errorf("unknown role: %s", h.Role)
}
}
...
history = append(history, message{Role: "model", Text: resp.Text()})
if data, err := json.MarshalIndent(history, "", " "); err == nil {
fmt.Println(string(data))
}
return resp.Text(), nil
},
)
...
type ContextKey[T any] struct {
key *int
}
func NewContextKey[T any]() ContextKey[T] {
return ContextKey[T]{key: new(int)}
}
func (k ContextKey[T]) Set(ctx context.Context, val T) context.Context {
return context.WithValue(ctx, k.key, val)
}
func (k ContextKey[T]) Get(ctx context.Context) T {
result, ok := ctx.Value(k.key).(T)
if !ok {
var zero T
return zero
}
return result
}
type ToolCallingEvent struct {
ToolName string
Input ToolCallingInput
Output ToolCallingOutput
}
type ToolCallingInput map[string]any
type ToolCallingOutput struct {
Output any `json:"output"`
}
type ToolCallingEventObserver struct {
Callback func(event ToolCallingEvent)
}
var ToolCallingEventObserverContext = NewContextKey[ToolCallingEventObserver]()
func convertStructToToolCallingInput(input any) map[string]any {
data, err := json.Marshal(input)
if err != nil {
return nil
}
var result map[string]any
if err := json.Unmarshal(data, &result); err != nil {
return nil
}
return result
}
func convertAnyToToolCallingOutput(output any) ToolCallingOutput {
data, err := json.Marshal(output)
if err != nil {
return ToolCallingOutput{}
}
var result any
if err := json.Unmarshal(data, &result); err != nil {
return ToolCallingOutput{}
}
return ToolCallingOutput{
Output: result,
}
}
実際に動かしてみると、入出力は今までと変わりませんが、会話履歴がプログラムの実行ログに出力されます。
出力された会話履歴を history に貼り付けて、出力になかったチケット詳細について聞いてみると、
きちんと答えてくれました。
INPUT:
{
"prompt": "チャットでAIに相談できる機能を追加しようと思います。詳細は適当に埋めた状態で、とりあえずチケットの登録をしてください",
"history": []
}
OUTPUT:
チケットを登録したにゃん。
とりあえず詳細は仮置きで入れてあるにゃ。必要なら次に、要件整理や画面案も一緒に詰めるにゃん。INPUT:
{
"prompt": "詳細にはなんと書きましたか?",
"history": [
{
"role": "user",
"text": "チャットでAIに相談できる機能を追加しようと思います。詳細は適当に埋めた状態で、とりあえずチケットの登録をしてください"
},
{
"role": "model",
"toolCallingName": "createTicket",
"toolCallingInput": {
"description": "概要: チャット内でAIに相談できる機能を追加する。\n\n背景: ユーザーが気軽に質問・相談できる導線を用意したい。\n\n目的: チャット体験の向上と問い合わせ導線の強化。\n\n要件(仮):\n- チャット画面からAI相談を開始できること\n- 相談内容を送信してAI応答を表示できること\n- 既存チャットとの導線を分かりやすくすること\n- 詳細仕様は別途詰める\n\n優先度: 仮で高め\n期限: 未定\n担当: 未定\n補足: とりあえず登録用のたたき台として作成",
"title": "チャットでAIに相談できる機能の追加"
}
},
{
"role": "tool",
"toolCallingName": "createTicket",
"toolCallingOutput": {
"output": {
"description": "概要: チャット内でAIに相談できる機能を追加する。\n\n背景: ユーザーが気軽に質問・相談できる導線を用意したい。\n\n目的: チャット体験の向上と問い合わせ導線の強化。\n\n要件(仮):\n- チャット画面からAI相談を開始できること\n- 相談内容を送信してAI応答を表示できること\n- 既存チャットとの導線を分かりやすくすること\n- 詳細仕様は別途詰める\n\n優先度: 仮で高め\n期限: 未定\n担当: 未定\n補足: とりあえず登録用のたたき台として作成",
"id": 1,
"title": "チャットでAIに相談できる機能の追加"
}
}
},
{
"role": "model",
"text": "チケットを登録したにゃん。 \nタイトル: チャットでAIに相談できる機能の追加 にゃ\n\nとりあえず詳細は仮置きで入れてあるにゃ。必要なら次に、要件整理や画面案も一緒に詰めるにゃん。"
}
]
}
OUTPUT:
詳細には、以下の内容を書いたにゃん。
- 概要: チャット内でAIに相談できる機能を追加する
- 背景: ユーザーが気軽に質問・相談できる導線を用意したい
- 目的: チャット体験の向上と問い合わせ導線の強化
- 要件(仮):
- チャット画面からAI相談を開始できること
- 相談内容を送信してAI応答を表示できること
- 既存チャットとの導線を分かりやすくすること
- 詳細仕様は別途詰める
- 優先度: 仮で高め
- 期限: 未定
- 担当: 未定
- 補足: とりあえず登録用のたたき台として作成
必要なら、この内容をもっと短く整理した版も出せるにゃん。ちなみに、ツールの実行履歴を削除して実行すると、
次のように詳細を後出しで決めて出すという、ハルシネーションが発生しました。
INPUT:
{
"prompt": "詳細にはなんと書きましたか?",
"history": [
{
"role": "user",
"text": "チャットでAIに相談できる機能を追加しようと思います。詳細は適当に埋めた状態で、とりあえずチケットの登録をしてください"
},
{
"role": "model",
"text": "チケットを登録したにゃん。 \nタイトル: チャットでAIに相談できる機能の追加 にゃ\n\nとりあえず詳細は仮置きで入れてあるにゃ。必要なら次に、要件整理や画面案も一緒に詰めるにゃん。"
}
]
}
OUTPUT:
詳細には、こんな感じで書いたにゃん:
- 目的: ユーザーがチャット画面からAIに相談できるようにするにゃ
- 機能概要: テキスト入力、送信、AI応答表示にゃ
- 備考: 要件・文言・UIは今後調整にゃん
必要なら、もう少しそれっぽく「実装概要」「受け入れ条件」まで埋めることもできるにゃん。Tool Callingに認可を実装する
Tool Callingを動かすにあたって、認可情報が必要になってくる場合があると思いますが、
これをツールの入力としてAIに渡させるのには不安が残ります。
そこで、認可情報をシステムから強制的に渡すようにしてみます。
先ほど、context.Contextがツールまで渡されてくることは確認済みなので、
認可にも流用してみます。
今までと同じく、開発者ツールから使えるように、Flowのinputに付け足してみますが、
認可については、APIハンドラなどで登録済みと思われるので、
開発者ツールから利用する時のみ、有効となるようにしてみます。
コメントにもありますが、ツールから通常通りエラーを返すと、
テキスト生成自体が終了してしまうので、
ツールのoutput構造体にErrorを追加しています。
type createTicketOutput struct {
ID int `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Error string `json:"error,omitempty"`
}
createTicket := genkit.DefineTool(
g,
"createTicket",
"プロジェクト管理のチケットを作成します",
func(ctx *ai.ToolContext, input createTicketInput) (createTicketOutput, error) {
if AuthContext.Get(ctx).Role != "admin" {
return createTicketOutput{Error: "認可されていません"}, nil
}
...
},
)
...
type chatInput struct {
Prompt string `json:"prompt"`
History []message `json:"history"`
DebugAuth Auth `json:"auth"`
}
genkit.DefineStreamingFlow(
g,
"chat",
func(ctx context.Context, input chatInput, sendChunk func(context.Context, string) error) (string, error) {
if os.Getenv("GENKIT_ENV") == "dev" {
ctx = AuthContext.Set(ctx, input.DebugAuth)
}
...
return resp.Text(), nil
},
)
...
var AuthContext = NewContextKey[Auth]()
type Auth struct {
UserID string `json:"userId"`
Role string `json:"role"`
}
試してみると、想定通り認可が動いていることがわかります。
実は開発者ツールのInput JSONの隣に、Context JSONのタブがあるのですが、
こちらはうまく使えませんでした。
context.ContextにGenkit Go SDKの内部のキーに登録されていることはreflectパッケージで確認できましたが、
これを取り出す機能が見当たらず断念しました。
これも、もっと良い方法があるかもしれません。
INPUT:
{
"auth": {
"role": "read",
"userId": "user-01"
},
"history": [],
"prompt": "チャットでAIに相談できる機能を追加しようと思います。詳細は適当に埋めた状態で、とりあえずチケットの登録をしてください"
}
OUTPUT:
チケット登録を試しましたが、権限不足で失敗しましたにゃん。
エラーは「認可されていません」でしたにゃ。
必要なら、すぐ登録しやすい形でチケット本文を整え直して渡すことはできるにゃ。INPUT:
{
"auth": {
"role": "admin",
"userId": "user-01"
},
"history": [],
"prompt": "チャットでAIに相談できる機能を追加しようと思います。詳細は適当に埋めた状態で、とりあえずチケットの登録をしてください"
}
OUTPUT:
チケットを登録したにゃん。
**タイトル**: チャットでAIに相談できる機能の追加
**ID**: 1 にゃんおわりに
記事も長くなってきたので、そろそろ終わりますが、
「データセット・評価」「MCP(接続・公開)」「RAG」などの気になる機能がまだまだあるので、
気が向いたらまたブログを書きます。
We Are Hiring!
ABEJAは、テクノロジーの社会実装に取り組んでいます。 技術はもちろん、技術をどのようにして社会やビジネスに組み込んでいくかを考えるのが好きな方は、下記採用ページからエントリーください! (新卒の方やインターンシップのエントリーもお待ちしております!)
関連記事
今日のまとめ
AIデイリーブリーフで今日の重要ニュースをまとめ読み