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> | 예 | 실행할 루트 에이전트 |
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> | No | 컨텍스트 압축 설정 |
run_config | Option<RunConfig> | No | 실행 옵션 |
context_cache_config | Option<ContextCacheConfig> | No | 러너 수준 컨텍스트 캐시 수명 주기(실험적 — 아래 참조) |
cache_capable | Option<Arc<dyn CacheCapable>> | No | 캐시를 지원하는 모델 참조(실험적 — 아래 참조) |
request_context | Option<RequestContext> | 아니요 | 인증 미들웨어 컨텍스트 |
cancellation_token | Option<CancellationToken> | 아니요 | 협조적 취소 |
프롬프트 캐싱
캐싱은 provider 수준의 사항이며 Runner 설정이 필요하지 않습니다. 각 provider 통합은 요청이 조립되는 지점에서 이를 처리합니다:
| 공급자 | 메커니즘 | 기본값 |
|---|---|---|
| Anthropic / Bedrock | cache_control breakpoints | 켜짐 (AnthropicConfig::prompt_caching, with_prompt_caching(false)로 제외 가능) |
| OpenAI | 서버 측 프롬프트 캐싱, 보존을 위한 PromptCacheRetention | 자동 |
| Gemini | 2.5/3.x의 암시적 캐싱 — 공유 접두사는 코드 변경 없이 할인 혜택을 받음 | 자동 |
추가 설정 없이도 캐시 적중 여부를 관찰할 수 있습니다. Gemini 통합은 각 응답에 cachedContentTokenCount를 기록합니다.
context_cache_config과cache_capable는 실험적이며 설정하지 않은 상태로 두어야 합니다. 이들은 Runner에서 Gemini의 명시적cachedContentsAPI를 유도합니다. 그 API는 캐시가system_instruction,tools,tool_config를 대체해야 합니다 — 이들 중 하나와 함께 캐시를 보내면INVALID_ARGUMENT로 거부됩니다. Runner는 agent가 tools를 해석하기 전에 cache를 선택하므로, 해당 요청을 구성할 수 없으며, 현재 이 필드를 활성화해도 cache hit가 발생하지 않습니다. Gemini의 보장된(최선의 노력 방식이 아닌) 캐싱은 다른 provider들이 하는 방식과 함께 model integration에 속합니다.
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?;
문자열 검증에 실패하면(비어 있거나, null byte를 포함하거나, 길이 제한을 초과하는 경우), run_str()는 agent loop를 시작하기 전에 오류를 반환합니다. typed UserId/SessionId를 사용하는 기존의 run() 메서드는 변경되지 않습니다.
중단 및 실행 격리
run은 run()가 stream을 반환하는 즉시 등록되고, 그 stream이 drop될 때 deregister됩니다 — 한 번도 poll되지 않은 채 drop되는 경우도 포함됩니다.
| 메서드 | 범위 |
|---|---|
interrupt(session_id) | 해당 세션 ID에 대해, 앱과 사용자 전체에 걸쳐 진행 중인 모든 실행을 취소합니다 |
interrupt_identity(app_name, user_id, session_id) | 하나의 정확한 ID에 대한 실행을 취소합니다 |
active_runs() | 진행 중인 모든 실행의 식별자; 반복되는 식별자는 동시 실행을 의미함 |
active_session_ids() | 진행 중인 실행의 중복 제거된 세션 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가 더 정확한 형식입니다. 단일 Runner가 하나 이상의 앱 또는 사용자를
서빙할 때는 interrupt_identity를 사용하는 것이 좋습니다.
실행은 세션 ID가 아니라 고유한 run ID로 추적되므로, 동일한 식별자에 대한 두 run은 별도로 추적되며 각 run은 자신만 등록 해제합니다.
영속성은 식별자에 바인딩됨
Runner가 저장하는 모든 이벤트 — 사용자 턴, 모델 응답, 전송 이벤트,
플러그인 이벤트, 그리고 compaction 이벤트 — 는
SessionService::append_event_for_identity를 통해 전체
(app_name, user_id, session_id) 트리플과 함께 기록됩니다. 자연 키가
복합인 SessionService는 따라서 각 이벤트를 해당 테넌트에 바인딩할 수 있으며, 원시 세션 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
실행 중 에이전트에 제공되는 컨텍스트:
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 응답의 여러 tool 호출이 어떻게 분배되는지 제어합니다:
| 전략 | 동작 |
|---|---|
Sequential (기본값) | LLM가 반환한 순서대로 도구를 한 번에 하나씩 실행 |
Parallel | 모든 도구를 동시에 실행; 안전성은 호출자가 책임짐 |
Auto | 안전한 읽기 전용 하위 집합을 동시에 실행한 다음, 나머지 호출은 모두 순차적으로 실행 |
LlmAgentBuilder별로 설정:
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,
}
에이전트 전송
Runner는 다중 에이전트 전송을 자동으로 처리합니다:
// 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에서 대상 에이전트를 찾습니다
- 새 활성 에이전트로 세션 상태를 업데이트합니다
- 새 에이전트로 실행을 계속합니다
컨텍스트 압축
장시간 실행되는 세션의 경우, 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 →