• О компании Milvus
  • Начать работу
  • Понятия
  • Руководство пользователя
  • Импорт данных
  • Инструменты искусственного интеллекта
  • Руководство по администрированию
  • Инструменты
  • Интеграции
  • Учебные пособия
  • Часто задаваемые вопросы
  • API Reference

Создание долгосрочной памяти агентов с помощью EverOS и Milvus

EverOS — это система памяти для ИИ-агентов, ориентированная в первую очередь на Markdown. Она извлекает устойчивые воспоминания из диалогов, сохраняет Markdown в качестве источника достоверной информации и создает производный индекс с возможностью поиска.

В этом руководстве мы создадим помощника по проекту, который запоминает решения о релизах из разных диалогов. Мы добавим диалоги о запуске Project Atlas наряду с не связанными с ним диалогами о других проектах. EverOS будет использовать LLM для извлечения воспоминаний, а Milvus — для хранения индексов BM25 и векторных индексов, используемых для гибридного поиска.

Conversations
      |
      v
EverOS + LLM ------> Markdown memory files
      |
      | embedding model
      v
Milvus ------> BM25 + vector hybrid search

LLM и модель встраивания выполняют разные задачи. LLM преобразует диалог в структурированные воспоминания. Модель встраивания преобразует эти воспоминания и последующие поисковые запросы в векторы. Для базового гибридного поиска в этом руководстве не требуется модель переранжирования.

Необходимые условия

Вам понадобится:

В этом руководстве используется подключение к серверу Milvus по адресу http://localhost:19530. EverOS также поддерживает Zilliz Cloud с использованием тех же настроек URI и токена. Бэкенд Milvus в EverOS ожидает удалённый конечный пункт и не принимает путь к файлу Milvus Lite.

Установите EverOS

Создайте локальный проект и установите EverOS с дополнительными зависимостями Milvus:

mkdir everos-milvus-demo
cd everos-milvus-demo

uv init --bare --python 3.12
uv add "everos[milvus]"

В команде намеренно не указана конкретная версия, поэтому при новой установке будет установлен последний совместимый выпуск EverOS.

Инициализируйте отдельный корневой каталог памяти для этого руководства:

export EVEROS_ROOT="$PWD/everos-data"
uv run everos init --root "$EVEROS_ROOT"

EverOS создает файлы everos.toml и ome.toml в этом каталоге. Туда же будут записываться извлеченные наборы данных.

Настройте OpenAI и Milvus

Укажите ключ API OpenAI и настройте EverOS с помощью переменных среды:

export OPENAI_API_KEY="YOUR_OPENAI_API_KEY"
export MILVUS_URI="http://localhost:19530"

export EVEROS_INDEX__BACKEND="milvus"
export EVEROS_MILVUS__URI="$MILVUS_URI"
export EVEROS_MILVUS__COLLECTION_PREFIX="everos_bootcamp"

export EVEROS_LLM__MODEL="gpt-5.4-mini"
export EVEROS_LLM__API_KEY="$OPENAI_API_KEY"
export EVEROS_LLM__BASE_URL="https://api.openai.com/v1"

export EVEROS_EMBEDDING__MODEL="text-embedding-3-small"
export EVEROS_EMBEDDING__API_KEY="$OPENAI_API_KEY"
export EVEROS_EMBEDDING__BASE_URL="https://api.openai.com/v1"
export EVEROS_EMBEDDING__DIMENSIONS="1024"

export EVEROS_MEMORIZE__MODE="chat"

EverOS использует OpenAI как для извлечения данных из памяти, так и для встраивания. По умолчанию text-embedding-3-small возвращает размеры 1536, но EverOS передает настроенное значение dimensions в OpenAI. В этом руководстве запрашиваются размеры 1024, чтобы они соответствовали схемам Milvus, управляемым EverOS.

Режим памяти chat позволяет сосредоточить внимание в этом примере на пользовательских памятях. EverOS управляет коллекциями Milvus и их схемами, поэтому вам не нужно создавать их самостоятельно.

Запустите EverOS

Запустите HTTP-сервер EverOS:

uv run everos server start --root "$EVEROS_ROOT"

Оставьте этот терминал открытым. При запуске EverOS подключается к Milvus и создает семь коллекций производных индексов с настроенным префиксом.

Откройте ещё один терминал в том же каталоге проекта и проверьте работу службы:

curl http://127.0.0.1:8000/health

Пример вывода:

{
  "status": "ok",
  "version": "1.3.0",
  "capabilities": {
    "llm": true,
    "embed": true,
    "rerank": false,
    "multimodal_llm": false,
    "parser": true
  },
  "cascade": {
    "healthy": true,
    "pending": 0
  }
}

Ответ содержит дополнительные поля, характеризующие работоспособность системы. Важными значениями для данного руководства являются status: "ok", llm: true, embed: true и cascade.healthy: true.

Добавление диалогов проекта

Следующая программа на Python отправляет в EverOS десять независимых диалогов. Atlas имеет отдельные диалоги запуска и отката. Восемь диалогов о других проектах служат отвлекающими факторами, чтобы при последующем поиске система могла идентифицировать правильные воспоминания о проекте.

Сохраните следующий код как add_memories.py:

import json
import time
from urllib.request import Request, urlopen


API_URL = "http://127.0.0.1:8000/api/v2/memory"
NOW = int(time.time() * 1000)

conversations = [
    (
        "atlas-release",
        [
            {
                "sender_id": "maya",
                "sender_name": "Maya",
                "role": "user",
                "timestamp": NOW,
                "content": (
                    "For Project Atlas, we decided to launch with a 10% canary "
                    "on September 30. Promote to all users only after the checkout "
                    "error rate stays below 1% for 30 minutes."
                ),
            },
            {
                "sender_id": "assistant",
                "role": "assistant",
                "timestamp": NOW + 1_000,
                "content": (
                    "Understood. I will remember the Atlas launch date, canary "
                    "percentage, and promotion gate."
                ),
            },
        ],
    ),
    (
        "atlas-rollback",
        [
            {
                "sender_id": "maya",
                "sender_name": "Maya",
                "role": "user",
                "timestamp": NOW + 10_000,
                "content": (
                    "The Atlas rollback owner is Priya. Roll back immediately if "
                    "checkout errors exceed 2% for five minutes, and keep the "
                    "previous container image available for 24 hours."
                ),
            },
            {
                "sender_id": "assistant",
                "role": "assistant",
                "timestamp": NOW + 11_000,
                "content": (
                    "Got it. Priya owns rollback, with the 2% five-minute trigger "
                    "and a 24-hour image retention window."
                ),
            },
        ],
    ),
    (
        "orion-pricing",
        [
            {
                "sender_id": "maya",
                "sender_name": "Maya",
                "role": "user",
                "timestamp": NOW + 20_000,
                "content": (
                    "Project Orion will test annual billing with the education "
                    "segment. The pricing review is scheduled for October 12."
                ),
            },
            {
                "sender_id": "assistant",
                "role": "assistant",
                "timestamp": NOW + 21_000,
                "content": (
                    "I will remember Orion's annual billing experiment and October "
                    "pricing review."
                ),
            },
        ],
    ),
    (
        "vega-mobile",
        [
            {
                "sender_id": "maya",
                "sender_name": "Maya",
                "role": "user",
                "timestamp": NOW + 30_000,
                "content": (
                    "For Project Vega, the mobile team chose offline drafts as the "
                    "next milestone. Elena will review the interaction design on "
                    "October 18."
                ),
            },
            {
                "sender_id": "assistant",
                "role": "assistant",
                "timestamp": NOW + 31_000,
                "content": (
                    "Noted. Vega's next milestone is offline drafts, followed by "
                    "Elena's design review."
                ),
            },
        ],
    ),
    (
        "nova-warehouse",
        [
            {
                "sender_id": "maya",
                "sender_name": "Maya",
                "role": "user",
                "timestamp": NOW + 40_000,
                "content": (
                    "Project Nova will migrate the analytics warehouse to Iceberg. "
                    "Marcus owns the checksum rehearsal scheduled for October 22."
                ),
            },
            {
                "sender_id": "assistant",
                "role": "assistant",
                "timestamp": NOW + 41_000,
                "content": (
                    "I will remember Nova's warehouse migration and Marcus's "
                    "checksum rehearsal."
                ),
            },
        ],
    ),
    (
        "helios-support",
        [
            {
                "sender_id": "maya",
                "sender_name": "Maya",
                "role": "user",
                "timestamp": NOW + 50_000,
                "content": (
                    "Project Helios needs weekend support coverage for the APAC "
                    "region. Imani will publish the rotation schedule on November 1."
                ),
            },
            {
                "sender_id": "assistant",
                "role": "assistant",
                "timestamp": NOW + 51_000,
                "content": (
                    "Noted. Helios needs APAC weekend coverage, and Imani owns the "
                    "rotation schedule."
                ),
            },
        ],
    ),
    (
        "luna-onboarding",
        [
            {
                "sender_id": "maya",
                "sender_name": "Maya",
                "role": "user",
                "timestamp": NOW + 60_000,
                "content": (
                    "Project Luna will replace the onboarding tour with a checklist. "
                    "The localized copy is due from the content team on October 25."
                ),
            },
            {
                "sender_id": "assistant",
                "role": "assistant",
                "timestamp": NOW + 61_000,
                "content": (
                    "I will remember Luna's checklist approach and the localization "
                    "deadline."
                ),
            },
        ],
    ),
    (
        "aurora-observability",
        [
            {
                "sender_id": "maya",
                "sender_name": "Maya",
                "role": "user",
                "timestamp": NOW + 70_000,
                "content": (
                    "Project Aurora will retain detailed telemetry for 30 days. "
                    "The operations team should alert after three consecutive "
                    "heartbeat misses."
                ),
            },
            {
                "sender_id": "assistant",
                "role": "assistant",
                "timestamp": NOW + 71_000,
                "content": (
                    "Understood. Aurora keeps 30 days of telemetry and alerts after "
                    "three missed heartbeats."
                ),
            },
        ],
    ),
    (
        "comet-invoices",
        [
            {
                "sender_id": "maya",
                "sender_name": "Maya",
                "role": "user",
                "timestamp": NOW + 80_000,
                "content": (
                    "Project Comet will add downloadable invoice PDFs for enterprise "
                    "accounts. Finance will approve the tax-field layout on October 28."
                ),
            },
            {
                "sender_id": "assistant",
                "role": "assistant",
                "timestamp": NOW + 81_000,
                "content": (
                    "Noted. Comet covers enterprise invoice PDFs and an October tax "
                    "layout review."
                ),
            },
        ],
    ),
    (
        "solstice-research",
        [
            {
                "sender_id": "maya",
                "sender_name": "Maya",
                "role": "user",
                "timestamp": NOW + 90_000,
                "content": (
                    "Project Solstice is prototyping voice notes for field researchers. "
                    "The research team will interview 12 participants in November."
                ),
            },
            {
                "sender_id": "assistant",
                "role": "assistant",
                "timestamp": NOW + 91_000,
                "content": (
                    "I will remember Solstice's voice-note prototype and the planned "
                    "participant interviews."
                ),
            },
        ],
    ),
]


def post(path, payload):
    request = Request(
        f"{API_URL}/{path}",
        data=json.dumps(payload).encode(),
        headers={"Content-Type": "application/json"},
        method="POST",
    )
    with urlopen(request, timeout=300) as response:
        return json.load(response)["data"]


for session_id, messages in conversations:
    added = post(
        "add",
        {
            "session_id": session_id,
            "app_id": "project-assistant",
            "project_id": "launch-planning",
            "messages": messages,
            "defer_extraction": True,
        },
    )
    flushed = post(
        "flush",
        {
            "session_id": session_id,
            "app_id": "project-assistant",
            "project_id": "launch-planning",
        },
    )
    print(f"{session_id}: {added['status']} -> {flushed['status']}")

Запустите его из каталога проекта:

uv run python add_memories.py

Пример вывода:

atlas-release: accumulated -> extracted
atlas-rollback: accumulated -> extracted
orion-pricing: accumulated -> extracted
vega-mobile: accumulated -> extracted
nova-warehouse: accumulated -> extracted
helios-support: accumulated -> extracted
luna-onboarding: accumulated -> extracted
aurora-observability: accumulated -> extracted
comet-invoices: accumulated -> extracted
solstice-research: accumulated -> extracted

Установка defer_extraction в значение true позволяет сохранять каждый диалог в постоянном буфере без запроса к LLM на определение границы. Следующий вызов /flush помечает конец данной сессии и запускает одно извлечение. Затем EverOS записывает извлеченный эпизод в формат Markdown и асинхронно встраивает его в индекс Milvus.

Просмотр памяти Markdown

Сгенерированный файл эпизода хранится в рамках области приложения, проекта и пользователя:

find "$EVEROS_ROOT/project-assistant/launch-planning/users/maya/episodes" \
  -type f -name "*.md"

Пример вывода (дата в имени файла отражает время запуска примера):

everos-data/project-assistant/launch-planning/users/maya/episodes/episode-2026-09-08.md

Откройте файл, чтобы увидеть воспоминания, извлеченные LLM. Сокращённый отрывок выглядит следующим образом:

## ep_20260908_00000001

**owner_id**: maya
**session_id**: atlas-release
**sender_ids**: [maya, assistant]

### Subject
Maya's Project Atlas Launch Decision: September 30 Canary and Promotion Criteria

### Content
Maya decided that Project Atlas would launch with a 10% canary on September 30.
The promotion to all users would occur only after the checkout error rate remained
below 1% for 30 minutes.

Точная формулировка, идентификаторы и временные метки могут отличаться, поскольку воспоминания извлекаются LLM. Исходные файлы Markdown остаются надежным источником достоверной информации; на их основе можно восстановить индекс Milvus.

Поиск воспоминаний

Используйте гибридный поиск, чтобы определить, что следует запомнить, прежде чем Atlas начнёт работу. Сохраните следующий код как search_memories.py:

import json
import time
from urllib.request import Request, urlopen


URL = "http://127.0.0.1:8000/api/v2/memory/search"
payload = {
    "user_id": "maya",
    "app_id": "project-assistant",
    "project_id": "launch-planning",
    "query": "What should I remember before Atlas goes live?",
    "method": "hybrid",
    "top_k": 4,
}


def search():
    request = Request(
        URL,
        data=json.dumps(payload).encode(),
        headers={"Content-Type": "application/json"},
        method="POST",
    )
    with urlopen(request, timeout=300) as response:
        return json.load(response)["data"]["episodes"]


expected_sessions = {"atlas-release", "atlas-rollback"}

for _ in range(30):
    episodes = search()
    top_results = episodes[:2]
    if {episode["session_id"] for episode in top_results} == expected_sessions:
        break
    time.sleep(2)
else:
    raise RuntimeError("The expected Atlas memories were not indexed in time")

for rank, episode in enumerate(top_results, start=1):
    print(f"{rank}. {episode['session_id']} | score={episode['score']:.3f}")
    print(f"   {episode['subject']}")

Запустите поиск:

uv run python search_memories.py

Справочный вывод (оценки и формулировки могут отличаться):

1. atlas-release | score=0.492
   Project Atlas Launch Plan: 10% Canary Rollout on September 30 with Error Rate Gate
2. atlas-rollback | score=0.400
   Atlas Rollback Plan Details: Priya as Owner, 2% Error Trigger, 24-Hour Image Retention

Оба диалога Atlas возвращаются перед восемью не связанными с запросом диалогами. EverOS отправляет запрос на конечную точку встраивания OpenAI, запрашивает у Milvus кандидатов по BM25 и векторным представлениям в рамках приложения и проекта Maya, а затем объединяет два списка результатов.

Просмотрите коллекции Milvus

EverOS создает по одной коллекции для каждого поддерживаемого типа производственной памяти. Используйте команду ` MilvusClient `, чтобы отобразить количество строк в каждой из них:

import os

from pymilvus import MilvusClient


prefix = "everos_bootcamp"
client = MilvusClient(uri=os.environ.get("MILVUS_URI", "http://localhost:19530"))

memory_kinds = [
    "agent_case",
    "agent_skill",
    "atomic_fact",
    "episode",
    "foresight",
    "knowledge_topic",
    "user_profile",
]

for kind in memory_kinds:
    name = f"{prefix}_{kind}"
    if client.has_collection(collection_name=name):
        result = client.query(
            collection_name=name,
            filter="",
            output_fields=["count(*)"],
        )
        print(f"{kind}: {result[0]['count(*)']} rows")

client.close()

См. вывод проверенного запуска:

agent_case: 0 rows
agent_skill: 0 rows
atomic_fact: 50 rows
episode: 10 rows
foresight: 0 rows
knowledge_topic: 0 rows
user_profile: 1 rows

Точное количество атомарных фактов может варьироваться в зависимости от вывода LLM. Десять строк эпизодов соответствуют десяти сохраненным диалогам. Остальные коллекции доступны для режимов памяти и функций EverOS, которые не используются в данном конкретном примере.

Использование другого развертывания Milvus

Чтобы использовать другой конечный пункт Milvus Server или Zilliz Cloud, обновите файл EVEROS_MILVUS__URI. Укажите EVEROS_MILVUS__TOKEN, если конечный пункт требует аутентификации. Код для приема данных и поиска остается без изменений.

Заключение

Объединяя EverOS с Milvus, вы можете превратить разговоры в долговечные воспоминания и извлекать их по ключевым словам и семантическим сигналам. Вы можете адаптировать тот же шаблон, чтобы предоставить помощникам и другим агентским приложениям долгосрочную память для ваших собственных пользователей, проектов и рабочих процессов.