Архитектура распределенной системы памяти

Коллективная память

В условиях ограниченных ресурсов (1 ГБ ОЗУ на Сервере 2 и отсутствие Docker) была разработана легковесная архитектура для распределенной системы памяти, где каждый сервер выполняет свою роль в обработке и хранении информации.

Сервер 1 (Kohana)

Хранит "твердые" факты в MySQL

Сервер 2 (Ubuntu)

Координатор памяти и хранилище векторов

Сервер 3 (LM Studio)

Генератор эмбеддингов

Архитектура потока данных

Сервер 1 (Kohana)

Отправляет текст на Сервер 2

Сервер 2 (Ubuntu)

Пересылает текст на Сервер 3 для получения вектора

Сервер 3 (LM Studio)

Возвращает вектор V

Сервер 2 (Ubuntu)

Сохраняет вектор в FAISS и возвращает результат на Сервер 1

Настройка Сервера 2 (Ubuntu)

Подготовка окружения

sudo apt update
sudo apt install python3-pip python3-venv -y
mkdir agent_memory && cd agent_memory
python3 -m venv venv
source venv/bin/activate
# Устанавливаем только необходимое
pip install fastapi uvicorn requests faiss-cpu numpy
      

Python-сервис "Коллективной Памяти"

import os
import requests
import numpy as np
import faiss
import json
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

# Конфигурация
LM_STUDIO_URL = "http://<SERVER_3_IP>:1234/v1/embeddings"
DIMENSION = 384  # Зависит от модели в LM Studio (например, all-MiniLM-L6-v2)
INDEX_FILE = "collective.index"
METADATA_FILE = "metadata.json"

# Инициализация FAISS индекса
if os.path.exists(INDEX_FILE):
    index = faiss.read_index(INDEX_FILE)
    with open(METADATA_FILE, 'r') as f:
        metadata = json.load(f)
else:
    index = faiss.IndexFlatL2(DIMENSION)
    metadata = []

def get_embedding(text):
    response = requests.post(LM_STUDIO_URL, json={
        "input": text,
        "model": "model-identifier"
    })
    return response.json()['data'][0]['embedding']

@app.post("/add")
async def add_memory(text: str, source_id: str):
    vector = np.array([get_embedding(text)]).astype('float32')
    index.add(vector)
    metadata.append({"text": text, "source_id": source_id})
    
    # Сохраняем состояние на диск
    faiss.write_index(index, INDEX_FILE)
    with open(METADATA_FILE, 'w') as f:
        json.dump(metadata, f)
    return {"status": "ok"}

@app.post("/search")
async def search_memory(text: str, top_k: int = 3):
    vector = np.array([get_embedding(text)]).astype('float32')
    distances, indices = index.search(vector, top_k)
    
    results = []
    for i, idx in enumerate(indices[0]):
        if idx != -1:
            results.append({
                "content": metadata[idx],
                "distance": float(distances[0][i])
            })
    return results

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)
      

Математическое обоснование

Проекция

Трансформация текста в точку в многомерном пространстве f: Text → ℝn через LM Studio

Поиск близости

Расчет Евклидова расстояния на Сервере 2: d(q, v) = √∑(qi - vi)2

Оптимизация

Использование IndexFlatL2 в FAISS на Сервере 2 идеально подходит для 1 ГБ ОЗУ, так как оно работает чрезвычайно быстро и не требует фоновых процессов БД.

Интеграция с Сервером 1 (Kohana)

Пример контроллера синхронизации памяти

class Controller_Memory extends Controller {

    public function action_sync()
    {
        $id = $this->request->query('id'); // ID записи из MySQL
        $post = ORM::factory('Post', $id);

        if ($post->loaded()) {
            $client = Request::factory('http://<SERVER_2_IP>:8000/add')
                ->method(HTTP_Request::POST)
                ->headers('Content-Type', 'application/json')
                ->body(json_encode([
                    'text' => $post->content,
                    'source_id' => 'mysql_post_' . $post->id
                ]));
            
            $response = $client->execute();
            $this->response->body("Memory stored: " . $response->body());
        }
    }

    public function action_ask()
    {
        $question = $this->request->query('q');
        $client = Request::factory('http://<SERVER_2_IP>:8000/search')
            ->method(HTTP_Request::POST)
            ->body(json_encode(['text' => $question]));
            
        $results = json_decode($client->execute()->body(), TRUE);
        
        // Теперь у нас есть данные из "Коллективного Разума"
        echo View::factory('memory/results', ['results' => $results]);
    }
}
      

Оптимизация для 1 GB RAM

  • Создайте swap-файл на 2ГБ
  • Запускайте через gunicorn с 1 воркером
  • Используйте модель с меньшей размерностью эмбеддинга (например, 384 вместо 1536)

Итог системы

MySQL (S1)

Хранит "твердые" факты

FAISS (S2)

Хранит "ассоциации" (вектора)

LM Studio (S3)

Выступает в роли "таламуса"

Агенты могут находиться где угодно, делая запросы к S2, получая в ответ опыт всей системы