跳转至

智能体记忆

功能概述

AgentMemory 功能是 Koog 框架的一个组件,它允许 AI 智能体在对话中存储、检索和使用信息。

目的

AgentMemory 功能通过以下方式解决 AI 智能体交互中保持上下文连贯性的挑战:

  • 存储从对话中提取的重要事实。
  • 按概念、主题和范围组织信息。
  • 在未来的交互中按需检索相关信息。
  • 基于用户偏好和历史记录实现个性化。

架构

AgentMemory 功能建立在分层结构之上。该结构的元素在以下部分列出并解释。

事实

事实 是存储在记忆中的独立信息片段。事实代表实际存储的信息。事实有两种类型:

  • SingleFact:与概念关联的单个值。例如,IDE 用户当前偏好的主题:
// Storing favorite IDE theme (single value)
val themeFact = SingleFact(
    concept = Concept(
        "ide-theme", 
        "User's preferred IDE theme", 
        factType = FactType.SINGLE),
    value = "Dark Theme",
    timestamp = Clock.System.now().toEpochMilliseconds(),
)
  • MultipleFacts:与概念关联的多个值。例如,用户掌握的所有语言:
// Storing programming languages (multiple values)
val languagesFact = MultipleFacts(
    concept = Concept(
        "programming-languages",
        "Languages the user knows",
        factType = FactType.MULTIPLE
    ),
    values = listOf("Kotlin", "Java", "Python"),
    timestamp = Clock.System.now().toEpochMilliseconds(),
)

概念

概念 是具有关联元数据的信息类别。

  • Keyword:概念的唯一标识符。
  • Description:对概念所代表内容的详细说明。
  • FactType:概念存储的是单个事实还是多个事实(FactType.SINGLEFactType.MULTIPLE)。

主题

主题 是事实可以关联的实体。

主题的常见示例包括:

  • User:个人偏好和设置
  • Environment:与应用程序环境相关的信息

有一个预定义的 MemorySubject.Everything,您可以用作所有事实的默认主题。此外,您可以通过扩展 MemorySubject 抽象类来定义自己的自定义记忆主题:

object MemorySubjects {
    /**
     * Information specific to the local machine environment
     * Examples: Installed tools, SDKs, OS configuration, available commands
     */
    @Serializable
    data object Machine : MemorySubject() {
        override val name: String = "machine"
        override val promptDescription: String =
            "Technical environment (installed tools, package managers, packages, SDKs, OS, etc.)"
        override val priorityLevel: Int = 1
    }

    /**
     * Information specific to the user
     * Examples: Conversation preferences, issue history, contact information
     */
    @Serializable
    data object User : MemorySubject() {
        override val name: String = "user"
        override val promptDescription: String =
            "User information (conversation preferences, issue history, contact details, etc.)"
        override val priorityLevel: Int = 1
    }
}

范围

记忆范围 是事实相关的上下文:

  • Agent:特定于某个智能体。
  • Feature:特定于某个功能。
  • Product:特定于某个产品。
  • CrossProduct:跨多个产品相关。

配置与初始化

该功能通过 AgentMemory 类与智能体管道集成,该类提供了保存和加载事实的方法,并且可以作为功能安装在智能体配置中。

配置

AgentMemory.Config 类是 AgentMemory 功能的配置类。

class Config(
    var memoryProvider: AgentMemoryProvider = NoMemory,
    var scopesProfile: MemoryScopesProfile = MemoryScopesProfile(),

    var agentName: String,
    var featureName: String,
    var organizationName: String,
    var productName: String
) : FeatureConfig()

安装

要在智能体中安装 AgentMemory 功能,请遵循以下代码示例提供的模式。

val agent = AIAgent(
    promptExecutor = simpleOllamaAIExecutor(),
    llmModel = OllamaModels.Meta.LLAMA_3_2,
) {
    install(AgentMemory) {
        memoryProvider = memoryProvider
        agentName = "your-agent-name"
        featureName = "your-feature-name"
        organizationName = "your-organization-name"
        productName = "your-product-name"
    }
}

示例与快速入门

基本用法

以下代码片段演示了记忆存储的基本设置,以及如何将事实保存到记忆中和从记忆中加载事实。

1) 设置记忆存储

// Create a memory provider
val memoryProvider = LocalFileMemoryProvider(
    config = LocalMemoryConfig("customer-support-memory"),
    storage = SimpleStorage(JVMFileSystemProvider.ReadWrite),
    fs = JVMFileSystemProvider.ReadWrite,
    root = Path("path/to/memory/root")
)

2) 将事实存储到记忆中

memoryProvider.save(
    fact = SingleFact(
        concept = Concept("greeting", "User's name", FactType.SINGLE),
        value = "John",
        timestamp = Clock.System.now().toEpochMilliseconds(),
    ),
    subject = MemorySubjects.User,
    scope = MemoryScope.Product("my-app"),
)

3) 检索事实

// Get the stored information
val greeting = memoryProvider.load(
    concept = Concept("greeting", "User's name", FactType.SINGLE),
    subject = MemorySubjects.User,
    scope = MemoryScope.Product("my-app")
)
if (greeting.size > 1) {
    println("Memories found: ${greeting.joinToString(", ")}")
} else {
    println("Information not found. First time here?")
}

使用记忆节点AgentMemory 功能提供了以下预定义记忆节点,可用于智能体策略:

以下是一个如何在智能体策略中实现节点的示例:

val strategy = strategy("example-agent") {
    // Node to automatically detect and save facts
    val detectFacts by nodeSaveToMemoryAutoDetectFacts<Unit>(
        subjects = listOf(MemorySubjects.User, MemorySubjects.Machine)
    )

    // Node to load specific facts
    val loadPreferences by node<Unit, Unit> {
        withMemory {
            loadFactsToAgent(
                llm = llm,
                concept = Concept("user-preference", "User's preferred programming language", FactType.SINGLE),
                subjects = listOf(MemorySubjects.User)
            )
        }
    }

    // Connect nodes in the strategy
    edge(nodeStart forwardTo detectFacts)
    edge(detectFacts forwardTo loadPreferences)
    edge(loadPreferences forwardTo nodeFinish)
}

确保记忆安全

您可以使用加密技术来确保敏感信息在记忆提供者使用的加密存储中得到保护。

// Simple encrypted storage setup
val secureStorage = EncryptedStorage(
    fs = JVMFileSystemProvider.ReadWrite,
    encryption = Aes256GCMEncryptor("your-secret-key")
)

示例:记住用户偏好

以下是一个在实际场景中如何使用 AgentMemory 来记住用户偏好的示例,具体是记住用户最喜欢的编程语言。

memoryProvider.save(
    fact = SingleFact(
        concept = Concept("preferred-language", "What programming language is preferred by the user?", FactType.SINGLE),
        value = "Kotlin",
        timestamp = Clock.System.now().toEpochMilliseconds(),
    ),
    subject = MemorySubjects.User,
    scope = MemoryScope.Product("my-app")
)

高级用法

使用记忆的自定义节点

您也可以在任意节点中使用来自 withMemory 子句的记忆。现成的 loadFactsToAgentsaveFactsFromHistory 高级抽象将事实保存到历史记录、从中加载事实,并更新 LLM 聊天:

val loadProjectInfo by node<Unit, Unit> {
    withMemory {
        loadFactsToAgent(
            llm = llm,
            concept = Concept("preferred-language", "What programming language is preferred by the user?", FactType.SINGLE)
        )
    }
}

val saveProjectInfo by node<Unit, Unit> {
    withMemory {
        saveFactsFromHistory(
            llm = llm,
            concept = Concept("preferred-language", "What programming language is preferred by the user?", FactType.SINGLE),
            subject = MemorySubjects.User,
            scope = MemoryScope.Product("my-app")
        )
    }
}

自动事实检测

您还可以要求 LLM 使用 nodeSaveToMemoryAutoDetectFacts 方法从智能体的历史记录中检测所有事实:

val saveAutoDetect by nodeSaveToMemoryAutoDetectFacts<Unit>(
    subjects = listOf(MemorySubjects.User, MemorySubjects.Machine)
)

在上面的示例中,LLM 将搜索与用户相关的事实和与项目相关的事实,确定概念,并将其保存到记忆中。

最佳实践

  1. 从简单开始

    • 首先使用无加密的基本存储
    • 先使用单一事实,再过渡到多个事实
  2. 良好组织

    • 使用清晰的概念名称
    • 添加有用的描述
    • 将相关信息保存在同一主体下
  3. 处理错误

try {
    memoryProvider.save(fact, subject, scope)
} catch (e: Exception) {
    println("Oops! Couldn't save: ${e.message}")
}

有关错误处理的更多详细信息,请参阅 错误处理和边界情况

错误处理和边界情况

AgentMemory 功能包含多种处理边界情况的机制:

  1. NoMemory 提供者:一个默认实现,当未指定记忆提供者时不存储任何内容。

  2. 主体特异性处理:加载事实时,该功能会根据定义的 priorityLevel 优先处理来自更具体主体的事实。

  3. 范围过滤:可以按范围过滤事实,以确保仅加载相关信息。

  4. 时间戳跟踪:事实存储时带有时间戳,以跟踪其创建时间。

  5. 事实类型处理:该功能支持单一事实和多个事实,并对每种类型进行适当处理。

API 文档有关 AgentMemory 功能的完整 API 参考,请参阅 agents-features-memory 模块的参考文档。

特定包的 API 文档:

FAQ 与故障排除

如何实现自定义记忆提供者?

要实现自定义记忆提供者,请创建一个实现 AgentMemoryProvider 接口的类:

class MyCustomMemoryProvider : AgentMemoryProvider {
    override suspend fun save(fact: Fact, subject: MemorySubject, scope: MemoryScope) {
        // Implementation for saving facts
    }

    override suspend fun load(concept: Concept, subject: MemorySubject, scope: MemoryScope): List<Fact> {
        // Implementation for loading facts by concept
    }

    override suspend fun loadAll(subject: MemorySubject, scope: MemoryScope): List<Fact> {
        // Implementation for loading all facts
    }

    override suspend fun loadByDescription(
        description: String,
        subject: MemorySubject,
        scope: MemoryScope
    ): List<Fact> {
        // Implementation for loading facts by description
    }
}

从多个主题加载时,事实如何确定优先级?

事实根据主题特异性确定优先级。加载事实时,如果同一概念有来自多个主题的事实,将使用最具体主题的事实。

我可以为同一概念存储多个值吗?

可以,通过使用 MultipleFacts 类型。定义概念时,将其 factType 设置为 FactType.MULTIPLE

val concept = Concept(
    keyword = "user-skills",
    description = "Programming languages the user is skilled in",
    factType = FactType.MULTIPLE
)

这允许您为该概念存储多个值,这些值将以列表形式检索。