Pi Agent: 自定义工具开发

最后更新:2026-08-31

内置工具不够用?自己写一个——Pi Agent 的工具协议简单到你 10 分钟就能上手。


1. 工具协议

所有 Pi Agent 工具都遵循统一协议:

PYTHON
from pi_agent import tool

@tool(
    name="tool_name",
    description="工具描述",
    parameters={...}
)
def tool_function(param1: str, param2: int = 0) -> dict:
    # 工具逻辑
    return {"result": "value"}

关键规则:


2. 基础工具开发

▶ 示例 1:Markdown 转 HTML 工具(难度⭐)

PYTHON
from pi_agent import tool
import markdown

@tool(
    name="md_to_html",
    description="将 Markdown 文本转换为 HTML",
    parameters={
        "md_text": {"type": "string", "description": "Markdown 文本"},
        "title": {"type": "string", "description": "页面标题", "required": False}
    }
)
def md_to_html(md_text: str, title: str = "") -> dict:
    html_body = markdown.markdown(md_text, extensions=["tables", "fenced_code"])
    if title:
        html = f"<html><head><title>{title}</title></head><body>{html_body}</body></html>"
    else:
        html = html_body
    return {"html": html, "length": len(html)}

▶ 示例 2:JSON 数据查询工具(难度⭐⭐)

PYTHON
from pi_agent import tool
import json

@tool(
    name="json_query",
    description="从 JSON 数据中查询指定路径的值",
    parameters={
        "data": {"type": "string", "description": "JSON 字符串"},
        "path": {"type": "string", "description": "查询路径,如 users.0.name"}
    }
)
def json_query(data: str, path: str) -> dict:
    try:
        obj = json.loads(data)
    except json.JSONDecodeError as e:
        return {"error": f"JSON 解析失败: {e}"}
    
    keys = path.split(".")
    current = obj
    for key in keys:
        if key.isdigit():
            key = int(key)
        try:
            current = current[key]
        except (KeyError, IndexError, TypeError) as e:
            return {"error": f"路径 '{path}' 不存在: {e}"}
    
    return {"value": current, "type": type(current).__name__}

3. 异步工具

需要网络请求或文件 I/O 的工具应使用异步:

PYTHON
from pi_agent import tool
import aiohttp

@tool(
    name="http_get",
    description="发送 HTTP GET 请求",
    parameters={
        "url": {"type": "string", "description": "请求 URL"},
        "headers": {"type": "object", "description": "请求头", "required": False}
    },
    async_tool=True
)
async def http_get(url: str, headers: dict = None) -> dict:
    async with aiohttp.ClientSession() as session:
        async with session.get(url, headers=headers) as resp:
            body = await resp.text()
            return {
                "status": resp.status,
                "body": body[:5000],
                "headers": dict(resp.headers)
            }

4. 工具链

多个工具可以串联成处理流水线:

PYTHON
from pi_agent import Agent, tool

@tool(name="fetch_url", description="获取 URL 内容")
def fetch_url(url: str) -> dict:
    import requests
    resp = requests.get(url)
    return {"content": resp.text}

@tool(name="extract_links", description="提取 HTML 中的链接")
def extract_links(html: str) -> dict:
    import re
    links = re.findall(r'href=["\']([^"\']+)["\']', html)
    return {"links": links}

@tool(name="check_status", description="检查 URL 是否可访问")
def check_status(url: str) -> dict:
    import requests
    try:
        resp = requests.head(url, timeout=5)
        return {"url": url, "status": resp.status_code, "ok": resp.status_code < 400}
    except Exception as e:
        return {"url": url, "status": 0, "ok": False, "error": str(e)}

agent = Agent(name="link_checker", tools=["fetch_url", "extract_links", "check_status"])
result = agent.run("检查 https://example.com 上所有链接的可用性")

5. 错误处理

(1) 工具内部错误

PYTHON
@tool(name="safe_divide", description="安全除法")
def safe_divide(a: float, b: float) -> dict:
    if b == 0:
        return {"error": "除数不能为零", "suggestion": "请提供非零的除数"}
    return {"result": a / b}

(2) 参数验证

PYTHON
from pi_agent import tool, ToolError

@tool(name="send_email", description="发送邮件")
def send_email(to: str, subject: str, body: str) -> dict:
    if "@" not in to:
        raise ToolError("收件人邮箱格式不正确")
    if not subject.strip():
        raise ToolError("邮件主题不能为空")
    # 发送逻辑...
    return {"status": "sent", "to": to}

6. 工具测试

PYTHON
import pytest
from my_tools import json_query

def test_json_query_basic():
    result = json_query('{"name": "Alice"}', "name")
    assert result["value"] == "Alice"

def test_json_query_nested():
    data = '{"users": [{"name": "Bob"}]}'
    result = json_query(data, "users.0.name")
    assert result["value"] == "Bob"

def test_json_query_invalid_path():
    result = json_query('{"a": 1}', "b")
    assert "error" in result

❓ 常见问题

Q 工具函数能访问 Agent 的状态吗?
A 不能直接访问。但可以通过 context 参数传入 Agent 状态,或使用钩子函数在工具调用前后读取/修改状态。
Q 工具返回的数据量有限制吗?
A 没有硬性限制,但建议控制在 10KB 以内。超长返回值会增加上下文消耗和延迟。
Q 工具能调用其他工具吗?
A 不能直接调用。但 Agent 会在多步推理中自动串联工具。如果你需要组合操作,写一个组合工具更高效。

📖 小节


📝 作业

  1. 基础题(难度⭐):创建一个简单的字符串处理工具(如统计字数、去重)。
  2. 进阶题(难度⭐⭐):创建一个异步的 API 调用工具,支持 GET 和 POST 方法。
  3. 挑战题(难度⭐⭐⭐):创建一个"数据库查询"工具链,包含连接、查询、格式化三个步骤,并编写完整的错误处理和测试。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏