官方更新 Google Developers Blog 正文 ok 1779 words

Genkit 推出 Agents API:为全栈对话式 AI 铺平基础

英文标题:Build agentic full-stack apps with Genkit

Genkit 开源框架发布 Agents API 预览版,将消息历史、工具调用循环、流式传输和状态持久化封装为统一接口,开发者只需定义一个 Agent 即可驱动全栈对话式 AI。该 API 支持服务端管理或客户端管理的灵活状态持久化,并内置多代理协调、人工审批、长时间运行任务与丰富的开发者 UI。此更新大幅简化了构建智能助手、副驾等对话式应用的基础设施,对全栈及前端开发者极具价值。
打开原文相关 9 · 机会 9
media11.7 KB media88.4 KB media12.8 KB

中文

Agent Development Kit: 轻松构建多代理应用

Genkit Agents 宣布发布:对话式 AI 的全栈基础

Genkit 是一个开源框架,用于为任何平台构建全栈、AI 驱动和代理式应用,支持 TypeScript、Go、Dart 和 Python。一些最具吸引力的 AI 功能是对话式的,比如记住工单的客服助手或能跨多轮交互的副驾。每个这样的功能所需要的远不止一次 generate() 调用,如今构建它们意味着要手动串联消息历史、工具调用循环、流式传输、持久化以及前端协议。这些基础管线在每个项目中重复出现,却与让你应用脱颖而出的特性毫不相干。

Genkit 通过 Agents API 解决了这个问题,将所有上述能力封装在一个接口之后。你在服务端定义一个 Agent,然后使用相同的 chat() API 来驱动它,无论它是在进程内运行还是位于某个 HTTP 端点之后。

> Agents API 目前在 TypeScript 和 Go 中处于 预览 阶段。在小版本发布时可能引入破坏性变更。

定义一个 Agent

一个 Agent 首先需要一个名称和系统提示词。随着功能增长,你可以进一步添加工具、状态和会话存储。

go import genkitx "github.com/firebase/genkit/go/genkit/exp"

g := genkit.Init(ctx, genkit.WithPlugins(&googlegenai.GoogleAI{}), genkit.WithExperimental(), // 启用 Agents API 等预览特性。 )

assistant := genkitx.DefineAgent(g, "assistant", aix.InlinePrompt{ ai.WithModelName("googleai/gemini-flash-latest"), ai.WithSystem("You are a helpful assistant."), }, )

out, err := assistant.RunText(ctx, "Hello. What can you do?") if err != nil { log.Fatal(err) }

fmt.Println(out.Message.Text())

Go

同一个 Agent 对象非常灵活,既可以处理一次性回复,也能应对流式轮次、暂停的工具调用和多轮对话。你不需要随着功能增长而去寻找不同的抽象。

状态存于你所选之处

每一次对话都需要在轮次之间保持连续性,而由谁掌控则取决于你。

添加一个 store,Agent 就会变为 服务端管理 模式。服务端将消息、自定义状态和制品作为快照进行持久化,客户端只需回传会话 ID 即可继续对话。适合持久化的聊天应用、共享设备以及任何你不希望客户端承载整个对话的工作流。

go import firebasex "github.com/firebase/genkit/go/plugins/firebase/exp" import genkitx "github.com/firebase/genkit/go/genkit/exp"

store, err := firebasex.NewFirestoreSessionStore[WeatherState](ctx, g, firebasex.WithCollection("snapshots"), firebasex.WithCheckpointInterval(10), ) if err != nil { log.Fatal(err) }

weatherAgent := genkitx.DefineAgent(g, "weatherAgent", aix.InlinePrompt{ ai.WithSystem("Answer weather questions. Ask for a location when one is missing."), ai.WithTools(getWeather), }, aix.WithSessionStore(store), )

Go

你所配置的存储决定了快照存放的位置。对于生产环境,Firestore 提供了多个服务实例可共享的托管、多实例数据库。Genkit 也为本地工作提供了更轻量的存储,并允许你实现自己的存储,后面部分会介绍。

如果不设置 store,Agent 就是 客户端管理 模式:服务端返回完整状态,客户端在下一轮中将其发回。当你的应用已拥有持久化机制或需要无状态的服务端部署时,可以使用此模式。

每一次成功的服务端管理轮次都会写入一个快照,因此你可以通过 sessionId 恢复最新状态,或通过 snapshotId 从历史中的某个精确点进行分支。分支让用户可以从任意已保存的时刻探索另一种可能,而不会影响原始线程。

go // 继续对话中的最新状态。 out, err := weatherAgent.RunText(ctx, "Continue where we left off.", aix.WithSessionIDWeatherState, )

// 或者从特定保存点进行分支。 branch, err := weatherAgent.RunText(ctx, "Revise this plan for a smaller budget.", aix.WithSnapshotIDWeatherState, )

Go

除了消息历史,Agent 还携带另外两种状态。自定义状态 是你定义的类型化应用数据,是驱动下一轮次的紧凑控制和 UI 值,如工作流状态、任务列表或选中的实体。制品 是生成的并可能由用户自行审查、下载或进行版本管理的输出物,比如一份报告、一个补丁或一份行程计划。工具可以通过活动会话更新这二者,Genkit 则会将变更实时流式传输给客户端。

通过 HTTP 提供服务

每个 Agent 本身已经是一个可服务的动作,因此将它放在 HTTP 端点之后只需要几行代码。路由辅助函数返回描述符,你可以将其挂载到标准的 http.ServeMux 上,它们会自动配置轮次端点以及快照和中断的配套端点。

go import genkitx "github.com/firebase/genkit/go/genkit/exp"

mux := http.NewServeMux() for _, route := range genkitx.AllAgentRoutes(g) { mux.HandleFunc(route.Pattern(), route.Handler()) }

log.Fatal(http.ListenAndServe(":8080", mux))

Go

这正是下文客户端所使用的有线协议,因此 JavaScript 或 Go 后端都能以相同的方式服务任何客户端。

面向全栈集成的富客户端

将服务端和客户端连接在一起的关键是远程 Agent。remoteAgent() 返回的句柄拥有与本地 Agent 相同的 chat() 接口,因此驱动后端测试中 Agent 的代码和从浏览器驱动它的代码是一样的。不需要设计单独的请求和响应协议,也不需要发明流式传输格式。

我们即将推出一个 JavaScript 客户端,让 Web 前端能够连接相同的 Agent 端点。以下是一个从 TypeScript 前端连接到远程 Agent 的示例。

javascript import { remoteAgent } from 'genkit/beta/client';

const agent = remoteAgent({ url: 'http://localhost:8080/api/weatherAgent', });

const chat = agent.chat(); const res = await chat.send('Weather in Tokyo?');

console.log(res.text);

JavaScript

客户端通过 Agent 路由使用同一个有线协议,因此无论在 JavaScript 还是 Go 后端上都能以相同方式工作。它会按请求解析动态认证头、应用流式状态补丁,并使用对话 ID、快照 ID 或客户端管理的状态(取决于你的 Agent 使用哪种方式)继续下一轮。

流式传输也内置在同一个接口中。sendStream() 会提供块流和最终响应,每个块在生成时都可以包含文本、自定义状态或制品。

javascript const turn = agent.chat().sendStream('Write a long report.');

for await (const chunk of turn.stream) { if (chunk.text) process.stdout.write(chunk.text); if (chunk.custom) updateStatus(chunk.custom); if (chunk.artifact) renderArtifact(chunk.artifact); }

const res = await turn.response;

JavaScript

如果你已有使用 Vercel AI SDK UI 库的应用,@genkit-ai/vercel-ai 包提供了其 useChat 钩子的适配器。GenkitChatTransport 适配器将 useChat 连接到你的 Genkit Agent,这样你可以使用 Vercel 的 AI Elements 组件来拼装界面,同时在后端享有 Genkit 的全部优势。

内置人工审批

一个工具可以暂停 Agent 并将控制权交还给用户。模型判断需要外部输入,工具发起中断,客户端在轮次继续前进行批准、拒绝或提供缺失的值。这样就可以在支付、部署或任何你不希望自动执行的操作之前引入人工介入。

go import genkitx "github.com/firebase/genkit/go/genkit/exp" import "github.com/firebase/genkit/go/ai/exp/tool"

runShell := genkitx.DefineInterruptibleTool(g, "run_shell", "Run a shell command after a safety check.", func(ctx context.Context, input ShellInput, confirm *Confirmation) (ShellOutput, error) { if isRisky(input.Command) { if confirm == nil { return ShellOutput{}, tool.Interrupt(ShellInterrupt{ Command: input.Command, Reason: "The command can modify files.", }) } else if !confirm.Approved { return ShellOutput{}, errors.New("user rejected shell command execution") } }

    return execute(input.Command)
},

)

Go

轮次结束时,响应会带有 interrupted 原因和暂停的请求。一旦用户做出回答,客户端即可恢复执行,运行时会根据对话历史验证恢复的载荷,从而避免工具被伪造的输入欺骗执行。

运行时间超过请求时长的作业

有些轮次所需的时间超过了用户愿意等待的时长。借助服务端管理的状态,客户端可以将轮次分离,关闭标签页,稍后通过快照 ID 重新连接。Agent 会继续在服务端运行,并将进度写入待处理的快照,另一个会话可以轮询、等待或中止它。

javascript const chat = reportAgent.chat({ sessionId: 'report-123' }); const task = await chat.detach('Write the quarterly market report.');

// 持久化此快照,以便任何客户端稍后都能重新连接该作业。 savePendingSnapshot(task.snapshotId);

for await (const snapshot of task.poll({ intervalMs: 1000 })) { renderStatus(snapshot.status); if (snapshot.status === 'completed') renderMessages(snapshot.state.messages); }

JavaScript

这使得长时间运行的研究任务、多步骤规划和工具密集型工作流变得切实可行,而无需保持连接或构建单独的任务队列。

协调专业代理

当单个提示词无法面面俱到时,你可以将工作拆分给多个专门的 Agent,并让一个协调者组合它们的结果。Agents 中间件为每个子 Agent 注入一个委托工具,这样协调者模型就能将请求的不同部分路由给合适的专家。使用 Genkit 的子代理可让你完全掌控并实现自己的编排逻辑。

go import middlewarex "github.com/firebase/genkit/go/plugins/middleware/exp"

coordinator := genkit.DefineAgent(g, "coordinator", aix.InlinePrompt{ ai.WithSystem("Delegate to specialists, inspect their results, then answer the user."), ai.WithUse( &middlewarex.Agents{ Agents: []aix.AgentRef{researcher.Ref(), coder.Ref()}, MaxDelegations: 5, ArtifactStrategy: middlewarex.ArtifactStrategySession, }, &middlewarex.Artifacts{Readonly: true}, ), }, )

Go

委托在协调者的流中表现为普通的工具活动,而专业代理的制品可以合并到父会话中,这样最终回答就能建立在每位专家产出的基础上。

何时转而选择 ADK

Genkit Agent 是一种应用原语,旨在嵌入到全栈的、面向用户的应用中。而遇到以下情况时,可以考虑使用 Agent Development Kit (ADK):

  • 多代理编排是整个系统的核心,而不仅仅是一项功能。 ADK 专为复杂的代理拓扑而构建,而 Genkit 的委托中间件则刻意保持轻量,且并未内建在 Agent 抽象核心中。
  • 你需要的是一个托管的运行时,而不仅仅是一个库。 ADK 与 Gemini 企业代理平台上的 Agent 运行时可配合使用,提供托管、扩缩容和托管会话。

选择你的持久化方案

服务端管理的 Agent 通过会话存储来保存快照,Genkit 提供了多种存储实现,可根据运行环境进行选择:

  • 内存 用于测试、演示和单进程实验。
  • 文件 用于本地开发和需要快照在重启后依然存在的单主机应用。
  • Firestore 用于在 Google Cloud 或 Firebase 上运行的生产应用,无需编写存储代码即可获得托管、多实例数据库。
  • 自定义 当你需要使用自己的数据库、授权或具有特定的保留策略时。你可以使用 store 接口实现自己的持久化层。

在开发者 UI 中进行测试和探索

Agent 在 Genkit 的 开发者 UI 中是第一等公民。全新的 Agent Runner 让你可以在不编写客户端的情况下,启动对话、发送轮次、观察流式输出和状态更新、操作工具中断以及查看快照。这是在构建 Agent 时快速试验它,以及在调试时复现对话的最快方式。

快速入门

Agents API 将对话式、全栈 AI 中重复的基础管线变成了配置项,而非每次都重新构建。在服务端定义一个 Agent,需要持久化时为其提供一个存储,然后通过 remoteAgent() 使用相同的 chat() 接口从你的前端来驱动它。

前往全栈代理文档深入了解,如果你刚刚接触该框架,可以从 Genkit 入门开始。该 API 处于 Beta 阶段,我们期待你的反馈:提交 issue 告诉我们你的想法。

English / 原文

Agent Development Kit: Making it easy to build multi-agent applications

Announcing Genkit Agents: A full-stack foundation for conversational AI

Genkit is an open-source framework for building full-stack, AI-powered and agentic applications for any platform with support for TypeScript, Go, Dart, and Python. Some of the most compelling AI features are conversational, like a support assistant that remembers the ticket or a copilot that works across several turns. Each needs more than a single generate() call, and building one today means wiring up message history, the tool loop, streaming, persistence, and a frontend protocol by hand. That plumbing repeats on every project and has little to do with what makes your app distinct.

Genkit solves this with the Agents API, which packages all of that behind one interface. You define an agent on the server, then drive it with the same chat() API whether it runs in process or behind an HTTP endpoint.

> The Agents API is in preview today in TypeScript and Go. It can introduce breaking changes in minor version releases.

Define an agent

An agent needs a name and a system prompt to start. From there you add tools, state, and a session store as the feature grows.

import genkitx "github.com/firebase/genkit/go/genkit/exp"

g := genkit.Init(ctx,
    genkit.WithPlugins(&googlegenai.GoogleAI{}),
    genkit.WithExperimental(), // Enables preview features like Agents API.
)

assistant := genkitx.DefineAgent(g, "assistant",
    aix.InlinePrompt{
        ai.WithModelName("googleai/gemini-flash-latest"),
        ai.WithSystem("You are a helpful assistant."),
    },
)

out, err := assistant.RunText(ctx, "Hello. What can you do?")
if err != nil {
    log.Fatal(err)
}

fmt.Println(out.Message.Text())

Go

The same agent object is flexible and can handle a one-shot reply, a streamed turn, a paused tool call, and a multi-turn conversation. You do not reach for a different abstraction as the feature grows.

State that lives where you want it

Every conversation needs continuity between turns, and you decide who owns it.

Add a store and the agent becomes server-managed. The server persists messages, custom state, and artifacts as snapshots, and clients continue by sending back a session ID. Choose this for persistent chat apps, shared devices, and any workflow where the client should not carry the whole conversation.

import firebasex "github.com/firebase/genkit/go/plugins/firebase/exp"
import genkitx "github.com/firebase/genkit/go/genkit/exp"

store, err := firebasex.NewFirestoreSessionStore[WeatherState](ctx, g,
    firebasex.WithCollection("snapshots"),
    firebasex.WithCheckpointInterval(10),
)
if err != nil {
    log.Fatal(err)
}

weatherAgent := genkitx.DefineAgent(g, "weatherAgent",
    aix.InlinePrompt{
        ai.WithSystem("Answer weather questions. Ask for a location when one is missing."),
        ai.WithTools(getWeather),
    },
    aix.WithSessionStore(store),
)

Go

The store you configure decides where snapshots live. For production, Firestore gives you a managed, multi-instance database that several server instances can share. Genkit also ships lighter stores for local work and lets you implement your own, which the section below covers.

Leave the store off and the agent is client-managed: the server returns the full state and the client sends it back on the next turn. Use this when your app already owns persistence or you need stateless server deployments.

Every successful server-managed turn writes a snapshot, so you can resume the latest state by sessionId or branch from an exact point in history by snapshotId. Branching lets a user explore an alternative from any saved moment without disturbing the original thread.

// Continue the latest state in a conversation.
out, err := weatherAgent.RunText(ctx, "Continue where we left off.",
    aix.WithSessionID[WeatherState]("user-session-123"),
)

// Or branch from a specific saved point.
branch, err := weatherAgent.RunText(ctx, "Revise this plan for a smaller budget.",
    aix.WithSnapshotID[WeatherState](approvedPlanSnapshotID),
)

Go

Alongside message history, an agent carries two more kinds of state. Custom state is your typed application data, the compact control and UI values that drive the next turn, such as workflow status, a task list, or selected entities. Artifacts are generated outputs the user may inspect, download, or version on their own, such as a report, a patch, or an itinerary. A tool updates either one through the active session, and Genkit streams the changes to the client as they happen.

Serve it over HTTP

Every agent is already a servable action, so putting one behind an HTTP endpoint is a few lines. The route helpers return descriptors you mount on a standard http.ServeMux, and they wire up the turn endpoint plus the snapshot and abort companions for you.

import genkitx "github.com/firebase/genkit/go/genkit/exp"

mux := http.NewServeMux()
for _, route := range genkitx.AllAgentRoutes(g) {
    mux.HandleFunc(route.Pattern(), route.Handler())
}

log.Fatal(http.ListenAndServe(":8080", mux))

Go

That same wire protocol is what the client below speaks, so a JavaScript or Go backend serves any client identically.

A rich client for full-stack integration

The piece that ties your server and client together is the remote agent. remoteAgent() returns a handle with the same **chat()**interface as a local agent, so the code that drives an agent in your backend tests is the code that drives it from the browser. There is no separate request and response protocol to design, and no streaming format to invent.

We are launching a JavaScript client, so a web frontend can talk to the same agent endpoint. The following is an example of how to connect to a remote agent from a TypeScript frontend.

import { remoteAgent } from 'genkit/beta/client';

const agent = remoteAgent<WeatherState>({
  url: 'http://localhost:8080/api/weatherAgent',
});

const chat = agent.chat();
const res = await chat.send('Weather in Tokyo?');

console.log(res.text);

JavaScript

The client speaks one wire protocol over the agent route, so it works the same against a JavaScript or a Go backend. It resolves dynamic auth headers per request, applies streamed state patches, and continues the next turn with a session ID, a snapshot ID, or client-managed state, whichever your agent uses.

Streaming is built into the same interface. sendStream() gives you a chunk stream and a final response, and each chunk can carry text, custom state, or an artifact as it is produced.

const turn = agent.chat().sendStream('Write a long report.');

for await (const chunk of turn.stream) {
  if (chunk.text) process.stdout.write(chunk.text);
  if (chunk.custom) updateStatus(chunk.custom);
  if (chunk.artifact) renderArtifact(chunk.artifact);
}

const res = await turn.response;

JavaScript

If you already have apps that use the Vercel AI SDK UI library, the @genkit-ai/vercel-ai package provides an adapter for its useChat hook. The GenkitChatTransport adapter connects useChat to your Genkit agent, so you can assemble the interface from Vercel's AI Elements components while getting all the benefits of Genkit on the backend.

Human approval, built in

A tool can pause an agent and hand control back to the user. The model decides outside input is needed, the tool interrupts, and the client approves, rejects, or supplies the missing value before the turn continues. This is how you put a human in the loop before a payment, a deployment, or any action you do not want to run automatically.

import genkitx "github.com/firebase/genkit/go/genkit/exp"
import "github.com/firebase/genkit/go/ai/exp/tool"

runShell := genkitx.DefineInterruptibleTool(g, "run_shell",
    "Run a shell command after a safety check.",
    func(ctx context.Context, input ShellInput, confirm *Confirmation) (ShellOutput, error) {
        if isRisky(input.Command) {
            if confirm == nil {
                return ShellOutput{}, tool.Interrupt(ShellInterrupt{
                    Command: input.Command,
                    Reason:  "The command can modify files.",
                })
            } else if !confirm.Approved {
                return ShellOutput{}, errors.New("user rejected shell command execution")
            }
        }

        return execute(input.Command)
    },
)

Go

The turn finishes with an interrupted reason and the paused request on the response. The client resumes once the user answers, and the runtime validates the resume payload against session history so a tool cannot be tricked into running with forged input.

Work that outlives the request

Some turns take longer than a user wants to wait. With server-managed state, a client can detach a turn, close the tab, and reconnect later by snapshot ID. The agent keeps working on the server, writing progress to a pending snapshot that another session can poll, wait on, or abort.

const chat = reportAgent.chat({ sessionId: 'report-123' });
const task = await chat.detach('Write the quarterly market report.');

// Persist this so any client can reconnect to the work later.
savePendingSnapshot(task.snapshotId);

for await (const snapshot of task.poll({ intervalMs: 1000 })) {
  renderStatus(snapshot.status);
  if (snapshot.status === 'completed') renderMessages(snapshot.state.messages);
}

JavaScript

This makes long research jobs, multi-step planning, and tool-heavy workflows practical without holding a connection open or building a separate job queue.

Coordinate specialists

When one prompt cannot do everything well, you can split work across specialized agents and let an orchestrator combine their results. The Agents middleware injects a delegation tool for each sub-agent, so the orchestrator model can route parts of a request to the right specialist. Subagents with Genkit give you full control and the ability to implement your own orchestration.

import middlewarex "github.com/firebase/genkit/go/plugins/middleware/exp"

coordinator := genkit.DefineAgent(g, "coordinator",
    aix.InlinePrompt{
        ai.WithSystem("Delegate to specialists, inspect their results, then answer the user."),
        ai.WithUse(
            &middlewarex.Agents{
                Agents:           []aix.AgentRef{researcher.Ref(), coder.Ref()},
                MaxDelegations:   5,
                ArtifactStrategy: middlewarex.ArtifactStrategySession,
            },
            &middlewarex.Artifacts{Readonly: true},
        ),
    },
)

Go

Delegation shows up as ordinary tool activity in the orchestrator's stream, and specialist artifacts can merge into the parent session so the final answer can build on what each specialist produced.

When to reach for ADK instead

Genkit agents are an application primitive, built to live inside a full-stack, user-facing app. Consider the Agent Development Kit (ADK) instead when:

  • Multi-agent orchestration is the whole system, not just one feature. ADK is purpose-built for complex agent topologies, where Genkit's delegation middleware is deliberately lighter and not built into the core of the agent abstraction.
  • You want a managed runtime, not just a library. ADK pairs with Agent Runtime on the Gemini Enterprise Agent Platform for hosting, scaling, and managed sessions.

Choose your persistence

Server-managed agents store snapshots through a session store, and Genkit ships several so you can match the store to where you are running:

  • In-memory for tests, demos, and single-process experiments.
  • File for local development and single-host apps that need snapshots to survive a restart.
  • Firestore for production apps on Google Cloud or Firebase that want a managed, multi-instance database with no store code to write.
  • Custom when you need to use your own database, authorization, or have specific retention policies. You can implement your own persistence layer using the store interface.

Test and explore in the Developer UI

Agents are first-class in the Genkit Developer UI. The new Agent Runner lets you start a conversation, send turns, watch streamed output and state updates, drive tool interrupts, and inspect snapshots, all without writing a client. It is the fastest way to exercise an agent while you are building it and to reproduce a conversation when you are debugging one.

Get started

The Agents API turns the repeated plumbing of conversational, full-stack AI into something you configure rather than rebuild. Define an agent on the server, give it a store when you want persistence, and drive it from your frontend with the same chat() interface through remoteAgent().

Head to the Full-stack agents documentation to dive in, or get started with Genkit if you are new to the framework. The API is in Beta, so we want your feedback: file an issue with what you build and what you would change.

Happy coding! 🚀