En tant qu'ingénieur spécialisé dans les architectures multi-agents, j'ai passé les deux dernières années à construire des pipelines LangGraph en production. Ce que j'ai appris, souvent à mes dépens, c'est que le monitoring et le debugging représentent 60% du temps de développement. Dans ce tutoriel complet, je vais vous montrer comment maîtriser la visualisation des trajectoires d'exécution de vos agents.

Le contexte économique qui change tout

Avant de plonger dans le code, posons les bases financières. En 2026, les prix des modèles de langage ont considérablement évolué :

Sur HolySheep AI, je bénéficie d'un taux de change avantageux avec 1¥ = 1$, soit une économie de plus de 85% par rapport aux tarifs officiels. De plus, la latence moyenne est inférieure à 50ms, et j'ai reçu des crédits gratuits pour mes premiers tests.

Analyse comparative des coûts pour 10M tokens/mois

Calculons ensemble le coût mensuel réel selon le modèle utilisé :

ModèlePrix/MTokCoût 10M tokensLatence moyenne
DeepSeek V3.20,42$4,20$<50ms
Gemini 2.5 Flash2,50$25,00$<80ms
GPT-4.18,00$80,00$<100ms
Claude Sonnet 4.515,00$150,00$<120ms

Vous voyez l'impact ? En choisissant DeepSeek V3.2 sur HolySheep, je réduis mes coûts de 97% par rapport à Claude Sonnet 4.5 sur les API traditionnelles. C'est cette efficacité qui m'a permis de tester intensivement mes agents LangGraph sans crainte de factures explosives.

Architecture de monitoring LangGraph

LangGraph propose nativement un système de checkpointer et de store pour sauvegarder les états d'exécution. Voici mon architecture de monitoring complète que j'utilise en production.

Configuration de HolySheep avec LangGraph

"""
Configuration complète de l'environnement LangGraph avec HolySheep AI
Installation requise: pip install langgraph langgraph-sdk openai
"""

import os
from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, END
from langgraph.checkpoint.sqlite import SqliteSaver
from langgraph_sdk import get_client
from langgraph.callbacks.tracing.opentelemetry import OpenTelemetryTracer
from opentelemetry import trace

Configuration HolySheep - MON CLAVIER EN PROD

HOLYSHEEP_API_KEY = os.getenv("HOLYSHEEP_API_KEY", "YOUR_HOLYSHEEP_API_KEY") HOLYSHEEP_BASE_URL = "https://api.holysheep.ai/v1"

Configuration du monitoring

CHECKPOINT_DB = "checkpoints/agent_state.db" METRICS_FILE = "metrics/execution_metrics.json"

Modèles disponibles avec leurs coûts 2026

MODEL_CATALOG = { "deepseek": { "model": "deepseek-chat", "cost_per_mtok": 0.42, # $0.42/MTok - Le plus économique "latency_ms": 45, "provider": "holysheep" }, "gemini_flash": { "model": "gemini-2.0-flash", "cost_per_mtok": 2.50, "latency_ms": 75, "provider": "holysheep" }, "gpt4": { "model": "gpt-4.1", "cost_per_mtok": 8.00, "latency_ms": 95, "provider": "holysheep" }, "claude": { "model": "claude-sonnet-4-5", "cost_per_mtok": 15.00, "latency_ms": 110, "provider": "holysheep" } }

Configuration du client HolySheep

def get_holysheep_client(): """Initialise le client LangGraph avec HolySheep""" return get_client( url=HOLYSHEEP_BASE_URL, api_key=HOLYSHEEP_API_KEY ) print(f"✅ Configuration chargée: {len(MODEL_CATALOG)} modèles disponibles") print(f"💰 Économie moyenne vs API officielles: 85%+ (taux HolySheep: 1¥=1$)")

Implémentation du système de traçage visuel

"""
Système de visualisation des trajectoires d'exécution Agent
Inclut: timestamps, tokens consommés, décisions de routing, erreurs
"""

import json
import time
from datetime import datetime
from dataclasses import dataclass, field, asdict
from typing import List, Dict, Any, Optional
from collections import defaultdict
from langgraph.graph import StateGraph, END
from langgraph.prebuilt import ToolNode

@dataclass
class ExecutionStep:
    """Représente une étape d'exécution dans l'agent"""
    step_id: str
    timestamp: float
    node_name: str
    input_tokens: int = 0
    output_tokens: int = 0
    latency_ms: float = 0.0
    decision: Optional[str] = None
    error: Optional[str] = None
    metadata: Dict[str, Any] = field(default_factory=dict)
    
    def to_dict(self) -> Dict:
        return asdict(self)

class AgentTrajectoryTracker:
    """Tracker complet pour visualiser l'exécution de l'agent"""
    
    def __init__(self, agent_name: str):
        self.agent_name = agent_name
        self.steps: List[ExecutionStep] = []
        self.total_input_tokens = 0
        self.total_output_tokens = 0
        self.start_time = time.time()
        self._step_counter = 0
        self.cost_by_model = defaultdict(float)
        
    def record_step(
        self,
        node_name: str,
        input_tokens: int = 0,
        output_tokens: int = 0,
        latency_ms: float = 0.0,
        decision: Optional[str] = None,
        error: Optional[str] = None,
        model: str = "deepseek",
        **metadata
    ) -> str:
        """Enregistre une étape d'exécution avec métadonnées complètes"""
        self._step_counter += 1
        step_id = f"{self.agent_name}_step_{self._step_counter}_{int(time.time()*1000)}"
        
        step = ExecutionStep(
            step_id=step_id,
            timestamp=time.time(),
            node_name=node_name,
            input_tokens=input_tokens,
            output_tokens=output_tokens,
            latency_ms=latency_ms,
            decision=decision,
            error=error,
            metadata=metadata
        )
        
        self.steps.append(step)
        self.total_input_tokens += input_tokens
        self.total_output_tokens += output_tokens
        
        # Calcul du coût selon le modèle utilisé
        if model in MODEL_CATALOG:
            cost_per_input = MODEL_CATALOG[model]["cost_per_mtok"] / 1_000_000
            cost_per_output = MODEL_CATALOG[model]["cost_per_mtok"] / 1_000_000
            step_cost = (input_tokens * cost_per_input) + (output_tokens * cost_per_output)
            self.cost_by_model[model] += step_cost
        
        return step_id
    
    def get_trajectory_summary(self) -> Dict[str, Any]:
        """Génère un résumé complet de la trajectoire"""
        total_cost = sum(self.cost_by_model.values())
        total_latency = sum(s.latency_ms for s in self.steps)
        execution_time = time.time() - self.start_time
        
        return {
            "agent_name": self.agent_name,
            "total_steps": len(self.steps),
            "total_input_tokens": self.total_input_tokens,
            "total_output_tokens": self.total_output_tokens,
            "total_cost_usd": round(total_cost, 4),
            "total_latency_ms": round(total_latency, 2),
            "execution_time_s": round(execution_time, 2),
            "cost_breakdown_by_model": dict(self.cost_by_model),
            "steps": [s.to_dict() for s in self.steps]
        }
    
    def visualize_ascii(self) -> str:
        """Génère une visualisation ASCII de la trajectoire"""
        lines = [
            f"\n{'='*70}",
            f"🔍 Trajectoire d'exécution: {self.agent_name}",
            f"{'='*70}",
            f"📊 Métriques globales:",
            f"   • Étapes: {len(self.steps)}",
            f"   • Tokens input: {self.total_input_tokens:,}",
            f"   • Tokens output: {self.total_output_tokens:,}",
            f"   • Coût total: ${sum(self.cost_by_model.values()):.4f}",
            f"   • Latence totale: {sum(s.latency_ms for s in self.steps):.1f}ms",
            f"\n📍 Flux d'exécution:",
        ]
        
        for i, step in enumerate(self.steps, 1):
            status_icon = "✅" if not step.error else "❌"
            decision_info = f" → {step.decision}" if step.decision else ""
            error_info = f" [ERREUR: {step.error}]" if step.error else ""
            
            lines.append(
                f"   {status_icon} {i}. {step.node_name}{decision_info}{error_info}"
            )
            lines.append(f"      └─ Tokens: {step.input_tokens}/{step.output_tokens} | "
                        f"Latence: {step.latency_ms:.1f}ms")
        
        return "\n".join(lines)

Démonstration

tracker = AgentTrajectoryTracker("AgentRechercheMultiSources") tracker.record_step("start", model="deepseek", latency_ms=12) tracker.record_step("analyze_query", input_tokens=150, output_tokens=200, latency_ms=45, model="deepseek") tracker.record_step("route_decision", decision="web_search", latency_ms=8, model="deepseek") tracker.record_step("web_search", input_tokens=300, output_tokens=450, latency_ms=180, model="gemini_flash") tracker.record_step("synthesize", input_tokens=600, output_tokens=350, latency_ms=95, model="deepseek", decision="final_answer") print(tracker.visualize_ascii()) print(f"\n💰 Coût détaillé: {tracker.get_trajectory_summary()['cost_breakdown_by_model']}")

Intégration avec l'agent LangGraph complet

"""
Agent LangGraph complet avec monitoring intégré via HolySheep
Inclut: routing intelligent, outils multiples, gestion d'erreurs robuste
"""

import os
from typing import Literal
from langgraph.graph import StateGraph, START, END, MessagesState
from langgraph.prebuilt import ToolNode, tools_condition
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage
from langchain_holysheep import ChatHolySheep  # Client HolySheep pour LangChain

Initialisation du client HolySheep

llm = ChatHolySheep( model="deepseek-chat", # Modèle économique par défaut holysheep_api_key=os.getenv("HOLYSHEEP_API_KEY", "YOUR_HOLYSHEEP_API_KEY"), base_url="https://api.holysheep.ai/v1", temperature=0.7 )

Outils disponibles pour l'agent

from langchain_community.tools import WikipediaQueryRun, ArxivQueryRun from langchain_community.utilities import WikipediaAPIWrapper, ArxivAPIWrapper wikipedia = WikipediaQueryRun(api_wrapper=WikipediaAPIWrapper()) arxiv = ArxivQueryRun(api_wrapper=ArxivAPIWrapper()) tools = [wikipedia, arxiv] llm_with_tools = llm.bind_tools(tools)

Système de monitoring

tracker = AgentTrajectoryTracker("ProductionAgent_v2") class AgentState(MessagesState): """État de l'agent avec métadonnées de traçage""" step_counter: int total_cost: float routing_decision: str error_count: int def create_monitored_agent(): """Crée un agent LangGraph avec monitoring complet""" # Définir le graphe graph = StateGraph(AgentState) def should_continue(state: AgentState) -> Literal["tools", "end"]: """Décide si l'agent doit utiliser des outils ou terminer""" messages = state["messages"] last_message = messages[-1] # Surveillance de la décision de routing has_tools = hasattr(last_message, "tool_calls") and last_message.tool_calls routing = "tools" if has_tools else "end" tracker.record_step( node_name="router", decision=routing, model="deepseek", latency_ms=8 ) return routing # Nœud: Appel au modèle def call_model(state: AgentState): """Appelle le modèle LLM via HolySheep avec tracking""" messages = state["messages"] # Estimation des tokens d'entrée input_text = "\n".join([str(m.content) for m in messages]) input_tokens = len(input_text) // 4 # Approximation start = time.time() try: response = llm_with_tools.invoke(messages) latency = (time.time() - start) * 1000 # Estimation des tokens de sortie output_tokens = len(str(response.content)) // 4 tracker.record_step( node_name="llm_call", input_tokens=input_tokens, output_tokens=output_tokens, latency_ms=latency, model="deepseek", temperature=0.7 ) return {"messages": [response]} except Exception as e: tracker.record_step( node_name="llm_call", error=str(e), model="deepseek", latency_ms=(time.time() - start) * 1000 ) raise # Nœud: Exécution des outils tool_node = ToolNode(tools) # Construire le graphe graph.add_node("agent", call_model) graph.add_node("tools", tool_node) graph.add_edge(START, "agent") graph.add_conditional_edges( "agent", should_continue, {"tools": "tools", "end": END} ) graph.add_edge("tools", "agent") return graph.compile()

Exécution de l'agent avec monitoring

agent = create_monitored_agent() print("🤖 Agent créé avec monitoring HolySheep activé") print("📊 Coût DeepSeek V3.2 via HolySheep: 0,42$/MTok (économie 85%+)")

Test de l'agent

test_input = { "messages": [ HumanMessage(content="Explique-moi les dernières avancées en informatique quantique") ] }

Exécution avec traçage

for event in agent.stream(test_input, config={"recursion_limit": 10}): print(event)

Visualisation avancée avec TensorBoard et LangSmith

Pour une visualisation encore plus poussée, j'utilise une configuration combinée qui génère des traces compatibles avec LangSmith (via HolySheep) et des métriques exportables vers Prometheus/Grafana.

"""
Export des métriques LangGraph vers plusieurs formats de monitoring
Compatible: LangSmith, Prometheus, CSV, et dashboard HTML custom
"""

import json
import csv
from pathlib import Path
from datetime import datetime
from typing import List, Dict
from dataclasses import asdict

class MetricsExporter:
    """Exporteur multi-format pour les métriques d'agent"""
    
    def __init__(self, output_dir: str = "monitoring_exports"):
        self.output_dir = Path(output_dir)
        self.output_dir.mkdir(parents=True, exist_ok=True)
        self.session_id = datetime.now().strftime("%Y%m%d_%H%M%S")
    
    def export_to_langsmith_format(self, trajectory: Dict) -> str:
        """Exporte au format LangSmith pour visualisation web"""
        
        langsmith_traces = {
            "session_id": self.session_id,
            "timestamp": datetime.now().isoformat(),
            "runs": []
        }
        
        for step in trajectory.get("steps", []):
            run = {
                "name": step["node_name"],
                "start_time": step["timestamp"],
                "end_time": step["timestamp"] + (step["latency_ms"] / 1000),
                "extra": {
                    "tokens_in": step["input_tokens"],
                    "tokens_out": step["output_tokens"],
                    "latency_ms": step["latency_ms"],
                    "decision": step.get("decision"),
                    "error": step.get("error")
                }
            }
            langsmith_traces["runs"].append(run)
        
        output_file = self.output_dir / f"langsmith_{self.session_id}.json"
        with open(output_file, "w") as f:
            json.dump(langsmith_traces, f, indent=2)
        
        return str(output_file)
    
    def export_to_prometheus(self, trajectory: Dict) -> str:
        """Exporte au format Prometheus pour Grafana"""
        
        lines = [
            "# HELP agent_execution_steps_total Total steps executed",
            "# TYPE agent_execution_steps_total counter",
            f'agent_execution_steps_total{{agent="{trajectory["agent_name"]}"}} {trajectory["total_steps"]}',
            "",
            "# HELP agent_tokens_total Total tokens processed",
            "# TYPE agent_tokens_total counter",
            f'agent_tokens_total{{type="input","agent="{trajectory["agent_name"]}"}} {trajectory["total_input_tokens"]}',
            f'agent_tokens_total{{type="output","agent="{trajectory["agent_name"]}"}} {trajectory["total_output_tokens"]}',
            "",
            "# HELP agent_cost_total Total cost in USD",
            "# TYPE agent_cost_total gauge",
            f'agent_cost_total{{agent="{trajectory["agent_name"]}"}} {trajectory["total_cost_usd"]}',
            "",
            "# HELP agent_latency_ms Total latency in ms",
            "# TYPE agent_latency_ms gauge",
            f'agent_latency_ms{{agent="{trajectory["agent_name"]}"}} {trajectory["total_latency_ms"]}'
        ]
        
        output_file = self.output_dir / f"prometheus_{self.session_id}.txt"
        with open(output_file, "w") as f:
            f.write("\n".join(lines))
        
        return str(output_file)
    
    def export_to_csv(self, trajectory: Dict) -> str:
        """Exporte les étapes individuelles en CSV pour analyse Excel/Python"""
        
        output_file = self.output_dir / f"steps_{self.session_id}.csv"
        
        with open(output_file, "w", newline="") as f:
            writer = csv.DictWriter(f, fieldnames=[
                "step_id", "timestamp", "node_name", "input_tokens", 
                "output_tokens", "latency_ms", "decision", "error"
            ])
            writer.writeheader()
            
            for step in trajectory.get("steps", []):
                writer.writerow(step)
        
        return str(output_file)
    
    def generate_html_dashboard(self, trajectories: List[Dict]) -> str:
        """Génère un dashboard HTML interactif"""
        
        total_cost = sum(t["total_cost_usd"] for t in trajectories)
        total_steps = sum(t["total_steps"] for t in trajectories)