Idee en 30 secondes
Le logging semantique (semantic logging) pour les agents signifie que les evenements ont non seulement un format JSON, mais aussi un sens stable.
Cela veut dire que des etapes equivalentes dans differents runs sont loggees de la meme facon : meme event, memes champs cles, memes statuts.
Cela rend les logs exploitables pour la recherche, les alertes, l'analytique et le debugging en production.
Probleme principal
Beaucoup d'equipes ecrivent deja des logs structures, mais cela ne suffit souvent pas.
Dans differents services et versions d'agent, un meme evenement peut avoir des noms et champs differents :
tool_called, call_tool, tool.invoke.
Resultat : les logs existent, mais comparer les runs est difficile.
Le logging semantique est une solution au niveau du design des evenements, pas seulement une couche technique sur les logs.
En production, cela ressemble souvent a :
- une requete dans le systeme de logs renvoie trop de bruit ;
- les alertes deviennent instables a cause de noms d'evenements differents ;
- en incident, il faut faire une correspondance manuelle entre plusieurs formats d'evenements.
C'est pourquoi les systemes agentiques ont besoin d'un vocabulaire d'evenements partage et d'un schema de champs stable.
Comment ca fonctionne
Le logging semantique repose sur trois elements :
- un vocabulaire d'evenements coherent (
event taxonomy) ; - des champs stables pour chaque evenement ;
- des valeurs normalisees (
status,error_class,stop_reason).
event taxonomy est un contrat entre runtime, logs, dashboards et alertes.
Si ce contrat est casse, l'observability se casse aussi.
status utilise en general un ensemble limite de valeurs (par exemple : ok, error, timeout, cancelled; dans cet exemple on utilise la version simplifiee : ok / error).
Le logging semantique ne remplace pas le tracing, il le complete. Il rend les evenements non seulement visibles, mais aussi comparables entre services et releases. Le logging repond a "que s'est-il passe", le tracing repond a "comment cela s'est passe", et le semantic logging repond a "ce que cela signifie".
Vocabulaire minimal des evenements
| Evenement | Sens semantique | Champs cles |
|---|---|---|
| run_started | un nouveau run a demarre | run_id, trace_id, request_id, task_hash |
| agent_step | l'agent passe a l'etape suivante | step_index, step_type, actor |
| tool_call | debut d'un appel d'outil | tool_name, args_hash |
| tool_result | resultat d'un appel d'outil | tool_name, latency_ms, status, error_class |
| llm_result | resultat d'une etape modele | model, token_usage, latency_ms, status |
| policy_decision | decision policy/guardrails | rule_id, decision, reason_code |
| run_finished | run termine | stop_reason, total_steps, total_latency_ms |
policy_decision permet de voir non seulement les erreurs, mais aussi les causes de blocage et decisions de guardrails.
event_version permet de faire evoluer le schema d'evenements sans casser dashboards et alertes existants.
Quand l'utiliser
Le logging semantique complet n'est pas toujours necessaire.
Pour un scenario single-shot simple sans tools ni boucle d'execution, des logs de base suffisent souvent.
Mais le logging semantique devient critique quand :
- le systeme contient plusieurs agents ou services ;
- il faut construire des alertes et dashboards stables ;
- il est important de comparer le comportement entre releases ;
- les incidents doivent etre analyses vite, sans mapping manuel d'evenements.
Exemple d'implementation
Ci-dessous un exemple simplifie de logging semantique dans la runtime. L'idee est simple : logger uniquement des evenements du vocabulaire valide et normaliser les valeurs de champs.
import hashlib
import json
import logging
import time
import uuid
from enum import StrEnum
logger = logging.getLogger("agent")
class EventName(StrEnum):
RUN_STARTED = "run_started"
AGENT_STEP = "agent_step"
TOOL_CALL = "tool_call"
TOOL_RESULT = "tool_result"
LLM_RESULT = "llm_result"
POLICY_DECISION = "policy_decision"
RUN_FINISHED = "run_finished"
def stable_hash(value):
# default=str donne une compatibilite de base
# dans les systemes critiques, preferer une serialisation explicite (par ex. ISO 8601)
payload = json.dumps(
value,
sort_keys=True,
ensure_ascii=False,
default=str,
).encode("utf-8")
return hashlib.sha256(payload).hexdigest()
def normalize_status(ok):
return "ok" if ok else "error"
def normalize_error(error):
if error is None:
return None
return type(error).__name__
def log_semantic(event_name: EventName, **fields):
logger.info(
event_name.value,
extra={
"event": event_name.value,
"event_version": 1,
"timestamp_ms": int(time.time() * 1000),
**fields,
},
)
def run_agent(agent, task, request_id=None):
run_id = str(uuid.uuid4())
trace_id = str(uuid.uuid4())
started_at = time.time()
step_index = 0
stop_reason = "max_steps"
run_status = "ok"
log_semantic(
EventName.RUN_STARTED,
run_id=run_id,
trace_id=trace_id,
request_id=request_id,
task_hash=stable_hash(task),
)
try:
for step in agent.iter(task): # step: reasoning ou tool execution
step_index += 1
step_started_at = time.time()
step_type = step.type
tool_name = getattr(step, "tool_name", None)
log_semantic(
EventName.AGENT_STEP,
run_id=run_id,
trace_id=trace_id,
step_index=step_index,
step_type=step_type,
actor=getattr(step, "actor", "agent_runtime"),
)
if step_type == "tool_call":
args = getattr(step, "args", {})
log_semantic(
EventName.TOOL_CALL,
run_id=run_id,
trace_id=trace_id,
step_index=step_index,
tool_name=tool_name,
args_hash=stable_hash(args),
)
try:
result = step.execute()
latency_ms = int((time.time() - step_started_at) * 1000)
if step_type == "tool_call":
log_semantic(
EventName.TOOL_RESULT,
run_id=run_id,
trace_id=trace_id,
step_index=step_index,
tool_name=tool_name,
latency_ms=latency_ms,
status=normalize_status(True),
error_class=None,
)
else:
log_semantic(
EventName.LLM_RESULT,
run_id=run_id,
trace_id=trace_id,
step_index=step_index,
model=getattr(step, "model", None),
token_usage=getattr(result, "token_usage", None),
latency_ms=latency_ms,
status=normalize_status(True),
)
# policy_decision est loggee apres l'execution du step
# (quand resultat ou erreur est connu)
if getattr(step, "policy_decision", None) is not None:
decision = step.policy_decision
log_semantic(
EventName.POLICY_DECISION,
run_id=run_id,
trace_id=trace_id,
step_index=step_index,
rule_id=decision.rule_id,
decision=decision.value,
reason_code=decision.reason_code,
)
except Exception as error:
latency_ms = int((time.time() - step_started_at) * 1000)
run_status = "error"
if step_type == "tool_call":
stop_reason = "tool_error"
log_semantic(
EventName.TOOL_RESULT,
run_id=run_id,
trace_id=trace_id,
step_index=step_index,
tool_name=tool_name,
latency_ms=latency_ms,
status=normalize_status(False),
error_class=normalize_error(error),
)
else:
stop_reason = "step_error"
log_semantic(
EventName.LLM_RESULT,
run_id=run_id,
trace_id=trace_id,
step_index=step_index,
model=getattr(step, "model", None),
latency_ms=latency_ms,
status=normalize_status(False),
error_class=normalize_error(error),
)
if getattr(step, "policy_decision", None) is not None:
decision = step.policy_decision
log_semantic(
EventName.POLICY_DECISION,
run_id=run_id,
trace_id=trace_id,
step_index=step_index,
rule_id=decision.rule_id,
decision=decision.value,
reason_code=decision.reason_code,
)
raise
if result.is_final:
stop_reason = "completed"
break
finally:
log_semantic(
EventName.RUN_FINISHED,
run_id=run_id,
trace_id=trace_id,
status=run_status,
stop_reason=stop_reason,
total_steps=step_index,
total_latency_ms=int((time.time() - started_at) * 1000),
)
En production, ces evenements sont generalement envoyes vers un systeme de logs centralise (par exemple ELK, Datadog ou ClickHouse), ou ils servent a construire requetes, dashboards et alertes.
Par exemple, un semantic event en JSON peut ressembler a :
{
"timestamp_ms": 1774106220000,
"event": "policy_decision",
"event_version": 1,
"run_id": "run_9fd2",
"trace_id": "tr_9fd2",
"step_index": 3,
"rule_id": "email_external_domain",
"decision": "deny",
"reason_code": "missing_user_confirmation"
}
Erreurs typiques
Meme avec des logs structures, le logging semantique casse souvent a cause des erreurs ci-dessous.
Evenements nommes differemment selon les services
Quand la meme action a des noms event differents, les requetes deviennent instables.
Cela rend plus difficile la detection d'un echec d'outil ou d'une phase precoce de spam d'outils.
Texte libre au lieu de champs normalises
Des champs comme "error": "something failed" sont peu exploitables en analytique.
Mieux vaut des champs separes status, error_class, reason_code avec des valeurs fixes.
Absence de event_version
Sans version d'evenement, les changements de schema cassent silencieusement dashboards, saved queries et alertes. L'evolution du schema doit donc etre explicite.
Raw prompts ou raw args logges sans redaction
C'est un risque de securite et de conformite. Plus sur : stocker un hash ou une version anonymisee des champs.
Auto-verification
Ci-dessous, une checklist courte de base pour le logging semantique avant release.
Progression: 0/9
⚠ L'observability de base manque
Le système sera difficile à déboguer en production. Commencez par run_id, structured logs et tracing des tool calls.
FAQ
Q : Quelle difference entre semantic logging et JSON-logging classique ?
R : JSON-logging definit seulement le format. Semantic logging definit le sens : noms d'evenements stables, champs stables, valeurs normalisees.
Q : Est-ce que semantic logging remplace le tracing ?
R : Non. Le tracing montre le chemin d'execution, tandis que semantic logging rend les evenements de ce chemin exploitables pour recherche, alertes et analytique.
Q : Quel minimum de semantic logging faut-il pour un premier release production ?
R : Vocabulaire de base (run_started, tool_call, tool_result, run_finished), run_id/trace_id stables, status, error_class et stop_reason.
Q : Faut-il migrer tous les anciens logs immediatement ?
R : Non. Mieux vaut commencer par les nouveaux evenements et les chemins critiques, puis migrer progressivement les anciens formats.
Pages liees
Suite du sujet :
- Observability pour agents IA — modele global de tracing, logging et metriques.
- Logging d'agents — quels evenements capturer dans la runtime.
- Tracing d'agent — comment voir le chemin complet d'un run.
- Tracing distribue d'agents — comment connecter les evenements entre services.
- Debugging des runs d'agent — analyse pratique des incidents.