Realtime 세션에서의 메모리

호출 사이마다 당신을 잊어버리는 음성은 키오스크처럼 느껴집니다. ADK-Rust는 realtime agent가 말하기 전에 사용자에 대해 배운 것을 다시 떠올리고, 대화 중간에 새로운 사실을 선별해 저장할 수 있게 해줍니다. 이는 MemoryServiceIntegratedRealtimeRunner에 연결함으로써 가능합니다.

두 가지가 함께 작동합니다:

  1. 자동 컨텍스트 주입 + 턴 저장 — 통합 계층이 이를 대신 처리합니다.
  2. 도구를 통한 agent 주도 메모리 관리 — agent가 브리지된 knowledge-graph 도구를 사용해 무엇을 보관할 가치가 있는지 결정합니다.

1. 자동 주입과 저장

MemoryService를 연결하면 runner가 왕복 처리를 맡습니다:

let runner = IntegratedRealtimeRunner::builder()
    .model(model)
    .config(config)
    .identity("support", &user_id, &session_id)
    .session_service(sessions)
    .memory_service(memory.clone())
    .integration_config(IntegrationConfig {
        inject_memory_context: true,   // query memory at connect
        max_memory_injection: 10,      // cap injected items
        store_to_memory: true,         // write each completed turn back
        persist_transcripts: true,
    })
    .build()?;
  • 연결 시점에는, memory가 사용자에 대해 조회되고 그 결과가 session context에 반영되어, agent는 관련된 이력을 이미 알고 있는 상태로 인사를 건넵니다.
  • 각 turn마다, 완료된 교환 내용이 다시 저장됩니다(store_to_memory일 때), 그래서 다음 session은 이번 session을 기반으로 이어집니다.

어떤 MemoryService도 사용할 수 있습니다 — 개발용 in-memory 구현이든, 아래의 knowledge-graph backend든 가능합니다.

2. knowledge-graph backend

adk-memory의 **GraphMemoryService**는 memory를 bi-temporal knowledge graph로 저장합니다: entity와 relationship를 각각 event time (사실이 참이었던 시점)과 ingestion time (시스템이 이를 학습한 시점) 기준으로 추적합니다. 즉, agent는 예전에 다른 상태였다는 사실에 걸려 넘어지지 않고 "사용자의 현재 계획이 무엇인가요?"라는 질문에 답할 수 있습니다 — 이전 fact는 현재를 덮어쓰는 대신 history에 남습니다.

use adk_memory::GraphMemoryService;
use std::sync::Arc;

let kg = Arc::new(GraphMemoryService::new(/* backing store */));

그것을 memory_service로 전달하면 위의 자동 주입이 이제 graph를 활용합니다.

3. agent가 memory를 선별하도록 하기

가장 강력한 패턴은 agent에게 graph 자체에 기록할 tools를 주는 것입니다. 그러면 모든 transcript를 그대로 쌓아두는 대신 의도적으로 기억하게 됩니다. adk-tool(feature graph-memory-tools)는 realtime session에 바로 연결되는 두 가지를 제공합니다:

도구에이전트가 이를 통해 하는 일
RememberTool (remember)중요한 사실을 저장합니다("이메일을 전화보다 선호함").
RelateTool (relate)두 개체를 연결합니다("Order A-10293 → belongs-to → this user").

Bridge them with .adk_tool(...) (see Tools):

use adk_tool::{RememberTool, RelateTool};

let runner = IntegratedRealtimeRunner::builder()
    .model(model).config(config)
    .identity("support", &user_id, &session_id)
    .memory_service(kg.clone())
    .adk_tool(Arc::new(RememberTool::new(kg.clone())))
    .adk_tool(Arc::new(RelateTool::new(kg)))
    .build()?;

그런 다음 에이전트에게 그것들을 사용하도록 지시합니다:

When you learn a durable fact about the customer (a preference, an account
detail, a decision), call `remember` to store it. Use `relate` to link orders
or items to the customer. Recall naturally — don't announce that you're saving.

Recall은 토큰 기반입니다: 그래프는 대화와 가장 관련 있는 엔티티/관계를 반환하며, max_memory_injection로 제한되므로 그래프가 커져도 컨텍스트는 작게 유지됩니다.

깊이 선택하기

  • 무상태 데모memory_service 없음. 각 세션은 새로 시작합니다.
  • 세션 간 연속성 → 자동 주입/저장을 사용하는 인메모리 또는 GraphMemoryService. 에이전트의 노력은 전혀 필요 없습니다.
  • 학습하는 에이전트GraphMemoryService + remember/relate 도구로, 에이전트가 대화 기록 더미 대신 사용자에 대한 깔끔하고 질의 가능한 모델을 직접 관리합니다.

MIA 코칭 예제는 그래프 + 도구를 사용해 코치가 세션 사이에도 사용자의 목표와 선호를 기억하도록 합니다; Examples를 보세요.

다음: 웹 앱 만들기 →

재개된 세션에 전달되는 내용

IntegratedRealtimeRunner::connect는 하나의 컨텍스트 블록을 만들고, 프로바이더 세션이 생성되기 전에 이를 시스템 지시에 앞에 붙입니다:

섹션소스제한
Earlier in this conversation:이전 세션의 이벤트IntegrationConfig::max_history_injection (기본값 20 턴)
Relevant recalled context:MemoryService::searchIntegrationConfig::max_memory_injection (기본값 10 항목)

둘 다 제한이 있는 이유는 지침이 세션 생성 시 한 번 전송되며 모델의 컨텍스트에 포함되기 때문입니다. 각 턴은 role: text로 렌더링되며, 400자로 잘리고, 텍스트가 없는 턴은 제외됩니다. 상한을 0으로 설정하면 해당 섹션이 비활성화됩니다.

IntegratedRealtimeRunner::instruction()는 세션이 생성될 때 사용된 값을 반환하므로, 전달된 컨텍스트를 추론하는 대신 검증할 수 있습니다:

runner.connect().await?;
let instruction = runner.instruction().await.unwrap_or_default();
assert!(instruction.contains("Relevant recalled context"));

중요: 이 컨텍스트는 이전에 로드되었다가 폐기되었습니다 — 이전 세션은 _session로 가져온 뒤 삭제되었고, 메모리 분기에는 주석에서 주입이 향후 개선 사항이라고 명시되어 있었음에도 "메모리 항목을 세션 컨텍스트에 주입 중"이라고 로그가 기록되었습니다. 재개된 세션은 둘 다 없이 시작되었지만, 로그는 그렇지 않다고 말했습니다.