Практические заметки: Эффективный по памяти ИИ-агент: создание движка контекста на Go
Пошаговое руководство по практическим заметкам: Эффективный по памяти ИИ-агент: создание движка контекста на Go: контракты, проверки и готовые блоки кода для команд, использующих эту модель.
Источники: The Memory-Efficient AI Agent: Building a Context Engine in Go
В следующих заметках описывается практический подход к разработке «ИИ-агента с эффективным использованием памяти: создание движка контекста на Go». Основное внимание уделяется контрактам, проверкам и шаблонам кода, которые можно легко вставить, а не мотивирующим аспектам. На этапе обзора сначала запишите контракт: необходимые входные данные, сигнал успешного выполнения и действия при частичной неудаче. Такой список поможет сохранять честность при последующих изменениях кода. Предпочитайте небольшие, тестируемые единицы кода вместо обширных скриптов. Если какой-то шаг не сработает, причина должна быть связана с одной конкретной функцией, а не с запутанной цепочкой операций.
Начало работы: импорты и модель данных
Процессы импорта и начальной настройки работают наилучшим образом, если рассматривать их как измеримую основу. Соберите один эталонный пример успешной работы, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объема работ. Рассматривайте этот этап как контракт между входными данными и проверенными результатами. Дайте названия создаваемым элементам, определите критерии успеха и не соглашайтесь на молчаливое частичное выполнение задач. Установите лимиты на количество операций за раз и за сессию. Инструменты агентов активно расширяют объем контекста; строгие ограничения предотвращают появление неожиданных счетов.
import (
"container/heap"
"container/list"
"encoding/gob"
"fmt"
"math"
"os"
"strings"
)
type MemoryItem struct {
ID int
Content string
Priority int
Vector []float64
index int
}
Механизм приоритизации: Max-Heap с использованием container/heap
Этап Max-Heap механизма определения приоритетов работает наилучшим образом, когда его рассматривают как измеримую структуру. Соберите один идеальный пример работы, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объёма работ. Записывайте временные показатели, а также стоимость токенов или запросов рядом с функциональными результатами. Отслеживание затрат на раннем этапе предотвращает неожиданные счёты при переходе от демо-среды к общедоступным средам. Сохраняйте состояние графа в упорядоченном и типизированном виде. Вложенные структуры скрывают информацию о том, какой узел заполнил тот или иной поле, и могут нарушить возобновление работы после прерываний.
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
}
Панорамное окно: временной контекст с использованием container/list
Этап временных данных с скользящим окном работает наилучшим образом, когда рассматривается как измеримая структура. Соберите один идеальный пример работы, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объёма обработки. Храните конфигурацию вне кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь граф обработки. Сохраняйте состояние графа простым и типизированным. Вложенные структуры данных маскируют информацию о том, какой узел заполнил тот или иной поле, что приводит к нарушению возобновления обработки после перерывов. Этап временных данных с скользящим окном работает наилучшим образом, когда рассматривается как измеримая структура. Соберите один идеальный пример работы, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объёма обработки. Предпочитайте небольшие, тестируемые единицы кода вместо обширных скриптов. Когда какой-либо шаг срабатывает некорректно, причина сбоя должна указывать на конкретную ответственность, а не на запутанную цепочку операций.
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)
}
Векторизация и сходство: семантический поиск с использованием math и strings
На этапе семантического анализа на основе векторизации и сходства необходимо заранее определить входные данные, ответственного за выполнение шага и критерии завершения перед внесением изменений в код. Операторы должны иметь возможность перезапустить шаг, исходя из известной точки контроля, без необходимости угадывания скрытого состояния. Рассматривайте этот этап как контракт между входными данными и проверенными выходными результатами. Укажите названия создаваемых файлов, определите критерии успешного выполнения и не допускайте молчаливого частичного завершения работы. Обеспечьте утверждение человеком тех операций, которые влекут за собой расходы или изменение производственных данных. Настройки, сделанные во время компиляции, не гарантируют полноты выполнения бизнес-задач.
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))
}
Персистентность: двоичная сериализация с использованием encoding/gob
Для бинарной сериализации данных с поддержкой сохранения состояния необходимо заранее определить входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии системы. Рядом с функциональными результатами следует записывать время выполнения и стоимость токенов или запросов. Отображение затрат на раннем этапе предотвращает неожиданные счета при переходе от демо-среды к общедоступным средам. Необходимо ввести человеческое утверждение для операций, связанных с тратой денег или изменением производственных данных. Подключение компонентов во время компиляции не гарантирует полноты реализации бизнес-логики.
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)
}
Выполнение: main()
Для основного этапа The Execution необходимо заранее определить входные данные, ответственного за выполнение шага и критерии завершения перед внесением изменений в код. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Храните конфигурацию вне кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь кодовый граф. Вводите ручное утверждение для операций, связанных с тратой денег или изменением производственных данных. Подключение компонентов во время компиляции не гарантирует полноты охвата бизнес-логики. Для основного этапа The Execution необходимо заранее определить входные данные, ответственного за выполнение шага и критерии завершения перед внесением изменений в код. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Предпочитайте небольшие, тестируемые единицы кода большим скриптам. При сбое шага он должен указывать на конкретную причину, а не на сложную взаимосвязь всех компонентов.
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>
Агенты ускорения с MCP
При работе над этапом «Агенты ускорения с MCP» сначала запишите контракт: необходимые входные данные, сигнал успешного выполнения и действия при частичной неудаче. Такой список помогает сохранять честность при последующих изменениях кода. Рассматривайте этот этап как контракт между входными данными и проверенными выходными результатами. Дайте названия элементам, определите критерии успешности и не допускайте безответственного частичного выполнения задачи. Фиксируйте название инструмента, хеш аргументов, время задержки и результат каждого вызова. Без такой записи отладка циклов агента занимает часы.
Основные выводы
При работе на этапе основных выводов сначала запишите условия контракта: необходимые входные данные, сигнал успешного выполнения и действия при частичной неудаче. Такой список помогает сохранять честность при последующих изменениях кода. Рядом с функциональными результатами занесите информацию о времени выполнения и стоимости токенов или запросов. Отображение затрат с самого начала предотвращает неожиданные счета при переходе от демо-среды к общедоступным средам. Устанавливайте контрольные точки после дорогостоящих шагов. Система возобновления работы не должна снова взимать плату за один и тот же вызов большой языковой модели, когда оператор пытается выполнить следующий узел.
Дополнительные ссылки и ресурсы
При работе на этапе «Дополнительные ресурсы» сначала запишите условия работы системы: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой чек-лист поможет сохранять честность последующих изменений в коде. Храните конфигурацию отдельно от кода приложения. Файлы с настройками окружения, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код. Создавайте контрольные точки после дорогостоящих операций. Система возобновления работы не должна снова взимать плату за один и тот же вызов большой языковой модели, если оператор попытается выполнить последующий шаг заново. При работе на этапе «Дополнительные ресурсы» сначала запишите условия работы системы: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой чек-лист поможет сохранять честность последующих изменений в коде. Предпочитайте небольшие, тестируемые модули большим скриптам. При сбое какого-либо шага причина неудачи должна быть связана с конкретной функцией, а не с запутанной цепочкой операций.
Чек-лист для эксплуатации
На этапе операционного чек-листа необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии.
Документируйте одновременно «идеальный» сценарий работы и сценарий восстановления. Повторные попытки, проверки человеком и обработка неработоспособных сообщений являются частью продукта, а не элементами последующей доработки.
Внедряйте утверждение человеком для операций, связанных с тратой денег или изменением производственных данных. Конфигурация во время компиляции не заменяет полноты бизнес-логики.
Напишите краткое руководство: как обновлять ключи, как опустошать очередь, как откатить последнюю загрузку данных.
Предпочитайте небольшие, тестируемые модули большим скриптам. При сбое шага причина должна быть связана с конкретной ответственностью, а не с запутанной цепочкой операций.
Внедряйте человеческое утверждение для операций, связанных с расходованием средств или изменением производственных данных. Конфигурация во время компиляции не гарантирует полноты функционала бизнес-приложения.
Перед внедрением всей стек-технологии заморозьте версии, сохраните эталонные записи для критически важных этапов и уточните шаги отката. В совместных средах необходимы ограничения на частоту запросов, проверки принадлежности пользователя и четко определенный ответственный за обновление секретов. Лучше выбирать надежность, чем креативные одноразовые демонстрации.
Примечание для d4b7557c44d8: не храните ключи поставщика в репозитории, установите лимит токенов на сессию и сохраняйте записи рядом с фикстурами для оценки, чтобы последующие замены моделей оставались сопоставимыми.
Для этапа 0 записки по укреплению безопасности необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Конфигурацию следует хранить отдельно от кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь кодовый граф.
Подробности укрепления 0/825: измеряйте время выполнения, класс ошибок и расход токенов для данной записки, затем принимайте решение о сохранении изменений на основе фиксированного набора критериев, а не на основе устных замечаний.
При работе над первым этапом записки по укреплению безопасности сначала запишите условия работы: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой чек-лист поможет сохранять честность при последующих изменениях кода. Лучше использовать небольшие, тестируемые модули вместо обширных скриптов. Если какой-то шаг не сработает, причина неудачи должна указывать на конкретную ответственность, а не на запутанную цепочку операций.
Подробности укрепления безопасности 1/825: измерьте время выполнения, класс ошибки и расход токенов для данной записки, затем решите, следует ли сохранять изменение, опираясь на заранее определенный набор критериев, а не на устные оценки.
Второй этап записки по укреплению безопасности работает лучше всего, когда его рассматривают как измеримую область. Соберите один идеальный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объема работ. Записывайте время выполнения, а также стоимость токенов или запросов рядом с функциональными результатами. Отслеживание затрат на раннем этапе предотвращает неожиданные расходы при переходе с демо-среды в общедоступные среды.
Подробности усиления безопасности 2/825: измерьте время выполнения, класс ошибки и расход токенов для этой записи, затем решите, следует ли сохранить изменения на основе фиксированного набора вопросов, а не на основе единичных примеров.
На третьем этапе работы над усилением безопасности определите входные данные, ответственного за шаг и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Документируйте как успешный путь выполнения, так и путь восстановления. Повторные попытки, проверки человеком и обработка неработающих сообщений являются частью продукта, а не последующими улучшениями.
Подробности усиления безопасности 3/825: измерьте время выполнения, класс ошибки и расход токенов для этой записи, затем решите, следует ли сохранить изменения на основе фиксированного набора вопросов, а не на основе единичных примеров.