agent-tool-calling-architect
Skill28 starsChanged 14 days agoMITView on GitHub
Design et implémentation de systèmes de tool calling pour agents IA. Function calling, API wrapping, schema design, MCP server, parallel calls, sécurité et monitoring. Se déclenche avec "tool calling", "function calling", "tools agent", "API tools", "agent tools", "créer un outil", "custom tool", "tool schema", "MCP tool". Also triggers on "function calling schema", "give my agent tools".
- Reads credentials
- Deletes or force-pushes
We split this skill into the rules it gives an agent
4 rules. About 0 are common, 4 are distinctive, and 0 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈4
Distinctive · estimate
This author's own choices. Worth a look.
0
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 365
Never say "happy to help", "sure!", "great question", "let me", or similar.
- Distinctive: line 368
Stop when done. No summary, no recap, no trailing commentary.
- Distinctive: line 369
No politeness wrappers. Direct and blunt.
- Distinctive: line 372
No emoji unless asked.
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Tool Calling Architect
Critères de décision : quel pattern choisir ?
| Situation | Pattern recommandé |
| Outil ponctuel dans un seul agent | Function calling inline (JSON schema dans le prompt) |
| Outil partagé entre plusieurs agents | MCP Server (stdio ou HTTP+SSE) |
| Enchaînement prévisible de 3+ étapes | Pipeline déterministe, pas tool chaining LLM |
| Actions parallèles indépendantes | Parallel tool calling natif (OpenAI/Anthropic) |
| Outil exposé à des LLMs tiers/clients | MCP Server avec authentification |
| Wrapping d'API REST existante | Tool = thin wrapper + validation Pydantic |
Workflow en 10 étapes
1. Définir le contrat de l'outil
Avant d'écrire du code, complète cette fiche :
- Nom : snake_case explicite (
get_invoice_by_id, pasget_data) - Description : une phrase claire sur CE QUE l'outil fait + QUAND l'utiliser (le LLM lit cette phrase pour décider d'appeler ou non)
- Inputs : chaque paramètre typé, obligatoire ou optionnel, avec valeurs autorisées si
enum - Output : format de retour standardisé (
status,dataouerror) - Effets de bord : lecture seule ? écriture ? irréversible ?
Règle : si la description de l'outil est ambiguë pour un développeur humain, elle le sera aussi pour le LLM.
2. Rédiger le JSON Schema
Format OpenAI-compatible (compatible Anthropic, Gemini, Mistral) :
SEARCH_WEB_SCHEMA = {
"name": "search_web",
"description": (
"Recherche des informations récentes sur le web. "
"Utilise cet outil uniquement quand tu as besoin d'informations "
"publiées après ta date de coupure ou de données factuelles précises."
),
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Requête en langage naturel, la plus spécifique possible"
},
"max_results": {
"type": "integer",
"description": "Nombre max de résultats retournés (1-10)",
"default": 5
},
"language": {
"type": "string",
"enum": ["fr", "en", "ar"],
"description": "Langue des résultats",
"default": "fr"
}
},
"required": ["query"],
"additionalProperties": False # bloque les paramètres inconnus
}
}Pièges courants dans les schémas :
- Oublier
"additionalProperties": False→ le LLM invente des champs - Description trop courte → appels au mauvais moment
- Trop de paramètres optionnels → le LLM les omet aléatoirement
3. Implémenter le tool (Python)
from pydantic import BaseModel, Field, field_validator
import httpx
class SearchInput(BaseModel):
query: str = Field(..., min_length=1, max_length=500)
max_results: int = Field(5, ge=1, le=10)
language: str = Field("fr", pattern="^(fr|en|ar)$")
@field_validator("query")
@classmethod
def no_injection(cls, v: str) -> str:
forbidden = ["<script", "DROP TABLE", "--"]
if any(f in v for f in forbidden):
raise ValueError("Requête invalide")
return v.strip()
async def search_web(query: str, max_results: int = 5, language: str = "fr") -> dict:
try:
inp = SearchInput(query=query, max_results=max_results, language=language)
except Exception as e:
return {"status": "error", "code": "INVALID_INPUT", "message": str(e)}
try:
async with httpx.AsyncClient(timeout=8.0) as client:
resp = await client.get(
"https://search-api/v1/search",
params=inp.model_dump(),
headers={"Authorization": f"Bearer {SEARCH_API_KEY}"}
)
resp.raise_for_status()
items = resp.json().get("items", [])[:inp.max_results]
return {"status": "success", "data": items}
except httpx.TimeoutException:
return {"status": "error", "code": "TIMEOUT", "message": "Search API timeout after 8s"}
except httpx.HTTPStatusError as e:
return {"status": "error", "code": f"HTTP_{e.response.status_code}", "message": str(e)}
except Exception as e:
return {"status": "error", "code": "UNKNOWN", "message": str(e)}4. Dynamic tool loading
Ne passe jamais 20+ outils dans chaque prompt. Charge uniquement les outils pertinents :
TOOL_REGISTRY = {
"search": {"schema": SEARCH_WEB_SCHEMA, "fn": search_web, "tags": ["read", "web"]},
"send_email": {"schema": SEND_EMAIL_SCHEMA, "fn": send_email, "tags": ["write", "email"]},
"query_db": {"schema": QUERY_DB_SCHEMA, "fn": query_db, "tags": ["read", "db"]},
}
def get_tools_for_task(tags: list[str]) -> list[dict]:
return [
t["schema"] for t in TOOL_REGISTRY.values()
if any(tag in t["tags"] for tag in tags)
]
# Utilisation
tools = get_tools_for_task(["read", "web"])Seuils pratiques :
- 1–5 outils : pas de filtrage nécessaire
- 6–15 outils : filtrer par catégorie selon le contexte
- 16+ outils : filtrage sémantique (embeddings) ou multi-agent avec spécialisation
5. Parallel tool calling
Les LLMs modernes (GPT-4o, Claude 3.5+, Gemini 1.5+) retournent plusieurs tool_calls en un seul tour. Exécute-les en parallèle :
import asyncio
async def dispatch_tool(tool_call) -> dict:
name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
fn = TOOL_REGISTRY[name]["fn"]
result = await fn(**args)
return {
"tool_call_id": tool_call.id,
"role": "tool",
"content": json.dumps(result)
}
# Dans la boucle agent
if response.tool_calls:
results = await asyncio.gather(
*[dispatch_tool(tc) for tc in response.tool_calls],
return_exceptions=True # une erreur n'annule pas les autres
)
messages.extend(results)6. Tool chaining — quand laisser le LLM décider vs. pipeline déterministe
LLM-driven chaining → quand les étapes sont imprévisibles (exploration, recherche)
Pipeline déterministe → quand l'ordre est toujours le même (ETL, workflow métier)Pipeline déterministe (exemple) :
async def invoice_pipeline(invoice_id: str) -> dict:
raw = await get_invoice_by_id(invoice_id) # step 1
if raw["status"] == "error": return raw
enriched = await enrich_with_client_data(raw["data"]) # step 2
return await generate_pdf(enriched["data"]) # step 3Expose ce pipeline comme un seul tool à l'agent. L'agent n'a pas à orchestrer les 3 étapes.
7. MCP Server (partage inter-agents)
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
app = Server("mon-mcp-server")
@app.list_tools()
async def list_tools():
return [
Tool(
name="search_web",
description="Recherche des infos récentes. Utiliser pour toute question post-2024.",
inputSchema={
"type": "object",
"properties": {
"query": {"type": "string"},
"max_results": {"type": "integer", "default": 5}
},
"required": ["query"]
}
)
]
@app.call_tool()
async def call_tool(name: str, arguments: dict):
if name == "search_web":
result = await search_web(**arguments)
return [TextContent(type="text", text=json.dumps(result))]
raise ValueError(f"Outil inconnu : {name}")
# Lancement stdio
async def main():
async with stdio_server() as (r, w):
await app.run(r, w, app.create_initialization_options())Choisir le transport :
stdio→ usage local (Claude Desktop, Claude Code, scripts)HTTP+SSE(FastMCP) → usage distant, multi-clients, auth OAuth2
8. Sécurité
Checklist à appliquer sur chaque outil :
- Validation input — Pydantic avec contraintes strictes (
min_length,pattern,enum) - Sanitisation — rejette les injections SQL, prompt injection, path traversal (
../) - Sandboxing — outils d'exécution de code dans Docker ou subprocess avec timeout
- Rate limiting — par outil ET par session (ex. max 10 calls/min pour
send_email) - Permission scoping — l'agent reçoit uniquement les outils autorisés pour la session
- Budget par outil — coût max par appel pour les APIs payantes (éviter les loops infinis)
- Audit log — chaque appel loggé avec tool_name, user_id, timestamp, inputs (sans secrets)
# Exemple rate limiting simple
from collections import defaultdict
import time
call_counts: dict[str, list[float]] = defaultdict(list)
def check_rate_limit(tool_name: str, user_id: str, max_calls: int = 10, window: int = 60):
key = f"{tool_name}:{user_id}"
now = time.time()
call_counts[key] = [t for t in call_counts[key] if now - t < window]
if len(call_counts[key]) >= max_calls:
raise PermissionError(f"Rate limit: {max_calls} appels/{window}s pour {tool_name}")
call_counts[key].append(now)9. Testing des tools
import pytest
@pytest.mark.asyncio
async def test_search_web_valid():
result = await search_web(query="Python asyncio tutorial", max_results=3)
assert result["status"] == "success"
assert len(result["data"]) <= 3
@pytest.mark.asyncio
async def test_search_web_empty_query():
result = await search_web(query="")
assert result["status"] == "error"
assert result["code"] == "INVALID_INPUT"
@pytest.mark.asyncio
async def test_search_web_timeout(monkeypatch):
async def slow_get(*args, **kwargs):
raise httpx.TimeoutException("timeout")
monkeypatch.setattr(httpx.AsyncClient, "get", slow_get)
result = await search_web(query="test")
assert result["code"] == "TIMEOUT"Matrice de test pour chaque outil :
- Input valide nominal → succès attendu
- Input invalide (type, longueur, enum) → erreur
INVALID_INPUT - API downstream en erreur (4xx, 5xx) → erreur propre (pas d'exception non catchée)
- Timeout → erreur
TIMEOUT - Parallel calls (si supporté) → pas de race condition
10. Monitoring
import structlog
import time
log = structlog.get_logger()
async def instrumented_dispatch(tool_call) -> dict:
name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
start = time.perf_counter()
result = await TOOL_REGISTRY[name]["fn"](**args)
duration_ms = (time.perf_counter() - start) * 1000
log.info(
"tool_call",
tool=name,
duration_ms=round(duration_ms, 1),
status=result.get("status"),
error_code=result.get("code"),
# NE PAS logger les inputs complets (secrets potentiels)
)
return resultMétriques à suivre :
- Taux d'erreur par outil (cible < 2%)
- Latence p95 par outil
- Top 5 outils les plus appelés (détecte les loops)
- Taux d'appels sans résultat utilisé (outil appelé, LLM ignore la réponse → symptôme de description floue)
Anti-patterns à éviter
| Anti-pattern | Symptôme | Correction |
| Outil fourre-tout | LLM appelle avec les mauvais params | Découpe en plusieurs outils spécialisés |
| Description vague ("do stuff") | Appels au mauvais moment | Ajouter "Utilise UNIQUEMENT quand..." |
| Exception non catchée | Agent crash sans message utile | try/except exhaustif, retour {"status":"error"} |
| Trop d'outils dans le prompt | Hallucination de tool names | Dynamic loading par tags/catégorie |
| Output non structuré (plain text) | LLM réinterprète mal | JSON standardisé avec status + data/error |
| Outil avec effets de bord cachés | Actions non voulues | Documenter les effets de bord dans la description |
| Idempotence ignorée | Double send d'email, double débit | Idempotency key côté outil et API downstream |
| Tests absents sur les timeouts | Silent failure en prod | Mocker les timeouts, vérifier le retour |
Communication Rules — MANDATORY
- Ultra-concise. No filler, no preamble, no pleasantries.
- Never say "happy to help", "sure!", "great question", "let me", or similar.
- Tool first, talk second. Act before explaining.
- Result first. Lead with outcome, not process.
- Stop when done. No summary, no recap, no trailing commentary.
- No politeness wrappers. Direct and blunt.
- Minimum words. If one word works, do not use ten.
- No unsolicited explanations.
- No emoji unless asked.
In this repository347 other files it gives its agents.
- agent-a2a-protocol-guideSkill
- agent-agent-observabilitySkill
- agent-api-caller-subagentSkill
- agent-autogen-guideSkill
- agent-browser-agent-builderSkill
- agent-claude-code-extension-builderSkill
- agent-code-review-subagentSkill
- agent-coding-agent-builderSkill
- agent-conflict-resolverSkill
- agent-consensus-builderSkill
- agent-context-managerSkill
- agent-cost-optimizerSkill
- agent-crewai-expertSkill
- agent-customer-support-agentSkill
- agent-data-analyst-agentSkill
- agent-database-query-subagentSkill
- agent-deployment-guideSkill
- agent-email-agent-builderSkill
- agent-evaluation-frameworkSkill
- agent-file-processor-subagentSkill
- agent-handoff-designerSkill
- agent-hierarchy-designerSkill
- agent-human-in-the-loop-designerSkill
- agent-langgraph-designerSkill
- agent-load-balancerSkill
- agent-marketplace-creatorSkill
- agent-mcp-server-builderSkill
- agent-memory-designerSkill
- agent-message-protocolSkill
- agent-monitoring-setupSkill
- agent-multi-agent-orchestratorSkill
- agent-openai-assistants-builderSkill
- agent-pipeline-composerSkill
- agent-pool-managerSkill
- agent-prompt-tunerSkill
- agent-research-agent-designerSkill
- agent-result-aggregatorSkill
- agent-retry-strategistSkill
- agent-sales-agent-builderSkill
- agent-security-hardenerSkill
- agent-semantic-kernel-guideSkill
- agent-skill-creatorSkill
- agent-skill-routerSkill
- agent-spawnerSkill
- agent-state-synchronizerSkill
- agent-subagent-delegatorSkill
- agent-supervisor-builderSkill
- agent-task-decomposerSkill
- agent-testing-frameworkSkill
- agent-voice-agent-builderSkill
- agent-web-scraper-subagentSkill
- agent-workflow-automation-agentSkill
- ai-ml-anomaly-detection-builderSkill
- ai-ml-computer-vision-guideSkill
- ai-ml-dataset-builderSkill
- ai-ml-ml-experiment-trackerSkill
- ai-ml-model-fine-tunerSkill
- ai-ml-model-optimization-guideSkill
- ai-ml-nlp-pipeline-designerSkill
- api-gateway-kong-api-gatewaySkill
- The first 60 shown.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

