> Niveau 1 de Agent Harness Patterns, un parcours de patterns sur le fonctionnement des agents d'IA. Version web : https://harnesspatterns.dev/fr/patterns/what-is-an-agent · Tous les patterns (en anglais) : https://harnesspatterns.dev/llms.txt

# Qu'est-ce qu'un agent ?

On appelle « agent » presque tout ce qui contient un modèle de langage. La définition utile est bien plus étroite, et elle tient en une question : qui décide de l'étape suivante ? Dans un agent, c'est le modèle.

## Le problème

Un chatbot, un script qui appelle deux fois un modèle et un système qui corrige des bugs tout seul : on les appelle tous des agents. Difficile alors de savoir ce que vous construisez, et encore plus de choisir le bon outil pour le travail.

L'animation est un petit jeu d'aventure. Un villageois demande : « J'ai perdu la clé du coffre du village. Où est-elle ? ». Le jeu a deux fonctions qui peuvent aider : `ask_villager` demande à quelqu'un ce qu'il a vu, et `search_area` fouille un endroit de la carte. Regardez qui décide laquelle s'exécute, et quand.

## Ce qui en fait un agent

Un agent, ce n'est pas « un LLM avec des outils », ni « un workflow malin ». C'est un système où **le modèle choisit le flux de contrôle** : quel outil appeler, dans quel ordre et quand s'arrêter. Votre code ne dit jamais « interroge le pêcheur, puis fouille le chêne ». Le modèle lit chaque résultat et décide de l'étape suivante.

La boucle qui rend cela possible, c'est le niveau suivant.

## Dans l'animation

- **Le villageois** (votre app): La question part d'une maison du village, et la réponse y revient.
- **Astor** (la boucle): La boucle de l'agent, avec le bandonéon des messages sur le dos. Il va là où les notes de l'Oracle l'envoient, et nulle part ailleurs.
- **L'Oracle** (LLM): Le modèle, dans une grotte entre deux feux. Il ne sort jamais : il ne sait que ce qu'il y a dans le bandonéon. Sans outils, il ne peut que parler, comme Petrus l'Inerte.
- **Le quai et les bois** (outils): `ask_villager` et `search_area` : vos propres fonctions. Un écran reste dans le noir jusqu'à ce qu'un chemin y mène.
- **Les écrans éclairés** (flux de contrôle): Tout le pattern en une image, aussi sur la mini-carte. Chaque chemin n'apparaît que quand l'Oracle demande cet outil, après avoir lu le dernier résultat. Dans un workflow, votre code les aurait tous tracés avant même que quelqu'un pose la question. La montagne reste dans le noir : le modèle n'en a jamais eu besoin.
- **Rubis et cœurs** (tokens, maxTurns): Chaque visite à l'Oracle coûte des rubis, parce que tout le bandonéon est relu, et un cœur du budget de tours. Les chiffres sont illustratifs.

## Le code

**Avec astorlm :** Enveloppez vos propres fonctions avec `tool()` et donnez-les à l'agent. La boucle est intégrée : le modèle choisit quels outils appeler, dans quel ordre, et quand il en sait assez pour répondre.

**À partir de zéro :** Les fonctions de votre jeu, données au modèle comme outils. La boucle elle-même est celle du niveau 2 ; la seule nouveauté, ce sont les outils qu'il reçoit.

**Avec astorlm**

```ts
import { OpenAIProvider, createLocalAgent, tool } from 'astorlm'
import { z } from 'zod'

// Any OpenAI-compatible endpoint: OpenAI, Ollama, LM Studio, vLLM, a proxy…
const provider = new OpenAIProvider({
  baseURL: 'http://localhost:11434/v1', // e.g. Ollama's default address
  model: 'your-model', // e.g. 'llama3.1', 'gpt-4o-mini'
  apiKey: 'YOUR_API_KEY', // local servers usually ignore it
})

// Your own game functions, wrapped as tools: a name, a description and an input schema.
const askVillager = tool({
  name: 'ask_villager',
  description: 'Ask someone in the village what they saw. Returns what they say.',
  schema: z.object({ name: z.string().describe('Who to ask, e.g. "fisher" or "baker"') }),
  execute: async ({ name }) => world.villager(name).say(),
})

const searchArea = tool({
  name: 'search_area',
  description: 'Search one spot on the map. Returns what is found there, if anything.',
  schema: z.object({ area: z.string().describe('A named spot, e.g. "old oak" or "bridge"') }),
  execute: async ({ area }) => world.search(area),
})

// The model decides which tools to call, in what order, and when to stop.
const agent = await createLocalAgent({
  provider,
  tools: [askVillager, searchArea],
  maxTurns: 5, // a cap on the laps, in case it never settles
})

await agent.run('I lost the key to the village chest. Where is it?') // "In the crow's nest on the old oak"
```

**TypeScript**

```ts
// An agent that finds a villager's lost key. Plain TypeScript, no SDK.

// Your game. In a real one these read the world state and the characters' dialogue.
async function askVillager(name: string) {
  const seen: Record<string, string> = {
    fisher: 'A crow flew off with something shiny, toward the old oak in the woods.',
  }
  return seen[name] ?? `The ${name} saw nothing.`
}
async function searchArea(area: string) {
  return area === 'old oak' ? "In the crow's nest: a small brass key." : `Nothing at the ${area}.`
}

// The same functions, as tools the model can ask for. Each one returns text.
const tools = {
  ask_villager: ({ name }: { name: string }) => askVillager(name),
  search_area: ({ area }: { area: string }) => searchArea(area),
}

// runAgent is the loop from level 2, with the tools passed in. Your code never says
// "ask the fisher, then search the oak": the model picks each step after reading the last result.
export async function agent(question: string): Promise<string> {
  return runAgent(question, tools)
}
```

**Python**

```python
# An agent that finds a villager's lost key. Standard library only, no SDK.

# Your game. In a real one these read the world state and the characters' dialogue.
def ask_villager(name):
    seen = {"fisher": "A crow flew off with something shiny, toward the old oak in the woods."}
    return seen.get(name, f"The {name} saw nothing.")

def search_area(area):
    return "In the crow's nest: a small brass key." if area == "old oak" else f"Nothing at the {area}."

# The same functions, as tools the model can ask for. Each one returns text.
TOOLS = {
    "ask_villager": lambda args: ask_villager(args["name"]),
    "search_area": lambda args: search_area(args["area"]),
}

# run_agent is the loop from level 2, with the tools passed in. Your code never says
# "ask the fisher, then search the oak": the model picks each step after reading the last result.
def agent(question):
    return run_agent(question, TOOLS)
```

## Quand en utiliser un, et ce que ça coûte

- **Utilisez-en un quand vous ne pouvez pas écrire les étapes à l'avance.** La clé pourrait être dans un nid, sous un pont ou déjà vendue à la boutique : dans du code, chaque nouveau cas est une branche de plus. L'agent les gère avec la même boucle, tant qu'il a les outils.
- **Chaque étape est un appel au modèle.** Deux outils ont signifié trois appels ici. Plus d'étapes, c'est plus de latence et plus de tokens : plafonnez les tours avec `maxTurns`.
- **La même question peut prendre un autre chemin.** Journalisez le chemin choisi par le modèle, pour comprendre pourquoi une réponse est sortie comme elle est sortie.
- **Les outils sont la frontière.** Le modèle ne peut faire que ce que vos outils permettent. Ce que vous lui donnez, c'est ce qu'il peut casser.

## Patterns liés

- [2 · La boucle de l'agent](https://harnesspatterns.dev/fr/patterns/agent-loop.md)
- [3 · Concevoir un outil](https://harnesspatterns.dev/fr/patterns/designing-a-tool.md)
- [12 · Planifier et réfléchir](https://harnesspatterns.dev/fr/patterns/plan-and-reflect.md)
