1. 项目概述从“身份”这个老话题说起“身份”这个词在技术圈里都快被说烂了。从用户登录时的用户名密码到微服务架构里的服务标识再到区块链上的钱包地址我们似乎每天都在和各种各样的“身份”打交道。但最近在设计和实现一个复杂的智能体系统时我遇到了一个非常具体且棘手的问题一个智能体它到底有几个“身份”或者说当我们谈论一个智能体的“身份”时我们究竟在指什么是它在代码世界里的唯一标识符还是它在用户或其他智能体眼中的那个“形象”这个问题看似哲学实则非常工程化直接关系到系统设计的健壮性、安全性和可扩展性。经过一番折腾和思考我逐渐理清了思路并将其归纳为“智能体身份的两层模型”。这个模型不是什么高深的理论而是从实际踩坑中总结出来的一套实践框架它帮我解决了不少关于权限、通信、状态管理和用户体验的混乱。如果你也在构建涉及多个交互实体的系统无论是聊天机器人、自动化工作流节点还是游戏里的NPC理解这两层身份的区别或许能让你少走很多弯路。简单来说我认为一个智能体或任何类似的主动/被动交互实体的身份可以清晰地划分为两层内在身份和外在身份。内在身份是系统赋予的、唯一的、不可变的“根身份”是智能体在系统内部的绝对标识而外在身份则是智能体在特定交互上下文中所呈现的、可变的“面具”或“角色身份”它决定了智能体如何被感知以及如何互动。这两层身份相互独立又紧密关联共同构成了智能体完整的身份图谱。接下来我就结合具体的项目实践拆解一下这两层身份分别是什么、为什么需要区分它们以及在实际编码和架构设计中如何落地。2. 核心概念拆解内在身份 vs. 外在身份2.1 内在身份系统的“身份证号”内在身份我习惯称之为“根身份”或“系统身份”。它是智能体在诞生之初由创建它的系统或平台所赋予的一个永久性、全局唯一的标识符。你可以把它想象成一个人的身份证号或者一个数据库记录的主键。它的核心特征包括唯一性与不变性这是最根本的特性。一个智能体在其生命周期内有且只有一个内在身份ID。这个ID一旦生成就永不改变。即使智能体的名字、头像、能力全部变了这个ID依然如影随形。在分布式系统中这通常是一个UUID、雪花算法生成的ID或者是公钥的哈希值。系统内部视角这个身份主要是给“系统”看的。后台的权限校验、计费统计、日志追踪、数据归属查询全部依赖于这个内在身份ID。它是连接智能体所有行为和数据的那根“金线”。权限与资源的锚点智能体能访问哪些API、能操作哪些数据库、能消耗多少计算资源这些权限的授予对象就是这个内在身份。它是访问控制列表中最核心的主体。不可伪造性理想情况下内在身份应该与某种密码学证明绑定如私钥签名确保“声称”拥有该身份的实体确实是当初创建它的那个实体。这是实现安全通信和防伪的基础。注意在设计初期很多团队会混淆“用户名”和“内在身份”。用户名是可变的、可能重复的不适合作为系统锚点。内在身份必须是一个机器友好的、不依赖于人类可读信息的抽象标识。2.2 外在身份交互中的“角色面具”外在身份我称之为“交互身份”或“角色身份”。这是智能体在具体的交互场景中主动选择或被动赋予的一个“形象”。它更像是智能体穿上的“戏服”在不同的“舞台”对话、任务、频道上可以换上不同的戏服。它的核心特征包括场景性与可变性一个智能体可以同时拥有多个外在身份每个身份对应一个特定的交互上下文。例如一个客服智能体在用户A的对话中它可能叫“小智”语气活泼在处理内部工单时它可能叫“Ticket-Agent-007”语气专业严谨。这两个都是它的外在身份。用户/他者视角这个身份主要是给与智能体交互的其他实体用户、其他智能体看的。它决定了智能体“看起来是什么样子”、“听起来是什么语气”。这包括了名称、头像、描述、对话风格、甚至部分“人设”。关系与上下文的载体外在身份是建立具体关系的桥梁。用户记住的是“小智”这个客服而不是背后那一串UUID。聊天历史、会话状态、用户偏好这些信息通常是绑定在“用户-智能体外在身份”这个关系对上而非直接绑定在智能体的根身份上。可配置与可继承外在身份往往可以通过配置文件、数据库或管理界面进行灵活设置。一个通用的任务处理智能体可以通过加载不同的“角色配置包”瞬间化身为法律顾问、编程助手或创意写手。这两层身份的分离带来了巨大的设计灵活性。内在身份确保了系统的稳定性和可追溯性而外在身份则赋予了智能体丰富的表现力和场景适应能力。3. 为什么必须区分两层身份—— 四个核心痛点与解决方案在早期版本中我们没有做区分用一个“名字”字段走天下结果很快陷入了混乱。下面是我遇到的几个典型痛点以及引入两层模型后是如何解决的。3.1 痛点一智能体“改名”引发的数据关联断裂场景我们的智能体叫“Alpha助手”。运营同学觉得名字不够亲切想改名为“贝塔小管家”。如果只用单一身份那么所有历史对话记录、用户评分、知识库归属查询凡是用了“Alpha助手”这个名称作为键的地方全部会失效。要么数据丢失要么需要极其复杂且容易出错的全局查找替换。两层模型解决方案内在身份ID如agent_98f4b1永远不变作为所有核心数据表的外键。“Alpha助手”和“贝塔小管家”都只是这个内在身份在不同时期所使用的外在身份显示名称。我们在外在身份表中增加记录并标记当前生效的外在身份。查询历史数据时通过不变的agent_id关联完美追溯。用户界面显示当前的外在身份名称即可。3.2 痛点二同一智能体在不同场景需要不同“人设”场景我们的智能体既服务于公开的问答社区也服务于企业内部的知识库。在公开社区它需要热情、通俗、避免内部术语在企业内部它需要专业、精准、可以使用内部缩写。如果只有一个身份它的回复风格会精神分裂或者为了兼顾而变得平庸。两层模型解决方案智能体的内在身份agent_98f4b1拥有两套外在身份配置外在身份A名称“社区小助手”人格配置community_persona.yaml知识库范围public_kb。外在身份B名称“内网知识官”人格配置internal_persona.yaml知识库范围internal_kb。当请求来自社区域名时系统自动为agent_98f4b1加载外在身份A的配置进行交互。当请求来自企业内网IP时则加载外在身份B。智能体的核心能力模型调用、逻辑处理是同一套但表现层和资源访问层完全隔离。3.3 痛点三权限管控的粒度问题场景我们有一个高级数据分析智能体它本质上权限很大可以访问所有业务数据库。但我们希望销售部门的同事只能用它来查询客户相关的聚合数据而不能看到财务细节。如果权限直接绑在智能体根身份上就无法实现这种基于场景的降权。两层模型解决方案内在身份agent_data_master拥有原始高权限。我们为销售场景创建一个外在身份名称“销售数据助手”并为其关联一个经过裁剪的权限策略。这个策略可能禁止访问finance.*表并对sales.customer表的查询增加行级过滤器。销售部门的同事通过一个特定的链接或应用入口发起对话这个入口会强制将智能体绑定到“销售数据助手”这个外在身份上。此后整个会话中的权限检查都将基于这个外在身份所关联的策略而非根身份的全量权限。这样就实现了“一芯多用安全隔离”。3.4 痛点四智能体的协作与委派场景智能体A需要将一个复杂任务分解并委派给擅长子任务的智能体B和C去执行。在任务链中需要清晰地记录是谁在什么时候发出了什么指令以及最终结果归属于谁。如果只有名称在B或C的外在身份发生变化时任务日志的可读性和可追溯性会变差。两层模型解决方案任务派发时派发方智能体A不仅传递任务内容还会传递自己的内在身份ID和当前使用的外在身份信息。执行方智能体B接收任务在它的执行日志中会同时记录“任务来自根身份agent_a_uuid 当时身份项目协调员Alpha”。无论智能体A的外在身份后来如何变化我们都可以通过其不变的根身份准确无误地追溯到所有它发起过的任务。同时当时的外在身份信息为日志提供了人类可读的上下文。4. 技术实现方案与核心数据结构理论说清楚了来看看怎么落地。这里我分享一套经过简化的、与具体技术栈无关的核心数据模型和关键流程。4.1 核心数据模型设计我们需要至少三张核心表来支撑这个模型表1智能体核心表这张表存储智能体的内在身份和不变的核心元信息。CREATE TABLE agents ( id UUID PRIMARY KEY, -- 内在身份ID全局唯一永不改变 creator_id UUID, -- 创建者ID public_key TEXT, -- 可选用于身份验证的公钥 capabilities JSONB, -- 核心能力描述如可调用的函数列表 created_at TIMESTAMPTZ NOT NULL, is_active BOOLEAN DEFAULT true );表2智能体外在身份表这张表存储智能体所有的“面具”。一个智能体可以有多个活跃的外在身份用于不同场景但通常在一个场景中只有一个生效。CREATE TABLE agent_external_identities ( id UUID PRIMARY KEY, agent_id UUID NOT NULL REFERENCES agents(id) ON DELETE CASCADE, -- 关联的内在身份 name VARCHAR(255) NOT NULL, -- 显示名称如“小智” avatar_url TEXT, -- 头像 description TEXT, -- 角色描述 persona_config JSONB, -- 人格/风格配置温度、回复模板、禁忌词等 context_binding VARCHAR(1024), -- 绑定到此身份的上下文标识符如频道ID、项目ID、租户ID is_default_for_context BOOLEAN DEFAULT false, -- 在此上下文中是否为默认身份 permission_policy_id UUID, -- 关联的权限策略ID指向独立的权限表 created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ ); -- 建立联合索引方便快速查找某个智能体在特定上下文下的身份 CREATE INDEX idx_agent_context ON agent_external_identities(agent_id, context_binding);表3身份-会话映射表这张表记录每次具体的交互会话使用的是哪个外在身份。这对于审计和会话状态恢复至关重要。CREATE TABLE identity_session_mappings ( session_id VARCHAR(255) PRIMARY KEY, -- 会话唯一标识 agent_id UUID NOT NULL, -- 内在身份 external_identity_id UUID NOT NULL, -- 本次会话使用的外在身份ID started_at TIMESTAMPTZ NOT NULL, last_active_at TIMESTAMPTZ );4.2 关键交互流程解析以一个用户发起对话为例看看数据是如何流动的请求路由与身份解析 用户通过一个特定链接蕴含了上下文信息如channel_idsupport发起请求。后端网关接收到请求后首先根据链接参数或用户令牌确定目标智能体的内在身份IDagent_id和当前上下文context_binding。外在身份加载 系统查询agent_external_identities表根据(agent_id, context_binding)找到匹配的外在身份记录。如果找到多条则选择is_default_for_context为true的那条。加载该记录中的所有配置信息name,persona_config,permission_policy_id。会话创建与绑定 生成一个唯一的session_id。在identity_session_mappings表中插入一条记录将session_id、agent_id和选中的external_identity_id绑定。这个session_id将贯穿本次对话的始终。请求处理与权限校验 在处理用户消息或智能体发起的行动如查询数据库、调用API时系统不再仅仅检查agent_id的权限而是检查其当前会话所绑定的外在身份所关联的permission_policy_id对应的权限策略。这实现了权限的场景化定制。响应渲染 智能体核心逻辑生成原始响应内容后在返回给用户前会使用当前外在身份的persona_config进行“包装”。这可能包括在回复前加上“我是{name}”按照配置的语气调整措辞过滤掉不符合角色设定的内容等。日志与审计 所有日志条目不仅记录agent_id还必须记录external_identity_id和session_id。这样在查看日志时我们既能追溯到不变的根实体又能知道当时它是以什么“角色”在行动便于事后分析和问题排查。4.3 配置管理实践外在身份的核心在于其可配置性。我们通常将persona_config设计为一个结构化的JSON/YAML文件内容可能包括# persona_config 示例 response_settings: temperature: 0.7 max_tokens: 1024 prefix: 您好我是{agent_name}很高兴为您服务。\n suffix: \n\n---\n*请对我的服务做出评价。* style_guide: tone: friendly_and_professional # 友好且专业 avoid_phrases: [我不确定, 这个没办法] preferred_phrases: [我来帮您查一下, 根据我的分析] knowledge_context: restricted_kb_ids: [kb_public_faq] # 只能访问特定知识库 default_retrieval_top_k: 3 function_calling: allowed_functions: [search_knowledge_base, get_business_hours] # 即使根身份有更多能力此外在身份也只能调用这些通过一个管理后台非技术人员如运营、业务负责人也可以轻松地为同一个智能体创建和管理多个不同的“角色面具”并实时生效无需重新部署代码。5. 高级应用场景与模式两层身份模型打开了许多高级应用的大门。5.1 身份继承与组合模式我们可以设计更复杂的身份关系。例如定义一个“基础客服”外在身份模板包含通用的礼貌用语和基础流程。然后为“英语客服”和“法语客服”创建两个外在身份它们都继承自“基础客服”模板并重写了语言相关的配置。这样既保证了统一性又实现了差异化。在代码上这可以通过配置的合并算法来实现如深度合并。加载外在身份时系统先加载其显式配置再递归加载其父模板的配置子配置覆盖父配置。5.2 动态身份与临时角色外在身份不一定都是预先配置好的。我们可以支持“动态身份”的创建。例如在一个临时拉起的项目群聊中智能体被邀请加入。系统可以基于智能体的根身份结合群聊的上下文项目名称、成员列表动态生成一个临时外在身份其名称可能是“{项目名}助手”其权限可能仅限于该项目相关的文档。当群聊解散这个临时身份也随之失效并清理。这实现了身份的“按需创建随用随弃”。5.3 身份切换与上下文感知一个智能体在一次长对话中能否主动切换外在身份答案是肯定的但这需要明确的边界和协议。例如用户说“现在请用专家的身份重新分析这个问题。” 智能体在理解指令后可以查询在当前上下文中自己可用的其他外在身份列表然后切换到名为“领域专家”的身份上并使用一套更严谨、参数不同的模型配置来重新生成回答。切换后新的回复风格和知识范围都会改变。这需要在会话状态中明确记录当前生效的外在身份ID并确保切换是符合权限和业务逻辑的。6. 实施中的陷阱与最佳实践在实施这套模型的过程中我踩过不少坑也总结出一些经验。6.1 陷阱一过度设计外在身份问题一开始我们把所有能想到的属性都塞进了外在身份配置里包括模型类型、API密钥、网络代理设置……结果导致配置极其复杂且很多属性在不同身份间重复难以维护。最佳实践遵循“最小差异化”原则。外在身份只配置那些真正因场景而异的属性。对于模型端点、基础密钥等基础设施类配置应该放在智能体根身份或更高层的环境配置中。外在身份只关心“表现层”和“场景化权限”。6.2 陷阱二身份查找的性能瓶颈问题每次请求都需要查询数据库来解析身份在高并发下成为瓶颈。最佳实践缓存是必须的使用Redis等缓存(agent_id, context_binding) - external_identity的映射关系。外在身份配置的变更需要触发缓存失效。建立会话级缓存一旦在会话开始时解析出身份就将完整的身份配置对象缓存在会话状态中如内存或分布式会话存储避免同会话内重复查询。使用轻量级标识符在API调用链中传递身份信息时使用简短的session_id或identity_token而不是完整的配置对象。6.3 陷阱三日志与监控的混乱问题日志里只记录了agent_id当出现问题时我们无法快速知道是哪个“角色”出了问题。最佳实践在所有日志格式中强制加入身份字段。使用结构化的日志格式确保每条日志都包含{agent_id, external_identity_id, session_id, context}。在监控仪表盘中按外在身份进行聚合。不要只监控“智能体A”的总体错误率而要监控“智能体A-身份客服”和“智能体A-身份审核员”各自的错误率、响应延迟等指标。这样能快速定位问题场景。6.4 陷阱四忽略身份的生命周期管理问题外在身份被创建后可能永远不被清理。特别是动态创建的临时身份会变成数据垃圾。最佳实践为外在身份设计明确的生命周期状态active,inactive,archived和过期时间ttl。建立后台任务定期清理过期的、非活跃的外在身份记录及其相关数据如特定的会话缓存。提供管理接口允许手动归档或删除不再需要的身份。7. 总结与展望回过头看“智能体身份的两层模型”本质上是一种关注点分离的设计思想。它将智能体是什么内在的、不变的实体和它如何被感知与交互外在的、可变的角色清晰地剥离开来。这种分离带来的好处是实实在在的系统更稳定了根身份不变灵活性大大增强角色随意换权限管理更精细了不同角色不同权限运维和排查问题也更清晰了日志有了完整上下文。这套模型不仅适用于智能体。仔细想想在微服务架构中一个服务实例内在身份是否也可以拥有多个对外暴露的API端点或接口版本外在身份在游戏设计中一个玩家账号内在身份是否可以在不同服务器或不同赛季中使用不同的角色外在身份其核心思想是相通的。目前我们的实现还相对基础。未来的探索方向可能包括更细粒度的身份属性继承与覆盖机制、基于策略的动态身份生成、跨智能体的身份联邦与信任传递等等。身份问题就像一个洋葱剥开一层总还有下一层。但有了“两层模型”这个坚实的起点面对更复杂的场景时我们至少有了一个清晰的分析框架和设计语言不至于再陷入最初的混沌之中。
智能体身份设计:两层模型解决权限、场景与数据关联难题
1. 项目概述从“身份”这个老话题说起“身份”这个词在技术圈里都快被说烂了。从用户登录时的用户名密码到微服务架构里的服务标识再到区块链上的钱包地址我们似乎每天都在和各种各样的“身份”打交道。但最近在设计和实现一个复杂的智能体系统时我遇到了一个非常具体且棘手的问题一个智能体它到底有几个“身份”或者说当我们谈论一个智能体的“身份”时我们究竟在指什么是它在代码世界里的唯一标识符还是它在用户或其他智能体眼中的那个“形象”这个问题看似哲学实则非常工程化直接关系到系统设计的健壮性、安全性和可扩展性。经过一番折腾和思考我逐渐理清了思路并将其归纳为“智能体身份的两层模型”。这个模型不是什么高深的理论而是从实际踩坑中总结出来的一套实践框架它帮我解决了不少关于权限、通信、状态管理和用户体验的混乱。如果你也在构建涉及多个交互实体的系统无论是聊天机器人、自动化工作流节点还是游戏里的NPC理解这两层身份的区别或许能让你少走很多弯路。简单来说我认为一个智能体或任何类似的主动/被动交互实体的身份可以清晰地划分为两层内在身份和外在身份。内在身份是系统赋予的、唯一的、不可变的“根身份”是智能体在系统内部的绝对标识而外在身份则是智能体在特定交互上下文中所呈现的、可变的“面具”或“角色身份”它决定了智能体如何被感知以及如何互动。这两层身份相互独立又紧密关联共同构成了智能体完整的身份图谱。接下来我就结合具体的项目实践拆解一下这两层身份分别是什么、为什么需要区分它们以及在实际编码和架构设计中如何落地。2. 核心概念拆解内在身份 vs. 外在身份2.1 内在身份系统的“身份证号”内在身份我习惯称之为“根身份”或“系统身份”。它是智能体在诞生之初由创建它的系统或平台所赋予的一个永久性、全局唯一的标识符。你可以把它想象成一个人的身份证号或者一个数据库记录的主键。它的核心特征包括唯一性与不变性这是最根本的特性。一个智能体在其生命周期内有且只有一个内在身份ID。这个ID一旦生成就永不改变。即使智能体的名字、头像、能力全部变了这个ID依然如影随形。在分布式系统中这通常是一个UUID、雪花算法生成的ID或者是公钥的哈希值。系统内部视角这个身份主要是给“系统”看的。后台的权限校验、计费统计、日志追踪、数据归属查询全部依赖于这个内在身份ID。它是连接智能体所有行为和数据的那根“金线”。权限与资源的锚点智能体能访问哪些API、能操作哪些数据库、能消耗多少计算资源这些权限的授予对象就是这个内在身份。它是访问控制列表中最核心的主体。不可伪造性理想情况下内在身份应该与某种密码学证明绑定如私钥签名确保“声称”拥有该身份的实体确实是当初创建它的那个实体。这是实现安全通信和防伪的基础。注意在设计初期很多团队会混淆“用户名”和“内在身份”。用户名是可变的、可能重复的不适合作为系统锚点。内在身份必须是一个机器友好的、不依赖于人类可读信息的抽象标识。2.2 外在身份交互中的“角色面具”外在身份我称之为“交互身份”或“角色身份”。这是智能体在具体的交互场景中主动选择或被动赋予的一个“形象”。它更像是智能体穿上的“戏服”在不同的“舞台”对话、任务、频道上可以换上不同的戏服。它的核心特征包括场景性与可变性一个智能体可以同时拥有多个外在身份每个身份对应一个特定的交互上下文。例如一个客服智能体在用户A的对话中它可能叫“小智”语气活泼在处理内部工单时它可能叫“Ticket-Agent-007”语气专业严谨。这两个都是它的外在身份。用户/他者视角这个身份主要是给与智能体交互的其他实体用户、其他智能体看的。它决定了智能体“看起来是什么样子”、“听起来是什么语气”。这包括了名称、头像、描述、对话风格、甚至部分“人设”。关系与上下文的载体外在身份是建立具体关系的桥梁。用户记住的是“小智”这个客服而不是背后那一串UUID。聊天历史、会话状态、用户偏好这些信息通常是绑定在“用户-智能体外在身份”这个关系对上而非直接绑定在智能体的根身份上。可配置与可继承外在身份往往可以通过配置文件、数据库或管理界面进行灵活设置。一个通用的任务处理智能体可以通过加载不同的“角色配置包”瞬间化身为法律顾问、编程助手或创意写手。这两层身份的分离带来了巨大的设计灵活性。内在身份确保了系统的稳定性和可追溯性而外在身份则赋予了智能体丰富的表现力和场景适应能力。3. 为什么必须区分两层身份—— 四个核心痛点与解决方案在早期版本中我们没有做区分用一个“名字”字段走天下结果很快陷入了混乱。下面是我遇到的几个典型痛点以及引入两层模型后是如何解决的。3.1 痛点一智能体“改名”引发的数据关联断裂场景我们的智能体叫“Alpha助手”。运营同学觉得名字不够亲切想改名为“贝塔小管家”。如果只用单一身份那么所有历史对话记录、用户评分、知识库归属查询凡是用了“Alpha助手”这个名称作为键的地方全部会失效。要么数据丢失要么需要极其复杂且容易出错的全局查找替换。两层模型解决方案内在身份ID如agent_98f4b1永远不变作为所有核心数据表的外键。“Alpha助手”和“贝塔小管家”都只是这个内在身份在不同时期所使用的外在身份显示名称。我们在外在身份表中增加记录并标记当前生效的外在身份。查询历史数据时通过不变的agent_id关联完美追溯。用户界面显示当前的外在身份名称即可。3.2 痛点二同一智能体在不同场景需要不同“人设”场景我们的智能体既服务于公开的问答社区也服务于企业内部的知识库。在公开社区它需要热情、通俗、避免内部术语在企业内部它需要专业、精准、可以使用内部缩写。如果只有一个身份它的回复风格会精神分裂或者为了兼顾而变得平庸。两层模型解决方案智能体的内在身份agent_98f4b1拥有两套外在身份配置外在身份A名称“社区小助手”人格配置community_persona.yaml知识库范围public_kb。外在身份B名称“内网知识官”人格配置internal_persona.yaml知识库范围internal_kb。当请求来自社区域名时系统自动为agent_98f4b1加载外在身份A的配置进行交互。当请求来自企业内网IP时则加载外在身份B。智能体的核心能力模型调用、逻辑处理是同一套但表现层和资源访问层完全隔离。3.3 痛点三权限管控的粒度问题场景我们有一个高级数据分析智能体它本质上权限很大可以访问所有业务数据库。但我们希望销售部门的同事只能用它来查询客户相关的聚合数据而不能看到财务细节。如果权限直接绑在智能体根身份上就无法实现这种基于场景的降权。两层模型解决方案内在身份agent_data_master拥有原始高权限。我们为销售场景创建一个外在身份名称“销售数据助手”并为其关联一个经过裁剪的权限策略。这个策略可能禁止访问finance.*表并对sales.customer表的查询增加行级过滤器。销售部门的同事通过一个特定的链接或应用入口发起对话这个入口会强制将智能体绑定到“销售数据助手”这个外在身份上。此后整个会话中的权限检查都将基于这个外在身份所关联的策略而非根身份的全量权限。这样就实现了“一芯多用安全隔离”。3.4 痛点四智能体的协作与委派场景智能体A需要将一个复杂任务分解并委派给擅长子任务的智能体B和C去执行。在任务链中需要清晰地记录是谁在什么时候发出了什么指令以及最终结果归属于谁。如果只有名称在B或C的外在身份发生变化时任务日志的可读性和可追溯性会变差。两层模型解决方案任务派发时派发方智能体A不仅传递任务内容还会传递自己的内在身份ID和当前使用的外在身份信息。执行方智能体B接收任务在它的执行日志中会同时记录“任务来自根身份agent_a_uuid 当时身份项目协调员Alpha”。无论智能体A的外在身份后来如何变化我们都可以通过其不变的根身份准确无误地追溯到所有它发起过的任务。同时当时的外在身份信息为日志提供了人类可读的上下文。4. 技术实现方案与核心数据结构理论说清楚了来看看怎么落地。这里我分享一套经过简化的、与具体技术栈无关的核心数据模型和关键流程。4.1 核心数据模型设计我们需要至少三张核心表来支撑这个模型表1智能体核心表这张表存储智能体的内在身份和不变的核心元信息。CREATE TABLE agents ( id UUID PRIMARY KEY, -- 内在身份ID全局唯一永不改变 creator_id UUID, -- 创建者ID public_key TEXT, -- 可选用于身份验证的公钥 capabilities JSONB, -- 核心能力描述如可调用的函数列表 created_at TIMESTAMPTZ NOT NULL, is_active BOOLEAN DEFAULT true );表2智能体外在身份表这张表存储智能体所有的“面具”。一个智能体可以有多个活跃的外在身份用于不同场景但通常在一个场景中只有一个生效。CREATE TABLE agent_external_identities ( id UUID PRIMARY KEY, agent_id UUID NOT NULL REFERENCES agents(id) ON DELETE CASCADE, -- 关联的内在身份 name VARCHAR(255) NOT NULL, -- 显示名称如“小智” avatar_url TEXT, -- 头像 description TEXT, -- 角色描述 persona_config JSONB, -- 人格/风格配置温度、回复模板、禁忌词等 context_binding VARCHAR(1024), -- 绑定到此身份的上下文标识符如频道ID、项目ID、租户ID is_default_for_context BOOLEAN DEFAULT false, -- 在此上下文中是否为默认身份 permission_policy_id UUID, -- 关联的权限策略ID指向独立的权限表 created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ ); -- 建立联合索引方便快速查找某个智能体在特定上下文下的身份 CREATE INDEX idx_agent_context ON agent_external_identities(agent_id, context_binding);表3身份-会话映射表这张表记录每次具体的交互会话使用的是哪个外在身份。这对于审计和会话状态恢复至关重要。CREATE TABLE identity_session_mappings ( session_id VARCHAR(255) PRIMARY KEY, -- 会话唯一标识 agent_id UUID NOT NULL, -- 内在身份 external_identity_id UUID NOT NULL, -- 本次会话使用的外在身份ID started_at TIMESTAMPTZ NOT NULL, last_active_at TIMESTAMPTZ );4.2 关键交互流程解析以一个用户发起对话为例看看数据是如何流动的请求路由与身份解析 用户通过一个特定链接蕴含了上下文信息如channel_idsupport发起请求。后端网关接收到请求后首先根据链接参数或用户令牌确定目标智能体的内在身份IDagent_id和当前上下文context_binding。外在身份加载 系统查询agent_external_identities表根据(agent_id, context_binding)找到匹配的外在身份记录。如果找到多条则选择is_default_for_context为true的那条。加载该记录中的所有配置信息name,persona_config,permission_policy_id。会话创建与绑定 生成一个唯一的session_id。在identity_session_mappings表中插入一条记录将session_id、agent_id和选中的external_identity_id绑定。这个session_id将贯穿本次对话的始终。请求处理与权限校验 在处理用户消息或智能体发起的行动如查询数据库、调用API时系统不再仅仅检查agent_id的权限而是检查其当前会话所绑定的外在身份所关联的permission_policy_id对应的权限策略。这实现了权限的场景化定制。响应渲染 智能体核心逻辑生成原始响应内容后在返回给用户前会使用当前外在身份的persona_config进行“包装”。这可能包括在回复前加上“我是{name}”按照配置的语气调整措辞过滤掉不符合角色设定的内容等。日志与审计 所有日志条目不仅记录agent_id还必须记录external_identity_id和session_id。这样在查看日志时我们既能追溯到不变的根实体又能知道当时它是以什么“角色”在行动便于事后分析和问题排查。4.3 配置管理实践外在身份的核心在于其可配置性。我们通常将persona_config设计为一个结构化的JSON/YAML文件内容可能包括# persona_config 示例 response_settings: temperature: 0.7 max_tokens: 1024 prefix: 您好我是{agent_name}很高兴为您服务。\n suffix: \n\n---\n*请对我的服务做出评价。* style_guide: tone: friendly_and_professional # 友好且专业 avoid_phrases: [我不确定, 这个没办法] preferred_phrases: [我来帮您查一下, 根据我的分析] knowledge_context: restricted_kb_ids: [kb_public_faq] # 只能访问特定知识库 default_retrieval_top_k: 3 function_calling: allowed_functions: [search_knowledge_base, get_business_hours] # 即使根身份有更多能力此外在身份也只能调用这些通过一个管理后台非技术人员如运营、业务负责人也可以轻松地为同一个智能体创建和管理多个不同的“角色面具”并实时生效无需重新部署代码。5. 高级应用场景与模式两层身份模型打开了许多高级应用的大门。5.1 身份继承与组合模式我们可以设计更复杂的身份关系。例如定义一个“基础客服”外在身份模板包含通用的礼貌用语和基础流程。然后为“英语客服”和“法语客服”创建两个外在身份它们都继承自“基础客服”模板并重写了语言相关的配置。这样既保证了统一性又实现了差异化。在代码上这可以通过配置的合并算法来实现如深度合并。加载外在身份时系统先加载其显式配置再递归加载其父模板的配置子配置覆盖父配置。5.2 动态身份与临时角色外在身份不一定都是预先配置好的。我们可以支持“动态身份”的创建。例如在一个临时拉起的项目群聊中智能体被邀请加入。系统可以基于智能体的根身份结合群聊的上下文项目名称、成员列表动态生成一个临时外在身份其名称可能是“{项目名}助手”其权限可能仅限于该项目相关的文档。当群聊解散这个临时身份也随之失效并清理。这实现了身份的“按需创建随用随弃”。5.3 身份切换与上下文感知一个智能体在一次长对话中能否主动切换外在身份答案是肯定的但这需要明确的边界和协议。例如用户说“现在请用专家的身份重新分析这个问题。” 智能体在理解指令后可以查询在当前上下文中自己可用的其他外在身份列表然后切换到名为“领域专家”的身份上并使用一套更严谨、参数不同的模型配置来重新生成回答。切换后新的回复风格和知识范围都会改变。这需要在会话状态中明确记录当前生效的外在身份ID并确保切换是符合权限和业务逻辑的。6. 实施中的陷阱与最佳实践在实施这套模型的过程中我踩过不少坑也总结出一些经验。6.1 陷阱一过度设计外在身份问题一开始我们把所有能想到的属性都塞进了外在身份配置里包括模型类型、API密钥、网络代理设置……结果导致配置极其复杂且很多属性在不同身份间重复难以维护。最佳实践遵循“最小差异化”原则。外在身份只配置那些真正因场景而异的属性。对于模型端点、基础密钥等基础设施类配置应该放在智能体根身份或更高层的环境配置中。外在身份只关心“表现层”和“场景化权限”。6.2 陷阱二身份查找的性能瓶颈问题每次请求都需要查询数据库来解析身份在高并发下成为瓶颈。最佳实践缓存是必须的使用Redis等缓存(agent_id, context_binding) - external_identity的映射关系。外在身份配置的变更需要触发缓存失效。建立会话级缓存一旦在会话开始时解析出身份就将完整的身份配置对象缓存在会话状态中如内存或分布式会话存储避免同会话内重复查询。使用轻量级标识符在API调用链中传递身份信息时使用简短的session_id或identity_token而不是完整的配置对象。6.3 陷阱三日志与监控的混乱问题日志里只记录了agent_id当出现问题时我们无法快速知道是哪个“角色”出了问题。最佳实践在所有日志格式中强制加入身份字段。使用结构化的日志格式确保每条日志都包含{agent_id, external_identity_id, session_id, context}。在监控仪表盘中按外在身份进行聚合。不要只监控“智能体A”的总体错误率而要监控“智能体A-身份客服”和“智能体A-身份审核员”各自的错误率、响应延迟等指标。这样能快速定位问题场景。6.4 陷阱四忽略身份的生命周期管理问题外在身份被创建后可能永远不被清理。特别是动态创建的临时身份会变成数据垃圾。最佳实践为外在身份设计明确的生命周期状态active,inactive,archived和过期时间ttl。建立后台任务定期清理过期的、非活跃的外在身份记录及其相关数据如特定的会话缓存。提供管理接口允许手动归档或删除不再需要的身份。7. 总结与展望回过头看“智能体身份的两层模型”本质上是一种关注点分离的设计思想。它将智能体是什么内在的、不变的实体和它如何被感知与交互外在的、可变的角色清晰地剥离开来。这种分离带来的好处是实实在在的系统更稳定了根身份不变灵活性大大增强角色随意换权限管理更精细了不同角色不同权限运维和排查问题也更清晰了日志有了完整上下文。这套模型不仅适用于智能体。仔细想想在微服务架构中一个服务实例内在身份是否也可以拥有多个对外暴露的API端点或接口版本外在身份在游戏设计中一个玩家账号内在身份是否可以在不同服务器或不同赛季中使用不同的角色外在身份其核心思想是相通的。目前我们的实现还相对基础。未来的探索方向可能包括更细粒度的身份属性继承与覆盖机制、基于策略的动态身份生成、跨智能体的身份联邦与信任传递等等。身份问题就像一个洋葱剥开一层总还有下一层。但有了“两层模型”这个坚实的起点面对更复杂的场景时我们至少有了一个清晰的分析框架和设计语言不至于再陷入最初的混沌之中。