AI文章智能速读 (TL;DR)
收起

面向读者:有一定 Go 基础的后端工程师,想上手大模型应用开发但不知从何入手
阅读时间:约 20 分钟
官方仓库:github.com/firebase/genkit/go


我最近在一个 Go 后端项目里接入了大模型,踩了不少坑才摸索出一套顺手的方式,这篇文章把整个过程和思路梳理出来,分享给同样在折腾这件事的朋友。


目录

  1. 写在前面:为什么 Go 开发者接入 AI 这么难?
  2. Genkit 是什么,它解决了哪些具体问题?
  3. 生态全景:不只是 Gemini,它支持哪些模型和存储?
  4. 四个核心概念,理解了就会用
  5. 动手写第一个完整示例(含 Tool Calling)
  6. Dev UI:调试大模型应用从此不再抓瞎
  7. Dotprompt:把提示词从代码里解放出来
  8. 集成到现有 Go 项目的建议与注意事项
  9. 总结

一、写在前面:为什么 Go 开发者接入 AI 这么难?

做过这件事的人都明白,最简单的"调用一个大模型接口"其实并不难,几十行 HTTP 代码就能搞定。难的是下面这些:

第一,Tool Calling(工具调用)非常繁琐。 当你希望大模型能够调用你自己写的函数——比如查数据库、调第三方 API——你需要手动把函数的入参结构描述成 JSON Schema 告诉模型,然后解析模型返回的调用指令,执行本地函数,再把结果拼回消息继续请求模型,整个过程完全是手工活,写起来又长又容易出 bug。

第二,多轮对话的状态管理很乱。 用户和 AI 来回聊几轮,你需要自己维护消息历史,稍不注意就超出模型的上下文窗口,或者上下文顺序乱掉。

第三,完全是黑盒,调试极其困难。 模型回答不对,你不知道是 Prompt 写得不好,还是 Tool 返回了错误数据,还是上下文被截断了。日志里一大坨 JSON 根本看不懂。

第四,今天用 Gemini,明天要换 DeepSeek,代码要大改。 没有统一抽象,换个模型就等于重写。

Genkit 的出现,就是为了把这些问题系统性地解决掉。


二、Genkit 是什么,它解决了哪些具体问题?

Google Genkit 是一个开源的大模型应用开发框架,Go 和 TypeScript 都有官方支持。它的核心思路是:在你的业务代码和大模型之间建立一层结构化的中间层,让你不必每次从零处理那些重复又容易出错的工程问题。

用一句话描述它的定位:Genkit 是 Go 生态里的大模型应用脚手架,而不只是一个 SDK 封装。

具体解决了什么:

痛点 Genkit 的解法
Tool Calling 手工解析 JSON Schema 直接用 Go struct,框架自动推导 Schema,并完整处理调用闭环
黑盒调试困难 内置本地 Dev UI,调用链、耗时、Token 消耗一览无余
换模型要改大量代码 统一的 Plugin 接口,换模型只改初始化一行
Prompt 写死在代码里,难以维护 Dotprompt 让提示词单独成文件,支持模板变量,改 Prompt 不用重新编译
线上缺乏可观测性 原生集成 OpenTelemetry,Trace、Span 开箱即用

三、生态全景:不只是 Gemini,它支持哪些模型和存储?

很多人看到 "Google Genkit" 就以为只能用 Google 自家的模型。这是个常见误解。Genkit 的插件体系是完全开放的,可以接入主流的各类模型和向量存储。

3.1 模型支持

官方内置插件:

  • plugins/googlegenai — 接入 Google AI Studio 和 Vertex AI,支持 Gemini 全系列(含 Gemini 3.8 Flash、Gemini 3.1 Pro)及 Imagen 图像生成模型
  • plugins/ollama — 接入本地 Ollama,支持 DeepSeek-R1/V3、Qwen2.5、Llama 3、Gemma 3 等开源模型,数据完全不出本地
  • plugins/compat_oai — OpenAI 协议兼容层,凡是实现了 OpenAI 接口标准的服务都能接入,包括 GPT-4o、o3,以及 vLLM、OneAPI、LiteLLM 搭建的私有代理网关

社区插件(BloomLabsInc/genkit-plugins 等):

  • Anthropic Claude(3.5 / 3.7 / Sonnet)
  • Groq(极速推理,支持 Llama、Mixtral)
  • Mistral
  • Azure OpenAI

模型切换对上层代码完全透明,换模型只改初始化:

go 复制代码
// 用 Gemini 3.8 Flash
g := genkit.Init(ctx,
    genkit.WithPlugins(&googlegenai.GoogleAI{}),
    genkit.WithDefaultModel("googleai/gemini-3.8-flash"),
)

// 换成本地 DeepSeek,其余代码一字不改
g := genkit.Init(ctx,
    genkit.WithPlugins(&ollama.Plugin{ServerAddress: "http://localhost:11434"}),
    genkit.WithDefaultModel("ollama/deepseek-r1:latest"),
)

// 换成 OpenAI
g := genkit.Init(ctx,
    genkit.WithPlugins(&compat_oai.Plugin{
        APIKey:  os.Getenv("OPENAI_API_KEY"),
        BaseURL: "https://api.openai.com/v1",
    }),
    genkit.WithDefaultModel("openai/gpt-4o"),
)

3.2 向量存储(用于 RAG / 知识库检索)

Genkit 定义了统一的 Indexer(入库)和 Retriever(检索)接口,支持:

  • pgvector(PostgreSQL 向量扩展,Go 项目首选)
  • Pinecone(云原生向量数据库)
  • Milvus / Weaviate / Qdrant / Chroma
  • 内存检索器(本地开发和单元测试用,零外部依赖)

3.3 当前主流模型型号速查

模型标识符 适用场景
googleai/gemini-3.8-flash 日常首选:延迟低、速度快、Tool Calling 决策能力强
googleai/gemini-3.1-pro 高复杂度任务:深度推理、代码分析、长文档处理
ollama/deepseek-r1:latest 私有化部署:数据不出内网,强逻辑推理
openai/gpt-4o OpenAI 生态:多模态,生态成熟

四、四个核心概念,理解了就会用

Genkit 的学习曲线比较平缓,掌握这四个概念就能上手绝大多数场景。

4.1 Plugin(插件)——模型的接入方式

Plugin 负责把具体的大模型或向量存储接进来。你在初始化时注册插件,后面所有地方都通过统一接口使用,不需要知道底层是哪家的模型。

go 复制代码
g := genkit.Init(ctx,
    genkit.WithPlugins(&googlegenai.GoogleAI{}),
)

4.2 Tool(工具)——让模型拥有行动能力

这是 Genkit 里最有意思的部分。大模型天生只能生成文本,它不知道"今天几号",也不能查你的数据库。Tool 让你把任意 Go 函数暴露给模型,模型在推理过程中可以自主决定"我需要调用这个函数来回答用户的问题"。

go 复制代码
// 定义一个 Tool:查询数据库里的订单状态
orderTool := genkit.DefineTool(
    g,
    "getOrderStatus",              // 工具名,模型用这个识别它
    "查询指定订单号的当前状态",      // 工具描述,模型用这个判断何时调用它
    func(ctx *ai.ToolContext, input struct {
        OrderID string `json:"order_id"`
    }) (string, error) {
        // 这里写真实的数据库查询逻辑
        status := db.QueryOrderStatus(input.OrderID)
        return status, nil
    },
)

注意:模型自动完成"决定调用哪个工具 → 框架执行 Go 函数 → 把结果返回给模型 → 模型根据结果生成回复"整个闭环,你不需要手写任何轮询或协调代码。

4.3 Flow(工作流)——可测试、可追踪的执行单元

Flow 是对你的 AI 业务逻辑的封装,相当于一个有类型签名、支持 Trace 追踪、可以在 Dev UI 里单独测试的函数。

go 复制代码
chatFlow := genkit.DefineFlow(
    g,
    "chatFlow",
    func(ctx context.Context, userMessage string) (string, error) {
        return genkit.GenerateText(ctx, g,
            ai.WithPrompt(userMessage),
            ai.WithTools(orderTool),
        )
    },
)

// 调用
result, err := chatFlow.Run(ctx, "帮我查一下订单 ORD-12345 的状态")

4.4 Generate(生成)——向模型发起推理

这是实际触发模型推理的地方。你可以在这里组合 Prompt、指定模型、挂载 Tool、设置输出格式。

go 复制代码
// 生成纯文本
text, err := genkit.GenerateText(ctx, g,
    ai.WithPrompt("用一句话介绍 Go 语言"),
)

// 生成结构化数据(模型输出会被严格校验并反序列化)
type ProductInfo struct {
    Name  string  `json:"name"`
    Price float64 `json:"price"`
}
resp, err := genkit.GenerateData[ProductInfo](ctx, g,
    ai.WithPrompt("描述一款笔记本电脑,返回名称和价格"),
)
fmt.Println(resp.Output.Name, resp.Output.Price)

五、动手写第一个完整示例(含 Tool Calling)

废话不多说,直接上一个可以跑起来的完整例子。这个例子包含两个 Tool:获取当前时间、查询城市天气(模拟),可以清晰地看到 Tool Calling 的完整工作过程。

第一步:初始化项目

bash 复制代码
mkdir my-genkit-app && cd my-genkit-app
go mod init my-genkit-app
go get github.com/firebase/genkit/go@latest
go get github.com/firebase/genkit/go/plugins/googlegenai@latest

API Key 获取地址(免费):https://aistudio.google.com/

bash 复制代码
export GEMINI_API_KEY="你申请到的 Key"

第二步:写代码

创建 main.go:

go 复制代码
package main

import (
	"context"
	"fmt"
	"log"
	"strings"
	"time"

	"github.com/firebase/genkit/go/ai"
	"github.com/firebase/genkit/go/genkit"
	"github.com/firebase/genkit/go/plugins/googlegenai"
)

// 工具的输入类型,Genkit 会自动将 struct 转为 JSON Schema 传给模型
type WeatherInput struct {
	City string `json:"city" description:"要查询的城市名,比如北京、深圳"`
}

type WeatherResult struct {
	City        string `json:"city"`
	Temperature string `json:"temperature"`
	Description string `json:"description"`
}

func main() {
	ctx := context.Background()

	// 初始化 Genkit,插件负责对接 Google Gemini
	// GEMINI_API_KEY 环境变量会被自动读取
	g := genkit.Init(ctx,
		genkit.WithPlugins(&googlegenai.GoogleAI{}),
		genkit.WithDefaultModel("googleai/gemini-3.8-flash"),
	)

	// Tool 1:获取系统当前时间
	// 大模型没有实时时钟,这个 Tool 让它能回答"现在几点"这类问题
	timeTool := genkit.DefineTool(g,
		"getCurrentTime",
		"获取服务器当前的精确时间",
		func(ctx *ai.ToolContext, _ struct{}) (string, error) {
			return time.Now().Format("2006年01月02日 15:04:05 MST"), nil
		},
	)

	// Tool 2:查询城市天气(这里用模拟数据,实际项目里接真实 API 或数据库)
	weatherTool := genkit.DefineTool(g,
		"getCityWeather",
		"查询指定城市的实时天气情况",
		func(ctx *ai.ToolContext, input WeatherInput) (*WeatherResult, error) {
			// 模拟天气数据,实际替换成真实 API 调用
			data := map[string]WeatherResult{
				"北京": {City: "北京", Temperature: "28°C", Description: "晴,微风"},
				"上海": {City: "上海", Temperature: "23°C", Description: "多云转阵雨"},
				"深圳": {City: "深圳", Temperature: "32°C", Description: "高温,有雷阵雨"},
			}
			for key, result := range data {
				if strings.Contains(input.City, key) {
					r := result
					return &r, nil
				}
			}
			return &WeatherResult{
				City:        input.City,
				Temperature: "未知",
				Description: "暂无该城市数据",
			}, nil
		},
	)

	// 定义一个 Flow,把工具挂上去
	// Flow 会出现在 Dev UI 里,可以直接在浏览器里测试
	assistantFlow := genkit.DefineFlow(g,
		"assistantFlow",
		func(ctx context.Context, question string) (string, error) {
			return genkit.GenerateText(ctx, g,
				ai.WithPrompt(question),
				ai.WithTools(timeTool, weatherTool),
			)
		},
	)

	// 测试几个问题,观察模型如何自主决定调用哪个工具
	questions := []string{
		"现在是几点?",
		"北京今天天气怎么样,出门需要带伞吗?",
		"上海和深圳今天哪个城市更热?",
	}

	for _, q := range questions {
		fmt.Printf("\n🙋 问:%s\n", q)
		answer, err := assistantFlow.Run(ctx, q)
		if err != nil {
			log.Printf("出错了: %v", err)
			continue
		}
		fmt.Printf("🤖 答:%s\n", answer)
		fmt.Println("────────────────────────────────────────")
	}
}

第三步:运行

bash 复制代码
go run main.go

你会观察到:

  • 问"现在几点"→ 模型调用 getCurrentTime,用返回的真实时间回答
  • 问"北京天气"→ 模型提取城市参数,调用 getCityWeather,根据天气数据给出出行建议
  • 问"上海和深圳哪个更热"→ 模型连续调用两次 getCityWeather,对比数据后给出结论

整个过程你没有写任何协调逻辑,Tool Calling 的闭环完全由框架处理。


六、Dev UI:调试大模型应用从此不再抓瞎

这是 Genkit 最让我惊喜的功能。

开发大模型应用有一个普遍的痛点:你不知道模型到底"看到了什么",也不知道 Tool 有没有被调用,调用时传了什么参数、返回了什么。出问题了只能靠猜。

Genkit 的 Dev UI 直接把这些信息可视化了。

启动方式

首先全局安装 CLI(只需安装一次):

bash 复制代码
npm install -g genkit-cli

然后用 Genkit 启动你的 Go 服务:

bash 复制代码
genkit start -- go run main.go

浏览器会自动打开 http://localhost:4000。

你能在 Dev UI 里做什么

直接在浏览器里测试 Flow: 找到 assistantFlow,输入任意问题,点击运行,不需要 Postman 或写测试代码。

查看完整的调用链 Trace: 每次运行都会生成一条 Trace 记录,展示成瀑布图。你能精确看到:

  • 第 1 步:模型收到了什么请求(完整的 messages JSON)
  • 第 2 步:模型决定调用哪个工具,传了什么参数
  • 第 3 步:工具返回了什么数据
  • 第 4 步:模型基于工具结果生成了最终回复
  • 每一步精确到毫秒的耗时

Token 消耗统计: 每次调用的输入 Token、输出 Token、是否命中缓存,方便你评估成本和优化 Prompt。


七、Dotprompt:把提示词从代码里解放出来

随着项目复杂度提升,你会越来越讨厌把提示词硬编码在 Go 字符串里——改个措辞要重新编译,多人协作时 Prompt 的修改和代码逻辑混在一起,很难管理。

Genkit 提供了 Dotprompt,让提示词独立成文件。

格式示例

创建文件 prompts/support.prompt:

复制代码
---
model: googleai/gemini-3.8-flash
config:
  temperature: 0.5
  maxOutputTokens: 1024
input:
  schema:
    userName: string
    issue: string
---
你是一名专业的客服助手,态度耐心、回答简洁。

用户名:{{userName}}
用户反馈的问题:{{issue}}

请分析问题的可能原因,并提供 2-3 个解决方案,每个方案用一句话说明步骤。

在 Go 代码里加载和执行

go 复制代码
// 在初始化 Genkit 时指定 prompt 文件目录
g := genkit.Init(ctx,
    genkit.WithPlugins(&googlegenai.GoogleAI{}),
    genkit.WithPromptDir("./prompts"),
)

// 加载并执行,不需要重新编译 Go 代码
prompt := genkit.LookupPrompt(g, "support")
resp, err := prompt.Execute(ctx, map[string]any{
    "userName": "张三",
    "issue":    "App 登录时提示网络错误,但我的网络是正常的",
})
fmt.Println(resp.Text())

这带来的好处很实际: 产品经理或算法同学可以直接修改 .prompt 文件,不需要懂 Go,不需要改业务代码,提交 PR 即可上线。


八、集成到现有 Go 项目的建议与注意事项

8.1 初始化放在哪里

genkit.Init 返回的 *genkit.Genkit 实例应该在服务启动时初始化一次,然后在各个 Handler 里复用。可以存在包级变量、全局配置结构或通过依赖注入传入,选哪种取决于你们项目的现有风格。

go 复制代码
// 示例:在独立的 ai 包里初始化
package genai

import (
    "context"
    "github.com/firebase/genkit/go/genkit"
    "github.com/firebase/genkit/go/plugins/googlegenai"
)

var G *genkit.Genkit

func Init(ctx context.Context, apiKey, model string) {
    G = genkit.Init(ctx,
        genkit.WithPlugins(&googlegenai.GoogleAI{APIKey: apiKey}),
        genkit.WithDefaultModel("googleai/" + model),
    )
    registerTools() // 在这里统一注册所有 Tool
}

8.2 Tool 的安全边界

给大模型提供的工具要特别注意权限控制:

  • 查询操作只读:数据库 Tool 应该只允许 SELECT,永远不要把 DELETE/UPDATE 直接暴露给模型
  • 限制返回量:查询结果加 Limit,防止大量数据塞满上下文窗口,浪费 Token
  • 高危操作需人工确认:涉及资金、发送消息、修改关键配置等操作,应该让模型返回"操作方案",由用户确认后再执行,而不是直接执行

8.3 处理大模型的响应延迟

大模型推理通常需要 1-5 秒,对用户来说等待感很强。有两种常用方案:

流式输出(Streaming): 边生成边返回,用户看到"打字机效果",首字时间大幅缩短。

go 复制代码
err = genkit.Generate(ctx, g,
    ai.WithPrompt(userMessage),
    ai.WithStreaming(func(ctx context.Context, chunk *ai.ModelResponseChunk) error {
        // 每生成一个 chunk 就推给前端(通过 SSE 或 WebSocket)
        fmt.Print(chunk.Text())
        return nil
    }),
)

异步处理: 对于不需要实时返回的场景(比如对接飞书/钉钉机器人),HTTP 收到消息后立即返回 200,用 goroutine 异步处理后通过机器人 API 主动发消息,避免平台超时重试。

8.4 上下文与多轮对话

Genkit 本身不帮你存对话历史,这是你的业务逻辑要管的事情。通常做法是按用户或会话 ID 在 Redis 里缓存最近 N 轮消息,每次请求时带上:

go 复制代码
history := sessionStore.Get(sessionID) // 从 Redis 取历史消息

resp, err := genkit.GenerateText(ctx, g,
    ai.WithMessages(history...),       // 注入历史上下文
    ai.WithPrompt(newUserMessage),
)

sessionStore.Append(sessionID, newUserMessage, resp) // 存回去

九、总结

Genkit 不是一个用来展示 Demo 的玩具,它是认真做 AI 应用工程化的框架。从模型 Plugin 的插拔设计,到 Tool Calling 的自动闭环,再到 Dev UI 带来的调试体验,能感受到这是有真实工程痛点积累沉淀下来的产品。

对 Go 开发者来说,最大的价值可能是:你不需要另起炉灶学一套 Python 全家桶,在你熟悉的语言和工程体系里,就能把大模型这块接好、用好、维护好。

如果你想开始试,最简单的路径是:

  1. go get github.com/firebase/genkit/go@latest 把依赖装上
  2. 去 aistudio.google.com 申请一个免费的 Gemini API Key
  3. 把本文第五节的代码跑起来
  4. 用 genkit start -- go run main.go 打开 Dev UI 看看

然后你就会开始想——我现在手头的哪个功能,可以用这个方式加一个 AI 能力进去?


参考资料

评论交流

发表你的评论...
0 / 500
评论交流 (0)
暂无评论,快来抢占第一条沙发吧~
1
0
AIAI