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

# Votre boîte à outils

Avant tout agent, quatre pièces. Un modèle qui ne fait que lire et écrire du texte, un system prompt qui lui dit qui être, une liste de messages que votre code renvoie à chaque fois, et des outils qu'il peut demander. Tout ce qui suit sur cette carte est construit avec elles.

## Les quatre pièces

- Le modèle `model`
   Le personnage
   Lit du texte, écrit du texte. Il sait beaucoup de choses grâce à son entraînement, mais rien de votre app, de votre utilisateur ni d'aujourd'hui.
- Le system prompt `system`
   Équipement
   Des instructions placées en tête de chaque requête : qui il est, ses règles, son ton et les faits qu'il ne peut pas connaître seul.
- Les messages `messages[]`
   Sac
   La conversation jusqu'ici. Votre code garde cette liste et l'envoie en entier à chaque appel.
- Les outils `tools`
   Compétences
   Des cartes qui décrivent des fonctions que le modèle peut demander. Il ne peut que demander : c'est votre code qui les exécute.

## Le modèle ne se souvient de rien

C'est ce qui surprend le plus. Un modèle n'a aucune mémoire d'un appel à l'autre. Chaque requête repart de zéro, et il ne sait que ce qui se trouve dans cette requête : le system prompt, les messages et la liste d'outils.

Dans l'animation, la deuxième question arrive seule et l'Oracle demande « quelle ville ? », alors qu'on vient de le lui dire. Il ne « se souvient » que lorsque votre code renvoie les messages précédents. Les applications de chat ont l'air de se souvenir parce qu'elles renvoient toute la conversation à chaque fois.

Deux conséquences. L'historique est à vous : c'est à vous de le garder, de l'élaguer et de le stocker. Et chaque message que vous gardez est renvoyé à chaque appel, donc une longue conversation coûte un peu plus à chaque fois.

## Du texte, ou une demande

Avec des outils dans sa liste, une réponse peut être l'une de deux choses : du texte pour l'utilisateur, ou une demande d'appel d'un outil avec certaines entrées. Le modèle n'exécute jamais rien. Il écrit `get_forecast(city, date)` et s'arrête ; l'exécuter, c'est le travail de votre code.

Regardez la date : le modèle a transformé « demain » en `2026-09-26` parce que le system prompt lui a dit quel jour on est. Les faits qu'il ne peut pas connaître seul ont leur place là.

## Le code

**Avec astorlm :** Un agent astorlm possède les mêmes quatre pièces. Il garde l'historique pour vous d'un appel à `run()` à l'autre, et quand le modèle demande un outil, il l'exécute et lui renvoie le résultat. Cette boucle, c'est le niveau 2.

**À partir de zéro :** Les quatre pièces et une seule requête, pas encore de boucle. Remplissez le bloc `LLM` avec votre propre endpoint, votre modèle et votre clé.

**Avec astorlm**

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

// A tool: the card the model reads (name, description, schema) plus your code behind it.
const getForecast = tool({
  name: 'get_forecast',
  description: 'Daily forecast for one city: rain chance and min/max temp. date is YYYY-MM-DD.',
  schema: z.object({ city: z.string(), date: z.string() }),
  execute: async ({ city, date }) => forecastLine(city, date), // your code; the model never sees it
})

const agent = await createLocalAgent({
  // Any OpenAI-compatible endpoint: OpenAI, Ollama, LM Studio, vLLM, a proxy…
  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
  }),
  systemPrompt: 'You are Nimbus, a weather assistant. Today is 2026-09-25. Answer in one short line.',
  contextFiles: [], // by default astorlm also appends AGENTS.md and CLAUDE.md from the working folder
  tools: [getForecast],
})

// The agent keeps the history for you, so the second run knows about the first.
await agent.run('I’m in Buenos Aires.')
await agent.run('Will it rain tomorrow?') // asks for get_forecast(Buenos Aires, 2026-09-26), runs it, answers
console.log(agent.getMessages().length) // every message so far, resent on every call
```

**TypeScript**

```ts
// The four pieces, with no agent yet: one request in, one reply out. Plain fetch, no SDK.

// Any OpenAI-compatible endpoint: OpenAI, Ollama, LM Studio, vLLM, a proxy…
const LLM = {
  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
}

type ToolCall = { id: string; function: { name: string; arguments: string } }
type Message =
  | { role: 'user'; content: string }
  | { role: 'assistant'; content: string | null; tool_calls?: ToolCall[] }

// 1. The system prompt: who it is, its rules, and facts it can't know on its own.
const system = 'You are Nimbus, a weather assistant. Today is 2026-09-25. Answer in one short line.'

// 2. The history. The model remembers nothing between calls: this array IS its memory.
const messages: Message[] = []

// 3. A tool, as the model sees it: a name, a description and its inputs. Never the code.
const tools = [
  {
    type: 'function',
    function: {
      name: 'get_forecast',
      description: 'Daily forecast for one city: rain chance and min/max temp. date is YYYY-MM-DD.',
      parameters: {
        type: 'object',
        properties: { city: { type: 'string' }, date: { type: 'string' } },
        required: ['city', 'date'],
      },
    },
  },
]

// 4. The model: every call sends ALL of the above, and gets back one message.
async function ask(text: string): Promise<Message> {
  messages.push({ role: 'user', content: text })
  const res = await fetch(`${LLM.baseURL}/chat/completions`, {
    method: 'POST',
    headers: { 'content-type': 'application/json', authorization: `Bearer ${LLM.apiKey}` },
    body: JSON.stringify({ model: LLM.model, messages: [{ role: 'system', content: system }, ...messages], tools }),
  })
  const reply: Message = (await res.json()).choices[0].message
  messages.push(reply) // keep it, or the next call won't know it happened
  return reply
}

await ask('I’m in Buenos Aires.') // "Got it! How can I help?"
const reply = await ask('Will it rain tomorrow?')
// The reply is either text (reply.content) or a tool request (reply.tool_calls):
// get_forecast({ city: "Buenos Aires", date: "2026-09-26" })
// Running it and sending the result back, in a loop, is level 2.
```

**Python**

```python
# The four pieces, with no agent yet: one request in, one reply out. Standard library only, no SDK.
import json
import urllib.request

# Any OpenAI-compatible endpoint: OpenAI, Ollama, LM Studio, vLLM, a proxy...
LLM = {
    "base_url": "http://localhost:11434/v1",  # e.g. Ollama's default address
    "model": "your-model",  # e.g. "llama3.1", "gpt-4o-mini"
    "api_key": "YOUR_API_KEY",  # local servers usually ignore it
}

# 1. The system prompt: who it is, its rules, and facts it can't know on its own.
SYSTEM = "You are Nimbus, a weather assistant. Today is 2026-09-25. Answer in one short line."

# 2. The history. The model remembers nothing between calls: this list IS its memory.
messages = []

# 3. A tool, as the model sees it: a name, a description and its inputs. Never the code.
TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "get_forecast",
            "description": "Daily forecast for one city: rain chance and min/max temp. date is YYYY-MM-DD.",
            "parameters": {
                "type": "object",
                "properties": {"city": {"type": "string"}, "date": {"type": "string"}},
                "required": ["city", "date"],
            },
        },
    }
]

# 4. The model: every call sends ALL of the above, and gets back one message.
def ask(text):
    messages.append({"role": "user", "content": text})
    request = urllib.request.Request(
        f"{LLM['base_url']}/chat/completions",
        data=json.dumps({
            "model": LLM["model"],
            "messages": [{"role": "system", "content": SYSTEM}, *messages],
            "tools": TOOLS,
        }).encode(),
        headers={"Content-Type": "application/json", "Authorization": f"Bearer {LLM['api_key']}"},
    )
    with urllib.request.urlopen(request) as response:
        reply = json.load(response)["choices"][0]["message"]
    messages.append(reply)  # keep it, or the next call won't know it happened
    return reply

ask("I'm in Buenos Aires.")  # "Got it! How can I help?"
reply = ask("Will it rain tomorrow?")
# The reply is either text (reply["content"]) or a tool request (reply["tool_calls"]):
# get_forecast(city="Buenos Aires", date="2026-09-26")
# Running it and sending the result back, in a loop, is level 2.
```

## Points de vigilance

- **Gardez le system prompt court et précis.** Il voyage avec chaque appel. Rôle, règles, ton et les faits dont le modèle a besoin ; pas un manuel.
- **Décidez ce que garde l'historique.** Tout renvoyer indéfiniment devient lent et coûteux, et finit par ne plus tenir. L'élaguer et le résumer est un pattern à part entière.
- **Un modèle sans outils répondra quand même.** Interrogez-le sur des données en temps réel et il devinera, avec aplomb. Si la réponse dépend de quelque chose qu'il ne peut pas voir, donnez-lui un outil.

## Patterns liés

- [1 · Qu'est-ce qu'un agent ?](https://harnesspatterns.dev/fr/patterns/what-is-an-agent.md)
- [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)
- [7 · Le sac à dos déborde](https://harnesspatterns.dev/fr/patterns/compaction.md)
