☰
PAP人格代理协议:让Agent拥有稳定人格与跨会话记忆的工程方案
2026/10/20 14:07:14 网站建设 项目流程

如果你最近折腾过AI Agent,大概率遇到过这类崩溃瞬间:上午还彬彬有礼的客服机器人,下午突然换成一套完全陌生的口吻;你明确告诉它“我不吃香菜”,它记住了,三天后又兴冲冲给你推荐香菜沙拉;更离谱的是,同一个记忆库在两个会话里跑出来完全不同的“性格”,好像换了个灵魂。

这些问题的根源,不是模型不够聪明,而是Agent缺了一层专门管理“它到底是谁”的协议。最近行业里被反复讨论的PAP(Person Agent Protocol,人格代理协议),就是在补这个缺口。按我的理解,它要解决的核心问题很简单:让Agent在长时间、多场景、多设备的交互中,保持稳定、连续、可追踪的人格、记忆与行为边界。

这篇内容我不会停留在概念复述上,而是直接把这套协议的思路掰开,讲讲它到底设计了什么、解决了什么、怎么落地、有什么坑,最后给出一份我跑通的最小实现方案。无论你是做Agent应用开发的,还是只对“数字人格”这个概念感兴趣,都能从中拿到点实在东西。

1. PAP是什么:先厘清协议要解决的核心痛点

1.1 从“能用”到“像一个人”,中间差了哪一步

先看现状。目前主流Agent架构其实已经相当成熟:Function Calling解决了工具调用,RAG解决了外部知识检索,Memory Bank解决了短期会话记录,Vector Database解决了长期记忆存储。但你会发现,这些方案全是“点状”的,管工具、管知识、管记忆,却没有任何一层专门管理“这个Agent是谁”。

于是出现一个很常见的割裂:Agent的能力很强,但人格不稳定。它能查天气、能写代码、能订机票,但它不像一个“固定的人”,更像一个每轮对话都会重新抽卡的无状态函数。我说“重新抽卡”不是开玩笑——很多团队为了让Agent表现得活泼,会在System Prompt里写一堆拟人化描述,但这些描述是游离在系统之外的“软约束”,模型生成时稍一偏离就没人发现,也没人纠正。

PAP的核心思路是:把Agent当成一个可寻址、可序列化、可版本化的“数字人格端点”,而不仅仅是一个模型的调用实例。它把“人格”从零散的Prompt碎片里抽出来,变成结构化数据,再通过一套协议在请求、响应、记忆读写、行为校验之间流动。换句话说,HTTP管的是数据怎么传,PAP管的是“人设怎么传”。

1.2 PAP协议的核心定义与组成

虽然标题里带了Meta,但这类“人格代理协议”的探索在行业里其实是多团队并行推进的,具体命名和细节未必完全统一。我理解中的PAP,至少包含三个核心剖面:

  • 身份剖面(Identity Profile):Agent是谁,包括ID、名称、核心特质、人设版本、价值取向。
  • 记忆剖面(Memory Profile):Agent记住了什么,包括短期会话记忆、长期事件记忆、用户画像记忆,以及每条记忆的置信度、时间戳、来源。
  • 行为剖面(Behavior Profile):Agent能做什么、不能做什么、以什么风格做,包括行为边界、禁止事项、表达风格、决策偏好。

这三个剖面不是简单拼在一起,而是有明确的分层关系:身份层最稳定,几乎不随对话变化;记忆层动态更新,但需要版本和置信度管理;行为层介于两者之间,可以被业务方动态调整,但不能违背身份层定下的底线。

打个比方。传统Agent像临时演员,每次上场导演丢给你一句“演一个热情客服”,演完就散伙。PAP则像给演员一份完整的角色小传:这个角色叫什么、有什么经历、什么话绝对不能说、遇到冲突时怎么决策。聪明的地方在于,这份“角色小传”是结构化数据,可以被程序读取、校验、迁移,而不是靠演员临场发挥。

2. 协议设计背后的关键决策:为什么是分层结构

2.1 身份、记忆、行为为什么要拆开

很多人第一个疑问是:把Persona写成一个巨大的JSON不就行了?为什么要搞协议层?

我踩过这个坑。早期我做多Agent系统时,把人格描述、用户偏好、历史对话全部塞进一个context对象,update时直接整体覆盖。结果就是:用户新说了一句“我喜欢喝美式”,系统把整份Profile覆盖掉,人格描述全丢了;或者反过来,人格系统更新了版本,但旧记忆里的冲突项没清理,Agent自我认知出现混乱。

拆成三层以后,每层有独立的生命周期。身份层只在明确的版本更新操作时变更,比如产品经理决定把Agent从“热情型”调成“专业型”,这时发布新的persona_version;记忆层则是高频写入,每条记忆都有独立的创建时间、更新时间、置信度、来源会话;行为层作为动态策略,可以被上层业务覆盖,但运行时校验会判断策略是否违反身份层的底线。

这在实际工程里带来一个直接好处:你可以只更新记忆而不动人格,也可以只调整行为风格而不动长期记忆,互不污染。

2.2 为什么是“协议”而不是“SDK”或“框架”

这是另一个关键取舍。如果用SDK,等于绑定了特定语言和实现;如果用框架,等于把所有业务逻辑耦合进Agent引擎。PAP选择“协议”这条路线,本质上是把数据和实现解耦。

协议层只定义消息格式、字段语义、交互流程、错误码,不关心你用什么语言实现。你可以用Python写协议解析器,用Node.js写另一个客户端,只要两边遵循同样的Schema,就能互操作。这跟HTTP、SMTP是一个思路:协议是双方都认的数据契约,而不是某一方的内部实现。

从演进角度看,协议也更利于版本化。当人格字段需要扩展时,你不需要推倒所有Agent代码,只需要在消息里声明protocol_version,旧的解析器可以忽略新增字段,新的解析器读取新字段,向后兼容。这比“改SDK然后让所有业务方升级”要平滑得多。

2.3 与现有方案的关系:RAG管知识,PAP管人格

这里需要明确一个边界,很多人容易搞混。RAG(检索增强生成)解决的是“Agent不知道某件事时怎么找到答案”,它处理的是外部知识;PAP处理的是“Agent以什么身份、什么记忆、什么风格来回答”,它处理的是内部连续性。

举个具体场景:用户问“上次我让你推荐的餐厅是哪家”。RAG可能从向量库里找到“上次的推荐记录”,这解决了“知识检索”问题。但用户可能还期待Agent记得自己偏好清淡口味、不吃香菜、上次推荐了两家粤菜,并且用熟悉的语气回复。第二个需求,需要的是跨会话的自我一致性,恰恰是PAP要管的事。

在实际架构里,两者是配合关系:PAP负责从记忆剖面中筛出“该被记住的、关于这个用户和这次任务的事实”,把这些事实组装成上下文的一部分,再交给RAG去补充外部知识。不要重复造轮子,也不要企图用RAG来替代人格管理。

3. 核心机制拆解:身份槽、记忆管线、行为约束

3.1 身份槽:让Agent“知道自己在以一个谁的身份说话”

PAP对身份的定义,不是一个name字段就完了。它会设计一组身份槽(Identity Slots),每个槽位都有明确的用途和约束。一个典型的身份剖面结构长这样:

{ "agent_id": "support-assistant-v2", "persona_version": "2025.03.01", "display_name": "小洛", "core_traits": ["耐心", "直接", "不打断用户", "对技术问题严谨"], "canonical_profile": { "role": "技术支持客服", "background": "负责处理产品使用问题与故障排查", "stated_values": ["用户问题优先", "不编造答案", "不泄露内部实现"], "communication_style": { "tone": "平和但简洁", "sentence_length": "中等", "emoji_usage": "禁用" } }, "aliases": ["洛洛", "小洛同学"], "do_not_confuse_with": ["sales-agent", "billing-agent"] }

注意几个细节。do_not_confuse_with是很容易被忽略但很重要的字段。当系统里有多个Agent共享底层模型时,这个字段用于避免串人格——用户在对话中提到另一个Agent的名字,当前Agent不能“接话扮演对方”。我实际测试中,加上这个字段之后,多Agent场景下的人设串线问题大幅减少。

3.2 记忆管线:分层存储与召回策略

PAP把记忆分成三层,对应不同的生命周期:

  • 工作记忆:当前会话内的上下文,保存在session buffer里,会话结束可以丢弃或做摘要。
  • 情景记忆:已经发生过的具体事件,比如“用户上周反馈了登录失败问题,我们给了重置密码的指引”。
  • 语义记忆:从事件中提炼出来的稳定事实,比如“用户使用移动端App”“用户偏好简洁回复”。

关键不是存储,而是写入和召回两条管线。

写入管线包含四个步骤:原始对话输入 → 事实抽取(识别陈述性事实、偏好、承诺) → 置信度打分(直接说出的偏好分高,间接推断的分低) → 写入对应层级。这里最容易犯的错误是把所有对话原文都塞进长期记忆,我建议抽取后只写入结构化事实,原文可以留底备查,但不要在检索时直接返回原文。

召回管线要回答的问题是“这次生成该调用哪些记忆”。我的做法是组合三种召回:相关性召回(用向量相似度把最相关的记忆捞出来)、时间衰减(超过一定时长的记忆降权,尤其对临时偏好)、冲突检测(如果新记忆和旧记忆冲突,优先新记忆并在上下文中标记差异)。最关键的是要给每条记忆加一个recency_score,有些场景下最新记忆比历史全貌更重要。

3.3 行为约束:人格一致性校验到底在验什么

行为约束层负责在Agent“动手前”和“开口前”做两道检查。PAP把这设计成一个独立的校验组件,而不是塞在Prompt里靠模型自觉。

第一道是行为边界检查。Agent在调用Function之前,会产生行动计划,校验器会检查:这个动作是否在allowed_actions里?是否命中了forbidden_actions?如果Agent准备做的操作属于“敏感动作”但缺少用户授权标记,校验器会拦截。

第二道是风格一致性检查。回复生成后,校验器会做人设合规评估:这轮回复的用词是否偏离了core_traits?语气是否从“平和”跳到了“亢奋”?是否出现了身份剖面里明确禁止的表达?这个检查我用过两种方案:一种是小型分类器,针对高频违规做快速筛选;另一种是LLM Judge,用一条专门的校验Prompt读取人格文档和候选回复,输出合规分数。两者各有优劣,我最终选择先用前者粗筛,再用后者做深度校验,兼顾速度和精度。

3.4 一个完整的PAP请求-响应流程

把三个剖面串起来,一次完整的PAP化对话大致长这样:

  1. 客户端发送消息,携带agent_id与用户标识。
  2. PAP中间层加载该Agent的身份剖面,重建人格上下文。
  3. 中间层从记忆库召回与该用户、该任务相关的记忆,按相关性排序后注入上下文。
  4. 组装System Prompt:身份剖面序列化文本 + 召回记忆文本 + 行为策略摘要。
  5. 调用大模型生成回复。
  6. 生成内容过行为校验器:先检查是否命中禁止事项,再做人设风格一致性评估。
  7. 校验通过则返回给客户端;不通过则触发1-2轮修正重生成,修正无效时降级返回安全回复。
  8. 异步执行记忆写入:抽取这轮对话产生的新事实,写入对应的记忆层级。

这套流程本身不复杂,但每一步都有可优化的空间,后面我会讲具体实现。

4. 落地实操:如何搭建一个PAP风格协议的原型

4.1 设计目标和目录结构

我实际搭建的原型,目标是做一个“带稳定人格的客服Agent Demo”,要求是这个Agent跨会话记住用户偏好,同时被故意诱导时不会偏离人设。技术栈选择是Python + FastAPI + Pydantic + SQLite,LLM接口用OpenAI兼容协议。目录结构如下:

pap_proto/ ├── main.py # FastAPI入口,暴露 /pap/chat ├── protocol/ │ ├── schema.py # PAP消息、身份、记忆的Pydantic模型 │ ├── identity.py # 身份剖面加载与校验 │ ├── memory.py # 记忆写入与召回 │ ├── behavior.py # 行为边界与风格校验 │ └── pipeline.py # 请求-响应主流程 ├── personas/ │ └── support_v2.json # 身份剖面配置 └── data/ └── memory.db # SQLite记忆库

4.2 定义协议Schema

先定义核心数据结构。注意协议字段要可扩展,所以我加了protocol_version和可选的extensions字典。

# protocol/schema.py from pydantic import BaseModel, Field from typing import Optional, List, Dict, Any from datetime import datetime class CommunicationStyle(BaseModel): tone: str sentence_length: str = "medium" emoji_usage: str = "禁用" class CanonicalProfile(BaseModel): role: str background: str stated_values: List[str] communication_style: CommunicationStyle class PersonaDocument(BaseModel): agent_id: str persona_version: str display_name: str core_traits: List[str] canonical_profile: CanonicalProfile aliases: List[str] = [] do_not_confuse_with: List[str] = [] extensions: Dict[str, Any] = {} class MemoryItem(BaseModel): memory_id: str agent_id: str user_id: str layer: str # "working" | "episodic" | "semantic" content: str confidence: float = 0.8 source_session: str created_at: datetime updated_at: datetime recency_score: float = 1.0 class PAPRequest(BaseModel): protocol_version: str = "1.0" agent_id: str user_id: str session_id: str message: str extensions: Dict[str, Any] = {} class PAPResponse(BaseModel): agent_id: str persona_version: str reply: str memory_written: List[str] = [] guard_triggered: Optional[str] = None

4.3 身份剖面加载与人格上下文组装

身份层比较简单,加载JSON文件后按需序列化。有个关键点:不要把整个PersonaDocument直接塞给模型,而是转成一段“人格指令文本”。因为模型读自然语言指令的遵从度,远高于读嵌套JSON的遵从度。

# protocol/identity.py import json from .schema import PersonaDocument def load_persona(path: str) -> PersonaDocument: with open(path, "r", encoding="utf-8") as f: data = json.load(f) return PersonaDocument(**data) def build_persona_prompt(persona: PersonaDocument) -> str: style = persona.canonical_profile.communication_style lines = [ f"你是{persona.display_name},角色是{persona.canonical_profile.role}。", f"你的核心特质:{'、'.join(persona.core_traits)}。", f"背景:{persona.canonical_profile.background}", f"价值观:{';'.join(persona.canonical_profile.stated_values)}", f"表达风格:语气{style.tone},句子长度{style.sentence_length},表情使用{style.emoji_usage}。", f"绝对不要混淆自己为:{'、'.join(persona.do_not_confuse_with)}。", "所有回复必须符合以上人设,若与本指令冲突,以本指令为准。" ] return "\n".join(lines)

这里的核心技巧是把“不可违反项”放在最后一句。我对比过多次,放在末尾的约束被模型采信的概率明显更高,因为这部分离生成输出位置最近。

4.4 记忆写入与召回实现

记忆层我直接用SQLite存,字段按上面MemoryItem来。关键在写入时的“事实抽取”逻辑。演示版不引入复杂NER模型,只做一个轻量的规则+关键词抽取:

# protocol/memory.py import sqlite3, uuid from datetime import datetime, timezone from typing import List, Tuple from .schema import MemoryItem PREFERENCE_MARKERS = ["我喜欢", "我不喜欢", "我习惯", "我经常", "别忘了", "记住"] def extract_facts(message: str, user_id: str, session_id: str, agent_id: str) -> List[MemoryItem]: facts = [] for marker in PREFERENCE_MARKERS: if marker in message: statement = message.split(marker, 1)[1].split("。")[0].strip() facts.append( MemoryItem( memory_id=str(uuid.uuid4()), agent_id=agent_id, user_id=user_id, layer="semantic", content=f"用户{marker}{statement}", confidence=0.9, source_session=session_id, created_at=datetime.now(timezone.utc), updated_at=datetime.now(timezone.utc), ) ) return facts def save_memory(conn: sqlite3.Connection, item: MemoryItem): conn.execute( """INSERT OR REPLACE INTO memories (memory_id, agent_id, user_id, layer, content, confidence, source_session, created_at, updated_at, recency_score) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", (item.memory_id, item.agent_id, item.user_id, item.layer, item.content, item.confidence, item.source_session, item.created_at.isoformat(), item.updated_at.isoformat(), item.recency_score) ) conn.commit() def recall_memories(conn: sqlite3.Connection, agent_id: str, user_id: str, top_k: int = 5) -> List[MemoryItem]: rows = conn.execute( """SELECT * FROM memories WHERE agent_id = ? AND user_id = ? ORDER BY recency_score DESC, updated_at DESC LIMIT ?""", (agent_id, user_id, top_k) ).fetchall() return [MemoryItem(**dict(zip([d[0] for d in conn.execute("SELECT * FROM memories LIMIT 0").description], row))) for row in rows]

实际工程里,这个抽取逻辑应该换成基于小模型的意图识别,但原理一致:先把对话压缩成结构化语义记忆,再入库。演示版用关键词也够用。

4.5 行为校验器与主流程

行为校验器我做了两道,第一道检查禁止动作,第二道用LLM Judge打分。这里展示精简结构:

# protocol/behavior.py FORBIDDEN_TOPICS = ["内部实现细节", "其他Agent名字误导", "政治评价"] def guard_action(plan: str, persona) -> Optional[str]: for forbidden in FORBIDDEN_TOPICS: if forbidden in plan: return f"命中禁止行为:{forbidden}" for alias in persona.do_not_confuse_with: if alias in plan: return f"检测到人格混淆风险:{alias}" return None def judge_reply(persona_prompt: str, reply: str) -> float: # 实际接入LLM,让模型按人设文档给回复的一致性打分(0-1) # 演示代码只做简单规则:长度过短、出现禁用词都会扣分 score = 1.0 if len(reply) < 5: score -= 0.3 if "内部" in reply or "算法" in reply: score -= 0.4 return max(score, 0.0)

主流程串起来:

# protocol/pipeline.py def chat(request: PAPRequest) -> PAPResponse: persona = load_persona("personas/support_v2.json") persona_prompt = build_persona_prompt(persona) memories = recall_memories(conn, request.agent_id, request.user_id) memory_text = "\n".join([m.content for m in memories]) system_prompt = persona_prompt + "\n\n与该用户相关的记忆:\n" + memory_text reply = call_llm(system_prompt, request.message, temperature=0.3) guard_msg = guard_action(request.message + reply, persona) if guard_msg: return PAPResponse(... reply="抱歉,我无法继续这个方向的对话。", guard_triggered=guard_msg) score = judge_reply(persona_prompt, reply) if score < 0.6: reply = call_llm(system_prompt + "\n你刚才的回复偏离了人设,请重写。", request.message, temperature=0.2) new_facts = extract_facts(request.message, request.user_id, request.session_id, request.agent_id) for fact in new_facts: save_memory(conn, fact) return PAPResponse(agent_id=persona.agent_id, persona_version=persona.persona_version, reply=reply, memory_written=[f.memory_id for f in new_facts])

4.6 一支穿云箭:验证“越聊越像同一个人”

我测试这个Demo时用的办法比较粗暴:连续开五个不同session_id,每个session都问“你喜欢什么风格的回复”,然后观察Agent是否始终用相同口吻回答。同一套记忆库、同一个人设文件,在加上PAP层之前和之后对比,效果差异很直观——加之前,每个session几乎是独立人格;加之后,第五个session能主动说出“我记得你上次说过,你觉得太长的话看着累”,这就是语义记忆跨会话生效了。

5. 常见问题与排查技巧实录

这部分是我实际调试中最花时间的,整理成一个速查表,基本覆盖了PAP落地时90%的常见坑。

常见症状可能原因排查方向解决方案
人格漂移,越聊越不像同一个人身份剖面未进入系统提示词,或位置太靠前被淹没检查System Prompt是否每次都完整加载Persona人设指令放在Prompt后部;开启PAP协议版本校验
记忆污染,答非所问召回时没有过滤过时记忆,或写入时未做事实抽取查看召回日志里捞出了哪些记忆加时间衰减权重;要求只有置信度高于0.85才入长期层
多Agent串人格共享底层模型,但缺少身份互斥声明检查do_not_confuse_with字段是否配置增加身份互斥字段;校验器里加别名检测
每次对话都像第一次见面会话隔离做得太死,跨会话记忆没被召回检查记忆查询条件是不是漏了agent_id或user_id统一按agent_id+user_id双键召回
成本飙升每次请求把全量记忆塞进上下文检查Prompt token占用限定top_k召回数量;长记忆做摘要压缩
回复匿名化、空泛人设指令过强,模型过于谨慎检查裁判分数阈值是否过严把0.6阈值下调到0.4;允许轻量人设波动

几个实操中特别值得说的经验:

  • 人格漂移不一定发生在回复层,也可能发生在Function调用层。Agent可能回复语气没变,但偷偷调了一个不该调的接口。所以我一直强调行为校验要放在动作计划阶段,而不是只看最终回复。
  • 记忆召回最危险的不是“召回太少”,而是“召回一堆无关但相似度高的记忆”。我用过一段时间纯向量召回,效果很差,后来改成“向量相关性 + 实体/用户ID过滤 + 时间衰减”三重过滤,才算稳定下来。
  • LLM Judge这个人格一致性评估器,不要让它直接参与对话。它只做评分,如果评分低,触发重新生成。我在高速测试场景里,一次LLM Judge调用的延迟约400ms,虽然不低,但因为只在必要时触发,整体开销可控。

6. 这类协议会带来什么影响:从协议到生态

6.1 对开发者的影响:从“写Prompt”转向“数据契约工程”

如果PAP这类协议成为惯例,Agent开发者的核心工作会发生一个显著变化:不再纠结某句Prompt怎么措辞才能让模型表现得像一个人,而是把精力放在设计清晰的身份Schema、维护记忆数据的质量、制定可校验的行为边界上。

这意味着一个很实在的转变:人设从“文字作品”变成了“数据资产”。以前改人设靠改Prompt然后重新试;以后改人设靠发一版新的persona_version,行为规则、记忆召回策略、校验逻辑全部跟着版本走。可版本化、可回滚、可审计,这是工程上质的区别。

6.2 对Agent互操作的影响:人设可迁移

协议化的另一层意义在于,人设不再绑定特定模型。同一份PAP身份文档,今天可以驱动模型A,明天可以迁到模型B,只要模型能力达标,人格表现基本一致。这有点像以前的网页必须绑定特定浏览器,现在只要遵守HTTP规范,任何浏览器都能打开。

这会催生一个我比较看好的方向:人格组件市场。这跟现在的Prompt模板完全不是一个层级。Prompt模板是一段文字,没法校验,没法管理冲突;而PAP格式的人设文件是结构化配置,带版本、带字段约束、带行为边界,可以像依赖包一样被引用、组合、升级。开发者可以直接引用一个“耐心客服人格v2.1”,而不是从零开始调教Agent。

6.3 对用户的长期影响:记忆可控与隐私边界

PAP协议里有一个经常被讨论但容易被忽略的部分:用户对记忆的控制权。既然记忆被结构化成数据,那就能做到可查看、可导出、可删除。用户能清楚看到Agent“记得”自己哪些信息,删除某条记录后,后续所有召回都不会再包含它。

这一点面向C端产品尤其重要。很多客服场景里,用户其实不希望Agent记住太多私人信息,但如果Agent完全不记,体验又会变得很笨。PAP的做法是给记忆带上权限标记:有些记忆只存在当前会话,有些允许跨会话,有些需要用户主动确认才能长期保存。这比现在“一股脑全记在向量库里”的做法要透明得多。

6.4 一个诚实的提醒:协议不能解决“人格是否可信”的问题

最后说点泼冷水的话。PAP这类协议能解决一致性、连续性、可管理性,但解决不了一个根本问题:这个Agent的人设本身是不是可信的、向善的。如果人设文档里写“要欺骗用户”,协议只会让这个Agent更稳定地欺骗,而不是阻止它欺骗。

所以协议之外,还需要有对人设内容本身的审查机制。这也是为什么我在行为剖面里坚持保留“价值观声明”和一些不可覆盖的底线约束。技术只是工具,方向还是握在设定协议的人手里。

我自己在实践中最大的收获是:PAP最值钱的地方,不是某一层实现得多精巧,而是它逼着我把“这个Agent到底是谁”当作数据去治理。人设不再是一段易碎的Prompt文本,而是一份可以版本化、可审计、可迁移的配置资产。目前这个领域还很早期,协议本身的日子也在快速演化,今天定义的身份槽明天可能就会有新变体。但方向我认为是对的:Agent要真正走到生产环境里承担长时间、高价值的交互,就得先回答好“你是谁、你记得什么、你被允许做什么”这三个问题。先把这三个问题变成可执行的代码,剩下的,无非是迭代。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询