news 2026/10/14 3:14:27

CangjieMagic 记忆机制完全指南:从 Memory 接口到 ShortMemory 语义检索实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CangjieMagic 记忆机制完全指南:从 Memory 接口到 ShortMemory 语义检索实现
  • 人工智能
  • 大模型
  • AI Agent
  • RAG
  • 工具调用
  • MCP 服务
  • MCP Clients
  • 多智能体

【免费下载链接】CangjieMagic

基于仓颉编程语言构建的 LLM Agent DSL,其主要特点包括:声明式 DSL、支持 MCP 协议,支持任务智能规划等。

项目地址:https://gitcode.com/Cangjie-TPC/CangjieMagic
点击查看免费下载

CangjieMagic 是一个基于仓颉编程语言构建的 LLM Agent 框架,其 memory 包 提供了以语义相似度为核心的短期记忆能力。本指南围绕 docs/package_docs/memory.md 定义的ShortMemory类,从接口契约、内部实现、Agent 集成到实战配置逐层展开,帮助你掌握在 CangjieMagic 中为 Agent 启用记忆、检索历史对话并定制记忆策略的完整方法。

读完本文你将能够:理解Memory接口的语义与ShortMemory的向量检索原理;通过@agent宏的memory属性或编程方式为 Agent 注入记忆;知道当前记忆的 in-memory 特性与局限,并具备扩展自定义记忆实现的思路。

从抽象接口到具体实现:记忆包的总体结构

CangjieMagic 的记忆能力分为两层:契约层与实现层。

契约层定义在 src/core/memory/memory.cj,属于magic.core.memory包。它声明了最小且稳定的记忆抽象——Memory接口,任何记忆实现都必须提供两个方法:

public interface Memory { /** * Update the memory */ func update(segment: String): Unit /** * According to the user question, find related content in the memory */ func search(question: String): Array<String> }

这两个方法恰好对应memory包文档(docs/package_docs/memory.md)中列出的全部公开 API:

方法签名语义
searchfunc search(question: String): Array<String>根据用户问题,在记忆中查找相关内容,返回匹配的文本片段数组
updatefunc update(segment: String): Unit向记忆中追加一段文本

而实现层则位于 src/memory/short_memory.cj,属于magic.memory包。ShortMemory是Memory接口的唯一内置实现,通过内存中的向量数据库完成语义化存储与检索。

public class ShortMemory <: Memory { private let vecSet = SemanticSet(vectorDB: InMemoryVectorDatabase(), indexMap: SimpleIndexMap(), embeddingModel: Config.defaultEmbeddingModel) override public func update(segment: String): Unit { vecSet.put(segment) } override public func search(question: String): Array<String> { return vecSet.search(question, number: 5) } }

从源码可以清晰看出ShortMemory的三个关键决策:

  1. 语义检索而非关键词匹配:存储与查询都依赖向量数据库与嵌入模型,按语义相似度召回,而非简单的字符串包含;
  2. 纯内存、非持久化:InMemoryVectorDatabase即“内存向量数据库”,进程退出后数据即丢失,与教程文档中“目前记忆仅支持 in-memory 非持久化数据”(见 docs/tutorial.md)的说明一致;
  3. 固定召回数量:search固定返回语义最相近的 5 条文本。

值得一提的是,ShortMemory并不依赖框架的全局配置项在运行时查找嵌入模型,而是在类初始化时直接绑定Config.defaultEmbeddingModel(见 src/config/config.cj 中defaultEmbeddingModel的声明)。这意味着在使用记忆前,必须先通过ModelManager配置好默认嵌入模型,否则ShortMemory内部的向量构建环节会因缺少模型而失败。

ShortMemory 底层原理:SemanticSet 与向量检索链路

ShortMemory只有寥寥十几行,其核心能力全部委托给了SemanticSet。理解SemanticSet及其背后的SemanticMap、InMemoryVectorDatabase、SimpleIndexMap,才能真正掌握记忆的工作机理。

SemanticSet:键值一体的语义集合

src/rag/vdb/semantic_set.cj 中对SemanticSet的定位是一句话:“SemanticSet 是一种 key 与 value 相同的特殊 SemanticMap”。它内部持有一个SemanticMap,对外暴露三个方法:

public func put(value: T): Unit { semanticMap.put(value.toString(), value) } public func search(query: String, number!: Int64 = 5, minDistance!: Float64 = 0.3): Array<T> { return semanticMap.search(query, number: number, minDistance: minDistance) }
  • put:把文本片段本身同时当作 key 和 value 写入,这正是“记忆”的本质——记忆体里存的就是原始文本;
  • search:查询时用问题文本生成向量,在集合中按相似度召回,默认召回 5 条、最小相似度阈值为0.3。

SemanticMap:向量化存储与索引的中枢

src/rag/vdb/semantic_map.cj 中SemanticMap把三个组件串联成完整链路:嵌入模型生成向量 → 向量数据库做相似度排序 → 索引映射回原始文本。

写入路径put(key, value):

public func put(key: String, value: T): Unit { let keyVec = this.vecBuilder.createEmbeddingVector(key) vectorDB.addVector(keyVec) indexMap.add(value) }

查询路径search(query, ...):

public func search(query: String, number!: Int64 = 5, minDistance!: Float64 = 0.3): Array<T> { let queryVec = this.vecBuilder.createEmbeddingVector(query) vectorDB.search(queryVec, number: number, minDistance: minDistance) |> map { sr: SearchResult => indexMap.get(sr.index) } |> collectArray }

查询时先对问题文本调用VectorBuilder.createEmbeddingVector生成查询向量,交给向量数据库排序,再把命中的向量下标通过索引映射回原始文本片段返回。其中VectorBuilder(见 src/rag/vdb/vector_builder.cj)封装了对EmbeddingModel.create(EmbeddingRequest(content))的调用。

InMemoryVectorDatabase:基于余弦相似度的排序与截断

src/rag/vdb/in_memory_vector_database.cj 是记忆的“存储引擎”,其search逻辑值得细看:

public override func search(queryVec: Vector, number!: Int64 = 5, minDistance!: Float64 = 0.6): Array<SearchResult> { if (vectorBuffer.isEmpty()) { LogUtils.info("Vector buffer is empty") return [] } let tempList = vectorBuffer.clone() tempList.sortBy(stable: true) { a: VectorWithIndex, b: VectorWithIndex => if (a.vector.cosineSimilarity(queryVec) > b.vector.cosineSimilarity(queryVec)) { return Ordering.LT } ... } ... }

关键行为有三点:

  • 相似度指标为余弦相似度(cosineSimilarity),值越大表示与查询问题语义越接近,排序后取前number个;
  • 双重截断:既限制返回条数,又设minDistance相似度下限,低于阈值的命中会直接break丢弃,避免召回无关内容;
  • 空库保护:向量库为空时直接返回空数组并输出一条 info 日志,而不是报错。

注意SemanticSet、SemanticMap与InMemoryVectorDatabase的默认minDistance分别为0.3、0.3、0.6,说明记忆检索的相似度门槛由上层决定,最终以向量库阈值为准。

SimpleIndexMap:下标到文本的映射

src/rag/vdb/simple_index_map.cj 用HashMap<String, String>维护“向量下标 → 文本内容”的映射:add(content)按当前map.size分配下标,get(index)按下标取回文本。该实现实现了序列化接口并支持save/load到 JSON 文件,但ShortMemory当前并未启用持久化——向量库的save/load在InMemoryVectorDatabase中直接抛出UnsupportedException(见 src/rag/vdb/in_memory_vector_database.cj),这也再次印证了短期记忆“非持久化”的定位。

记忆在 Agent 中的生命周期:谁来写入、何时写入

ShortMemory自身只提供update/search两个方法,真正把它接入 Agent 运行流程的是 Agent 基类。在 src/agent/base/abs_agent.cj 的chat方法中可以看到写入时机:

let resp = this.executor.run(this, processedRequest) match (resp.status) { case AgentResponseStatus.Success => // Update the agent memory if needed if (let Some(memory) <- this.memory) { memory.update(resp.execution.chatRound.toString()) } case _ => () }

写入时机有两个明确特征:

  • 仅在执行成功时写入:AgentResponseStatus.Success分支之外(包括取消、失败)不会更新记忆;
  • 写入内容为整轮对话:resp.execution.chatRound.toString()将一轮完整的问答(用户问题与助手回复)序列化为文本片段后写入记忆。

读取时机则由各执行器在构造 prompt 时调用memory.search。例如 src/agent_executor/common/prompt_builder.cj、src/agent_executor/react/react_prompt_utils.cj 等文件都引用了Memory——执行器在组装系统提示时会从记忆中检索与当前问题语义相近的历史片段,注入 prompt,让 Agent 具备“跨轮次回忆起之前聊过什么”的能力。BaseAgent的构造函数也以memory!: Option<Memory> = None的形式接受外部记忆实现(见 docs/tutorial.md 与 docs/package_docs/agent.base.md),默认不启用。

快速上手:为 Agent 开启记忆的两种方式

方式一:@agent 宏声明式开启(推荐)

CangjieMagic 的 DSL 层把记忆封装成了@agent宏的一个布尔属性。在 docs/package_docs/dsl.md 的属性表中:

属性类型说明
memoryBool是否启用短期记忆,默认关闭

用法示例(取自 docs/how-to.md 的配置清单):

@agent[ description: "A helpful assistant for weather information", model: "deepseek:deepseek-chat", temperature: 0.7, memory: true, executor: "react" ] class WeatherAgent { }

宏展开阶段,src/dsl/agent.cj 的buildAgentMemoryProp会依据memory属性生成记忆属性:

private func buildAgentMemoryProp(agentAttr: AgentAttr): Tokens { let tokens = if (agentAttr.memory) { quote(Some(ShortMemory())) } else { quote(None) } ... }

也就是说,memory: true会在生成的 Agent 类中注入一个惰性初始化的memory: Option<Memory>属性,首次访问时才构造ShortMemory()实例。这也是绝大多数场景下推荐的使用方式:一行属性即完成记忆的装配。

方式二:编程式注入自定义记忆

如果不想依赖 DSL,也可以直接用BaseAgent的构造函数注入记忆实现:

import magic.core.memory.* import magic.memory.ShortMemory let agent = BaseAgent( model: ModelManager.createChatModel("ollama:phi3"), executor: AgentExecutorManager.createExecutor("react"), memory: Some(ShortMemory()) )

BaseAgent的完整构造签名(见 docs/tutorial.md)为:

class BaseAgent <: Agent { public init( name!: String = "Base Agent", description!: String = "", temperature!: Option<Float64> = None, systemPrompt!: String = "", toolManager!: ToolManager = SimpleToolManager(), model!: Option<ChatModel> = None, executor!: Option<AgentExecutor> = None, retriever!: Option<Retriever> = None, memory!: Option<Memory> = None, interceptor!: Option<Interceptor> = None ) }

memory参数类型是Option<Memory>而非具体的ShortMemory,这意味着你可以传入任何实现了Memory接口的自定义类型——这为扩展持久化记忆、数据库记忆等留出了清晰的扩展点。

前提与限制:使用记忆前必须配置嵌入模型

由于ShortMemory在构造时直接读取Config.defaultEmbeddingModel(src/config/config.cj),且SemanticMap在缺少嵌入模型时会抛出异常(getOrThrow({ => Exception("Embedding model is not set") }),见 src/rag/vdb/semantic_map.cj),因此启用记忆前必须先完成默认嵌入模型的设置。

以 OpenA 兼容的嵌入模型为例(可通过ModelManager按需配置):

import magic.model.ModelManager // 使用 ModelManager 创建并设置为默认嵌入模型 ModelManager.defaultEmbeddingModel = ModelManager.createEmbeddingModel("ollama:nomic-embed-text")

当前框架内置了多种嵌入模型适配器,包括 ollama、dashscope、llamacpp、openai 等,可在ModelManager中按提供商前缀(如ollama:、dashscope:、openai:)创建对应实例。

需要牢记的当前版本限制:

  • 仅限内存:ShortMemory基于InMemoryVectorDatabase,进程重启后记忆全部丢失,不适用于需要长期跨会话记忆的场景;
  • 固定召回条数:search固定返回 5 条,若业务上需要更多上下文,可参考SemanticSet.search的number参数自行实现记忆类;
  • 依赖默认嵌入模型:必须先设置Config.defaultEmbeddingModel,否则运行期构造ShortMemory或首次检索时会抛出异常。

扩展思路:如何定制自己的记忆实现

如果你需要持久化记忆、更大的召回量或不同的相似度阈值,最自然的做法是实现自己的Memory子类,复用框架已有的向量组件,例如把SemanticSet换成可持久化的组合:

public class PersistentMemory <: Memory { // 可替换为 Faiss 等支持 save/load 的向量库组合 // 例如 SemanticSet(vectorDB: FaissVectorDatabase(), indexMap: JsonlIndexMap(), ...) public override func update(segment: String): Unit { // 持久化写入 } public override func search(question: String): Array<String> { // 自定义召回数量与阈值 } }

框架在 src/rag/vdb 目录下还提供了FaissVectorDatabase、JsonlIndexMap、SimpleIndexMap等多个可替换组件(见 src/rag/vdb/faiss_vector_database.cj、src/rag/vdb/jsonl_index_map.cj),它们支持save/load持久化,可作为构建长期记忆的基础设施。之后通过memory: Some(PersistentMemory())或 DSL 生成的memory属性 setter 注入即可。

总结

CangjieMagic 的记忆能力虽然抽象简洁——Memory接口仅两个方法、ShortMemory仅一个类——但背后是一条完整的语义检索链路:Embedding 模型向量化 →SemanticSet/SemanticMap组织存储 →InMemoryVectorDatabase余弦相似度排序 →SimpleIndexMap还原文本。在使用层面,@agent宏的memory: true即可一键启用,执行成功后整轮对话会被写入记忆,下一次提问时语义相近的历史片段会被检索注入 prompt。当前实现为纯内存非持久化,适合会话内的跨轮上下文增强;对于长期记忆需求,Memory接口与SemanticSet等向量组件的可组合性为你预留了清晰的定制路径。

  • 人工智能
  • 大模型
  • AI Agent
  • RAG
  • 工具调用
  • MCP 服务
  • MCP Clients
  • 多智能体

【免费下载链接】CangjieMagic

基于仓颉编程语言构建的 LLM Agent DSL,其主要特点包括:声明式 DSL、支持 MCP 协议,支持任务智能规划等。

项目地址:https://gitcode.com/Cangjie-TPC/CangjieMagic
点击查看免费下载

相关推荐

上一篇:kOps etcd 运维管理指南:etcd-manager 架构、备份恢复与 etcdctl 直接数据访问
下一篇:FoundationDB 设计食谱实战:在有序键值存储之上构建数据模型、索引与数据结构

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 3:14:24

Unity Windows原生窗口控制:无边框与状态栏适配实战

1. 项目概述&#xff1a;为什么在Unity里“动”Windows原生窗口是个刚需做Unity桌面应用的开发者&#xff0c;尤其是开发工具类、工业控制面板、数字孪生看板或者需要深度集成Windows系统行为的项目时&#xff0c;迟早会撞上这个墙&#xff1a;Unity默认的窗口管理太“温柔”了…

作者头像 李华
网站建设 2026/10/14 3:13:42

Python爬虫必备:同步与异步HTTP库全解析

在进行数据分析以及竞品调研的时候, 首先需要明确, 最基础也是最关键的一个步骤, 就是必须把数据从网络上面抓取下来。而在整个过程中里最常用的两个 HTTP 库是同步的和异步的这两个&#xff08;具体的库名称可能需要读者自行对应上下文填补空缺&#xff09;, 这两者加在一起就…

作者头像 李华
网站建设 2026/10/14 3:13:22

Coze智能体搭建实战:从零跑通工作流与Web应用

1. 这不是“又一个Coze教程”&#xff0c;而是一份能让你真正跑通第一个智能体的实操手记我带过不少刚接触Coze的朋友&#xff0c;从某高校实验室的研究生&#xff0c;到某公司转型做AI产品的运营同事&#xff0c;再到完全没写过代码的自由职业者。他们问得最多的问题从来不是“…

作者头像 李华
网站建设 2026/10/14 3:12:22

CSDN + AI:程序员的新生产力 (e 付费专栏)

1. 引言:AI 时代,程序员的生产力正在被重新定义 从代码补全到智能问答,从文档生成到代码审查,AI 正在以前所未有的速度渗透进程序员的日常工作流。作为国内最大的开发者社区,CSDN 也在积极拥抱这一趋势,将 AI 能力融入内容创作、技术学习和代码开发的全链路。本文将从多…

作者头像 李华
网站建设 2026/10/14 3:11:12

douyin-downloader 完整指南:一条命令下载抖音无水印视频

douyin-downloader 完整指南&#xff1a;一条命令下载抖音无水印视频 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallback su…

作者头像 李华
网站建设 2026/10/14 3:10:24

在任意 Linux 发行版免安装运行 LocalSend AppImage 的完整指南

在任意 Linux 发行版免安装运行 LocalSend AppImage 的完整指南 【免费下载链接】localsend An open-source cross-platform alternative to AirDrop 项目地址: https://gitcode.com/GitHub_Trending/lo/localsend LocalSend AppImage 把应用和全部运行库压进一个可执行…

作者头像 李华