[Go to site: main page, start]

Skip to content

记忆:通过 MemoryService 实现长期知识存储

Supported in ADKPython 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)定义了管理可搜索的长期知识存储的接口。它支持以下操作:

  • 摄入信息:
    • add_session_to_memory:接收一个已完成的 Session,并将相关信息添加到长期知识存储中。这种方式非常适合自动捕获对话的核心内容。
    • add_events_to_memory:追加事件增量(例如最新的对话轮次),无需重新摄入整个会话。当你需要在长时间运行的会话中途写入记忆时非常有用。
    • add_memory:将显式的 MemoryEntry 对象直接添加到记忆中。此方法提供精细控制,适用于从其他来源注入特定事实。
  • 搜索信息(search_memory): 允许智能体(通常通过 Tool)查询知识存储,并根据搜索查询检索相关片段或上下文。

add_events_to_memoryadd_memory 是可选的,并非每个服务都实现了它们,因此在依赖它们之前,请确认你选择的服务是否支持。

选择合适的记忆服务

Python ADK 提供了三种 MemoryService 实现。请参考下表决定哪种最适合你的智能体。

功能 InMemoryMemoryService VertexAiMemoryBankService VertexAiRagMemoryService
持久性 无,重启后数据丢失 有,由 Agent Platform 管理 有,存储在 Knowledge Engine 中
主要用途 原型开发、本地开发和简单测试。 从用户对话中构建有意义的、持续演化的记忆。 对完整对话语料库进行向量搜索检索,或与其他 RAG 索引内容一起使用。
记忆提取 存储完整对话 从对话中提取有意义的信息,并由 LLM 驱动与现有记忆进行整合 存储完整对话,由 Knowledge Engine 建立索引。
搜索能力 基本关键字匹配。 高级语义搜索。 基于 Knowledge Engine 的向量相似度搜索。
设置复杂度 无,这是默认选项。 低。需要在 Agent Platform 上创建一个 Agent Runtime 实例。 中等。需要 Knowledge Engine
依赖项 无。 Google Cloud Project、Agent Platform API Google Cloud Project、Knowledge Engine、Agent Platform SDK(可选安装)。
使用场景 当你想要在原型开发阶段跨多个会话的聊天历史进行搜索时。 当你希望智能体记住并从过去的交互中学习时。 当你已有 RAG 基础设施或想要检索原始对话记录时。

VertexAiRagMemoryService 仅在安装了 Agent Platform SDK 后才会从 google.adk.memory 中导出。Memory Bank 和基于 RAG 的记忆在下方的记忆库RAG 记忆中进行了说明。


InMemoryMemoryService

InMemoryMemoryService 将会话信息存储在应用程序的内存中,并使用基本关键字匹配进行搜索。它无需任何设置,最适合原型开发和不需要持久性的简单测试场景。

from google.adk.memory import InMemoryMemoryService
memory_service = InMemoryMemoryService()
import { InMemoryMemoryService } from '@google/adk';
const memoryService = new InMemoryMemoryService();
import (
  "google.golang.org/adk/v2/memory"
  "google.golang.org/adk/v2/session"
)

// 服务必须在运行器之间共享以共享状态和记忆。
sessionService := session.InMemoryService()
memoryService := memory.InMemoryService()
import com.google.adk.memory.InMemoryMemoryService;

InMemoryMemoryService 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="我最喜欢的项目是 Alpha 项目。")], role="user")

    # 运行智能体
    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(
        # 使用第二个智能体,它有记忆工具
        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="我最喜欢的项目是什么?")], role="user")

    # 运行第二个智能体
    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())
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);
});
import (
    "context"
    "fmt"
    "log"
    "strings"

    "google.golang.org/adk/v2/agent"
    "google.golang.org/adk/v2/agent/llmagent"
    "google.golang.org/adk/v2/memory"
    "google.golang.org/adk/v2/model/gemini"
    "google.golang.org/adk/v2/runner"
    "google.golang.org/adk/v2/session"
    "google.golang.org/adk/v2/tool"
    "google.golang.org/adk/v2/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 agent.Context.
func memorySearchToolFunc(tctx agent.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 agent.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 com.google.adk.agents.LlmAgent;
import com.google.adk.agents.RunConfig;
import com.google.adk.events.Event;
import com.google.adk.runner.InMemoryRunner;
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 {

  public static void main(String[] args) {
    String appName = "memory_example_app";
    String userId = "mem_user";
    String model = "gemini-flash-latest";

    // An agent that can recall past information using the load_memory tool.
    LlmAgent agent =
        LlmAgent.builder()
            .model(model)
            .name("MemoryAgent")
            .instruction(
                "Answer the user's question. Use the 'load_memory' tool "
                    + "if the answer might be in past conversations.")
            .tools(new LoadMemoryTool())
            .build();

    // InMemoryRunner bundles in-memory session and memory services and shares
    // them across every session it creates.
    InMemoryRunner runner = new InMemoryRunner(agent, appName);

    // --- Turn 1: capture information in one session ---
    Session captureSession =
        runner.sessionService().createSession(appName, userId).blockingGet();
    Content statement =
        Content.fromParts(Part.fromText("My favorite project is Project Alpha."));
    runner
        .runAsync(userId, captureSession.id(), statement, RunConfig.builder().build())
        .blockingSubscribe();

    // Persist the finished session to memory.
    Session completedSession =
        runner
            .sessionService()
            .getSession(appName, userId, captureSession.id(), Optional.empty())
            .blockingGet();
    runner.memoryService().addSessionToMemory(completedSession).blockingAwait();

    // --- Turn 2: recall the information in a new session ---
    Session recallSession =
        runner.sessionService().createSession(appName, userId).blockingGet();
    Content question = Content.fromParts(Part.fromText("What is my favorite project?"));
    runner
        .runAsync(userId, recallSession.id(), question, RunConfig.builder().build())
        .blockingForEach(
            (Event event) -> {
              if (event.finalResponse()) {
                event
                    .content()
                    .flatMap(Content::parts)
                    .ifPresent(
                        parts ->
                            parts.forEach(part -> part.text().ifPresent(System.out::println)));
              }
            });
  }
}
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}")
                }
            }
        }
    }

在工具中搜索记忆

你也可以在自定义工具中通过工具上下文来搜索记忆。

from google.adk.tools import ToolContext

async def search_past_conversations(
    query: str, tool_context: ToolContext
) -> dict:
    response = await tool_context.search_memory(query)
    return {
        "results": [
            part.text
            for entry in response.memories
            for part in (entry.content.parts or [])
            if part.text
        ]
    }
// memorySearchToolFunc is the implementation of the memory search tool.
// This function demonstrates accessing memory via agent.Context.
func memorySearchToolFunc(tctx agent.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,
))
// 在工具实现中
async runAsync({ args, toolContext }: RunAsyncToolRequest) {
  const query = args['query'] as string;
  const response = await toolContext.searchMemory(query);
  // 处理响应
  return {
    memories: response.memories.map(m => m.content.parts?.map(p => p.text).join(' ')).join('\n')
  };
}
// 在工具实现中
public Single<ToolOutput> execute(ToolContext context) {
  String query = ...; // 从参数中获取查询关键词
  return context.searchMemory(query)
      .map(response -> {
          // 处理响应
          return new ToolOutput(response.memories().toString());
      });
}
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 ?: "" }
    } ?: ""
}

记忆库

VertexAiMemoryBankService 将你的智能体连接到 Memory Bank,这是一个全托管的 Google Cloud 服务,为对话式智能体提供复杂且持久的记忆能力。

工作原理

该服务处理两个关键操作:

  • 生成记忆: 在对话结束时,你可以将会话的事件发送到 Memory Bank,它会智能地处理并将信息存储为"记忆"。
  • 检索记忆: 你的智能体代码可以向 Memory Bank 发出搜索查询,以检索过去对话中的相关记忆。

使用 add_memory 直接摄入记忆

除了从会话历史中生成记忆外,VertexAiMemoryBankService 还支持通过 add_memory 方法直接摄入记忆。此方法让你可以精确控制存储在 Memory Bank 中的事实。

其工作方式取决于 enable_consolidation 选项:

  • 直接创建(默认): 默认情况下,add_memory 调用底层的 memories.create API。你提供的每个 MemoryEntry 都会作为一个独立的记忆条目被添加。

    from google.adk.memory import VertexAiMemoryBankService
    from google.adk.memory.memory_entry import MemoryEntry
    from google.genai.types import Content, Part
    
    memory_service = VertexAiMemoryBankService(...)
    
    await memory_service.add_memory(
        app_name="my-app",
        user_id="user-123",
        memories=[
            MemoryEntry(content=Content(parts=[Part(text="The user's favorite color is blue.")]))
        ]
    )
    
  • 带整合的创建: 如果你在 custom_metadata 中将 enable_consolidation 设置为 True,服务将使用 memories.generate API。此设置允许 Memory Bank 智能地将新的记忆条目与现有的相关记忆进行整合,防止冗余并构建更连贯的知识库。

    await memory_service.add_memory(
        app_name="my-app",
        user_id="user-123",
        memories=[
            MemoryEntry(content=Content(parts=[Part(text="The user's favorite color is light blue.")]))
        ],
        custom_metadata={"enable_consolidation": True}
    )
    

先决条件

在使用此功能之前,你需要具备以下条件:

  1. Google Cloud 项目: 已启用 Agent Platform API。
  2. Agent Runtime: 你需要在 Agent Platform 上创建一个 Agent Runtime。你不需要将智能体部署到 Agent Runtime 就可以使用 Memory Bank。此设置将为你提供配置所需的 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 智能体连接到 Google Cloud 的更多信息,请参阅连接到 Google Cloud 和 Agent Platform

配置

要将你的智能体连接到 Memory Bank,请在启动 ADK 服务器(adk webadk api_server)时使用 --memory_service_uri 标志。统一资源标识符(URI)的格式必须为 agentengine://<agent_engine_id>

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

或者,你可以通过手动实例化 VertexAiMemoryBankService 并将其传递给 Runner 来配置你的智能体使用 Memory Bank。

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

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

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

RAG 记忆

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 包含两个用于检索记忆的预置工具:

  • PreloadMemory: 在每轮开始时始终检索记忆(类似于回调)。
  • LoadMemory: 仅当你的智能体认为检索记忆有帮助时才进行检索。

示例:

from google.adk.agents import Agent
from google.adk.tools import preload_memory

agent = Agent(
    model=MODEL_ID,
    name='weather_sentiment_agent',
    instruction="...",
    tools=[preload_memory]
)
import { LlmAgent, PRELOAD_MEMORY } from '@google/adk';

const agent = new LlmAgent({
    model: MODEL_ID,
    name: 'weather_sentiment_agent',
    instruction: "...",
    tools: [PRELOAD_MEMORY]
});
import (
    "google.golang.org/adk/v2/agent/llmagent"
    "google.golang.org/adk/v2/tool"
    "google.golang.org/adk/v2/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()),
        )
}

要从会话中提取记忆,你需要调用 add_session_to_memory。例如,你可以通过回调自动执行此步骤:

from google.adk.agents import Agent
from google.adk.tools import preload_memory

async def auto_save_session_to_memory_callback(callback_context):
    await callback_context.add_session_to_memory()

agent = Agent(
    model=MODEL,
    name="Generic_QA_Agent",
    instruction="回答用户的问题",
    tools=[preload_memory],
    after_agent_callback=auto_save_session_to_memory_callback,
)
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: "回答用户的问题",
    tools: [PRELOAD_MEMORY],
    afterAgentCallback: autoSaveSessionToMemoryCallback,
});
import (
    "context"
    "google.golang.org/adk/v2/agent"
    "google.golang.org/adk/v2/agent/llmagent"
    "google.golang.org/adk/v2/session"
    "google.golang.org/adk/v2/tool"
    "google.golang.org/adk/v2/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},
})
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)。此操作提取关键数据并将其持久化到长期知识存储中,例如 Agent Runtime Memory Bank。
  3. 后续查询: 在不同的会话或同一会话中,你可能提出一个需要过去上下文的问题,例如"我们上周讨论了关于项目 X 的什么内容?"。
  4. 智能体使用记忆工具: 配备了记忆检索工具的智能体(例如内置的 load_memory 工具)识别到需要过去上下文。它调用该工具,提供搜索查询(例如"上周讨论项目 X")。
  5. 执行搜索: 该工具在内部调用 memory_service.search_memory(app_name=..., user_id=..., query=...)
  6. 返回结果: MemoryService 搜索其存储,使用关键字匹配或语义搜索,并将匹配的片段作为 SearchMemoryResponse 返回,其中包含 MemoryEntry 对象列表,每个对象持有 content,以及所有可选的:authortimestampcustom_metadata
  7. 智能体使用结果: 该工具将这些结果返回给智能体,通常作为上下文或函数响应的一部分。智能体然后可以使用这些检索到的信息来制定对用户的最终回答。

智能体可以访问多个记忆服务吗?

  • 通过标准配置:不行。 框架(adk webadk api_server)设计为一次配置一个记忆服务,通过 --memory_service_uri 标志。该单一服务被连接到运行器,并通过 tool_context.search_memory()callback_context.search_memory() 暴露。
  • 在智能体代码中:可以。 你可以实例化第二个 BaseMemoryService 并从自定义工具中调用它,该工具已经拥有用于框架配置服务的 ToolContext

例如,你的智能体可以使用框架配置的 InMemoryMemoryService 来处理对话历史,并手动实例化第二个服务,如 VertexAiMemoryBankServiceVertexAiRagMemoryService(用于文档语料库)或任何其他 BaseMemoryService 实现,用于独立的知识库。

示例:使用两个记忆服务

from google.adk.agents import Agent
from google.adk.memory import InMemoryMemoryService
from google.adk.tools import ToolContext

# 用于文档查找的第二个记忆服务;可以是任何 BaseMemoryService。
docs_memory = InMemoryMemoryService()


async def search_all_memory(query: str, tool_context: ToolContext) -> dict:
    """同时搜索对话记忆和文档语料库。"""
    conversational = await tool_context.search_memory(query)
    docs = await docs_memory.search_memory(
        app_name="docs", user_id="shared", query=query
    )
    return {
        "from_conversations": [
            part.text
            for entry in conversational.memories
            for part in (entry.content.parts or [])
            if part.text
        ],
        "from_docs": [
            part.text
            for entry in docs.memories
            for part in (entry.content.parts or [])
            if part.text
        ],
    }


agent = Agent(
    model="gemini-flash-latest",
    name="multi_memory_agent",
    instruction=(
        "使用对话历史和文档知识库回答问题。使用 search_all_memory 工具。"
    ),
    tools=[search_all_memory],
)