Agents IA autonomes : construire un agent avec Claude Agent SDK
Les agents IA ne sont plus de la science-fiction. Avec le Claude Agent SDK, vous pouvez créer des agents autonomes qui exécutent des tâches complexes : analyser du code, rechercher des informations, automatiser des workflows. Ce tutoriel vous guide pas à pas.
Qu'est-ce qu'un agent IA ?
Un agent IA est un système qui :
- Reçoit un objectif de haut niveau
- Décompose cet objectif en sous-tâches
- Exécute des actions via des outils
- Itère jusqu'à atteindre l'objectif
┌─────────────────────────────────────────────────────────┐
│ BOUCLE AGENT │
├─────────────────────────────────────────────────────────┤
│ │
│ Objectif ─────▶ Réflexion ─────▶ Action │
│ ▲ │ │
│ │ ▼ │
│ └──────── Observation ◀─────── Tool │
│ │
└─────────────────────────────────────────────────────────┘Agent vs Chatbot
| Aspect | Chatbot | Agent |
|---|---|---|
| Interaction | Question → Réponse | Objectif → Résultat |
| Actions | Aucune (texte uniquement) | Exécute des outils |
| Autonomie | Nulle | Élevée |
| Itération | Une seule | Boucle jusqu'à succès |
Installation et configuration
Prérequis
# Python 3.10+
python --version
# Créer un environnement virtuel
python -m venv venv
source venv/bin/activate # Linux/Mac
# ou
.\venv\Scripts\activate # WindowsInstallation du SDK
pip install anthropicConfiguration API
# config.py
import os
from anthropic import Anthropic
# Charger la clé API depuis l'environnement
client = Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY"))
if not client.api_key:
raise ValueError("ANTHROPIC_API_KEY environment variable not set")# Exporter la clé API
export ANTHROPIC_API_KEY="sk-ant-api..."Architecture d'un agent
Composants essentiels
# agent.py
from dataclasses import dataclass
from typing import List, Dict, Any, Callable
@dataclass
class Tool:
"""Définition d'un outil disponible pour l'agent."""
name: str
description: str
parameters: Dict[str, Any]
function: Callable
@dataclass
class Message:
"""Message dans la conversation."""
role: str # "user", "assistant", "tool"
content: str
class Agent:
"""Agent IA avec tools et mémoire."""
def __init__(self, client, model: str = "claude-sonnet-4-20250514"):
self.client = client
self.model = model
self.tools: List[Tool] = []
self.messages: List[Message] = []
self.system_prompt = ""
def add_tool(self, tool: Tool):
"""Ajouter un outil à l'agent."""
self.tools.append(tool)
def set_system_prompt(self, prompt: str):
"""Définir le prompt système."""
self.system_prompt = prompt
def run(self, objective: str, max_iterations: int = 10) -> str:
"""Exécuter l'agent jusqu'à atteindre l'objectif."""
self.messages.append(Message(role="user", content=objective))
for i in range(max_iterations):
response = self._call_model()
if response.stop_reason == "end_turn":
# L'agent a terminé
return response.content[0].text
if response.stop_reason == "tool_use":
# L'agent veut utiliser un outil
self._handle_tool_calls(response)
return "Max iterations reached without completion"Créer des outils
Outil : Lire un fichier
# tools/file_tools.py
import os
def read_file(path: str) -> str:
"""Lire le contenu d'un fichier."""
try:
with open(path, 'r', encoding='utf-8') as f:
return f.read()
except FileNotFoundError:
return f"Error: File not found: {path}"
except Exception as e:
return f"Error reading file: {str(e)}"
read_file_tool = {
"name": "read_file",
"description": "Read the contents of a file at the given path",
"input_schema": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "The path to the file to read"
}
},
"required": ["path"]
}
}Outil : Écrire un fichier
def write_file(path: str, content: str) -> str:
"""Écrire du contenu dans un fichier."""
try:
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, 'w', encoding='utf-8') as f:
f.write(content)
return f"Successfully wrote to {path}"
except Exception as e:
return f"Error writing file: {str(e)}"
write_file_tool = {
"name": "write_file",
"description": "Write content to a file at the given path",
"input_schema": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "The path to the file to write"
},
"content": {
"type": "string",
"description": "The content to write to the file"
}
},
"required": ["path", "content"]
}
}Outil : Exécuter une commande shell
import subprocess
def run_command(command: str, timeout: int = 30) -> str:
"""Exécuter une commande shell."""
try:
result = subprocess.run(
command,
shell=True,
capture_output=True,
text=True,
timeout=timeout
)
output = result.stdout
if result.stderr:
output += f"\nStderr: {result.stderr}"
if result.returncode != 0:
output += f"\nExit code: {result.returncode}"
return output or "Command completed with no output"
except subprocess.TimeoutExpired:
return f"Command timed out after {timeout} seconds"
except Exception as e:
return f"Error executing command: {str(e)}"
run_command_tool = {
"name": "run_command",
"description": "Execute a shell command and return the output",
"input_schema": {
"type": "object",
"properties": {
"command": {
"type": "string",
"description": "The shell command to execute"
},
"timeout": {
"type": "integer",
"description": "Timeout in seconds (default: 30)",
"default": 30
}
},
"required": ["command"]
}
}Outil : Recherche web
import requests
def web_search(query: str, num_results: int = 5) -> str:
"""Rechercher sur le web via SerpAPI ou similaire."""
api_key = os.environ.get("SERPAPI_KEY")
if not api_key:
return "Error: SERPAPI_KEY not configured"
try:
response = requests.get(
"https://serpapi.com/search",
params={
"q": query,
"api_key": api_key,
"num": num_results
}
)
data = response.json()
results = []
for item in data.get("organic_results", [])[:num_results]:
results.append(f"- {item['title']}: {item['link']}")
return "\n".join(results) if results else "No results found"
except Exception as e:
return f"Error searching: {str(e)}"
web_search_tool = {
"name": "web_search",
"description": "Search the web for information",
"input_schema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The search query"
},
"num_results": {
"type": "integer",
"description": "Number of results to return (default: 5)",
"default": 5
}
},
"required": ["query"]
}
}Agent complet : Code Reviewer
Implémentation
# code_reviewer_agent.py
from anthropic import Anthropic
import os
import json
client = Anthropic()
# Définition des outils
tools = [
{
"name": "read_file",
"description": "Read the contents of a file",
"input_schema": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "File path to read"}
},
"required": ["path"]
}
},
{
"name": "list_directory",
"description": "List files in a directory",
"input_schema": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "Directory path"}
},
"required": ["path"]
}
},
{
"name": "write_review",
"description": "Write the code review to a file",
"input_schema": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "Output file path"},
"content": {"type": "string", "description": "Review content"}
},
"required": ["path", "content"]
}
}
]
# Implémentation des outils
def execute_tool(name: str, input_data: dict) -> str:
if name == "read_file":
try:
with open(input_data["path"], 'r') as f:
return f.read()
except Exception as e:
return f"Error: {e}"
elif name == "list_directory":
try:
files = os.listdir(input_data["path"])
return "\n".join(files)
except Exception as e:
return f"Error: {e}"
elif name == "write_review":
try:
with open(input_data["path"], 'w') as f:
f.write(input_data["content"])
return f"Review written to {input_data['path']}"
except Exception as e:
return f"Error: {e}"
return f"Unknown tool: {name}"
# Prompt système
SYSTEM_PROMPT = """You are an expert code reviewer. Your task is to:
1. Explore the codebase using the list_directory and read_file tools
2. Analyze the code for:
- Security vulnerabilities
- Performance issues
- Code quality and maintainability
- Best practices violations
3. Write a detailed review using the write_review tool
Be thorough but constructive. Provide specific line references and suggestions.
Focus on actionable improvements."""
def run_code_review(project_path: str, output_path: str = "review.md"):
"""Exécuter une code review automatique."""
messages = [
{
"role": "user",
"content": f"Please review the code in {project_path} and write your review to {output_path}"
}
]
print(f"Starting code review of {project_path}...")
while True:
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=4096,
system=SYSTEM_PROMPT,
tools=tools,
messages=messages
)
# Ajouter la réponse de l'assistant
messages.append({
"role": "assistant",
"content": response.content
})
# Vérifier si terminé
if response.stop_reason == "end_turn":
print("Code review completed!")
# Extraire le texte final
for block in response.content:
if hasattr(block, 'text'):
print(block.text)
break
# Gérer les appels d'outils
if response.stop_reason == "tool_use":
tool_results = []
for block in response.content:
if block.type == "tool_use":
print(f"Using tool: {block.name}")
result = execute_tool(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result
})
messages.append({
"role": "user",
"content": tool_results
})
if __name__ == "__main__":
import sys
project = sys.argv[1] if len(sys.argv) > 1 else "."
run_code_review(project)Utilisation
# Exécuter la code review
python code_reviewer_agent.py ./src
# Résultat dans review.md
cat review.mdGestion de la mémoire
Mémoire de conversation
class ConversationMemory:
"""Mémoire de conversation avec limite de tokens."""
def __init__(self, max_tokens: int = 100000):
self.messages = []
self.max_tokens = max_tokens
def add(self, role: str, content: str):
self.messages.append({"role": role, "content": content})
self._trim_if_needed()
def _trim_if_needed(self):
"""Supprimer les messages les plus anciens si nécessaire."""
while self._estimate_tokens() > self.max_tokens and len(self.messages) > 2:
# Garder le premier message (objectif) et supprimer le suivant
self.messages.pop(1)
def _estimate_tokens(self) -> int:
"""Estimation grossière du nombre de tokens."""
total_chars = sum(len(m["content"]) for m in self.messages)
return total_chars // 4 # ~4 chars per token
def get_messages(self) -> list:
return self.messages.copy()Mémoire persistante avec SQLite
import sqlite3
import json
from datetime import datetime
class PersistentMemory:
"""Mémoire persistante pour l'agent."""
def __init__(self, db_path: str = "agent_memory.db"):
self.conn = sqlite3.connect(db_path)
self._create_tables()
def _create_tables(self):
self.conn.execute("""
CREATE TABLE IF NOT EXISTS facts (
id INTEGER PRIMARY KEY,
key TEXT UNIQUE,
value TEXT,
created_at TIMESTAMP,
updated_at TIMESTAMP
)
""")
self.conn.execute("""
CREATE TABLE IF NOT EXISTS conversations (
id INTEGER PRIMARY KEY,
session_id TEXT,
messages TEXT,
created_at TIMESTAMP
)
""")
self.conn.commit()
def remember(self, key: str, value: str):
"""Mémoriser un fait."""
now = datetime.now()
self.conn.execute("""
INSERT OR REPLACE INTO facts (key, value, created_at, updated_at)
VALUES (?, ?, COALESCE((SELECT created_at FROM facts WHERE key = ?), ?), ?)
""", (key, value, key, now, now))
self.conn.commit()
def recall(self, key: str) -> str:
"""Rappeler un fait."""
cursor = self.conn.execute(
"SELECT value FROM facts WHERE key = ?", (key,)
)
row = cursor.fetchone()
return row[0] if row else None
def search(self, query: str) -> list:
"""Rechercher dans la mémoire."""
cursor = self.conn.execute(
"SELECT key, value FROM facts WHERE key LIKE ? OR value LIKE ?",
(f"%{query}%", f"%{query}%")
)
return cursor.fetchall()
def save_conversation(self, session_id: str, messages: list):
"""Sauvegarder une conversation."""
self.conn.execute(
"INSERT INTO conversations (session_id, messages, created_at) VALUES (?, ?, ?)",
(session_id, json.dumps(messages), datetime.now())
)
self.conn.commit()Orchestration multi-agents
Architecture superviseur
class SupervisorAgent:
"""Agent superviseur qui délègue à des agents spécialisés."""
def __init__(self, client):
self.client = client
self.specialists = {}
def register_specialist(self, name: str, agent, description: str):
"""Enregistrer un agent spécialisé."""
self.specialists[name] = {
"agent": agent,
"description": description
}
def run(self, objective: str) -> str:
"""Exécuter avec délégation aux spécialistes."""
# Créer le prompt avec les spécialistes disponibles
specialists_desc = "\n".join([
f"- {name}: {info['description']}"
for name, info in self.specialists.items()
])
system_prompt = f"""You are a supervisor agent. You can delegate tasks to specialists:
{specialists_desc}
To delegate, respond with:
DELEGATE: <specialist_name>
TASK: <task description>
When the task is complete, synthesize the results."""
messages = [{"role": "user", "content": objective}]
while True:
response = self.client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=4096,
system=system_prompt,
messages=messages
)
text = response.content[0].text
# Vérifier s'il y a une délégation
if "DELEGATE:" in text:
specialist_name = self._extract_specialist(text)
task = self._extract_task(text)
if specialist_name in self.specialists:
print(f"Delegating to {specialist_name}: {task}")
result = self.specialists[specialist_name]["agent"].run(task)
messages.append({"role": "assistant", "content": text})
messages.append({
"role": "user",
"content": f"Result from {specialist_name}:\n{result}"
})
else:
messages.append({
"role": "user",
"content": f"Unknown specialist: {specialist_name}"
})
else:
# Pas de délégation, réponse finale
return text
def _extract_specialist(self, text: str) -> str:
for line in text.split("\n"):
if line.startswith("DELEGATE:"):
return line.replace("DELEGATE:", "").strip()
return ""
def _extract_task(self, text: str) -> str:
for line in text.split("\n"):
if line.startswith("TASK:"):
return line.replace("TASK:", "").strip()
return ""Exemple d'utilisation multi-agents
# Créer les agents spécialisés
code_agent = Agent(client)
code_agent.set_system_prompt("You are a code analysis expert.")
code_agent.add_tool(read_file_tool)
docs_agent = Agent(client)
docs_agent.set_system_prompt("You are a documentation expert.")
docs_agent.add_tool(write_file_tool)
# Créer le superviseur
supervisor = SupervisorAgent(client)
supervisor.register_specialist("code_analyzer", code_agent, "Analyzes code quality and security")
supervisor.register_specialist("doc_writer", docs_agent, "Writes documentation")
# Exécuter
result = supervisor.run(
"Analyze the src/ directory and generate documentation for the main modules"
)
print(result)Bonnes pratiques
Sécurité
# Toujours valider les chemins de fichiers
import os
def safe_path(base_dir: str, user_path: str) -> str:
"""Valider que le chemin reste dans le répertoire autorisé."""
full_path = os.path.realpath(os.path.join(base_dir, user_path))
if not full_path.startswith(os.path.realpath(base_dir)):
raise ValueError("Path traversal detected")
return full_path
# Limiter les commandes shell autorisées
ALLOWED_COMMANDS = ["ls", "cat", "grep", "find", "wc"]
def safe_command(command: str) -> bool:
"""Vérifier que la commande est autorisée."""
cmd = command.split()[0]
return cmd in ALLOWED_COMMANDSGestion des erreurs
def robust_tool_execution(tool_func, *args, max_retries: int = 3, **kwargs):
"""Exécuter un outil avec retry."""
for attempt in range(max_retries):
try:
return tool_func(*args, **kwargs)
except Exception as e:
if attempt == max_retries - 1:
return f"Tool failed after {max_retries} attempts: {str(e)}"
time.sleep(2 ** attempt) # Exponential backoffLogging
import logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger("agent")
class LoggedAgent(Agent):
def run(self, objective: str, **kwargs) -> str:
logger.info(f"Starting agent with objective: {objective}")
try:
result = super().run(objective, **kwargs)
logger.info(f"Agent completed successfully")
return result
except Exception as e:
logger.error(f"Agent failed: {str(e)}")
raiseConclusion
Le Claude Agent SDK permet de créer des agents IA puissants et autonomes. Les clés du succès :
- Outils bien définis : descriptions claires, validation des entrées
- Mémoire efficace : conversation + persistance pour les tâches longues
- Sécurité : validation des chemins, commandes autorisées, timeouts
- Observabilité : logging, métriques, traces
Commencez simple avec un agent mono-tâche, puis évoluez vers l'orchestration multi-agents selon vos besoins.
Pour les fondamentaux des agents : MCP : le protocole standardisé pour l'IA agentique
Pour choisir votre assistant : Claude Code vs Cursor vs GitHub Copilot : comparatif 2026