Yuxi 共享智能体资源选择的保存边界:多租户权限下的配置合并、保留与并发安全机制
Yuxi 共享智能体资源选择的保存边界多租户权限下的配置合并、保留与并发安全机制【免费下载链接】Yuxi可私有部署的多租户知识智能体平台统一 RAG、知识图谱、多智能体、MCP/Skills、沙盒与权限管理。Self-hosted knowledge agent platform for RAG, knowledge graphs and multi-agent workflows.项目地址: https://gitcode.com/GitHub_Trending/yu/Yuxi本指南基于 Yuxi 项目的一则已落地实现决策bug-fix 类型Owner 为AgentRepository深入讲解共享智能体在委托管理员delegated manager只拥有部分资源访问权时保存配置如何做到「不丢失创建者的完整期望选择、不越权新增引用、并发下不丢失最新隐藏引用」同时保持运行时只取交集的安全语义。读完本文你将掌握config_json.context字段补丁合并算法、PostgreSQL 行锁保护、null/空列表/非空列表三种资源策略语义以及前端「只提交变化字段」的配合实现可直接迁移到同类多租户智能体平台的设计中。背景问题委托管理员保存配置为什么可能破坏共享智能体在 Yuxi 的多租户模型里一个智能体可以被创建者共享给其他用户其中一部分用户还拥有「管理」权限manage_scope成为委托管理员。委托管理员与创建者通常拥有不同的资源访问范围创建者可能选择了 10 个 Skill而委托管理员只能访问其中 5 个。此时出现了一个隐蔽的数据完整性问题共享智能体保存完整期望配置委托管理员只能访问其中部分资源。编辑页按当前候选项过滤后整体保存会删除不可见的既有选择影响创建者后续运行。换句话说如果保存接口把前端提交的配置当作整体覆盖来处理那么委托管理员在编辑页上只看到 5 个可访问的 Skill保存时提交的列表就只剩这 5 个——创建者选中的另外 5 个 Skill 被静默删除创建者后续运行该智能体时行为被改变。这是原始缺陷对应需求 xhome #61的核心诉求委托管理员保存无关配置时必须保留完整选择而运行时继续只使用当前操作者可访问的资源。更麻烦的是资源引用往往交错排列例如[visible-a, hidden-a, visible-b, hidden-b]简单的前置/后置重排都会改变 Skill 与预加载说明的加载顺序影响运行行为而直接 API 调用也可以绕过前端过滤构成越权写入通道。决策总览字段补丁 行锁合并 运行时交集围绕上述问题决策确立了四个相互配合的支柱保存接口把config_json.context当作字段补丁处理省略字段保留原值只有显式提交的字段才参与合并对应 agent_repository.py 中的merge_agent_config_json。写入前按 Context Schema 和用户角色过滤可写字段并复用运行时资源选项解析访问范围对应 agent_config_service.py 的prepare_agent_config_write。AgentRepository在 PostgreSQL 行锁内读取最新配置并合并保护并发提交场景下最新的隐藏引用不被旧快照覆盖对应 agent_repository.py 的update方法。运行期继续计算期望选择与操作者可访问资源的交集且运行归一化不改写持久配置对应 context.py 的normalize_agent_context_config。用户操作参考统一维护在配置智能体 — 资源选择语义一节。后端核心merge_agent_config_json合并算法合并是保存边界的核心实现位于 agent_repository.py。整体流程深拷贝当前持久配置与提交补丁顶层浅合并merged {**current, **patch_copy}若补丁未包含context字段直接返回——这意味着保存名称、模型等非 context 字段完全不会触碰资源选择。对context内部同样做字段级合并merged_context {**current_context, **patch_context}未提交的字段保留原值。只对「提交了且属于资源字段集合」的字段执行资源引用合并。资源字段集合由yuxi.agents.context导出# backend/package/yuxi/agents/context.py#L397-L399 _DEFAULT_ALL_CONTEXT_FIELDS frozenset({tools, knowledges, mcps, skills}) _EMPTY_ALL_CONTEXT_FIELDS frozenset({subagents}) AGENT_RUNTIME_RESOURCE_FIELDS _DEFAULT_ALL_CONTEXT_FIELDS | _EMPTY_ALL_CONTEXT_FIELDS保存边界在运行时字段集之上额外处理预加载 Skill# backend/package/yuxi/repositories/agent_repository.py#L110 AGENT_RESOURCE_CONFIG_FIELDS AGENT_RUNTIME_RESOURCE_FIELDS | {preload_skills}非空列表可见增删 隐藏引用保留 交错顺序稳定对于提交的非空资源列表合并逻辑如下merge_agent_config_json中的核心分支权限前置校验每个资源字段必须出现在resource_access中否则抛错智能体资源字段 {field} 未经过权限校验杜绝绕过权限解析的直接写入。拒绝无权新增新请求中的每一项如果既不在当前操作者可访问集合中、也不在既有引用集合中抛ValueError无权新增智能体资源。这条规则堵住了「借既有隐藏引用混入新引用」的越权通道。保留旧引用kept_existing保留所有不可访问的旧引用即使本次未提交以及仍被请求保留的可见项且维持原相对顺序。追加新选择new_visible只包含可访问且此前不存在的新项按请求顺序追加到末尾。最终合并结果 [保留的旧引用原顺序, 新增可见项请求顺序]。这保证了 Skill 和预加载说明的加载顺序不因委托管理员编辑而被打乱。显式 null 与空列表整体切换资源策略null与空列表是显式的策略值表示整体切换该字段的资源策略不进入「保留隐藏引用」分支工具、知识库、MCP、Skill显式空列表表示「禁用该类资源」而省略字段或null则保持原值。子智能体subagents保留空列表即「全部可访问」的兼容语义——null与空列表都展示全部可访问子智能体。从 context.py 可以看到运行时对这组语义的对称处理subagents的空列表会在归一化时被转换为None全部可访问而tools/knowledges/mcps/skills的空列表保持[]不启用。合并算法的单元测试佐证test_agent_repository.py 中的测试精确刻画了这些规则def test_merge_agent_config_json_preserves_omitted_context_fields_and_hidden_skills(): 省略字段和不可见 Skill 引用均保持原值。 merged merge_agent_config_json( {context: {model: provider:model-a, skills: [fskill-{i} for i in range(10)]}, metadata: {source: owner}}, {context: {temperature: 0.2, skills: [fskill-{i} for i in range(5)]}}, resource_access{skills: {*(fskill-{i} for i in range(5))}}, ) assert merged[context][skills] [fskill-{i} for i in range(10)] # 10 项全部保留交错顺序测试则断言[visible-a, hidden-a, visible-b, hidden-b]在提交[visible-b, visible-c, hidden-a]后合并为[hidden-a, visible-b, hidden-b, visible-c]——隐藏引用保持相对顺序新增可见项追加到末尾。preload_skills的保留独立于skills允许列表互不干扰。写入前的可写字段过滤与访问范围解析保存接口并非直接把请求透传给 repository。agent_config_service.prepare_agent_config_writeagent_config_service.py在合并前完成两道检查按角色过滤可写字段调用filter_config_by_role依据 Context 字段的metadata.auth决定哪些字段可写——admin字段仅管理员/超级管理员可写superadmin字段仅超级管理员可写普通用户提交的越权字段会被过滤不会进入合并该语义在 context.py 的_role_can_modify中定义。注意auth只限制修改权限不提供字段保密能力。解析本次提交字段的可访问键对提交的非空资源字段调用resolve_agent_resource_optionscontext.py按当前操作者身份解析出可访问的工具、知识库、MCP、Skill、子智能体键集合组装成resource_access传给merge_agent_config_json。其中preload_skills复用skills的解析结果。也就是说「谁能改」「能改哪些值」都基于操作者当前身份实时解析而非依赖前端提交的候选列表从后端杜绝了直接 API 调用的越权写。并发安全PostgreSQL 行锁内的读-合并-写委托管理员与创建者可能并发保存同一共享智能体。若合并基于陈旧快照后提交者可能覆盖先提交者刚写入的隐藏引用。AgentRepository.update的解决方式agent_repository.pyif config_json is not None: result await self.db.execute(select(Agent.config_json).where(Agent.id agent.id).with_for_update()) row result.one_or_none() if row is None: raise ValueError(智能体不存在) agent.config_json merge_agent_config_json(row[0], config_json, resource_accessconfig_resource_access or {})关键点更新config_json时先在事务内用SELECT ... FOR UPDATE对目标行加锁读取数据库中的最新配置而非入参agent.config_json快照再执行合并最后提交。这样即使两个进程同时保存第二个事务也会等待锁释放后读到第一个事务的结果隐藏引用不会丢失。集成测试 test_agent_config_resource_authorization.py 中构造了并发写入场景concurrent_config[context][skills].append(hidden_concurrent)断言并发保存后skills同时包含configured与hidden_concurrent即「并发合并使用最新持久配置」。需要说明的设计取舍字段补丁与行锁保护的是隐藏引用这类「不可见字段」的并发更新同一可见字段的并发编辑仍按最后提交的补丁生效不引入配置版本协议——这是有意为之的简化。运行时交集持久配置不改写生效范围不越权保存侧保留完整期望选择安全性由运行侧兜底。normalize_agent_context_configcontext.py在每次运行前计算期望选择 × 当前操作者可访问资源的交集字段值为null时tools/knowledges/mcps/skills展开为当前用户可访问的全部资源subagents保持全部可访问语义。字段值为显式列表时用_normalize_selected_resource_keys过滤掉当前用户无权访问的键只保留交集。preload_skills额外限制在归一化后的skills范围内。这条归一化路径只影响本次运行的有效配置绝不回写数据库。对应到决策验证表委托管理员 B 运行时交集为 5而数据库持久列表仍为 10——「无权资源既不会进入有效配置持久列表也不收缩」。同时prepare_agent_runtime_context通过_runtime_prepared标志保证同一 Context 对象再次构图时复用准备结果避免重复解析。前端配合只提交变化字段、清空全部与使用全部后端语义要成立前端必须只提交修改过的配置字段而不是把编辑表单的完整上下文整体 PUT 回去。决策指出相关实现位于 AgentEditModal、配置表单与 store辅助工具函数见 agentConfigUtils.js普通列表编辑保留隐藏引用编辑页对不可见引用不显示也不提交取消最后一个可见项不会自动变成清空全部。清空全部用户显式执行清空操作时前端提交空列表对应工具的「禁用」策略后端据此整体移除全部引用含不可见项。子智能体对应操作显示为「使用全部」提交null/空列表恢复全部可访问语义null与空列表在界面上均展示全部可访问项。保存成功后前端采用后端返回的合并配置建立基线保证后续编辑基于真实持久状态而不是本地残缺快照。前端回归由 agentConfigSave.test.js 与 agentConfigUtils.test.js 覆盖旧 store 整体提交逻辑在「变动字段断言」处失败印证了这一改动。替代方案为何被否决决策明确排除了三种候选替代方案否决原因仅删除前端过滤无法保护绕过 UI 的直接 API 调用仅由前端拼回隐藏引用前端无法拥有访问权限判定与最新持久状态为每个资源增加独立增删 API扩大协议和维护范围现有保存接口已能用省略字段、非空列表与显式策略值表达全部操作后果与边界采纳本决策后的行为边界不可见的失效引用会继续保留拥有相应访问权限的管理者可以移除可见选择显式策略切换清空全部/使用全部可以整体清空引用。字段补丁 行锁保护并发更新下的最新隐藏引用可见字段的并发编辑按最后提交的补丁生效不引入版本协议。null与空列表的差异继续由各资源字段契约拥有不统一改变子智能体「空列表 全部可访问」的兼容行为。运行时归一化不改写持久配置用户操作参考统一见配置智能体 — 资源选择语义。验证与回归验收主张覆盖四条主线全部 Passed对应决策文档「验证」章节完整保存A 的 10 项选择在 B 只能访问其中 5 项时仍完整保存——真实 HTTP 集成后独立 PostgreSQL 回读验证负向案例为独立进程恢复旧整体覆盖导致隐藏保留断言失败。可见增删 顺序可见增删保留交错资源顺序新增无权引用被拒绝HTTP 422——由资源合并 unit 测试与创建/更新集成测试验证。并发合并使用最新持久配置——PostgreSQL 观察实际锁等待后并发提交再回读。运行时交集B 运行配置只含交集、数据库保留完整期望选择——使用真实数据库用户与资源归一化验证。后端回归位于 test_agent_repository.py、test_agent_config_service.py、backend/test/unit/toolkits/test_install_skill.py与 test_agent_config_resource_authorization.py前端回归位于 agentConfigSave.test.js 与 agentConfigUtils.test.js。验证方法学也值得借鉴真实 HTTP 集成测试在独立 Compose 槽位完成最终简化后在 main 开发环境运行docker compose exec -u 0 -T api uv run --no-sync --group test pytest test/unit -m not slow -q -o faulthandler_timeout30结果为 1782 passed、50 skipped使用现有依赖完成验证未修改依赖锁文件pnpm run lint:check、pnpm run test:unit269 passed与pnpm run build通过浏览器验证覆盖浅深色及 1440/1024/768/375 像素宽度。需要强调的是本次没有执行真实 worker E2E运行资源交集证据止于使用真实身份和数据库的归一化入口不应表述为完整模型调用验证。小结「共享智能体资源选择的保存边界」提供了一套可复制的多租户配置保存范式保存端用字段补丁 权限过滤 行锁合并保证持久配置的完整与并发安全运行端用交集归一化保证每次运行不越权两端以null/空列表/非空列表三种策略值为契约协同。它同时处理了顺序稳定性交错引用原序保留、协议最小化不引入版本号或独立增删 API与前端体验清空全部/使用全部两种显式操作三个层面是 Yuxi 中资源权限模型与配置持久化结合的典型实现相关完整配置字段与运行语义可继续阅读配置智能体与Agent 运行时上下文。【免费下载链接】Yuxi可私有部署的多租户知识智能体平台统一 RAG、知识图谱、多智能体、MCP/Skills、沙盒与权限管理。Self-hosted knowledge agent platform for RAG, knowledge graphs and multi-agent workflows.项目地址: https://gitcode.com/GitHub_Trending/yu/Yuxi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考