コンテンツにスキップ

メモリ:MemoryServiceによる長期的な知識

ADKでサポートPython v0.1.0TypeScript v0.2.0Go v0.1.0Java v0.1.0Kotlin v0.1.0

Session単一の進行中の会話の履歴(events)と一時データ(state)を追跡する方法を見てきました。しかし、エージェントが過去の会話から情報を思い出す必要がある場合はどうでしょうか?ここで、長期的な知識MemoryServiceの概念が登場します。

次のように考えてみてください。

  • Session / State: 特定のチャット中の短期記憶のようなものです。
  • 長期的な知識(MemoryService: エージェントが参照できる検索可能なアーカイブまたは知識ライブラリのようなもので、多くの過去のチャットや他のソースからの情報が含まれている可能性があります。

MemoryServiceの役割

BaseMemoryService(GoではService)は、この検索可能な長期的な知識ストアを管理するためのインターフェイスを定義します。次の4つの操作をサポートします。

  1. セッションの取り込み(add_session_to_memory): (通常は完了した)Sessionのコンテンツを取得し、関連情報を長期的な知識ストアに追加します。
  2. イベントの増分取り込み(add_events_to_memory): セッション全体を再取り込みせずに、イベントの差分(最新ターンなど)を追加します。長時間実行されるセッションの途中でメモリに書き込みたい場合に便利です。
  3. メモリ項目の直接書き込み(add_memory): イベントベースの抽出と並行して直接書き込みをサポートするサービス向けに、事前構築されたMemoryEntry項目を挿入します。
  4. 検索(search_memory): エージェント(通常はToolを介して)が知識ストアをクエリし、検索クエリに基づいて関連するスニペットを取得できるようにします。

操作2と3は任意です。基本クラスのadd_events_to_memoryadd_memoryの実装はNotImplementedErrorを発生させるため、利用する前に具体的なサービスが対応しているか確認してください。

適切なメモリサービスの選択

Python ADKには3つのMemoryService実装が含まれています。以下の表を使用して、エージェントに最適なものを決定してください。

機能 InMemoryMemoryService VertexAiMemoryBankService VertexAiRagMemoryService
永続性 なし(再起動時にデータは失われます) はい(Agent Platformで管理) はい(Knowledge Engineに保存)
主なユースケース プロトタイピング、ローカル開発、および簡単なテスト。 ユーザーの会話から意味のある、進化するメモリを構築します。 会話コーパス全体、または他のRAGインデックス済みコンテンツと併せたベクトル検索検索。
メモリ抽出 完全な会話を保存します 会話から意味のある情報を抽出し、既存のメモリと統合します(LLMを利用) Knowledge Engineでインデックス化された完全な会話を保存します。
検索機能 基本的なキーワードマッチング。 高度なセマンティック検索。 Knowledge Engineによるベクトル類似度検索。
セットアップの複雑さ なし。デフォルトです。 低。Agent Platform上のAgent Runtimeインスタンスが必要です。 中。Knowledge Engineが必要です。
依存関係 なし。 Google Cloudプロジェクト、Agent Platform API Google Cloudプロジェクト、Knowledge Engine、Agent Platform SDK(任意インストール)。
使用するタイミング プロトタイピングのために複数のセッションのチャット履歴を検索する場合。 エージェントに過去のやり取りを記憶させ、学習させたい場合。 既存のRAGインフラがある場合、または生の会話トランスクリプトを検索したい場合。

VertexAiRagMemoryServiceは、Agent Platform SDKがインストールされている場合にのみgoogle.adk.memoryからエクスポートされます。Memory BankとRAGベースのメモリについては、以下のMemory BankRAG Memoryで説明します。

インメモリメモリ

InMemoryMemoryServiceは、アプリケーションのメモリにセッション情報を保存し、検索のために基本的なキーワードマッチングを実行します。セットアップは不要で、永続性が必要ないプロトタイピングや簡単なテストシナリオに最適です。

from google.adk.memory import InMemoryMemoryService
memory_service = InMemoryMemoryService()
import (
  "google.golang.org/adk/memory"
  "google.golang.org/adk/session"
)

// 状態とメモリを共有するには、ランナー間でサービスを共有する必要があります。
sessionService := session.InMemoryService()
memoryService := memory.InMemoryService()
import com.google.adk.memory.InMemoryMemoryService;

InMemoryMemoryService memoryService = new InMemoryMemoryService();
import { InMemoryMemoryService } from '@google/adk';
const memoryService = new InMemoryMemoryService();
fun instantiateMemoryService() {
    val memoryService = InMemoryMemoryService()
}

例:メモリの追加と検索

この例では、簡単にするためにInMemoryMemoryServiceを使用した基本的なフローを示します。

import asyncio
from google.adk.agents import LlmAgent
from google.adk.sessions import InMemorySessionService, Session
from google.adk.memory import InMemoryMemoryService # MemoryServiceをインポート
from google.adk.runners import Runner
from google.adk.tools import load_memory # メモリをクエリするツール
from google.genai.types import Content, Part

# --- 定数 ---
APP_NAME = "memory_example_app"
USER_ID = "mem_user"
MODEL = "gemini-flash-latest" # 有効なモデルを使用

# --- エージェントの定義 ---
# エージェント1:情報をキャプチャするためのシンプルなエージェント
info_capture_agent = LlmAgent(
    model=MODEL,
    name="InfoCaptureAgent",
    instruction="ユーザーの発言を承認します。",
)

# エージェント2:メモリを使用できるエージェント
memory_recall_agent = LlmAgent(
    model=MODEL,
    name="MemoryRecallAgent",
    instruction="ユーザーの質問に答えます。答えが過去の会話にある可能性がある場合は、「load_memory」ツールを使用してください。",
    tools=[load_memory] # エージェントにツールを提供
)

# --- サービス ---
# 状態とメモリを共有するには、ランナー間でサービスを共有する必要があります
session_service = InMemorySessionService()
memory_service = InMemoryMemoryService() # デモ用にインメモリを使用

async def run_scenario():
    # --- シナリオ ---

    # ターン1:セッションでいくつかの情報をキャプチャする
    print("--- ターン1:情報のキャプチャ ---")
    runner1 = Runner(
        # 情報キャプチャエージェントから開始
        agent=info_capture_agent,
        app_name=APP_NAME,
        session_service=session_service,
        memory_service=memory_service # ランナーにメモリサービスを提供
    )
    session1_id = "session_info"
    await runner1.session_service.create_session(app_name=APP_NAME, user_id=USER_ID, session_id=session1_id)
    user_input1 = Content(parts=[Part(text="私のお気に入りのプロジェクトはプロジェクトアルファです。")])

    # エージェントを実行
    final_response_text = "(最終応答なし)"
    async for event in runner1.run_async(user_id=USER_ID, session_id=session1_id, new_message=user_input1):
        if event.is_final_response() and event.content and event.content.parts:
            final_response_text = event.content.parts[0].text
    print(f"エージェント1の応答:{final_response_text}")

    # 完了したセッションを取得
    completed_session1 = await runner1.session_service.get_session(app_name=APP_NAME, user_id=USER_ID, session_id=session1_id)

    # このセッションのコンテンツをメモリサービスに追加
    print("\n--- セッション1をメモリに追加 ---")
    await memory_service.add_session_to_memory(completed_session1)
    print("セッションがメモリに追加されました。")

    # ターン2:新しいセッションで情報を思い出す
    print("\n--- ターン2:情報の想起 ---")
    runner2 = Runner(
        # メモリツールを持つ2番目のエージェントを使用
        agent=memory_recall_agent,
        app_name=APP_NAME,
        session_service=session_service, # 同じサービスを再利用
        memory_service=memory_service   # 同じサービスを再利用
    )
    session2_id = "session_recall"
    await runner2.session_service.create_session(app_name=APP_NAME, user_id=USER_ID, session_id=session2_id)
    user_input2 = Content(parts=[Part(text="私のお気に入りのプロジェクトは何ですか?")])

    # 2番目のエージェントを実行
    final_response_text_2 = "(最終応答なし)"
    async for event in runner2.run_async(user_id=USER_ID, session_id=session2_id, new_message=user_input2):
        if event.is_final_response() and event.content and event.content.parts:
            final_response_text_2 = event.content.parts[0].text
    print(f"エージェント2の応答:{final_response_text_2}")

# この例を実行するには、次のスニペットを使用できます。
# asyncio.run(run_scenario())

# await run_scenario()
import (
    "context"
    "fmt"
    "log"
    "strings"

    "google.golang.org/adk/agent"
    "google.golang.org/adk/agent/llmagent"
    "google.golang.org/adk/memory"
    "google.golang.org/adk/model/gemini"
    "google.golang.org/adk/runner"
    "google.golang.org/adk/session"
    "google.golang.org/adk/tool"
    "google.golang.org/adk/tool/functiontool"
    "google.golang.org/genai"
)

const (
    appName = "go_memory_example_app"
    userID  = "go_mem_user"
    modelID = "gemini-2.5-flash"
)

// Args defines the input structure for the memory search tool.
type Args struct {
    Query string `json:"query" jsonschema:"The query to search for in the memory."`
}

// Result defines the output structure for the memory search tool.
type Result struct {
    Results []string `json:"results"`
}


// memorySearchToolFunc is the implementation of the memory search tool.
// This function demonstrates accessing memory via tool.Context.
func memorySearchToolFunc(tctx tool.Context, args Args) (Result, error) {
    fmt.Printf("Tool: Searching memory for query: '%s'\n", args.Query)
    // The SearchMemory function is available on the context.
    searchResults, err := tctx.SearchMemory(context.Background(), args.Query)
    if err != nil {
        log.Printf("Error searching memory: %v", err)
        return Result{}, fmt.Errorf("failed memory search")
    }

    var results []string
    for _, res := range searchResults.Memories {
        if res.Content != nil {
            results = append(results, textParts(res.Content)...)
        }
    }
    return Result{Results: results}, nil
}

// Define a tool that can search memory.
var memorySearchTool = must(functiontool.New(
    functiontool.Config{
        Name:        "search_past_conversations",
        Description: "Searches past conversations for relevant information.",
    },
    memorySearchToolFunc,
))


// This example demonstrates how to use the MemoryService in the Go ADK.
// It covers two main scenarios:
// 1. Adding a completed session to memory and recalling it in a new session.
// 2. Searching memory from within a custom tool using the tool.Context.
func main() {
    ctx := context.Background()

    // --- Services ---
    // Services must be shared across runners to share state and memory.
    sessionService := session.InMemoryService()
    memoryService := memory.InMemoryService() // Use in-memory for this demo.

    // --- Scenario 1: Capture information in one session ---
    fmt.Println("--- Turn 1: Capturing Information ---")
    infoCaptureAgent := must(llmagent.New(llmagent.Config{
        Name:        "InfoCaptureAgent",
        Model:       must(gemini.NewModel(ctx, modelID, nil)),
        Instruction: "Acknowledge the user's statement.",
    }))

    runner1 := must(runner.New(runner.Config{
        AppName:        appName,
        Agent:          infoCaptureAgent,
        SessionService: sessionService,
        MemoryService:  memoryService, // Provide the memory service to the Runner
    }))

    session1ID := "session_info"
    must(sessionService.Create(ctx, &session.CreateRequest{AppName: appName, UserID: userID, SessionID: session1ID}))

    userInput1 := genai.NewContentFromText("My favorite project is Project Alpha.", "user")
    var finalResponseText string
    for event, err := range runner1.Run(ctx, userID, session1ID, userInput1, agent.RunConfig{}) {
        if err != nil {
            log.Printf("Agent 1 Error: %v", err)
            continue
        }
        if event.LLMResponse.Content != nil && !event.LLMResponse.Partial {
            finalResponseText = strings.Join(textParts(event.LLMResponse.Content), "")
        }
    }
    fmt.Printf("Agent 1 Response: %s\n", finalResponseText)

    // Add the completed session to the Memory Service
    fmt.Println("\n--- Adding Session 1 to Memory ---")
    resp, err := sessionService.Get(ctx, &session.GetRequest{AppName: appName, UserID: userID, SessionID: session1ID})
    if err != nil {
        log.Fatalf("Failed to get completed session: %v", err)
    }
    if err := memoryService.AddSessionToMemory(ctx, resp.Session); err != nil {
        log.Fatalf("Failed to add session to memory: %v", err)
    }
    fmt.Println("Session added to memory.")

    // --- Scenario 2: Recall the information in a new session using a tool ---
    fmt.Println("\n--- Turn 2: Recalling Information ---")

    memoryRecallAgent := must(llmagent.New(llmagent.Config{
        Name:        "MemoryRecallAgent",
        Model:       must(gemini.NewModel(ctx, modelID, nil)),
        Instruction: "Answer the user's question. Use the 'search_past_conversations' tool if the answer might be in past conversations.",
        Tools:       []tool.Tool{memorySearchTool}, // Give the agent the tool
    }))

    runner2 := must(runner.New(runner.Config{
        Agent:          memoryRecallAgent,
        AppName:        appName,
        SessionService: sessionService,
        MemoryService:  memoryService,
    }))

    session2ID := "session_recall"
    must(sessionService.Create(ctx, &session.CreateRequest{AppName: appName, UserID: userID, SessionID: session2ID}))
    userInput2 := genai.NewContentFromText("What is my favorite project?", "user")

    var finalResponseText2 string
    for event, err := range runner2.Run(ctx, userID, session2ID, userInput2, agent.RunConfig{}) {
        if err != nil {
            log.Printf("Agent 2 Error: %v", err)
            continue
        }
        if event.LLMResponse.Content != nil && !event.LLMResponse.Partial {
            finalResponseText2 = strings.Join(textParts(event.LLMResponse.Content), "")
        }
    }
    fmt.Printf("Agent 2 Response: %s\n", finalResponseText2)
}
import {
    InMemoryMemoryService,
    InMemorySessionService,
    LOAD_MEMORY,
    LlmAgent,
    Runner
} from '@google/adk';
import { createUserContent } from '@google/genai';

// --- Constants ---
const APP_NAME = "memory_example_app";
const USER_ID = "mem_user";
const MODEL = "gemini-2.5-flash";

// --- Agent Definitions ---

// Agent 1: Simple agent to capture information
const infoCaptureAgent = new LlmAgent({
    model: MODEL,
    name: "InfoCaptureAgent",
    instruction: "Acknowledge the user's statement concisely.",
});

// Agent 2: Agent that can use memory
const memoryRecallAgent = new LlmAgent({
    model: MODEL,
    name: "MemoryRecallAgent",
    instruction: "Answer the user's question. Use the 'load_memory' tool if the answer might be in past conversations.",
    tools: [LOAD_MEMORY]
});

// Export for 'adk run' compatibility (to avoid 'No BaseAgent found' error)
export const root_agent = memoryRecallAgent;

// --- Services ---
const sessionService = new InMemorySessionService();
const memoryService = new InMemoryMemoryService();

async function runScenario() {
    // --- Turn 1: Capture some information in a session ---
    console.log("--- Turn 1: Capturing Information ---");
    const runner1 = new Runner({
        agent: infoCaptureAgent,
        appName: APP_NAME,
        sessionService,
        memoryService
    });

    const session1Id = "session_info";
    await sessionService.createSession({ appName: APP_NAME, userId: USER_ID, sessionId: session1Id });
    const userInput1 = createUserContent("My favorite project is Project Alpha.");

    let finalResponseText = "(No final response)";
    for await (const event of runner1.runAsync({ userId: USER_ID, sessionId: session1Id, newMessage: userInput1 })) {
        // Capture any text response from the agent
        if (event.author === infoCaptureAgent.name && event.content?.parts) {
            const text = event.content.parts.map(p => p.text || "").join("").trim();
            if (text) finalResponseText = text;
        }
    }
    console.log(`Agent 1 Response: ${finalResponseText}`);

    // Get the completed session and add to Memory
    const completedSession1 = await sessionService.getSession({ appName: APP_NAME, userId: USER_ID, sessionId: session1Id });
    console.log("\n--- Adding Session 1 to Memory ---");
    if (completedSession1) {
        await memoryService.addSessionToMemory(completedSession1);
        console.log("Session added to memory.");
    }

    // --- Turn 2: Recall the information in a new session ---
    console.log("\n--- Turn 2: Recalling Information ---");
    const runner2 = new Runner({
        agent: memoryRecallAgent,
        appName: APP_NAME,
        sessionService,
        memoryService
    });

    const session2Id = "session_recall";
    await sessionService.createSession({ appName: APP_NAME, userId: USER_ID, sessionId: session2Id });
    const userInput2 = createUserContent("What is my favorite project?");

    let finalResponseText2 = "(No final response)";
    for await (const event of runner2.runAsync({ userId: USER_ID, sessionId: session2Id, newMessage: userInput2 })) {
        // Capture any text response from the agent
        if (event.author === memoryRecallAgent.name && event.content?.parts) {
            const text = event.content.parts.map(p => p.text || "").join("").trim();
            if (text) finalResponseText2 = text;
        }
    }
    console.log(`Agent 2 Response: ${finalResponseText2}`);

    // Exit immediately to prevent the ADK CLI from starting an interactive loop
    process.exit(0);
}

// Execute the scenario
runScenario().catch(err => {
    console.error(err);
    process.exit(1);
});
package com.google.adk.examples.sessions;

import com.google.adk.agents.LlmAgent;
import com.google.adk.memory.InMemoryMemoryService;
import com.google.adk.runner.Runner;
import com.google.adk.sessions.InMemorySessionService;
import com.google.adk.sessions.Session;
import com.google.adk.tools.LoadMemoryTool;
import com.google.genai.types.Content;
import com.google.genai.types.Part;
import java.util.Optional;

public class MemoryExample {

  private static final String APP_NAME = "memory_example_app";
  private static final String USER_ID = "mem_user";
  private static final String MODEL = "gemini-flash-latest";

  public static void main(String[] args) {
    // サービス
    InMemorySessionService sessionService = new InMemorySessionService();
    InMemoryMemoryService memoryService = new InMemoryMemoryService();

    // エージェント 1: 情報を記録
    LlmAgent infoCaptureAgent = new LlmAgent.Builder()
        .model(MODEL)
        .name("InfoCaptureAgent")
        .instruction("ユーザーの発言を承認します。")
        .build();

    // エージェント 2: 情報を想起
    LlmAgent memoryRecallAgent = new LlmAgent.Builder()
        .model(MODEL)
        .name("MemoryRecallAgent")
        .instruction("ユーザーの質問に答えてください。答えが過去の会話にある可能性がある場合は 'load_memory' ツールを使用してください。")
        .tools(new LoadMemoryTool())
        .build();

    // ターン 1
    System.out.println("--- ターン 1: 情報を記録 ---");
    Runner runner1 = new Runner.Builder()
        .agent(infoCaptureAgent)
        .appName(APP_NAME)
        .sessionService(sessionService)
        .memoryService(memoryService)
        .build();

    String session1Id = "session_info";
    sessionService.createSession(APP_NAME, USER_ID, null, session1Id).blockingGet();

    Content userInput1 =
        Content.fromParts(Part.fromText("私のお気に入りのプロジェクトは Project Alpha です。"));

    runner1.runAsync(USER_ID, session1Id, userInput1)
        .blockingForEach(event -> {
          if (event.finalResponse() && event.content().isPresent()) {
            System.out.println("エージェント 1 の応答: " + event.content().get().parts().get(0).text().get());
          }
        });

    // メモリに追加
    System.out.println("\n--- セッション 1 をメモリに追加 ---");
    Session completedSession1 =
        sessionService.getSession(APP_NAME, USER_ID, session1Id, Optional.empty()).blockingGet();
    memoryService.addSessionToMemory(completedSession1).blockingAwait();
    System.out.println("セッションをメモリに追加しました。");

    // ターン 2
    System.out.println("\n--- ターン 2: 情報を想起 ---");
    Runner runner2 = new Runner.Builder()
        .agent(memoryRecallAgent)
        .appName(APP_NAME)
        .sessionService(sessionService)
        .memoryService(memoryService)
        .build();

    String session2Id = "session_recall";
    sessionService.createSession(APP_NAME, USER_ID, null, session2Id).blockingGet();

    Content userInput2 =
        Content.fromParts(Part.fromText("私のお気に入りのプロジェクトは何ですか?"));

    runner2.runAsync(USER_ID, session2Id, userInput2)
        .blockingForEach(event -> {
          if (event.finalResponse() && event.content().isPresent()) {
            System.out.println("エージェント 2 の応答: " + event.content().get().parts().get(0).text().get());
          }
        });
  }
}
fun main() =
    runBlocking {
        // --- Constants ---
        val appName = "memory_example_app"
        val userId = "mem_user"
        val model = Gemini(name = "gemini-flash-latest")

        // --- Agent Definitions ---

        // Agent 1: Simple agent to capture information
        val infoCaptureAgent =
            LlmAgent(
                name = "InfoCaptureAgent",
                model = model,
                instruction = Instruction("Acknowledge the user's statement."),
            )

        // Agent 2: Agent that can use memory
        val memoryRecallAgent =
            LlmAgent(
                name = "MemoryRecallAgent",
                model = model,
                instruction =
                    Instruction(
                        "Answer the user's question. Use the 'load_memory' tool " +
                            "if the answer might be in past conversations.",
                    ),
                tools = listOf(LoadMemoryTool()), // Give the agent the tool
            )

        // --- Services ---
        // Services must be shared across runners to share state and memory
        val sessionService = InMemorySessionService()
        val memoryService = InMemoryMemoryService()

        // --- Turn 1: Capturing Information ---
        println("--- Turn 1: Capturing Information ---")
        val runner1 =
            InMemoryRunner(
                agent = infoCaptureAgent,
                appName = appName,
                sessionService = sessionService,
                memoryService = memoryService,
            )
        val sessionId1 = "session_info"
        val userInput1 = Content.fromText(Role.USER, "My favorite project is Project Alpha.")

        // Run the agent
        runner1
            .runAsync(
                userId = userId,
                sessionId = sessionId1,
                newMessage = userInput1,
            ).collect { event ->
                event.content?.parts?.forEach { part ->
                    if (!part.text.isNullOrBlank()) {
                        println("Agent Response: ${part.text}")
                    }
                }
            }

        // Get the completed session using SessionKey
        val session1 = sessionService.getSession(SessionKey(appName, userId, sessionId1))

        // Add this session's content to the Memory Service
        println("\n--- Adding Session 1 to Memory ---")
        if (session1 != null) {
            memoryService.addSessionToMemory(session1)
            println("Session added to memory.")
        }

        // --- Turn 2: Recalling Information ---
        println("\n--- Turn 2: Recalling Information ---")
        val runner2 =
            InMemoryRunner(
                agent = memoryRecallAgent,
                appName = appName,
                sessionService = sessionService, // Reuse the same service
                memoryService = memoryService, // Reuse the same service
            )
        val sessionId2 = "session_recall"
        val userInput2 = Content.fromText(Role.USER, "What is my favorite project?")

        // Run the second agent
        runner2
            .runAsync(
                userId = userId,
                sessionId = sessionId2,
                newMessage = userInput2,
            ).collect { event ->
                event.content?.parts?.forEach { part ->
                    if (!part.text.isNullOrBlank()) {
                        println("Agent Response: ${part.text}")
                    }
                }
            }
    }

ツール内でのメモリ検索

tool.Contextを使用して、カスタムツール内からメモリを検索することもできます。

// memorySearchToolFunc is the implementation of the memory search tool.
// This function demonstrates accessing memory via tool.Context.
func memorySearchToolFunc(tctx tool.Context, args Args) (Result, error) {
    fmt.Printf("Tool: Searching memory for query: '%s'\n", args.Query)
    // The SearchMemory function is available on the context.
    searchResults, err := tctx.SearchMemory(context.Background(), args.Query)
    if err != nil {
        log.Printf("Error searching memory: %v", err)
        return Result{}, fmt.Errorf("failed memory search")
    }

    var results []string
    for _, res := range searchResults.Memories {
        if res.Content != nil {
            results = append(results, textParts(res.Content)...)
        }
    }
    return Result{Results: results}, nil
}

// Define a tool that can search memory.
var memorySearchTool = must(functiontool.New(
    functiontool.Config{
        Name:        "search_past_conversations",
        Description: "Searches past conversations for relevant information.",
    },
    memorySearchToolFunc,
))
// ツール実装の内部
public Single<ToolOutput> execute(ToolContext context) {
  String query = ...; // 引数からクエリを取得
  return context.searchMemory(query)
      .map(response -> {
        // 応答を処理
        return new ToolOutput(response.memories().toString());
      });
}
import { LlmAgent, PRELOAD_MEMORY } from '@google/adk';

const agent = new LlmAgent({
    model: MODEL_ID,
    name: 'weather_sentiment_agent',
    instruction: "...",
    tools: [PRELOAD_MEMORY]
});
suspend fun searchWithinTool(
    context: ToolContext,
    args: Map<String, Any>,
): String {
    val query = args["query"] as String
    val response =
        context.invocationContext.memoryService?.searchMemory(
            appName = context.invocationContext.session.key.appName,
            userId = context.invocationContext.session.key.userId,
            query = query,
        )
    // process response
    return response?.memories?.joinToString("\n") {
        it.content.parts.joinToString(" ") { p -> p.text ?: "" }
    } ?: ""
}

Memory Bank

VertexAiMemoryBankServiceは、エージェントをMemory Bankに接続します。これは、会話型エージェントに高度で永続的なメモリ機能を提供する、完全に管理されたGoogle Cloudサービスです。

仕組み

このサービスは、2つの主要な操作を処理します。

  • メモリの生成: 会話の最後に、セッションのイベントをメモリバンクに送信できます。メモリバンクは、情報をインテリジェントに処理して「メモリ」として保存します。
  • メモリの取得: エージェントコードは、メモリバンクに対して検索クエリを発行して、過去の会話から関連するメモリを取得できます。

前提条件

この機能を使用する前に、次のものが必要です。

  1. Google Cloudプロジェクト: Agent Platform APIが有効になっていること。
  2. Agent Runtime: Agent PlatformでAgent Runtimeを作成する必要があります。Memory Bankを使用するためにエージェントをAgent Runtimeへデプロイする必要はありません。これにより、構成に必要なAgent Runtime IDが提供されます。
  3. 認証: ローカル環境がGoogle Cloudサービスにアクセスできるように認証されていることを確認します。最も簡単な方法は、次を実行することです。
    gcloud auth application-default login
    
  4. 環境変数: このサービスには、Google CloudプロジェクトIDとロケーションが必要です。それらを環境変数として設定します。
    export GOOGLE_CLOUD_PROJECT="your-gcp-project-id"
    export GOOGLE_CLOUD_LOCATION="your-gcp-location"
    

構成

エージェントをメモリバンクに接続するには、ADKサーバー(adk webまたはadk api_server)を起動するときに--memory_service_uriフラグを使用します。URIはagentengine://<agent_engine_id>の形式である必要があります。

bash
adk web path/to/your/agents_dir --memory_service_uri="agentengine://1234567890"

または、VertexAiMemoryBankServiceを手動でインスタンス化し、それをRunnerに渡すことで、メモリバンクを使用するようにエージェントを構成できます。

from google import adk
from google.adk.memory import VertexAiMemoryBankService

agent_engine_id = agent_engine.api_resource.name.split("/")[-1]

memory_service = VertexAiMemoryBankService(
    project="PROJECT_ID",
    location="LOCATION",
    agent_engine_id=agent_engine_id
)

runner = adk.Runner(
    ...
    memory_service=memory_service
)

RAG Memory

VertexAiRagMemoryServiceは、会話をKnowledge Engineに保存し、ベクトル類似度で検索します。既存のRAGインフラがある場合、またはMemory Bankが生成するLLM抽出メモリではなく生の会話トランスクリプトを検索したい場合に使用します。Agent Platform SDKが必要です。

from google.adk.memory import VertexAiRagMemoryService

memory_service = VertexAiRagMemoryService(
    rag_corpus="projects/PROJECT_ID/locations/LOCATION/ragCorpora/CORPUS_ID",
    similarity_top_k=5,
    vector_distance_threshold=0.6,
)

エージェントでのメモリの使用

メモリサービスが構成されている場合、エージェントはツールまたはコールバックを使用してメモリを取得できます。ADKには、メモリを取得するための2つの組み込みツールが含まれています。

  • PreloadMemory: 各ターンの開始時に常にメモリを取得します(コールバックに似ています)。
  • LoadMemory: エージェントが役立つと判断したときにメモリを取得します。

例:

from google.adk.agents import Agent
from google.adk.tools.preload_memory_tool import PreloadMemoryTool

agent = Agent(
    model=MODEL_ID,
    name='weather_sentiment_agent',
    instruction="...",
    tools=[PreloadMemoryTool()]
)
import (
    "google.golang.org/adk/agent/llmagent"
    "google.golang.org/adk/tool"
    "google.golang.org/adk/tool/preloadmemorytool"
)

agent, _ := llmagent.New(llmagent.Config{
    Model:       model,
    Name:        "weather_sentiment_agent",
    Instruction: "...",
    Tools:       []tool.Tool{preloadmemorytool.New()},
})
import com.google.adk.agents.LlmAgent;
import com.google.adk.tools.LoadMemoryTool;

LlmAgent agent = new LlmAgent.Builder()
    .model(MODEL_ID)
    .name("weather_sentiment_agent")
    .instruction("...")
    .tools(new LoadMemoryTool())
    .build();
fun preloadMemoryAgent(model: Gemini) {
    val agent =
        LlmAgent(
            model = model,
            name = "weather_sentiment_agent",
            instruction = Instruction("..."),
            tools = listOf(PreloadMemoryTool()),
        )
}
import { LlmAgent, PRELOAD_MEMORY, SingleAgentCallback } from '@google/adk';

const autoSaveSessionToMemoryCallback: SingleAgentCallback = async (callbackContext) => {
    if (callbackContext.invocationContext.memoryService) {
        await callbackContext.invocationContext.memoryService.addSessionToMemory(
            callbackContext.invocationContext.session
        );
    }
};

const agent = new LlmAgent({
    model: MODEL,
    name: "Generic_QA_Agent",
    instruction: "Answer the user's questions",
    tools: [PRELOAD_MEMORY],
    afterAgentCallback: autoSaveSessionToMemoryCallback,
});

セッションからメモリを抽出するには、add_session_to_memoryを呼び出す必要があります。たとえば、コールバックを介してこれを自動化できます。

from google.adk.agents import Agent
from google import adk

async def auto_save_session_to_memory_callback(callback_context):
    await callback_context._invocation_context.memory_service.add_session_to_memory(
        callback_context._invocation_context.session)

agent = Agent(
    model=MODEL,
    name="Generic_QA_Agent",
    instruction="ユーザーの質問に答えます",
    tools=[adk.tools.preload_memory_tool.PreloadMemoryTool()],
    after_agent_callback=auto_save_session_to_memory_callback,
)
import (
    "context"
    "google.golang.org/adk/agent"
    "google.golang.org/adk/agent/llmagent"
    "google.golang.org/adk/session"
    "google.golang.org/adk/tool"
    "google.golang.org/adk/tool/loadmemorytool"
)

func autoSaveSessionToMemoryCallback(ctx agent.CallbackContext, s session.Session) (*genai.Content, error) {
    if err := ctx.Memory().AddSessionToMemory(context.Background(), s); err != nil {
        return nil, err
    }
    return nil, nil
}

agent, _ := llmagent.New(llmagent.Config{
    Model:               model,
    Name:                "Generic_QA_Agent",
    Instruction:         "ユーザーの質問に答えます。",
    Tools:               []tool.Tool{loadmemorytool.New()},
    AfterAgentCallbacks: []agent.AfterAgentCallback{autoSaveSessionToMemoryCallback},
})
import com.google.adk.agents.LlmAgent;
import com.google.adk.tools.LoadMemoryTool;
import io.reactivex.rxjava3.core.Maybe;

LlmAgent agent = new LlmAgent.Builder()
    .model(MODEL)
    .name("Generic_QA_Agent")
    .instruction("ユーザーの質問に答えてください。")
    .tools(new LoadMemoryTool())
    .afterAgentCallback((context) -> {
      return context.invocationContext().memoryService()
          .addSessionToMemory(context.invocationContext().session())
          .andThen(Maybe.empty());
    })
    .build();
suspend fun autoSaveSessionToMemoryCallback(
    context: CallbackContext,
): CallbackChoice<Unit, Content> {
    context.addSessionToMemory()
    return CallbackChoice.Continue(Unit)
}

fun agentWithCallback(model: Gemini) {
    val agent =
        LlmAgent(
            model = model,
            name = "Generic_QA_Agent",
            instruction = Instruction("Answer the user's questions"),
            tools = listOf(PreloadMemoryTool()),
            afterAgentCallbacks = listOf(AfterAgentCallback(::autoSaveSessionToMemoryCallback)),
        )
}

高度な概念

メモリの実際の仕組み

メモリワークフローには、内部的に次の手順が含まれます。

  1. セッションの対話: ユーザーはSessionServiceによって管理されるSessionを介してエージェントと対話します。イベントが追加され、状態が更新される場合があります。
  2. メモリへの取り込み: ある時点で(多くの場合、セッションが完了したと見なされるか、重要な情報を生成した場合)、アプリケーションはmemory_service.add_session_to_memory(session)を呼び出します。これにより、セッションのイベントから関連情報が抽出され、長期的な知識ストア(インメモリ辞書またはエージェントエンジンメモリバンク)に追加されます。
  3. 後のクエリ: 異なる(または同じ)セッションで、ユーザーは過去のコンテキストを必要とする質問をする場合があります(例:「先週、プロジェクトXについて何を話し合いましたか?」)。
  4. エージェントがメモリツールを使用: メモリ取得ツール(組み込みのload_memoryツールなど)を備えたエージェントは、過去のコンテキストの必要性を認識します。ツールを呼び出し、検索クエリ(例:「先週のプロジェクトXの議論」)を提供します。
  5. 検索の実行: ツールは内部的にmemory_service.search_memory(app_name, user_id, query)を呼び出します。
  6. 結果の返却: MemoryServiceはストアを検索し(キーワードマッチングまたはセマンティック検索を使用)、関連するスニペットをMemoryResultオブジェクトのリストを含むSearchMemoryResponseとして返します(それぞれが関連する過去のセッションのイベントを保持している可能性があります)。
  7. エージェントが結果を使用: ツールはこれらの結果をエージェントに返し、通常はコンテキストまたは関数応答の一部として返します。その後、エージェントはこの取得した情報を使用して、ユーザーへの最終的な回答を作成できます。

エージェントは複数のメモリサービスにアクセスできますか?

  • 標準構成を介して:いいえ。 フレームワーク(adk webadk api_server)は、--memory_service_uriフラグを介して一度に1つのメモリサービスで構成するように設計されています。この単一のサービスは、エージェントに提供され、組み込みのself.search_memory()メソッドを介してアクセスされます。構成の観点からは、そのプロセスによって提供されるすべてのエージェントに対して1つのバックエンド(InMemoryVertexAiMemoryBankService)しか選択できません。

  • エージェントのコード内:はい、もちろんです。 エージェントのコード内で別のメモリサービスを直接手動でインポートしてインスタンス化することを妨げるものは何もありません。これにより、単一のエージェントターン内で複数のメモリソースにアクセスできます。

たとえば、エージェントはフレームワークで構成されたInMemoryMemoryServiceを使用して会話履歴を思い出し、VertexAiMemoryBankServiceを手動でインスタンス化して技術マニュアルの情報を検索できます。

例:2つのメモリサービスの使用

エージェントのコードでそれを実装する方法は次のとおりです。

from google.adk.agents import Agent
from google.adk.memory import InMemoryMemoryService, VertexAiMemoryBankService
from google.genai import types

class MultiMemoryAgent(Agent):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        self.memory_service = InMemoryMemoryService()
        # ドキュメント検索用に2番目のメモリサービスを手動でインスタンス化します
        self.vertexai_memorybank_service = VertexAiMemoryBankService(
            project="PROJECT_ID",
            location="LOCATION",
            agent_engine_id="AGENT_ENGINE_ID"
        )

    async def run(self, request: types.Content, **kwargs) -> types.Content:
        user_query = request.parts[0].text

        # 1. フレームワーク提供のメモリを使用して会話履歴を検索します
        #    (構成されている場合はInMemoryMemoryServiceになります)
        conversation_context = await self.memory_service.search_memory(query=user_query)

        # 2. 手動で作成したサービスを使用してドキュメントナレッジベースを検索します
        document_context = await self.vertexai_memorybank_service.search_memory(query=user_query)

        # 両方のソースからのコンテキストを組み合わせて、より良い応答を生成します
        prompt = "過去の会話から、私は覚えています:\n"
        prompt += f"{conversation_context.memories}\n\n"
        prompt += "技術マニュアルから、私は見つけました:\n"
        prompt += f"{document_context.memories}\n\n"
        prompt += f"これらすべてに基づいて、'{user_query}'に対する私の答えは次のとおりです。"

        return await self.llm.generate_content_async(prompt)
/**
 * Example of using two memory services in Kotlin.
 */
suspend fun searchAllMemory(
    toolContext: ToolContext,
    query: String,
    docsMemory: InMemoryMemoryService,
): Map<String, List<String>> {
    // Search the conversational memory (configured in the runner)
    val conversational =
        toolContext.invocationContext.memoryService?.searchMemory(
            appName = toolContext.invocationContext.session.key.appName,
            userId = toolContext.invocationContext.session.key.userId,
            query = query,
        )

    // Search a separate docs knowledge base
    val docs =
        docsMemory.searchMemory(
            appName = "docs",
            userId = "shared",
            query = query,
        )

    return mapOf(
        "from_conversations" to
            (
                conversational?.memories?.map {
                    it.content.parts.joinToString(" ") { p -> p.text ?: "" }
                } ?: emptyList()
            ),
        "from_docs" to
            docs.memories.map {
                it.content.parts.joinToString(" ") { p -> p.text ?: "" }
            },
    )
}

fun multiMemoryAgent(model: Gemini) {
    // docs_memory could be any MemoryService implementation
    val docsMemory = InMemoryMemoryService()

    val agent =
        LlmAgent(
            model = model,
            name = "multi_memory_agent",
            instruction =
                Instruction(
                    "Answer questions using both your conversation history and the " +
                        "docs knowledge base. Use the search_all_memory tool.",
                ),
            // In a real app, you'd wrap searchAllMemory in a @Tool annotated class
            // and pass docsMemory to its constructor.
        )
}