发布自定义工具

如何构建、打包并将你自己的 CrewAI 兼容工具发布到 PyPI,以便任何 CrewAI 用户都可以安装和使用它们。

概览

CrewAI 的工具系统是为扩展而设计的。如果你构建了一个可能对其他人也有帮助的工具,你可以将它打包为一个独立的 Python 库,发布到 PyPI,并让任何 CrewAI 用户都能使用它——无需向 CrewAI 仓库提交 PR。

本指南将带你完成整个流程:实现工具契约、组织你的包结构,以及发布到 PyPI。

如果你只是为自己的项目需要一个自定义工具,请改看 Create Custom Tools 指南。

工具契约

每个 CrewAI 工具都必须满足以下两种接口之一:

选项 1:继承 BaseTool

继承 crewai.tools.BaseTool 并实现 _run 方法。定义 namedescription,并可选定义 args_schema 以进行输入校验。

  1. from crewai.tools import BaseTool
  2. from pydantic import BaseModel, Field
  3. class GeolocateInput(BaseModel):
  4. """GeolocateTool 的输入模式。"""
  5. address: str = Field(..., description="需要进行地理编码的街道地址。")
  6. class GeolocateTool(BaseTool):
  7. name: str = "Geolocate"
  8. description: str = "将街道地址转换为纬度 / 经度坐标。"
  9. args_schema: type[BaseModel] = GeolocateInput
  10. def _run(self, address: str) -> str:
  11. # 在这里实现你的逻辑
  12. return f"40.7128, -74.0060"

选项 2:使用 @tool 装饰器

对于更简单的工具,@tool 装饰器可以将一个函数转换为 CrewAI 工具。该函数必须包含 docstring(会被用作工具描述)以及类型注解。

  1. from crewai.tools import tool
  2. @tool("Geolocate")
  3. def geolocate(address: str) -> str:
  4. """将街道地址转换为纬度 / 经度坐标。"""
  5. return "40.7128, -74.0060"

关键要求

无论你使用哪种方式,你的工具都必须:

  • 具有 name —— 一个简短且描述明确的标识符。
  • 具有 description —— 告诉代理何时以及如何使用该工具。这会直接影响代理使用你工具的效果,因此请写得清晰且具体。
  • 实现 _runBaseTool)或提供 函数体@tool)—— 即同步执行逻辑。
  • 为所有参数和返回值添加 类型注解
  • 返回一个 字符串 结果(或可以被有意义地转换为字符串的内容)。

可选:异步支持

如果你的工具执行的是 I/O 密集型工作,可以实现 _arun 以支持异步执行:

  1. class GeolocateTool(BaseTool):
  2. name: str = "Geolocate"
  3. description: str = "将街道地址转换为纬度 / 经度坐标。"
  4. def _run(self, address: str) -> str:
  5. # 同步实现
  6. ...
  7. async def _arun(self, address: str) -> str:
  8. # 异步实现
  9. ...

可选:使用 args_schema 进行输入校验

将一个 Pydantic 模型定义为你的 args_schema,即可获得自动输入校验和清晰的错误信息。如果你不提供它,CrewAI 会根据 _run 方法的签名自动推断。

  1. from pydantic import BaseModel, Field
  2. class TranslateInput(BaseModel):
  3. """TranslateTool 的输入模式。"""
  4. text: str = Field(..., description="要翻译的文本。")
  5. target_language: str = Field(
  6. default="en",
  7. description="目标语言的 ISO 639-1 语言代码。",
  8. )

对于已发布的工具,推荐显式定义 schema——这样可以带来更好的代理行为,以及更清晰的用户文档。

可选:环境变量

如果你的工具需要 API 密钥或其他配置,可以通过 env_vars 声明它们,以便用户知道需要设置什么:

  1. from crewai.tools import BaseTool, EnvVar
  2. class GeolocateTool(BaseTool):
  3. name: str = "Geolocate"
  4. description: str = "将街道地址转换为纬度 / 经度坐标。"
  5. env_vars: list[EnvVar] = [
  6. EnvVar(
  7. name="GEOCODING_API_KEY",
  8. description="地理编码服务的 API 密钥。",
  9. required=True,
  10. ),
  11. ]
  12. def _run(self, address: str) -> str:
  13. ...

包结构

请将你的项目组织为标准的 Python 包。下面是推荐的目录结构:

  1. crewai-geolocate/
  2. ├── pyproject.toml
  3. ├── LICENSE
  4. ├── README.md
  5. └── src/
  6. └── crewai_geolocate/
  7. ├── __init__.py
  8. └── tools.py

pyproject.toml

  1. [project]
  2. name = "crewai-geolocate"
  3. version = "0.1.0"
  4. description = "一个用于对街道地址进行地理定位的 CrewAI 工具。"
  5. requires-python = ">=3.10"
  6. dependencies = [
  7. "crewai",
  8. ]
  9. [build-system]
  10. requires = ["hatchling"]
  11. build-backend = "hatchling.build"

crewai 声明为依赖项,这样用户会自动获得兼容版本。

__init__.py

重新导出你的工具类,这样用户就可以直接导入它们:

  1. from crewai_geolocate.tools import GeolocateTool
  2. __all__ = ["GeolocateTool"]

命名约定

  • 包名:使用前缀 crewai-(例如 crewai-geolocate)。这样当用户在 PyPI 中搜索时更容易发现你的工具。
  • 模块名:使用下划线(例如 crewai_geolocate)。
  • 工具类名:使用 PascalCase,并以 Tool 结尾(例如 GeolocateTool)。

测试你的工具

在发布之前,请确认你的工具可以在一个 crew 中正常工作:

  1. from crewai import Agent, Crew, Task
  2. from crewai_geolocate import GeolocateTool
  3. agent = Agent(
  4. role="Location Analyst",
  5. goal="为给定地址找到坐标。",
  6. backstory="一位地理空间数据专家。",
  7. tools=[GeolocateTool()],
  8. )
  9. task = Task(
  10. description="找出 Washington, DC 的 1600 Pennsylvania Avenue 的坐标。",
  11. expected_output="该地址的纬度和经度。",
  12. agent=agent,
  13. )
  14. crew = Crew(agents=[agent], tasks=[task])
  15. result = crew.kickoff()
  16. print(result)

发布到 PyPI

当你的工具测试完成并准备就绪后:

  1. # 构建包
  2. uv build
  3. # 发布到 PyPI
  4. uv publish

如果这是你第一次发布,你需要一个 PyPI 账户 和一个 API token

发布之后

用户可以通过以下方式安装你的工具:

  1. pip install crewai-geolocate

或者使用 uv:

  1. uv add crewai-geolocate

然后在他们的 crew 中使用它:

  1. from crewai_geolocate import GeolocateTool
  2. agent = Agent(
  3. role="Location Analyst",
  4. tools=[GeolocateTool()],
  5. # ...
  6. )