Runner
来自 adk-runner 的执行运行时,用于协调 agent 执行。
概览
Runner 管理 agent 执行的完整生命周期:
- 会话管理(创建/检索会话)
- 内存注入(搜索并注入相关记忆)
- 工件处理(作用域化的工件访问)
- 事件流式传输(处理并转发事件)
- Agent փոխանց送(处理多 agent 移交)
安装
[dependencies]
adk-runner = "2.0.0"
RunnerConfig
使用所需服务配置 runner:
use adk_runner::{Runner, RunnerConfig};
use adk_session::InMemorySessionService;
use adk_artifact::InMemoryArtifactService;
use std::sync::Arc;
let config = RunnerConfig {
app_name: "my_app".to_string(),
agent: Arc::new(my_agent),
session_service: Arc::new(InMemorySessionService::new()),
artifact_service: Some(Arc::new(InMemoryArtifactService::new())),
memory_service: None,
plugin_manager: None,
run_config: None,
compaction_config: None,
context_cache_config: None,
cache_capable: None,
request_context: None,
cancellation_token: None,
};
let runner = Runner::new(config)?;
RunnerConfigBuilder(推荐)
使用 typestate builder 来构造 Runner。该 builder 在编译时强制要求必填字段,并为所有可选字段提供默认值,因此未来版本新增字段不会破坏你的代码:
use adk_runner::Runner;
let runner = Runner::builder()
.app_name("my_app")
.agent(Arc::new(my_agent))
.session_service(Arc::new(InMemorySessionService::new()))
// Optional fields — only set what you need
.artifact_service(Arc::new(InMemoryArtifactService::new()))
.build()?;
该 builder 需要三个字段:app_name、agent 和 session_service。其他所有字段都是可选的,并且有合理的默认值。只有在这三个必填字段全部设置完成后,才可使用 build() 方法——少一个会在编译时报错,而不是运行时报错。
配置字段
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
app_name | String | 是 | 应用标识符 |
agent | Arc<dyn Agent> | 是 | 要执行的根 agent |
session_service | Arc<dyn SessionService> | 是 | 会话存储后端 |
artifact_service | Option<Arc<dyn ArtifactService>> | 否 | 资源存储 |
memory_service | Option<Arc<dyn Memory>> | 否 | 长期记忆 |
plugin_manager | Option<Arc<PluginManager>> | 否 | 插件生命周期钩子 |
compaction_config | Option<EventsCompactionConfig> | 否 | 上下文压缩设置 |
run_config | Option<RunConfig> | 否 | 执行选项 |
context_cache_config | Option<ContextCacheConfig> | 否 | 运行器级上下文缓存生命周期(实验性——见下文) |
cache_capable | Option<Arc<dyn CacheCapable>> | 否 | 支持缓存的模型引用(实验性——见下文) |
request_context | Option<RequestContext> | 否 | 认证中间件上下文 |
cancellation_token | Option<CancellationToken> | 否 | 协作式取消 |
提示缓存
缓存是提供方级别的事项,不需要 Runner 配置。 每个提供方集成都会在组装请求时处理它:
| 提供方 | 机制 | 默认 |
|---|---|---|
| Anthropic / Bedrock | cache_control 断点 | 开启(AnthropicConfig::prompt_caching,可通过 with_prompt_caching(false) 退出) |
| OpenAI | 服务端提示缓存,PromptCacheRetention 用于保留 | 自动 |
| Gemini | 2.5/3.x 上的隐式缓存——共享前缀可在无需代码更改的情况下获得折扣 | 自动 |
无需任何额外接线即可观察到缓存命中:Gemini 集成会在每个响应上记录 cachedContentTokenCount。
context_cache_config和cache_capable是实验性的,应保持未设置。 它们会驱动 Gemini 的 显式cachedContentsAPI 由 Runner 发起。该 API 要求缓存 替换system_instruction、tools和tool_config—— 将缓存与其中任何一个一起发送都会被INVALID_ARGUMENT拒绝。Runner 会在 agent 解析其工具之前选择缓存,因此它无法组装该请求,并且启用这些字段目前不会产生缓存命中。Gemini 的有保证(而非尽力而为)缓存应属于模型集成层,与其他提供者的实现方式放在一起。
运行 Agents
使用用户输入执行一个 agent:
use adk_core::{Content, SessionId, UserId};
use futures::StreamExt;
let user_content = Content::new("user").with_text("Hello!");
let mut stream = runner.run(
UserId::new("user-123")?,
SessionId::new("session-456")?,
user_content,
).await?;
while let Some(event) = stream.next().await {
match event {
Ok(e) => {
if let Some(content) = e.content() {
for part in &content.parts {
if let Some(text) = part.text() {
print!("{}", text);
}
}
}
}
Err(e) => eprintln!("Error: {}", e),
}
}
字符串便捷方法
对于简单的调用点,run_str() 接受普通的 &str 参数,并在内部处理 newtype 转换:
let mut stream = runner.run_str(
"user-123",
"session-456",
Content::new("user").with_text("Hello!"),
).await?;
如果字符串验证失败(为空、包含空字节,或超过长度限制),run_str() 会在启动 agent 循环之前返回错误。现有的带类型化 UserId/SessionId 的 run() 方法保持不变。
中断与运行隔离
一旦 run() 返回其流,该运行就会被注册;当该流被丢弃时就会被注销——即使它在从未被轮询的情况下被丢弃也是如此。
| 方法 | 范围 |
|---|---|
interrupt(session_id) | 取消该会话 ID 的所有进行中的运行,跨应用和用户 |
interrupt_identity(app_name, user_id, session_id) | 取消某个精确身份的运行 |
active_runs() | 每个进行中的 run 的标识;重复的标识表示并发运行 |
active_session_ids() | 进行中 run 的去重 session ID |
// Cancel one tenant's run without touching another that shares the session ID
let cancelled = runner.interrupt_identity("my-app", "user-1", "session-1");
会话 ID 只在应用和用户范围内唯一,因此 interrupt(session_id) 是
更宽泛的形式,而 interrupt_identity 是更精确的形式。若单个
interrupt_identity 需要服务多个应用或用户,优先使用
Runner。
运行通过唯一的 run ID 而不是 session ID 来跟踪,因此同一身份的两个运行会被分别跟踪,并且各自只注销自己。
持久化与身份绑定
Runner 持久化的每个事件——用户回合、模型响应、传输事件、插件事件以及压缩事件——都通过
SessionService::append_event_for_identity 以及完整的
(app_name, user_id, session_id) 三元组写入。其自然键为
复合键的 SessionService 因而可以将每个事件绑定到其租户,并且可以完全拒绝或忽略原始 session ID 的 append_event 路径。
执行流程
┌─────────────────────────────────────────────────────────────┐
│ Runner.run() │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 1. Session Retrieval │
│ │
│ SessionService.get(app_name, user_id, session_id) │
│ → Creates new session if not exists │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 2. Agent Selection │
│ │
│ Check session state for active agent │
│ → Use root agent or transferred agent │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 3. Context Creation │
│ │
│ InvocationContext with: │
│ - Session (mutable) │
│ - Artifacts (scoped to session) │
│ - Memory (if configured) │
│ - Run config │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 4. Agent Execution │
│ │
│ agent.run(ctx) → EventStream │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 5. Event Processing │
│ │
│ For each event: │
│ - Update session state │
│ - Handle transfers │
│ - Forward to caller │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 6. Session Save │
│ │
│ SessionService.append_event(session, events) │
└─────────────────────────────────────────────────────────────┘
InvocationContext
执行期间提供给 agents 的上下文:
pub trait InvocationContext: CallbackContext {
/// The agent being executed
fn agent(&self) -> Arc<dyn Agent>;
/// Memory service (if configured)
fn memory(&self) -> Option<Arc<dyn Memory>>;
/// Current session
fn session(&self) -> &dyn Session;
/// Execution configuration
fn run_config(&self) -> &RunConfig;
/// Signal end of invocation
fn end_invocation(&self);
/// Check if invocation has ended
fn ended(&self) -> bool;
}
RunConfig
执行选项:
pub struct RunConfig {
/// Streaming mode for responses
pub streaming_mode: StreamingMode,
// ... other fields (tool_confirmation_decisions, cached_content, etc.)
}
ToolExecutionStrategy
控制来自单个 LLM 响应的多个工具调用如何分发:
| 策略 | 行为 |
|---|---|
Sequential(默认) | 按 LLM 返回的顺序一次执行一个工具 |
Parallel | 并发执行所有工具;安全性由调用方负责 |
Auto | 并发执行安全的只读子集,然后按顺序执行所有剩余调用 |
通过 LlmAgentBuilder 按每个 agent 设置:
use adk_core::ToolExecutionStrategy;
let agent = LlmAgentBuilder::new("fast_agent")
.model(model)
.tool_execution_strategy(ToolExecutionStrategy::Auto)
.tool(Arc::new(
search_tool
.with_read_only(true)
.with_concurrency_safe(true),
))
.tool(Arc::new(save_tool)) // runs after the concurrent safe subset
.build()?;
在 Auto 模式下,分发循环会同时查询 is_read_only() 和 is_concurrency_safe()。其所选工具在两种方法中都返回 true 的调用会先并发运行;其余所有调用随后顺序运行。Parallel 作为显式的调用方覆盖,会绕过这些元数据检查。无论采用哪种策略,结果始终会按原始 LLM 返回的顺序重新组装。失败的工具会生成 JSON 错误响应,而不会中止批处理。
pub enum StreamingMode {
/// No streaming, return complete response
None,
/// Server-Sent Events (default)
SSE,
/// Bidirectional streaming (realtime)
Bidi,
}
Agent 转移
Runner 会自动处理多 agent 转移:
// In an agent's tool or callback
if should_transfer {
// Set transfer in event actions
ctx.set_actions(EventActions {
transfer_to_agent: Some("specialist_agent".to_string()),
..Default::default()
});
}
Runner 将会:
- 检测事件中的转移请求
- 在 sub_agents 中找到目标 agent
- 使用新的活动 agent 更新 session 状态
- 继续使用新 agent 执行
上下文压缩
对于长时间运行的 session,启用自动上下文压缩以保持 LLM 上下文窗口有界:
use adk_runner::{Runner, RunnerConfig, EventsCompactionConfig};
use adk_agent::LlmEventSummarizer;
use std::sync::Arc;
let summarizer = LlmEventSummarizer::new(model.clone());
let config = RunnerConfig {
// ... other fields ...
compaction_config: Some(EventsCompactionConfig {
compaction_interval: 3, // Compact every 3 invocations
overlap_size: 1, // Keep 1 event overlap for continuity
summarizer: Arc::new(summarizer),
}),
// ...
};
当压缩触发时,较旧的事件会被一个摘要事件替换。conversation_history() 会自动使用该摘要而不是原始事件。
有关完整文档,请参见 Context Compaction。
与 Launcher 集成
Launcher 内部使用 Runner:
// Launcher creates Runner with default services
Launcher::new(agent)
.app_name("my_app")
.run()
.await?;
// Equivalent to using the builder:
let runner = Runner::builder()
.app_name("my_app")
.agent(agent)
.session_service(Arc::new(InMemorySessionService::new()))
.build()?;
自定义 Runner 用法
对于高级场景,请直接使用 Runner:
use adk_runner::Runner;
// Production configuration using the builder
let runner = Runner::builder()
.app_name("production_app")
.agent(my_agent)
.session_service(Arc::new(SqliteSessionService::new(db_pool)))
.artifact_service(Arc::new(S3ArtifactService::new(s3_client)))
.memory_service(Arc::new(QdrantMemoryService::new(qdrant_client)))
.build()?;
// Use in HTTP handler with run_str() for convenience
async fn chat_handler(runner: &Runner, request: ChatRequest) -> Response {
let stream = runner.run_str(
&request.user_id,
&request.session_id,
request.content,
).await?;
// Stream events to client
Response::sse(stream)
}
上一页: ← Core Types | 下一页: Launcher →