Accueil / Articles

Notes pratiques : L’agent IA économe en mémoire : création d’un moteur de contexte en Go

Guide pas à pas des notes pratiques : L’agent IA économe en mémoire : création d’un moteur de contexte en Go – contrats, vérifications et emplacements de code prêts à l’emploi pour les équipes utilisant ce modèle.

Éditeur : REACTAPP.TOP10 min de lecture2311 mots

Sources: The Memory-Efficient AI Agent: Building a Context Engine in Go

Les notes suivantes reconstituent une approche pratique pour aborder « The Memory-Efficient AI Agent: Building a Context Engine in Go ». L’accent est mis sur les contrats, les vérifications et les placeholders de code interchangeables, plutôt que sur une présentation motivante. Lors de la phase d’aperçu, notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Préférez des unités petites et testables à des scripts volumineux. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité et non un processus complexe et embrouillé.

Démarrer : Imports et le modèle de données

Les étapes d’importation et de préparation initiale fonctionnent le mieux lorsqu’elles sont considérées comme une surface mesurable. Capturez un exemple réussi exemplaire, un cas d’échec et la note de réversion avant d’élargir le périmètre. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définez des critères de succès et refusez toute mise en œuvre partielle silencieuse. Fixez un budget de tokens par tour et par session. Les outils agents élargissent l’étendue du contexte de manière importante ; des plafonds stricts empêchent que les démonstrations se transforment en factures inattendues.

import (
	"container/heap"
	"container/list"
	"encoding/gob"
	"fmt"
	"math"
	"os"
	"strings"
)
type MemoryItem struct {
	ID       int
	Content  string
	Priority int
	Vector   []float64
	index    int
}

Le moteur de priorité : Max-Heap avec container/heap

La phase Max-Heap du moteur de priorité fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un enregistrement exemplaire, un cas d’échec et la note de réversion avant d’élargir le périmètre. Enregistrez les temps d’exécution ainsi que le coût des tokens ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite des factures inattendues lorsque le parcours passe de l’environnement de démonstration aux environnements partagés. Gardez l’état du graphe plat et typé : les blocs imbriqués masquent le fait que tel nœud a écrit telle champ et perturbent la reprise après interruption.

type PriorityQueue []*MemoryItem

func (pq PriorityQueue) Len() int { return len(pq) }
func (pq PriorityQueue) Less(i, j int) bool {
	return pq[i].Priority > pq[j].Priority // Max-heap logic
}
func (pq PriorityQueue) Swap(i, j int) {
	pq[i], pq[j] = pq[j], pq[i]
	pq[i].index = i
	pq[j].index = j
}
func (pq *PriorityQueue) Push(x interface{}) {
	n := len(*pq)
	item := x.(*MemoryItem)
	item.index = n
	*pq = append(*pq, item)
}
func (pq *PriorityQueue) Pop() interface{} {
	old := *pq
	n := len(old)
	item := old[n-1]
	old[n-1] = nil
	item.index = -1
	*pq = old[0 : n-1]
	return item
}

La fenêtre glissante : contexte temporel avec container/list

La phase Temporal à fenêtre glissante fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un enregistrement idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans devoir lire l’ensemble du graphe. Gardez l’état du graphe plat et typé. Les blocs imbriqués masquent le fait que tel nœud a écrit telle champ et perturbent la reprise après interruption. La phase Temporal à fenêtre glissante fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un enregistrement idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Préférez des unités petites et testables aux scripts complexes. Lorsqu’une étape échoue, l’échec doit pointer vers une seule responsabilité plutôt que vers un processus embrouillé.

type SlidingWindow struct {
	queue    *list.List
	capacity int
}

func NewSlidingWindow(capacity int) *SlidingWindow {
	return &SlidingWindow{queue: list.New(), capacity: capacity}
}
func (sw *SlidingWindow) Add(item *MemoryItem) {
	if sw.queue.Len() >= sw.capacity {
		sw.queue.Remove(sw.queue.Front())
	}
	sw.queue.PushBack(item)
}

Vectorisation et similarité : Recherche sémantique avec math et strings

Pour l’étape de vectorisation et de similarité sémantique, définissez les entrées, le responsable de l’étape ainsi que les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Nommez les artefacts, définissez des vérifications de succès et refusez toute exécution partielle silencieuse. Faites approuver par un humain les étapes qui entraînent des dépenses ou modifient des données de production. Une connexion en temps de compilation ne garantit pas la complétude du processus métier.

func TextToVector(text string) []float64 {
	vocab := []string{"database", "connection", "fix", "leak", "system", "user"}
	vec := make([]float64, len(vocab))
	words := strings.Fields(strings.ToLower(text))
	for _, word := range words {
		for i, v := range vocab {
			if strings.Contains(word, v) {
				vec[i]++
			}
		}
	}
	return vec
}
func CosineSimilarity(a, b []float64) float64 {
	var dot, normA, normB float64
	for i := range a {
		dot += a[i] * b[i]
		normA += a[i] * a[i]
		normB += b[i] * b[i]
	}
	if normA == 0 || normB == 0 {
		return 0
	}
	return dot / (math.Sqrt(normA) * math.Sqrt(normB))
}

Persistence : Sérialisation binaire avec encoding/gob

Pour la sérialisation binaire de la persistance avec étapes, définissez les entrées, le responsable de l’étape et les critères de fin avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Enregistrez les temps d’exécution ainsi que le coût des jetons ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite les factures inattendues lorsque le parcours passe de l’environnement de démonstration à des environnements partagés. Faites approuver par un humain les actions qui entraînent des dépenses ou modifient des données de production. La connexion en temps de compilation ne garantit pas la complétude du processus métier.

type AgentMemory struct {
	LongTerm []*MemoryItem
}
func SaveMemory(filename string, mem *AgentMemory) error {
	file, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer file.Close()
	return gob.NewEncoder(file).Encode(mem)
}

L’exécution : main()

Pour l’étape principale de The Execution, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans devoir lire l’ensemble du système. Mettez en place une approbation humaine pour les actions qui entraînent des dépenses ou modifient des données de production. Une connexion effectuée en temps de compilation ne garantit pas la complétude du processus métier. Pour l’étape principale de The Execution, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Préférez des unités petites et testables aux scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité plutôt qu’un processus embrouillé.

pq := &PriorityQueue{}
heap.Init(pq)
sw := NewSlidingWindow(3)
rawItems := []struct {
	content  string
	priority int
}{
	{"System: You are a helpful assistant.", 10},
	{"User: How do I fix the database connection?", 5},
	{"CRITICAL: Fix database connection leak.", 9},
}

for _, ri := range rawItems {
	item := &MemoryItem{
		Content:  ri.content,
		Priority: ri.priority,
		Vector:   TextToVector(ri.content),
	}
	heap.Push(pq, item)
	sw.Add(item)
}
fmt.Println("--- Sliding Window (capacity 3) ---")
for e := sw.queue.Front(); e != nil; e = e.Next() {
	fmt.Printf("  Window holds: '%s'\n", e.Value.(*MemoryItem).Content)
}
for i := 0; i < 2; i++ {
	sw.Add(&MemoryItem{
		ID:       i + 1,
		Content:  fmt.Sprintf("Stream item %d: recent event", i+1),
		Priority: 1,
		Vector:   TextToVector("recent event"),
	})
}
fmt.Println("  After 2 more stream items added:")
for e := sw.queue.Front(); e != nil; e = e.Next() {
	fmt.Printf("  Window holds: '%s'\n", e.Value.(*MemoryItem).Content)
}
query := "database connection fix"
queryVec := TextToVector(query)

var bestMatch *MemoryItem
maxSim := -1.0

fmt.Printf("Querying for: '%s'\n", query)
for _, item := range *pq {
	sim := CosineSimilarity(item.Vector, queryVec)
	fmt.Printf("Comparing with: '%s' (Sim: %.2f)\n", item.Content, sim)
	if sim > maxSim {
		maxSim = sim
		bestMatch = item
	}
}
fmt.Printf("\nBest match found: '%s' (Sim: %.2f)\n", bestMatch.Content, maxSim)
mem := &AgentMemory{LongTerm: *pq}
SaveMemory("agent_memory.gob", mem)
fmt.Println("Memory state saved to agent_memory.gob")
PS C:\Users\User\ai-memory> go run .\ai_memory_experiment.go
--- Sliding Window (capacity 3) ---
  Window holds: 'System: You are a helpful assistant.'
  Window holds: 'User: How do I fix the database connection?'
  Window holds: 'CRITICAL: Fix database connection leak.'
  After 2 more stream items added:
  Window holds: 'CRITICAL: Fix database connection leak.'
  Window holds: 'Stream item 1: recent event'
  Window holds: 'Stream item 2: recent event'

Querying for: 'database connection fix'
Comparing with: 'System: You are a helpful assistant.' (Sim: 0.00)
Comparing with: 'User: How do I fix the database connection?' (Sim: 0.87)
Comparing with: 'CRITICAL: Fix database connection leak.' (Sim: 0.87)

Best match found: 'User: How do I fix the database connection?' (Sim: 0.87)
Memory state saved to agent_memory.gob
PS C:\Users\User\ai-memory>

Agents d’accélération avec MCP

Lors de la phase des agents d’accélération avec MCP, notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de maintenir l’honnêteté des modifications ultérieures du code. Considérez cette phase comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définez des vérifications de succès et refusez les terminations partielles silencieuses. Enregistrez le nom de l’outil, le hash des arguments, la latence et le résultat pour chaque appel. Déboguer des boucles d’agent sans cette trace fait perdre des heures.

Points clés

Lors de l’étape des points clés, notez d’abord les éléments requis pour le contrat : les données nécessaires, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Enregistrez les temps d’exécution ainsi que le coût des tokens ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite les factures inattendues lorsque le processus passe de l’environnement de démonstration à des environnements partagés. Faites un point après les étapes coûteuses. Le système de reprise ne doit pas facturer à nouveau la même appel du LLM lorsque l’opérateur réessaie un nœud ultérieur.

Liens et ressources supplémentaires

Lors de la phase des Ressources de liens supplémentaires, notez d’abord les conditions du contrat : entrées requises, signal de succès et conséquences en cas d’échec partiel. Cette liste de contrôle permet de garantir l’intégrité des modifications ultérieures du code. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans devoir lire l’ensemble du système. Créez des points de contrôle après les étapes coûteuses. Le système de reprise ne doit pas facturer à nouveau la même appel d’LLM lorsque l’opérateur réessaie une étape ultérieure. Lors de la phase des Ressources de liens supplémentaires, notez d’abord les conditions du contrat : entrées requises, signal de succès et conséquences en cas d’échec partiel. Cette liste de contrôle permet de garantir l’intégrité des modifications ultérieures du code. Préférez des unités petites et testables aux scripts complexes. Lorsqu’une étape échoue, l’erreur doit indiquer une responsabilité précise plutôt qu’un processus embrouillé.

Liste de contrôle opérationnelle

Pour l’étape de la liste de contrôle opérationnelle, définissez les entrées, le responsable de l’étape et les critères d’achèvement avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché.

Dokumentez ensemble le parcours normal et le parcours de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages échoués font partie intégrante du produit, et non d’une mise en forme ultérieure.

Imposez une approbation humaine pour les actions qui entraînent des dépenses ou modifient des données de production. Une connexion en temps de compilation ne garantit pas la complétude du processus métier.

Rédigez un petit manuel d’utilisation : comment rotationner les clés, comment vider la file d’attente, comment annuler la dernière ingestion.

Préférez des unités petites et testables plutôt que des scripts complexes. Lorsqu’une étape échoue, l’erreur doit indiquer une seule responsabilité et non un processus embrouillé.

Apportez une validation humaine pour les étapes qui engagent des dépenses ou modifient les données de production. La connexion en temps de compilation ne garantit pas une couverture complète des besoins métier.

Au préalable de promouvoir l’ensemble technique, figez les versions, conservez une transcription d’ référence pour le chemin critique, et vérifiez les étapes de réversion. Les environnements partagés nécessitent des limites de fréquence, des contrôles d’attribution et un responsable clair pour la rotation des secrets. Préférez une fiabilité solide à des démonstrations brillantes mais ponctuelles.

Note de batch pour d4b7557c44d8 : gardez les clés du fournisseur hors du répertoire de code, fixez un plafond pour les tokens par session, et stockez les transcriptions à côté des fichiers de configuration d’évaluation afin que les remplacements ultérieurs de modèles restent comparables.

Pour l’étape 0 des notes de renforcement de sécurité, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans avoir à lire l’ensemble du système.

Détail de renforcement 0/825 : mesurez le temps d’exécution, la classe de l’erreur et la consommation de tokens pour cette note, puis décidez s’il convient de conserver la modification en vous basant sur un ensemble fixe de critères plutôt que sur des observations subjectives.

Lors de la première étape des notes de renforcement, notez d’abord les éléments essentiels : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Préférez des unités petites et testables plutôt que des scripts complexes. Lorsqu’une étape échoue, l’erreur doit indiquer une responsabilité précise plutôt qu’un processus embrouillé.

Détail de renforcement 1/825 : mesurez le temps d’exécution, la catégorie de l’erreur et la consommation de tokens pour cette note, puis décidez si vous souhaitez conserver la modification en vous basant sur un ensemble de critères prédéfinis plutôt que sur des observations subjectives.

La deuxième étape des notes de renforcement fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Recueillez un exemple idéal de fonctionnement, un cas d’échec et des notes de réversion avant d’élargir le périmètre. Enregistrez les temps d’exécution ainsi que le coût en tokens ou en requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite des surprises lors du passage de l’environnement de démonstration aux environnements partagés.

Détail de renforcement 2/825 : mesurer le temps d’exécution, la classe d’erreur et la consommation de tokens pour cette note, puis décider de conserver ou non le changement en se basant sur un ensemble de questions prédéfini plutôt que sur des anecdotes.

Pour la phase 3 de la note de renforcement, définir les entrées, le responsable de l’étape et les critères d’achèvement avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Documenter ensemble le parcours normal et le parcours de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie du produit, et non d’une mise en forme ultérieure.

Détail de renforcement 3/825 : mesurer le temps d’exécution, la classe d’erreur et la consommation de tokens pour cette note, puis décider de conserver ou non le changement en se basant sur un ensemble de questions prédéfini plutôt que sur des anecdotes.