Pi Agent: Custom Tool Development
Última atualização: 2026-08-31
--- title: "Desenvolvimento de Ferramentas Personalizadas" description: "Aprofunde-se no desenvolvimento de ferramentas personalizadas do Pi Agent, incluindo protocolo de ferramentas, validacao de parametros, tratamento de erros e padroes avancados." order: 15 lang: pt-br
Ferramentas integradas não são suficientes? Escreva as suas — o protocolo de ferramentas do Pi Agent é tão simples que você pode começar em 10 minutos.
1. Protocolo de Ferramentas
Todas as ferramentas do Pi Agent seguem um protocolo unificado:
from pi_agent import tool
@tool(
name="tool_name",
description="Tool description",
parameters={...}
)
def tool_function(param1: str, param2: int = 0) -> dict:
return {"result": "value"}
Regras-chave:
- Funções devem ter anotações de tipo
- Retorno deve ser dict ou str
- Parâmetros declarados via campo parameters do decorador
- Exceções são capturadas pelo Pi Agent e convertidas em mensagens amigáveis
2. Desenvolvimento Básico de Ferramentas
Exemplo 1: Ferramenta Markdown para HTML (Dificuldade: ⭐)
from pi_agent import tool
import markdown
@tool(
name="md_to_html",
description="Convert Markdown text to HTML",
parameters={
"md_text": {"type": "string", "description": "Markdown text"},
"title": {"type": "string", "description": "Page title", "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)}
Exemplo 2: Ferramenta de Consulta JSON (Dificuldade: ⭐⭐)
from pi_agent import tool
import json
@tool(
name="json_query",
description="Query values from JSON data by path",
parameters={
"data": {"type": "string", "description": "JSON string"},
"path": {"type": "string", "description": "Query path, e.g. 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 parse failed: {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 '{path}' not found: {e}"}
return {"value": current, "type": type(current).__name__}
3. Ferramentas Assíncronas
Ferramentas que precisam de rede ou I/O de arquivos devem ser assíncronas:
from pi_agent import tool
import aiohttp
@tool(
name="http_get",
description="Send HTTP GET request",
parameters={
"url": {"type": "string", "description": "Request URL"},
"headers": {"type": "object", "description": "Request headers", "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. Cadeias de Ferramentas
Múltiplas ferramentas podem ser encadeadas em um pipeline de processamento:
from pi_agent import Agent, tool
@tool(name="fetch_url", description="Fetch URL content")
def fetch_url(url: str) -> dict:
import requests
resp = requests.get(url)
return {"content": resp.text}
@tool(name="extract_links", description="Extract links from HTML")
def extract_links(html: str) -> dict:
import re
links = re.findall(r'href=["\']([^"\']+)["\']', html)
return {"links": links}
@tool(name="check_status", description="Check if URL is accessible")
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("Check all links on https://example.com for availability")
5. Tratamento de Erros
from pi_agent import tool, ToolError
@tool(name="safe_divide", description="Safe division")
def safe_divide(a: float, b: float) -> dict:
if b == 0:
return {"error": "Division by zero", "suggestion": "Provide a non-zero divisor"}
return {"result": a / b}
@tool(name="send_email", description="Send email")
def send_email(to: str, subject: str, body: str) -> dict:
if "@" not in to:
raise ToolError("Invalid recipient email format")
if not subject.strip():
raise ToolError("Email subject cannot be empty")
return {"status": "sent", "to": to}
6. Teste de Ferramentas
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
❓ Perguntas Frequentes
P: Ferramentas podem acessar o estado do Agent? R: Não diretamente. Passe o estado do Agent via parâmetro de contexto, ou use hooks para ler/modificar o estado ao redor das chamadas de ferramentas. P: Limite de tamanho de retorno de dados da ferramenta? R: Sem limite rígido, mas recomendamos manter abaixo de 10KB. Retornos longos aumentam consumo de contexto e latência. P: Ferramentas podem chamar outras ferramentas? R: Não diretamente. O Agent encadeia ferramentas automaticamente no raciocínio multi-step. Para operações combinadas, escreva uma ferramenta composta.
❓ Resumo
- Protocolo de ferramenta: decorador @tool + anotações de tipo + valor de retorno dict
- Ferramentas de rede/IO usam modo assíncrono
- Cadeias de ferramentas permitem que múltiplas ferramentas se encadeiem automaticamente
- Tratamento de erros: retorne dict de erro ou levante ToolError
- Teste com pytest para validação de entrada/saída
📝 Exercícios
- Básico (Dificuldade: ⭐): Crie uma ferramenta simples de processamento de strings (contagem de palavras, deduplicação).
- Intermediário (Dificuldade: ⭐⭐): Crie uma ferramenta assíncrona de chamada de API suportando GET e POST.
- Avançado (Dificuldade: ⭐⭐⭐): Crie uma cadeia de ferramentas de "consulta de banco de dados" com etapas de conexão, consulta e formatação, completa com tratamento de erros e testes.