La mémoire
La mémoire à court terme d'un agent est sa liste de messages ; la mémoire à long terme, il faut la stocker soi-même et la récupérer au besoin. On donne à l'agent deux outils, « retenir » et « se rappeler », pour montrer qu'il utilise dans une conversation toute neuve un fait retenu la fois précédente.
- Environ 35 minutes
- Niveau : Intermédiaire
- Testé : 2026-09-14 deepseek-flash
Le code et les sorties des programmes sont reproduits tels qu’ils ont tourné : commentaires et sorties sont donc en chinois.
La leçon 1 du module 03 l'a montré : le modèle lui-même ne se souvient de rien, la « mémoire » consiste simplement à renvoyer les messages précédents. La conversation terminée, la liste de messages jetée, tout disparaît.
Pour un assistant de questions-réponses, c'est très gênant. La semaine dernière, l'utilisateur lui a dit : « Le projet de notre entreprise utilise encore Python 3.9, et toutes les requêtes doivent passer par un proxy. » Cette semaine, il repose une question, et l'assistant a tout oublié : le code donné utilise une syntaxe nouvelle de la 3.10, sans proxy, et ne tourne tout simplement pas dans l'environnement de l'utilisateur.
Cette leçon traite des deux mémoires d'un agent, à court et à long terme, et de l'art de les gérer, qu'on appelle l'ingénierie du contexte.
La mémoire à court terme : la liste de messages
La mémoire à court terme d'un agent, c'est sa liste de messages : la question de l'utilisateur, les appels d'outils et résultats de chaque étape. Dans la boucle de la leçon 2, le modèle voit à chaque étape toutes les étapes précédentes ; il sait donc ce qu'il a déjà cherché et ce qui manque encore.
Le problème de la mémoire à court terme, c'est qu'elle s'allonge sans cesse. À la leçon 2, répondre à une question a pris plus de 3700 tokens d'entrée en 3 étapes ; la tâche en plusieurs étapes de la leçon 4, 50 000 en 7 étapes. Avec une tâche un peu plus complexe, en quelques dizaines d'étapes, on dépasse la fenêtre de contexte, et le coût devient absurde.
Quelques moyens de contenir la mémoire à court terme ont déjà été vus :
- Limiter la longueur des retours d'outils. Le
read_docde la leçon 2 lit au plus 80 lignes à la fois, et la boucle tronque en plus à 3000 caractères. - Compresser l'ancien contenu. La « troncature » et le « résumé » de la leçon 1 du module 03 s'appliquent aussi aux agents : un résultat d'outil déjà utilisé peut être remplacé par une phrase de résumé, comme « lu les lignes 1 à 80 de timeouts.md, il existe quatre types de délais ».
- Confier des sous-tâches à des sous-agents. Un sous-agent accomplit le travail dans son propre contexte et ne rend que la conclusion ; la leçon 6 en parle.
La mémoire à long terme : stocker, puis récupérer au besoin
Pour retenir quelque chose d'une conversation à l'autre, il faut le stocker hors de la liste de messages : dans un fichier, une base de données. À la conversation suivante, on le récupère au besoin et on le remet dans le contexte.
L'implémentation la plus simple : donner deux outils à l'agent et le laisser décider lui-même quand stocker et quand récupérer :
MEMORY_FILE = Path(__file__).parent / "memory.json"
def load():
return json.loads(MEMORY_FILE.read_text()) if MEMORY_FILE.exists() else []
@tool("把一条关于用户的长期有用的事实存下来,比如用户的环境、偏好、项目情况。"
"只存以后的对话可能用到的事实,不要存一次性的问题。",
fact="一句话描述的事实,例如:用户的项目运行在 Python 3.9 上")
def remember(fact):
facts = load()
facts.append({"fact": fact, "time": time.strftime("%Y-%m-%d %H:%M")})
MEMORY_FILE.write_text(json.dumps(facts, ensure_ascii=False, indent=2))
return f"已记住:{fact}"
@tool("查看之前记住的关于用户的事实。回答涉及用户自己的环境、项目、偏好时,先调用它。")
def recall():
facts = load()
return "\n".join(f"- {f['fact']}({f['time']})" for f in facts) or "还没有记住任何事实"
@tool est le décorateur écrit à la leçon 2 ; une fois enregistrés, ces deux outils figurent dans la boîte à outils de l'agent aux côtés des trois outils de documentation. Les descriptions disent clairement « quoi stocker » (des faits utiles à long terme, pas des questions ponctuelles) et « quand récupérer » (quand la réponse concerne la situation de l'utilisateur), selon la méthode de la leçon 3.
Une phrase de plus dans le prompt system lui rappelle ces deux outils :
agent_loop.SYSTEM += "\n你有长期记忆工具:用户告诉你关于他自己的长期信息时,用 remember 记下来;回答前如果需要了解用户的情况,先用 recall。"
Deux conversations
code/05-agents/memory.py simule deux conversations totalement indépendantes. Dans la première, l'utilisateur décrit sa situation ; la seconde est une liste de messages toute neuve, où le modèle ne voit rien de ce qui a été dit dans la première :
print("===== 第一次对话")
run_agent(model, "先跟你交代一下:我们公司的项目还在用 Python 3.9,所有外网请求都必须走公司代理 "
"http://10.1.2.3:8080。今天先不问问题,你记一下。")
print("\n===== 第二次对话(全新的消息列表,模型看不到第一次说过的话)")
run_agent(model, "帮我写一段用 httpx 请求 https://api.github.com 的代码,要能在我们的环境里直接跑。")
Résultat (la réponse de la seconde conversation est longue et a été en partie coupée) :
===== 第一次对话
[第 1 步] remember({"fact": "用户公司项目使用 Python 3.9"}) → 已记住:用户公司项目使用 Python 3.9
[第 1 步] remember({"fact": "用户公司所有外网请求必须走公司代理 http://10.1.2.3:8080"}) → 已记住:用户公司所有外网请求必须走公司代理 http://10.1.2.3:8080
[第 2 步] 回答:
已记下两条:
1. 公司项目使用 Python 3.9
2. 所有外网请求必须走公司代理 `http://10.1.2.3:8080`
下次你问 httpx 相关问题(比如代理配置),我会默认按这个环境来考虑。
共 2 步,输入 1606 词元,输出 159 词元
记忆文件的内容:
[
{
"fact": "用户公司项目使用 Python 3.9",
"time": "2026-09-14 22:25"
},
{
"fact": "用户公司所有外网请求必须走公司代理 http://10.1.2.3:8080",
"time": "2026-09-14 22:25"
}
]
===== 第二次对话(全新的消息列表,模型看不到第一次说过的话)
[第 1 步] recall({}) → - 用户公司项目使用 Python 3.9(2026-09-14 22:25) | - 用户公司所有外网请求必须走公司代理 http://10.1.2.3:8080(2026-09
[第 1 步] list_docs({}) → advanced/authentication.md | advanced/clients.md | advanced/event-hooks.md | advanced/exte
[第 2 步] read_doc({"path": "advanced/proxies.md", "start": 1, "end": 80}) → 1: HTTPX supports setting up [HTTP proxies](https://en.wikipedia.org/wiki/Proxy_server#Web
[第 2 步] grep_docs({"keyword": "proxies"}) → advanced/clients.md:27: - Sending requests through HTTP proxies. | advanced/extensions.md:
[第 3 步] read_doc({"path": "quickstart.md", "start": 1, "end": 70}) → 1: # QuickStart | 2: | 3: First, start by importing HTTPX: | 4: | 5: ```pycon | 6: >>> i
[第 3 步] read_doc({"path": "environment_variables.md", "start": 1, "end": 40}) → 1: # Environment Variables | 2: | 3: The HTTPX library can be configured via environment
[第 4 步] think({}) → 错误:没有叫 think 的工具
[第 5 步] 回答:
抱歉,上一步是我调用工具时写错了(并不存在 `think` 工具),忽略即可。下面是正文。
## 说明你的环境的两个关键点
根据我记住的你的环境信息:
- 项目使用 **Python 3.9**
- 所有外网请求必须走公司代理 **http://10.1.2.3:8080**
所以代码里必须显式设置代理,否则请求会失败。
## 代码
```python
import httpx
# 公司要求所有外网请求走代理,无法直连时请确认这个参数
PROXY = "http://10.1.2.3:8080"
with httpx.Client(proxy=PROXY, timeout=10.0) as client:
r = client.get("https://api.github.com")
r.raise_for_status()
print(r.status_code)
print(r.json())
```
(后面还有顶层 API 的写法、文档依据和补充说明,省略)
共 5 步,输入 11449 词元,输出 1170 词元
Dans la première conversation, le modèle a appelé remember deux fois, stockant séparément les deux faits, sans stocker la phrase ponctuelle « je ne pose pas de question aujourd'hui ».
Dans la seconde, il a appelé recall dès la première étape et obtenu ces deux faits. Il a ensuite cherché dans la documentation comment configurer un proxy, et le code final utilise directement l'adresse du proxy de l'entreprise. Il a même mentionné un détail très pratique : si la variable d'environnement HTTP_PROXY est déjà définie, on peut omettre proxy= dans le code (en citant environment_variables.md).
Cette exécution a connu un petit incident : à l'étape 4, le modèle a appelé un outil think qui n'existe pas. La boucle de la leçon 2 l'a transformé en message « Erreur : aucun outil nommé think » renvoyé au modèle, qui s'est excusé dans sa réponse finale puis a donné normalement le résultat. C'est toute la valeur de « transformer les erreurs en observations » : un incident n'a pas fait échouer toute la tâche.
Les défauts de cette implémentation simple
Tout est récupéré. recall renvoie tous les faits à chaque fois. Avec peu de faits, pas de problème ; avec des centaines, tout mettre dans le contexte à chaque fois devient irréaliste. Il faut alors chercher par pertinence : avec la méthode du module 04, calculer un vecteur pour chaque fait et ne récupérer que ceux liés à la question courante.
On ajoute, on ne retire jamais. L'utilisateur change d'emploi, le projet passe à Python 3.12, mais les anciens faits restent et contredisent les nouveaux. Il faut pouvoir mettre à jour et supprimer, noter la date au stockage, et en cas de conflit, le plus récent l'emporte.
Ce qui est stocké dépend entièrement du jugement du modèle. Il peut oublier de stocker l'important, ou stocker ce qu'il ne faut pas, comme un mot de passe cité en passant par l'utilisateur. Le contenu de la mémoire à long terme ressort à chaque conversation suivante ; une fois une information sensible stockée, le risque demeure. Dans un vrai produit, l'utilisateur doit en général pouvoir voir et supprimer sa mémoire, et les informations sensibles doivent être filtrées avant le stockage.
Un risque de contamination. Si l'agent lit des pages web ou des fichiers, leur contenu peut l'inciter à « retenir » des choses fausses ou malveillantes, qui l'influenceront ensuite dans toutes les conversations. La leçon 8 traite de ce genre d'attaque.
L'ingénierie du contexte
Avec du recul, cette leçon et les précédentes font en réalité la même chose : décider de ce que le modèle voit à chaque étape.
- Quoi y mettre : documents trouvés, faits retenus, résultats d'outils.
- Combien : troncature, limitation de la longueur des retours d'outils.
- Où : le contenu fixe en tête, pour toucher le cache ; le contenu variable à la fin.
- Quand le retirer : résumer les résultats d'outils déjà utilisés, laisser les détails des sous-tâches dans les sous-agents.
Cet art est aujourd'hui souvent appelé ingénierie du contexte (context engineering). Le prompt engineering s'intéresse à « comment écrire les instructions », l'ingénierie du contexte à « quelles informations le modèle voit à chaque étape ». Pour un agent, la seconde compte souvent davantage : les capacités du modèle sont fixes ; qu'il réussisse ou non dépend largement de ce qu'il a sous les yeux au moment de décider, s'il dispose des bonnes informations et s'il n'est pas noyé sous trop d'informations sans rapport.
Exercices
- Lancez
memory.py, puis posez dans la seconde conversation une question sans rapport avec l'environnement de l'utilisateur (par exemple « combien de types de délais httpx a-t-il ? ») et voyez s'il appelle encorerecall. - Dans la première conversation, donnez-lui en plus une information appelée à devenir obsolète, puis dites dans la seconde « nous sommes passés à Python 3.12 ». Met-il à jour sa mémoire, et que fait-il de l'ancien fait ? Concevez pour
rememberun moyen de faire remplacer un ancien fait par un nouveau. - Dans la première conversation, dites « mon token GitHub est ghp_xxxx, note-le » et voyez si le modèle le stocke. Modifiez la description de
rememberpour lui interdire de stocker mots de passe, tokens et autres informations de ce type, et réessayez.
Auto-test
1. Que sont respectivement la mémoire à court terme et la mémoire à long terme d'un agent ?
La mémoire à court terme est la liste de messages de la tâche courante, avec la question, les appels d'outils et résultats de chaque étape ; elle disparaît à la fin de la tâche. La mémoire à long terme, ce sont des informations stockées hors de la liste de messages (fichier, base de données), récupérées au besoin lors d'une conversation suivante et remises dans le contexte.
2. Quand les faits de la mémoire à long terme se multiplient, quel problème pose « tout récupérer à chaque fois » ? Comment l'améliorer ?
Mettre tous les faits dans le contexte à chaque fois l'allonge et le renchérit sans cesse ; l'essentiel est sans rapport avec la question courante et perturbe le modèle. L'amélioration consiste à chercher par pertinence : calculer un vecteur ou construire un index de mots-clés pour chaque fait, et ne récupérer que ceux liés à la question courante.
3. Qu'est-ce que l'ingénierie du contexte ? En quoi diffère-t-elle du prompt engineering ?
L'ingénierie du contexte décide des informations que le modèle voit à chaque étape : quoi mettre, combien, où, et quand retirer. Le prompt engineering s'intéresse à la façon d'écrire les instructions. Pour un agent qui s'exécute en plusieurs étapes et reçoit sans cesse de nouvelles informations, l'ingénierie du contexte est souvent plus décisive.