尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

PRISM框架:基于意图感知结构化记忆的长视野智能体高效检索系统

PRISM框架:基于意图感知结构化记忆的长视野智能体高效检索系统 1. 项目背景当长视野智能体需要“记住”更多时最近在折腾基于大语言模型LLM的智能体Agent项目特别是那些需要处理长序列任务、进行多轮复杂决策的场景。一个绕不开的核心挑战就是“记忆”问题。你肯定也遇到过让Agent去规划一个复杂的项目或者进行一场多轮对话它常常会“忘记”几轮之前的关键信息或者把不同任务的上下文搞混导致后续决策质量断崖式下降。这背后的根本原因是LLM本身有限的上下文窗口Context Window与长视野任务Long-Horizon Task对海量、结构化历史信息的需求之间的矛盾。传统的解决方案比如简单的向量检索Vector Retrieval把对话历史或任务记录一股脑塞进向量数据库需要时再召回。这方法听起来简单但实际用起来问题一大堆。最头疼的就是“检索噪声”——你问的是“项目A的第三步具体参数”向量检索可能给你召回一堆“项目B的第四步”或者“项目A第一步的闲聊”相关性很差。更麻烦的是智能体在执行任务时其“意图”Intent是动态演进的。一开始它可能想“写一份报告”中途会细化为“收集数据、分析趋势、撰写结论”。如果检索系统不理解这个意图链就无法提供真正对当前决策有帮助的记忆片段。这就引出了我们这次要深入探讨的核心PRISM框架。这个框架的全称是“Pareto-Efficient Retrieval over Intent-Aware Structured Memory for Long-Horizon Agents”翻译过来就是“面向长视野智能体的、基于意图感知结构化记忆的帕累托高效检索”。名字很长但拆解开来它精准地命中了上述所有痛点结构化记忆Structured Memory、意图感知Intent-Aware、帕累托高效检索Pareto-Efficient Retrieval。它不是另一个花哨的模型而是一个精巧的工程与算法框架旨在让智能体的“记忆系统”变得更聪明、更高效。简单来说PRISM想解决的是如何让一个需要长期执行复杂任务的智能体像一位经验丰富的项目经理一样不仅能记住所有历史细节还能在需要时精准地调取与当前“思考意图”最相关、且综合价值最高的那部分记忆而不是被无关信息淹没。接下来我们就一层层剥开PRISM的设计思路与实现逻辑。2. PRISM的核心设计哲学从“垃圾堆”检索到“档案馆”调阅要理解PRISM首先要摒弃“记忆就是一堆文本片段”的旧观念。在PRISM的视角里智能体的记忆应该是一个高度结构化、语义丰富、且与执行过程紧密绑定的知识图谱。我们可以用一个类比来理解传统向量检索像在一个堆满了杂乱纸张历史对话、任务日志的房间里凭感觉翻找。你可能记得关键词但找到的纸片可能不完整、过时或者根本不对题。PRISM的结构化记忆像在一个管理完善的档案馆。每份文件记忆单元都按照标准格式归档有清晰的标题动作、摘要结果、分类标签任务ID、步骤、实体并且文件之间通过引用关系因果、时序连接在一起。PRISM的设计哲学建立在三个支柱上这也是它得名的原因2.1 支柱一意图感知Intent-Aware这是PRISM的灵魂。智能体在长任务中的“意图”并非一成不变而是随着环境反馈和自身推理不断演化的。PRISM并不试图直接读懂LLM“黑盒”中的想法而是通过一种元数据标注和意图推理的方式来间接感知。具体是如何实现的记忆写入时的意图标注每当智能体执行一个动作Action并产生一个观察Observation时这个“动作-观察”对不会直接以原始文本存入。系统会要求LLM或一个轻量级模型为这个记忆单元生成一组意图标签Intent Tags。这些标签可能包括高层目标如project_planning,data_analysis。具体子任务如collect_user_requirements,generate_chart_for_sales_data。涉及的核心实体如customer_A,Q3_report.docx。动作类型如query_database,call_api,generate_text。检索时的意图查询当智能体需要进行记忆检索时PRISM不会仅仅依赖用户当前查询的原始文本。它会引导智能体或通过一个提示先对当前的意图进行自我澄清和描述生成一个结构化的“意图查询向量”。这个向量包含了当前步骤希望达成的目标、关注的实体、欲采取的动作类型等。实操心得在实际搭建时意图标签的生成质量至关重要。我们最初直接用LLM生成自由文本标签发现噪声很大。后来改为提供一个可扩展的、带描述的标签体系供LLM选择并引入少量样本进行微调Few-Shot Learning标签的一致性和准确性大幅提升。这步的投入直接决定了后续检索的精度。2.2 支柱二结构化记忆Structured Memory光有标签还不够记忆单元之间的关系是理解长任务上下文的关键。PRISM将记忆组织成一个时序-因果图Temporal-Causal Graph。每个记忆单元Memory Unit是一个结构体至少包含以下字段{ “task_id”: “T001”, “step_id”: 3, “timestamp”: “2023-10-27T10:30:00Z”, “action”: “调用数据分析API参数为{period: ‘Q3’, metric: ‘revenue’}”, “observation”: “API返回成功生成图表ID: chart_789趋势显示增长15%”, “intent_tags”: [“data_analysis”, “Q3_report”, “revenue_trend”], “entities”: [“chart_789”, “Q3”, “revenue”], “parent_step_id”: 2, “causal_links”: [“依赖于步骤2的数据准备完成”] }通过task_id,step_id,parent_step_id,causal_links这些字段记忆单元被自然地组织成一个有向无环图DAG。这带来了巨大优势追溯与推理可以轻松回答“这个结果是怎么得来的”沿因果链回溯或“这个决策影响了后续哪些步骤”沿时序链前进。范围化检索检索时可以限定在特定任务task_id或任务片段某个step_id范围内极大减少噪声。2.3 支柱三帕累托高效检索Pareto-Efficient Retrieval这是PRISM在检索阶段的创新。传统检索通常优化单一目标比如余弦相似度最高。但在智能体场景下一个好的记忆片段应该同时满足多个目标与当前意图高度相关Relevance、信息新鲜度适中Recency、与当前上下文具有因果或逻辑连贯性Coherence。PRISM将检索建模为一个多目标优化问题。它不再返回一个简单的Top-K列表而是寻找“帕累托前沿”Pareto Frontier上的记忆单元集合。所谓帕累托前沿是指在这些记忆单元中你无法在提升某一个目标如相关性的同时不损害其他目标如新鲜度。检索过程简述候选生成首先利用意图标签和实体进行快速过滤从结构化记忆中拉出一个初步的候选集。多维度打分对每个候选记忆单元计算多个分数S_rel: 意图查询向量与记忆单元意图标签的语义相似度。S_rec: 基于时间戳的新鲜度分数如指数衰减。S_coh: 基于图结构的连贯性分数如与当前对话中最近提及的记忆单元在因果图上的距离。帕累托排序使用快速帕累托排序算法如非支配排序Non-dominated Sorting将候选记忆单元分层。排名第一层的就是“帕累托最优解集”这些记忆单元在所有目标上综合表现最好。多样性选择从帕累托最优解集中再根据一定策略如聚类后从不同簇中选取选择最终返回的K个记忆单元确保信息的多样性避免返回一堆高度同质的片段。踩坑实录我们最初直接对三个分数进行加权求和结果总是顾此失彼调权重调到崩溃。改为帕累托排序后系统自动找到了那些“相关性不错、同时也比较新、且上下文连贯”的记忆决策质量稳定了很多。这背后的思想是让算法去平衡多个目标而不是让人去猜一个完美的权重。3. 系统架构与模块拆解理解了核心思想我们来看PRISM的具体系统架构。它可以分为四大模块形成一个完整的工作流。3.1 记忆编码与存储模块这个模块负责将智能体的原始交互动作/观察转化为结构化的记忆单元并存入图数据库和向量数据库。信息提取器接收原始的(action, observation, task_context)。使用LLM或预训练模型进行命名实体识别NER提取关键的实体如文件名、API名、数据字段。意图标注器这是核心组件。输入提取的实体和原始文本调用LLM根据预定义的意图标签体系为当前记忆单元打上多个意图标签。这里通常采用思维链Chain-of-Thought提示让LLM解释为什么选择这些标签以提高准确性。图关系构建器根据当前任务上下文如当前步骤ID、上一步结果自动推断并填充parent_step_id和causal_links字段。例如如果当前动作是“分析数据”而上一步是“数据清洗完成”则可以建立一条从“数据清洗”到“分析数据”的因果链接。存储图数据库如Neo4j, NebulaGraph存储完整的结构化记忆单元及其关系。用于执行复杂的图遍历查询如“找出所有导致最终失败的关键决策点”。向量数据库如Chroma, Weaviate, Pinecone将记忆单元的“核心内容”通常是actionobservation的摘要和intent_tags列表分别编码成向量并建立索引。用于快速的语义相似度检索。3.2 意图感知查询模块当智能体需要检索记忆时该模块负责生成一个富含意图信息的查询。意图自省提示系统会向智能体或一个专用的查询理解模型发送一个提示例如“你当前正在执行[任务描述]的第X步目标是[子目标]。为了帮助你做出更好决策请详细描述你此刻最关心哪些方面的历史信息包括1. 涉及的主题或目标2. 关注的特定实体3. 你打算进行的操作类型。”结构化查询生成将LLM返回的自然语言描述再次利用一个小型模型或规则转化为结构化的查询对象包含target_intents: [“budget_analysis”, “vendor_evaluation”]target_entities: [“vendor_C”, “Q4”]action_type: “compare”task_scope: “T002” (可选限定任务范围)3.3 帕累托检索引擎这是系统的算法核心接收结构化查询并返回排序后的记忆片段。召回层利用target_intents和target_entities在图数据库中进行标签匹配和实体查询快速缩小范围。同时将查询的文本描述“比较供应商C在Q4的表现”送入向量数据库进行语义召回。取两者的并集形成粗筛候选池。多目标打分层对候选池中的每一个记忆单元m_i并行计算三个分数相关性分数S_rel(m_i, q)计算查询的target_intents与m_i.intent_tags的向量相似度如平均余弦相似度。新鲜度分数S_rec(m_i)公式可为exp(-λ * (t_now - m_i.timestamp))其中λ是衰减系数。连贯性分数S_coh(m_i, C)C是当前对话中已激活的最近N个记忆单元集合。计算m_i与集合C中所有单元在图数据库中的最短路径距离的平均值的倒数。距离越近分数越高。帕累托排序层采用经典的非支配排序遗传算法NSGA-II中的快速非支配排序步骤。遍历所有候选记忆单元比较它们的三维分数向量(S_rel, S_rec, S_coh)。如果一个单元A在所有维度上都不差于B且至少在一个维度上严格优于B则称A支配B。找出所有不被任何其他单元支配的单元列为第一前沿Front 1。然后将它们暂时移除再找出剩下的单元中不被支配的列为第二前沿Front 2以此类推。第一前沿的单元就是帕累托最优集。前沿内排序与选择对于同一前沿通常是第一前沿内的单元可以采用一个简单的聚合函数如加权和此时权重影响较小进行微排序或者直接随机选择。最终选取Top-K个单元返回。3.4 记忆注入与上下文管理模块检索到的记忆单元需要以合适的方式注入到LLM的上下文窗口中。格式化成文将结构化的记忆单元转换成LLM易于理解的自然语言描述。例如“【步骤5任务T001】当时你调用了数据分析API参数Q3, revenue成功生成了图表chart_789观察到收入增长15%的趋势。这一步的意图是分析Q3收入数据。”上下文窗口优化由于上下文窗口有限需要精心选择注入哪些以及多少记忆。PRISM通常采用“最近记忆相关记忆”的策略。将当前对话中最近的几条记忆保证连贯性与帕累托检索返回的最相关记忆混合并截断最旧的、或分数最低的部分确保总长度不超过限制。提示词模板设计固定的提示词位置如将整理好的记忆放在系统提示System Prompt或用户查询User Query之前并用明确的标记如## Past Experience ##分隔指导LLM利用这些记忆。4. 实战部署从零搭建一个简化版PRISM理论说了这么多我们来动手搭建一个简化版的PRISM系统用于一个“多步骤研究助手”智能体。这个助手需要阅读多篇论文并回答综合性问题。4.1 环境准备与工具选型LLM后端使用 OpenAI GPT-4 API 或开源的 Llama 3.1通过 Ollama 本地部署。我们将用它进行意图标注、查询理解和最终答案生成。向量数据库选用ChromaDB轻量级、易集成适合原型开发。图数据库选用Neo4j社区版其Cypher查询语言非常适合表达记忆单元间的关系。对于简化版如果关系简单也可以用SQLite模拟但会失去图遍历的优势。应用框架使用LangChain或LlamaIndex来编排整个链条。这里我们用LangChain因为它对自定义记忆组件的支持更灵活。编程语言Python 3.10。安装核心依赖pip install langchain langchain-openai chromadb neo4j python-dotenv # 如果需要本地LLM pip install ollama4.2 第一步定义记忆结构并实现编码器我们首先定义Python数据类来表示记忆单元。from pydantic import BaseModel, Field from datetime import datetime from typing import List, Optional from enum import Enum class ActionType(str, Enum): SEARCH “search” READ “read” SUMMARIZE “summarize” COMPARE “compare” ANSWER “answer” class MemoryUnit(BaseModel): task_id: str step_id: int timestamp: datetime Field(default_factorydatetime.now) action: str # 自然语言描述如“搜索了关于强化学习在机器人控制中的应用” action_type: ActionType observation: str # 结果如“找到了三篇相关论文A, B, C” intent_tags: List[str] Field(default_factorylist) # 如 [“rl”, “robotics”, “survey”] entities: List[str] Field(default_factorylist) # 如 [“Paper_A”, “robot_arm”] parent_step_id: Optional[int] None causal_link: Optional[str] None # 简化只存一个父步骤的原因 class Config: use_enum_values True接下来实现MemoryEncoder它利用LLM为原始动作/观察对打上意图标签。from langchain.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI import json class MemoryEncoder: def __init__(self, llm): self.llm llm self.intent_prompt ChatPromptTemplate.from_messages([ (“system”, “你是一个智能体记忆标注系统。请根据提供的动作和观察为其生成最相关的3-5个意图标签。标签应简洁使用英文小写单词或短语反映任务领域、动作目标和核心实体。只返回一个JSON数组例如[\”tag1\”, \”tag2\”, \”tag3\”]”), (“human”, “动作{action}\n观察{observation}”) ]) def encode(self, task_id: str, step_id: int, action: str, action_type: ActionType, observation: str, parent_step_idNone) - MemoryUnit: # 1. 调用LLM生成意图标签 chain self.intent_prompt | self.llm tag_json_str chain.invoke({“action”: action, “observation”: observation}).content try: intent_tags json.loads(tag_json_str) except json.JSONDecodeError: # 如果LLM返回不规范简单按空格分割 intent_tags [t.strip().lower() for t in tag_json_str.strip(‘[]’).replace(‘”’, ‘’).split(‘,’)] # 2. 简单的实体提取这里用规则模拟实际可用NER模型 entities [] for word in action.split() observation.split(): if word.isupper() or (word[0].isupper() and len(word) 3): # 简单启发式大写或长首字母大写单词 entities.append(word) # 3. 创建记忆单元 memory_unit MemoryUnit( task_idtask_id, step_idstep_id, actionaction, action_typeaction_type, observationobservation, intent_tagsintent_tags, entitiesentities, parent_step_idparent_step_id, causal_linkf“Follows step {parent_step_id}” if parent_step_id else None ) return memory_unit # 初始化 llm ChatOpenAI(model“gpt-4-turbo-preview”, temperature0) # 或使用本地Ollama模型 encoder MemoryEncoder(llm) # 示例智能体执行了一步“搜索” memory encoder.encode( task_id“research_rl_robot”, step_id1, action“在学术数据库中搜索‘reinforcement learning robot arm control’” action_typeActionType.SEARCH, observation“找到5篇论文其中3篇高度相关Paper1 (ICRA 2023), Paper2 (JFR), Paper3 (RSS 2022).” parent_step_idNone ) print(memory.intent_tags) # 可能输出[“reinforcement_learning”, “robot_arm”, “control”, “literature_search”]4.3 第二步实现存储层图库向量库我们将记忆单元同时存入Neo4j和Chroma。from neo4j import GraphDatabase import chromadb from chromadb.config import Settings class MemoryStorage: def __init__(self, neo4j_uri, neo4j_user, neo4j_password, chroma_persist_dir“./chroma_db”): # 初始化Neo4j驱动 self.neo4j_driver GraphDatabase.driver(neo4j_uri, auth(neo4j_user, neo4j_password)) # 初始化Chroma客户端 self.chroma_client chromadb.Client(Settings(persist_directorychroma_persist_dir, chroma_db_impl“duckdbparquet”)) # 获取或创建集合Collection self.collection self.chroma_client.get_or_create_collection(name“agent_memories”) def store_memory(self, memory: MemoryUnit): # 存储到Neo4j with self.neo4j_driver.session() as session: session.execute_write(self._create_memory_node, memory) if memory.parent_step_id: session.execute_write(self._link_to_parent, memory.task_id, memory.step_id, memory.parent_step_id) # 存储到Chroma # 将核心内容动作观察和意图标签拼接成文本用于向量化 document_text f“Action: {memory.action}\nObservation: {memory.observation}\nTags: {‘, ‘.join(memory.intent_tags)}” # 生成唯一ID doc_id f“{memory.task_id}_step_{memory.step_id}” # 元数据存储所有结构化字段便于过滤 metadata { “task_id”: memory.task_id, “step_id”: str(memory.step_id), “action_type”: memory.action_type, “intent_tags”: memory.intent_tags, “entities”: memory.entities, “timestamp”: memory.timestamp.isoformat() } self.collection.add( documents[document_text], metadatas[metadata], ids[doc_id] ) def _create_memory_node(self, tx, memory): query “”” MERGE (t:Task {id: $task_id}) CREATE (m:Memory { step_id: $step_id, action: $action, action_type: $action_type, observation: $observation, timestamp: $timestamp }) CREATE (t)-[:CONTAINS]-(m) FOREACH (tag IN $intent_tags | MERGE (it:IntentTag {name: tag}) CREATE (m)-[:HAS_TAG]-(it) ) FOREACH (ent IN $entities | MERGE (e:Entity {name: ent}) CREATE (m)-[:MENTIONS]-(e) ) RETURN m “”” tx.run(query, task_idmemory.task_id, step_idmemory.step_id, actionmemory.action, action_typememory.action_type, observationmemory.observation, timestampmemory.timestamp, intent_tagsmemory.intent_tags, entitiesmemory.entities) def _link_to_parent(self, tx, task_id, step_id, parent_step_id): query “”” MATCH (parent:Memory {step_id: $parent_step_id})-[:BELONGS_TO]-(:Task {id: $task_id}) MATCH (child:Memory {step_id: $step_id})-[:BELONGS_TO]-(:Task {id: $task_id}) CREATE (child)-[:FOLLOWS]-(parent) “”” tx.run(query, task_idtask_id, step_idstep_id, parent_step_idparent_step_id) def close(self): self.neo4j_driver.close() self.chroma_client.persist()4.3 第三步构建意图感知查询与帕累托检索器这是最复杂的部分我们实现一个简化版的帕累托检索。import numpy as np from typing import Dict, List, Tuple class ParetoRetriever: def __init__(self, storage: MemoryStorage, llm): self.storage storage self.llm llm self.query_prompt ChatPromptTemplate.from_messages([ (“system”, “你正在帮助智能体理解其当前的信息需求。请根据当前任务步骤描述生成一个结构化的查询对象包含意图标签和实体。”), (“human”, “当前步骤{current_step}\n历史上下文{recent_context}\n\n请列出1. 最相关的2-4个意图标签英文短语。2. 最关注的1-3个实体名。以JSON格式返回{\”intents\”: [\”tag1\”, \”tag2\”], \”entities\”: [\”entity1\”]}”) ]) def _parse_intent_query(self, current_step: str, recent_context: str) - Dict: 生成结构化的意图查询 chain self.query_prompt | self.llm response chain.invoke({“current_step”: current_step, “recent_context”: recent_context}) try: return json.loads(response.content) except: # 降级处理 return {“intents”: [“general”], “entities”: []} def _calculate_scores(self, candidate: Dict, query: Dict, recent_memory_ids: List[str]) - Tuple[float, float, float]: 计算相关性、新鲜度、连贯性三个分数 # 1. 相关性分数 (S_rel): 基于意图标签的重叠度简化版未用向量 candidate_tags set(candidate[“metadata”].get(“intent_tags”, [])) query_tags set(query.get(“intents”, [])) if not query_tags: s_rel 0.5 # 默认值 else: overlap len(candidate_tags query_tags) s_rel overlap / len(query_tags) # 2. 新鲜度分数 (S_rec): 基于时间衰减 from datetime import datetime, timezone candidate_time datetime.fromisoformat(candidate[“metadata”][“timestamp”]).replace(tzinfotimezone.utc) now datetime.now(timezone.utc) hours_diff (now - candidate_time).total_seconds() / 3600 s_rec np.exp(-0.1 * hours_diff) # 衰减系数λ0.1 # 3. 连贯性分数 (S_coh): 简化版如果候选记忆是最近记忆的父节点或子节点则得分高 s_coh 0.0 candidate_id candidate[“id”] # 这里需要查询图数据库来获取关系为简化我们假设如果候选ID在最近记忆的ID列表中“附近”则连贯性高 # 例如步骤号接近 candidate_step int(candidate[“metadata”][“step_id”]) recent_steps [int(id.split(‘_step_’)[1]) for id in recent_memory_ids if ‘_step_’ in id] if recent_steps: min_step_diff min([abs(candidate_step - rs) for rs in recent_steps]) s_coh 1.0 / (1.0 min_step_diff) # 步骤越近分数越高0~1 return s_rel, s_rec, s_coh def _pareto_sort(self, candidates_with_scores: List[Tuple[Dict, Tuple[float, float, float]]]) - List[Dict]: 快速非支配排序简化版只取第一前沿 num_candidates len(candidates_with_scores) dominates [[False] * num_candidates for _ in range(num_candidates)] # 构建支配关系矩阵 for i in range(num_candidates): for j in range(num_candidates): if i j: continue score_i candidates_with_scores[i][1] score_j candidates_with_scores[j][1] # 如果i在所有维度上都不差于j且至少一个维度严格优于j则i支配j if all(s_i s_j for s_i, s_j in zip(score_i, score_j)) and any(s_i s_j for s_i, s_j in zip(score_i, score_j)): dominates[i][j] True # 找出非支配解第一前沿 first_frontier [] for i in range(num_candidates): dominated_by_any False for j in range(num_candidates): if dominates[j][i]: # j支配i dominated_by_any True break if not dominated_by_any: first_frontier.append(candidates_with_scores[i][0]) # 只返回记忆数据 return first_frontier def retrieve(self, task_id: str, current_step_desc: str, recent_context: str, top_k: int 5) - List[Dict]: # 1. 生成意图查询 intent_query self._parse_intent_query(current_step_desc, recent_context) print(f“生成的意图查询: {intent_query}”) # 2. 从向量库召回基于语义 # 将当前步骤描述作为查询文本 semantic_results self.storage.collection.query( query_texts[current_step_desc], n_results20, where{“task_id”: task_id} # 限定任务范围 ) candidate_memories [] for i, doc_id in enumerate(semantic_results[‘ids’][0]): candidate_memories.append({ “id”: doc_id, “document”: semantic_results[‘documents’][0][i], “metadata”: semantic_results[‘metadatas’][0][i] }) # 3. 多目标打分 recent_memory_ids [m[“id”] for m in candidate_memories[:3]] # 假设最近3个是已激活的 scored_candidates [] for mem in candidate_memories: scores self._calculate_scores(mem, intent_query, recent_memory_ids) scored_candidates.append((mem, scores)) # 4. 帕累托排序 pareto_optimal_memories self._pareto_sort(scored_candidates) # 5. 如果帕累托集大于top_k按相关性分数微排序后截断 if len(pareto_optimal_memories) top_k: pareto_optimal_memories.sort(keylambda x: self._calculate_scores(x, intent_query, recent_memory_ids)[0], reverseTrue) return pareto_optimal_memories[:top_k] else: return pareto_optimal_memories4.4 第四步集成与测试最后我们将所有模块串联起来形成一个完整的智能体工作流。class PRISMAgent: def __init__(self, llm, encoder, storage, retriever): self.llm llm self.encoder encoder self.storage storage self.retriever retriever self.current_task_id “default_task” self.step_counter 0 self.recent_memories [] # 保存最近激活的记忆ID用于连贯性计算 def execute_step(self, action_description: str, action_type: ActionType, observation: str, parent_step_idNone): 智能体执行一步并存储记忆 self.step_counter 1 memory self.encoder.encode( task_idself.current_task_id, step_idself.step_counter, actionaction_description, action_typeaction_type, observationobservation, parent_step_idparent_step_id ) self.storage.store_memory(memory) self.recent_memories.append(memory) if len(self.recent_memories) 5: # 只保留最近5条 self.recent_memories.pop(0) return memory def query_memory(self, current_step_desc: str) - str: 查询相关记忆并格式化成提示词 recent_context “\n”.join([f“Step {m.step_id}: {m.action} - {m.observation}” for m in self.recent_memories[-2:]]) retrieved self.retriever.retrieve( task_idself.current_task_id, current_step_desccurrent_step_desc, recent_contextrecent_context, top_k3 ) if not retrieved: return “No relevant past memory found.” memory_text “## Relevant Past Experience ##\n” for mem in retrieved: meta mem[“metadata”] memory_text f“- [Step {meta[‘step_id’]}, {meta[‘action_type’]}] {mem[‘document’][:200]}...\n” return memory_text # 初始化并运行一个简单示例 llm ChatOpenAI(model“gpt-4-turbo-preview”, temperature0.1) encoder MemoryEncoder(llm) storage MemoryStorage(“bolt://localhost:7687”, “neo4j”, “password”) retriever ParetoRetriever(storage, llm) agent PRISMAgent(llm, encoder, storage, retriever) # 模拟智能体执行几步研究任务 agent.execute_step( “搜索关于‘大语言模型在代码生成中的应用’的文献” ActionType.SEARCH, “找到10篇论文其中核心论文包括Codex, AlphaCode, WizardCoder。” ) agent.execute_step( “阅读并总结Codex论文的核心方法” ActionType.READ, “Codex基于GPT-3在大量代码数据上微调擅长将自然语言描述转化为代码。” , parent_step_id1) agent.execute_step( “阅读并总结AlphaCode论文的核心方法” ActionType.READ, “AlphaCode使用Transformer模型结合大规模采样和聚类过滤在竞赛编程中表现出色。” , parent_step_id1) # 现在智能体需要回答一个综合问题 current_question “请比较Codex和AlphaCode在方法上的主要异同点。” # 首先查询相关记忆 relevant_memories_prompt agent.query_memory(current_question) print(“检索到的相关记忆”) print(relevant_memories_prompt) # 将记忆注入到给LLM的最终提示中 final_prompt f“””{relevant_memories_prompt} 基于以上历史经验请回答以下问题 {current_question} “”” answer llm.invoke(final_prompt).content print(“\n智能体的回答”) print(answer)运行这个流程你会看到智能体在回答比较性问题时能够精准地召回之前阅读并总结两篇论文的记忆而不是召回无关的搜索步骤从而给出更准确、连贯的回答。这证明了PRISM框架在提升长视野智能体记忆相关性方面的有效性。5. 性能调优、常见陷阱与扩展方向搭建完基础系统后真正的挑战在于调优和应对边界情况。以下是我们在实践中积累的一些关键经验。5.1 性能瓶颈分析与优化意图标注的延迟与成本每次动作都调用LLM如GPT-4生成标签延迟和API成本可能很高。优化策略缓存对相似的动作/观察对缓存其意图标签。轻量级模型训练或微调一个小型文本分类模型如BERT来执行意图标注仅在模型置信度低时回退到LLM。异步处理记忆编码可以异步进行不阻塞智能体的主执行链路。帕累托排序的计算开销当候选记忆很多时两两比较的复杂度是O(N²)。优化策略分层筛选先使用快速、廉价的方法如关键词匹配、任务ID过滤将候选集缩小到几十个再进行帕累托排序。近似算法使用更快的多目标优化近似算法或对分数进行标量化Scalarization但采用自适应权重。图数据库查询复杂度复杂的图遍历如多跳因果查询可能变慢。优化策略索引优化为step_id,task_id,intent_tags等属性建立索引。查询简化在大多数检索场景中优先使用向量检索和标签过滤仅在需要深度推理时才触发图查询。内存缓存将高频访问的子图或热点记忆单元缓存在应用内存中。5.2 实践中常见的“坑”与避坑指南意图标签体系设计不当标签太笼统如action或太具体如read_paper_about_transformer_attention_2023_acl都会导致检索失效。避坑标签体系需要精心设计最好结合具体领域。一个实用的方法是从一批真实任务日志中用LLM聚类生成候选标签再由人工审核、归并形成一个有层次结构的标签本体Ontology。记忆污染与冗余智能体可能会重复执行相似动作产生大量高度相似的记忆单元挤占存储和检索资源。避坑在记忆编码器中加入去重模块。计算新记忆单元与近期存储单元的向量相似度如果超过阈值可以选择合并更新而非新增。例如将“再次查询天气”的结果合并到之前的“查询天气”记忆中。帕累托前沿过于庞大有时很多记忆单元互不支配导致第一前沿包含太多项失去了筛选意义。避坑引入拥挤度比较Crowding Distance。在非支配排序后计算同一前沿内各解在目标空间上的“拥挤度”优先选择拥挤度大的即位于前沿稀疏区域的解这能保证返回的记忆在多个目标上分布更均匀、更具代表性。连贯性分数计算失真我们简化版的连贯性计算基于步骤号差值很脆弱。在实际任务中步骤号可能不连续或因果关系不是简单的线性前后关系。避坑利用图数据库计算真实的最短路径距离。在Neo4j中可以通过Cypher查询快速计算两个记忆节点在因果图上的最短路径长度作为连贯性分数的依据。这虽然增加了一次数据库查询但准确度大幅提升。5.3 高级扩展方向记忆抽象与压缩对于超长任务原始记忆会爆炸式增长。可以引入定期记忆摘要Memory Summarization机制。例如每完成一个任务阶段就用LLM生成该阶段的摘要并将摘要作为一个新的、更高层次的记忆单元存入同时将原始细节记忆归档或降低其检索优先级。主动遗忘与记忆重要性加权并非所有记忆都同等重要。可以引入重要性评分Importance Score基于记忆被成功检索并使用的频率、其导致的任务成功/失败结果等因素动态调整。低重要性的记忆可以被逐渐“遗忘”移至冷存储或删除。多模态记忆智能体不仅处理文本还可能处理图像、音频、结构化数据。PRISM框架可以扩展为支持多模态记忆单元例如将图像特征也编码成向量与文本意图标签一起参与多目标检索。分布式记忆与联邦检索在多个智能体协作的场景中可以设计联邦记忆系统。每个智能体拥有本地PRISM记忆同时可以安全地检索其他智能体的相关记忆在隐私和权限控制下实现经验共享和集体学习。PRISM框架为我们构建强大、可靠的长视野智能体提供了一个坚实的内存系统蓝图。它告诉我们智能体的“记忆”不应该是一个事后添加的附件而应该是一个与其核心决策过程深度集成、精心设计的子系统。从意图感知的结构化存储到多目标优化的帕累托检索每一步都蕴含着对智能体认知过程的深刻思考。虽然完整实现PRISM需要不少的工程努力但即使只采纳其核心思想——用结构化和意图来组织记忆用多目标权衡来指导检索——也足以让你现有的智能体项目摆脱“金鱼脑”的困境真正具备长期规划和持续学习的能力。
返回列表