news 2026/9/13 5:24:48

pydantic-ai 中用 PrefixTools 给 Capability 工具加命名空间前缀:原理、用法与测试验证

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
pydantic-ai 中用 PrefixTools 给 Capability 工具加命名空间前缀:原理、用法与测试验证

pydantic-ai 中用 PrefixTools 给 Capability 工具加命名空间前缀:原理、用法与测试验证

【免费下载链接】pydantic-aiHow Python does AI. Agents, realtime voice, image generation, embeddings. Every model, every interface, typed end to end.项目地址: https://gitcode.com/GitHub_Trending/py/pydantic-ai

本文介绍 pydantic-ai 的PrefixToolscapability:它包裹另一个 capability 并为其所有工具名加上统一前缀,用于在组合多个可能产生工具名冲突的 capability(如多个 MCP 服务器)时做命名空间隔离。读完本文,你将掌握PrefixTools的直接构造与prefix_tools()便捷方法两种用法、底层PrefixedToolset的前缀改写与调用还原机制、在 Agent 声明式 spec 中的写法,以及它与 toolset 层prefixed()的分工关系。

为什么需要 PrefixTools

pydantic-ai 的 capability 机制允许把 MCP、WebSearch、Toolset 等功能以"能力"的形式组合挂到 Agent 上。当同一个 Agent 挂上多个来源相似的能力时,很容易出现工具名冲突——例如两个 MCP 服务器都暴露了search工具,模型侧就会收到两个同名工具,导致歧义甚至报错。

PrefixTools的解决方式非常直接:它包裹(wrap)另一个 capability,并给该 capability 贡献的每个工具名加一个前缀(例如'mcp'使'search'变为'mcp_search'),从而为每个能力源划定独立的命名空间。其核心语义是:只有被包裹 capability 的工具会被加前缀,Agent 上的其他工具不受影响

基本用法:直接构造 PrefixTools

PrefixToolspydantic_ai.capabilities导出(见 导出定义),构造时接收两个关键参数:

  • wrapped:被包裹的 capability(继承自基类WrapperCapabilitywrapped字段);
  • prefix:加到工具名前的前缀字符串,最终工具名为{prefix}_{原名}

下面的示例展示了它的典型应用场景:为两个不同 MCP 服务器分别加api1api2前缀,避免两侧同名工具互相覆盖:

from pydantic_ai import Agent from pydantic_ai.capabilities import MCP, PrefixTools agent = Agent( 'openai:gpt-5.2', capabilities=[ PrefixTools(MCP(url='https://api1.example.com', native=True), prefix='api1'), PrefixTools(MCP(url='https://api2.example.com', native=True), prefix='api2'), ], )

这里内层的MCPcapability 通过url指定 MCP 服务器地址,native=True表示优先走提供商的原生 MCP 支持(源码中MCP.__init__明确要求native=True时必须提供url,见 mcp.py L76-L83)。你也可以用PrefixTools包裹任何返回工具集的能力,例如Toolset(...)

from pydantic_ai import Agent from pydantic_ai.capabilities import PrefixTools, Toolset from pydantic_ai.toolsets import FunctionToolset toolset = FunctionToolset() agent = Agent( 'openai:gpt-5', capabilities=[ PrefixTools( wrapped=Toolset(toolset), prefix='ns', ), ], )

便捷方法:任意 capability 上的 prefix_tools()

每个AbstractCapability都提供了prefix_tools便捷方法,返回一个以自身为wrappedPrefixTools包装器,避免手写构造参数:

MCP(url='https://mcp.example.com/api', native=True).prefix_tools('mcp')

其实现就是简单地把self传给包装器(见 abstract.py L1365-L1372):

def prefix_tools(self, prefix: str) -> PrefixTools[AgentDepsT]: """Returns a new capability that wraps this one and prefixes its tool names. Only this capability's tools are prefixed; other agent tools are unaffected. """ from .prefix_tools import PrefixTools return PrefixTools(wrapped=self, prefix=prefix)

对应的回归测试test_prefix_tools_convenience_method验证了Toolset(toolset).prefix_tools('ns')的结果确实是PrefixTools实例。

实现原理:从 PrefixTools 到 PrefixedToolset

PrefixTools本身是一个数据类,源码只有 67 行,核心逻辑集中在get_toolset()中(见 prefix_tools.py L59-L67):

def get_toolset(self) -> AgentToolset[AgentDepsT] | None: toolset = super().get_toolset() if toolset is None: return None if isinstance(toolset, AbstractToolset): return PrefixedToolset(toolset, prefix=self.prefix) # ToolsetFunc callable — wrap in DynamicToolset so PrefixedToolset can delegate return PrefixedToolset(DynamicToolsetAgentDepsT, prefix=self.prefix)

从源码结构看,这里处理了三种情况:

  1. 被包裹能力没有工具集get_toolset()返回None,例如纯指令/模型设置类 capability),PrefixTools原样返回None,不做任何前缀处理——测试test_prefix_tools_returns_none_when_no_toolset验证了这一点;
  2. 被包裹能力返回的是标准AbstractToolset,直接包一层PrefixedToolset
  3. 被包裹能力返回的是 callable 形式的动态工具集ToolsetFunc),先包一层DynamicToolset使其可委托,再套PrefixedToolset——测试test_prefix_tools_with_callable_toolset验证了此时模型侧看到的工具名是dyn_dynamic_tool

PrefixedToolset:改写名字与还原调用

真正"干活"的是 toolset 层的PrefixedToolset,它做两件事:

  • 暴露工具时改写名字get_tools()把内层每个工具重命名为{prefix}_{name},并同步替换工具定义中的name字段;
  • 执行调用时还原名字call_tool()name.removeprefix(self.prefix + '_')剥掉前缀,同时把RunContext.tool_name和工具定义恢复为原始名再委托执行,保证内层工具(及其钩子、审批逻辑)感知到的始终是未加前缀的名字:
async def call_tool(self, name, tool_args, ctx, tool): original_name = name.removeprefix(self.prefix + '_') ctx = replace(ctx, tool_name=original_name) tool = replace(tool, tool_def=replace(tool.tool_def, name=original_name)) return await super().call_tool(original_name, tool_args, ctx, tool)

这个"模型侧看到带前缀名、执行侧还原原名"的双向映射,使得前缀完全对底层工具透明。测试test_prefix_tools_tool_call_strips_prefix构造了一个模型返回ToolCallPart('ns_greet', ...)的场景,确认带前缀的调用能正确落到原始greet工具上。

边界验证:只前缀被包裹能力的工具

PrefixTools的作用范围严格限定在wrapped能力内部。测试test_prefix_tools_prefixes_wrapped_capability_tools同时注册了 capability 内的inner_tool和 Agent 级工具outer_tool,断言模型侧看到的名字是'ns_inner_tool,outer_tool'——只有前者被加前缀。

声明式 spec:PrefixTools.from_spec

pydantic-ai 支持用 JSON 风格的 spec 声明 Agent(Agent.from_spec),PrefixTools在其中有专门的序列化名'PrefixTools'get_serialization_name()返回该名,见 prefix_tools.py L42-L44)和配套的from_spec工厂(L46-L57):

@classmethod def from_spec(cls, *, prefix: str, capability: CapabilitySpec) -> PrefixTools[Any]: from pydantic_ai.agent.spec import load_capability_from_nested_spec wrapped = load_capability_from_nested_spec(capability) return cls(wrapped=wrapped, prefix=prefix)

capability参数接收与其他capabilities列表条目相同格式的嵌套 spec,既可以是带参数字典,也可以是裸类名。测试test_prefix_tools_from_spec覆盖了两种形态:

agent = Agent.from_spec( { 'model': 'test', 'capabilities': [ { 'PrefixTools': { 'prefix': 'search', 'capability': {'NativeTool': {'kind': 'web_search'}}, } }, ], }, )

此外,PrefixTools.from_spec也可以脱离Agent.from_spec单独使用(此时走默认注册表,见test_prefix_tools_from_spec_direct):

cap = PrefixTools.from_spec(prefix='ws', capability={'WebSearch': {'local': 'duckduckgo'}})

注册身份与延迟加载的继承

PrefixTools继承自WrapperCapability。这类包装器对运行期钩子(run/node/model/tool/output 各阶段的 before/after/wrap/error 回调、事件流、handle_deferred_tool_calls等)全部默认委托给内层能力,只有get_toolset()PrefixTools覆写——这正是"最小侵入"的包装模式:除工具改名外,被包裹能力的一切行为原样保留。

值得注意的是注册身份(iddefer_loadingdescription)的处理。WrapperCapability.__adopt_wrapped_identity(见 wrapper.py L77-L86)的规则是:包装器自身没有显式id时,采用被包裹能力的iddefer_loading。这带来两个实际后果:

  • 包装器可以直接"坐"在一个 deferred capability 之上而不丢失其延迟加载身份和加载目录(load catalog)中的位置——测试test_prefix_tools_inherits_wrapped_metadata_for_registration验证了PrefixTools会继承内层id='leaf-tools'defer_loading=True与描述,并以内层 id 注册进 capability map;
  • 包装器也可以显式覆盖这些元数据(如PrefixTools(github, prefix='github', id='github_prefixed')),并让自身成为 deferred capability 参与延迟加载,见test_prefix_tools_can_be_deferred

另外从源码结构看,WrapperCapability.apply特意采用"单次遍历、结果重放"而非每层递归两次的设计(wrapper.py L88-L98):注释中明确说明,若每层遍历两棵子树,n层包装链会导致2**n次遍历,"一堆prefix_tools()调用将无法正常在合理时间内完成解析"。也就是说,PrefixTools支持任意层数的嵌套堆叠,且遍历开销随深度线性增长。

与 toolset 层 PrefixedToolset 的分工

PrefixedToolset并非PrefixTools的私有实现,它同时也是 toolsets 层的公共能力:在 Toolsets 文档 中,任意 toolset 都可以通过prefixed()便捷方法链式改名,例如weather_toolset.prefixed('weather')使temperature_celsius变为weather_temperature_celsius。两者的分工可以这样理解:

维度PrefixedToolset/.prefixed()PrefixTools/.prefix_tools()
作用对象单个 toolset整个 capability(及其贡献的工具集)
典型场景CombinedToolset组合多个本地 toolset 时消歧组合多个 capability(如多个 MCP 服务器)时做命名空间隔离
附加影响无,纯名字变换继承被包裹能力的 id/描述/延迟加载等注册元数据;spec 中可声明嵌套能力

当加前缀生成的名字过长或可能让模型困惑时,还可以改用 RenamedToolset 做字典式精确重命名,而不是机械前缀。

小结

  • PrefixTools是 pydantic-ai capability 组合中的命名空间工具:PrefixTools(capability, prefix='api1')或更简洁的capability.prefix_tools('api1'),只改写被包裹能力的工具名,Agent 其他工具不受影响;
  • 底层链路为PrefixTools.get_toolset()PrefixedToolsetget_tools()负责把名字改为{prefix}_{name}call_tool()负责在还原RunContext.tool_name与工具定义后委托执行,对底层工具完全透明;
  • 包装器自动继承被包裹能力的iddefer_loading,可叠加延迟加载,也支持通过Agent.from_spec'PrefixTools'声明嵌套能力;
  • 关键源码位于 prefix_tools.py、wrapper.py、prefixed.py,行为边界可由 tests/test_capabilities.py 中的test_prefix_tools_*系列用例逐条核对。

【免费下载链接】pydantic-aiHow Python does AI. Agents, realtime voice, image generation, embeddings. Every model, every interface, typed end to end.项目地址: https://gitcode.com/GitHub_Trending/py/pydantic-ai

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

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

Qt/C++固高运动卡3轴运动台上位机开发:线程模型、回零与联动实现

简介:基于Qt与C开发的固高运动卡3轴运动台上位机程序及完整源码,主要面向毕业设计、课程设计与项目开发场景。程序围绕固高运动卡展开,实现三轴运动台的基本控制功能,界面设计覆盖运动控制、状态显示与参数配置,源代码…

作者头像 李华
网站建设 2026/9/13 5:24:14

PaddlePaddle源码审阅:证据驱动的静态切片方法论

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 5:24:09

AIGC检测技术原理与应用全解析

1. AIGC检测技术的基本原理与核心能力 AIGC(AI生成内容)检测技术本质上是通过分析文本特征来区分人工创作与机器生成内容的技术手段。当前主流检测系统主要基于深度学习模型,通过捕捉AI生成文本的特定模式来实现识别。这些模式包括但不限于&a…

作者头像 李华
网站建设 2026/9/13 5:23:33

轻量开源版IDEA?IntelliJ IDEA社区版安装配置与优化指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 5:22:20

AI时代职场新机遇:时间壁垒与人机协作

1. 技术变革中的就业焦虑与现实机遇每次技术革命都会引发就业市场的震荡。19世纪工业革命时期,英国纺织工人曾大规模破坏机械织布机,担心机器抢走他们的饭碗。如今面对AI技术的迅猛发展,类似的焦虑正在全球职场蔓延。但历史告诉我们&#xff…

作者头像 李华