Tool Calling in AI Agents

Comment les agents IA peuvent utiliser des outils et comment structurer ça en système d'ingénierie.

Le problème

Dans beaucoup de codebases, presque tout devient une entité.

On crée des classes ou des types pour :

  • une adresse
  • un montant
  • une période
  • un email
  • un pourcentage
  • un nom
  • un identifiant fiscal

Puis on leur ajoute :

  • un id
  • de la mutabilité
  • des méthodes de persistance implicites
  • des cycles de vie artificiels

Résultat :

  • le modèle devient inutilement lourd
  • des objets simples deviennent compliqués à comparer
  • des invariants basiques restent dispersés
  • le code manipule trop de structures “vivantes” alors qu’il manipule souvent juste des valeurs

L’erreur inverse existe aussi : traiter comme une simple valeur quelque chose qui a une vraie identité métier et un cycle de vie propre.

La distinction entre entity et value object sert à éviter ça.

L’idée simple

La différence utile est simple :

  • une entity est définie par son identité
  • un value object est défini par sa valeur

Autrement dit :

  • si deux objets représentent la même chose parce qu’ils ont le même identifiant, on est souvent face à une entity
  • si deux objets sont équivalents parce qu’ils portent exactement les mêmes données utiles, on est souvent face à un value object

Exemples simples :

  • un Customer, une Order, une Invoice sont souvent des entities
  • une Money, une EmailAddress, une DateRange, une PostalAddress sont souvent des value objects

Le point important est que cette distinction aide à mieux modéliser le métier. Ce n’est pas un exercice de vocabulaire.

Comment ça fonctionne

1. Une entity garde son identité dans le temps

Une entity peut changer sans cesser d’être la même chose.

Exemple :

  • un client change d’email
  • une commande change de statut
  • une facture passe de draft à paid

On considère toujours qu’il s’agit du même objet métier.

Ce qui reste stable n’est pas forcément sa donnée complète. C’est son identité.

2. Un value object est défini par ce qu’il contient

Un value object n’existe pas pour être suivi individuellement dans le temps. Il représente une valeur métier.

Exemples :

  • Money(100, "EUR")
  • EmailAddress("alice@example.com")
  • DateRange("2026-04-10", "2026-04-15")

Si deux value objects ont exactement la même valeur utile, ils sont interchangeables dans le modèle.

On ne se demande pas : “est-ce le même objet historique ?” On se demande : “est-ce la même valeur métier ?”

3. Les value objects sont souvent un bon endroit pour les invariants simples

Un value object est très utile pour encapsuler des règles locales.

Exemples :

  • un email doit avoir un format valide
  • un montant ne doit pas être négatif
  • une période doit avoir une date de fin après la date de début
  • une devise doit faire partie d’un ensemble connu

Plutôt que de laisser ces règles dispersées partout, on peut les concentrer dans le value object.

Schéma

Entity vs Value Object

Lecture utile :

  • une entity vit dans le temps
  • un value object porte surtout une signification par sa valeur
  • les deux ont des rôles différents dans le modèle

Exemple concret

Prenons un système de facturation.

On manipule :

  • une facture
  • un email client
  • un montant
  • une période de facturation

Facture comme entity

Une facture :

  • a une identité
  • change de statut
  • peut être payée
  • peut être annulée
  • est suivie dans le temps
export class Invoice {
  constructor(
    public readonly id: string,
    private status: "draft" | "issued" | "paid" | "cancelled",
    private customerEmail: EmailAddress,
    private total: Money
  ) {}

  issue(): void {
    if (this.status !== "draft") {
      throw new Error("Only a draft invoice can be issued");
    }

    this.status = "issued";
  }

  markAsPaid(): void {
    if (this.status !== "issued") {
      throw new Error("Only an issued invoice can be paid");
    }

    this.status = "paid";
  }
}

Ici, Invoice est clairement une entity.

Email et Money comme value objects

export class EmailAddress {
  constructor(public readonly value: string) {
    if (!value.includes("@")) {
      throw new Error("Invalid email address");
    }
  }

  equals(other: EmailAddress): boolean {
    return this.value === other.value;
  }
}

export class Money {
  constructor(
    public readonly amount: number,
    public readonly currency: string
  ) {
    if (amount < 0) {
      throw new Error("Amount cannot be negative");
    }
  }

  add(other: Money): Money {
    if (this.currency !== other.currency) {
      throw new Error("Currency mismatch");
    }

    return new Money(this.amount + other.amount, this.currency);
  }

  equals(other: Money): boolean {
    return this.amount === other.amount && this.currency === other.currency;
  }
}

Ici :

  • EmailAddress est défini par sa valeur
  • Money aussi
  • on ne suit pas leur cycle de vie comme celui d’une facture
  • ils servent à rendre le modèle plus précis

Mauvais exemple fréquent

Une erreur classique consiste à transformer un simple concept de valeur en pseudo-entité.

export class Address {
  constructor(
    public readonly id: string,
    public street: string,
    public city: string,
    public zipCode: string
  ) {}
}

Ce modèle pose plusieurs questions inutiles :

  • pourquoi Address a-t-elle une identité propre ici
  • a-t-on vraiment besoin de suivre cette adresse comme une chose indépendante
  • est-ce qu’on compare deux adresses par id ou par contenu

Dans beaucoup de cas, une adresse est mieux modélisée comme value object.

export class PostalAddress {
  constructor(
    public readonly street: string,
    public readonly city: string,
    public readonly zipCode: string
  ) {
    if (!street || !city || !zipCode) {
      throw new Error("Address is incomplete");
    }
  }

  equals(other: PostalAddress): boolean {
    return (
      this.street === other.street &&
      this.city === other.city &&
      this.zipCode === other.zipCode
    );
  }
}

C’est plus simple et souvent plus juste.

Comment reconnaître une entity

Quelques questions aident beaucoup.

L’objet a-t-il une identité métier propre

Exemples :

  • commande
  • facture
  • abonnement
  • réservation
  • client

Si oui, on est souvent côté entity.

L’objet change-t-il dans le temps tout en restant “le même”

Si oui, l’identité est probablement centrale.

Le métier a-t-il besoin de suivre son cycle de vie

Si l’objet passe par plusieurs états ou décisions importantes, c’est souvent un bon signal.

Comment reconnaître un value object

Le concept est-il surtout défini par ses données

Exemples :

  • montant
  • période
  • adresse email
  • pourcentage
  • distance
  • coordonnées

Deux instances avec les mêmes données sont-elles interchangeables

Si oui, on est souvent sur un value object.

Peut-on le rendre immuable sans gêner le métier

C’est souvent un très bon signal.

Pourquoi cette distinction compte en pratique

Quand on modélise mieux :

  • les entités portent les décisions et cycles de vie importants
  • les value objects encapsulent des règles locales utiles
  • les comparaisons deviennent plus claires
  • le modèle devient moins verbeux
  • les invariants simples arrêtent de fuiter partout

C’est un gain de lisibilité et de robustesse, pas seulement de style.

Exemple d’utilisation ensemble

export class Subscription {
  constructor(
    public readonly id: string,
    private readonly customerEmail: EmailAddress,
    private readonly monthlyPrice: Money
  ) {}

  getCustomerEmail(): EmailAddress {
    return this.customerEmail;
  }

  getMonthlyPrice(): Money {
    return this.monthlyPrice;
  }
}

Ici :

  • Subscription est une entity
  • EmailAddress et Money sont des value objects
  • le modèle exprime mieux les responsabilités de chacun

Points importants

  • Une entity est définie par son identité.
  • Un value object est défini par sa valeur.
  • Les value objects sont souvent un bon endroit pour des invariants simples.
  • Tous les concepts métier n’ont pas besoin d’un id.
  • L’immuabilité est souvent un bon choix pour les value objects.
  • Cette distinction aide à simplifier le modèle.

Erreurs fréquentes

Donner un id à tout

C’est souvent une trace du schéma SQL ou d’un réflexe technique, pas un vrai besoin métier.

Faire des value objects mutables

Ça complique inutilement le raisonnement et augmente les effets de bord.

Comparer des value objects par identité mémoire

Ce qui compte est leur valeur utile, pas leur emplacement mémoire.

Traiter comme value object quelque chose qui a un vrai cycle de vie

Par exemple une facture, une réservation ou un abonnement. Dans ce cas, on perd la notion d’identité métier.

Laisser des primitives partout

Utiliser directement string, number ou Date partout semble simple, mais laisse souvent les règles locales se disperser.

Conclusion

La distinction entre entity et value object aide à mieux représenter le métier.

Les entities portent une identité et un cycle de vie. Les value objects portent une valeur et des invariants locaux.

Le réflexe utile à retenir est simple :

si le concept compte pour ce qu’il est dans le temps, pense entity ; s’il compte pour ce qu’il vaut, pense value object.