Membangun Memori Agen Jangka Panjang dengan EverOS dan Milvus
EverOS adalah sistem memori berbasis Markdown untuk agen AI. Sistem ini mengekstrak memori yang tahan lama dari percakapan, mempertahankan Markdown sebagai sumber kebenaran, dan membangun indeks turunan yang dapat dicari.
Dalam tutorial ini, kita akan membangun asisten proyek yang mengingat keputusan rilis di berbagai percakapan terpisah. Kita akan menambahkan percakapan tentang peluncuran Project Atlas bersamaan dengan percakapan yang tidak terkait tentang proyek lain. EverOS akan menggunakan LLM untuk mengekstrak memori tersebut, sementara Milvus menyimpan indeks BM25 dan vektor yang digunakan untuk pencarian hibrida.
Conversations
|
v
EverOS + LLM ------> Markdown memory files
|
| embedding model
v
Milvus ------> BM25 + vector hybrid search
LLM dan model embedding memiliki tanggung jawab yang berbeda. LLM mengubah percakapan menjadi memori terstruktur. Model embedding mengubah memori tersebut dan kueri pencarian selanjutnya menjadi vektor. Pencarian hibrida dasar dalam tutorial ini tidak memerlukan model reranking.
Prasyarat
Anda memerlukan:
- Python 3.12 atau yang lebih baru
uv- Server Milvus yang aktif
- Kunci API OpenAI
Tutorial ini terhubung ke Server Milvus di http://localhost:19530. EverOS juga mendukung Zilliz Cloud melalui pengaturan URI dan token yang sama. Backend Milvus-nya mengharapkan titik akhir jarak jauh dan tidak menerima jalur file Milvus Lite.
Instal EverOS
Buat proyek lokal dan instal EverOS beserta dependensi Milvus opsionalnya:
mkdir everos-milvus-demo
cd everos-milvus-demo
uv init --bare --python 3.12
uv add "everos[milvus]"
Perintah ini sengaja tidak menetapkan versi tertentu, sehingga instalasi baru akan menggunakan rilis EverOS terbaru yang kompatibel.
Inisialisasi akar memori terpisah untuk tutorial ini:
export EVEROS_ROOT="$PWD/everos-data"
uv run everos init --root "$EVEROS_ROOT"
EverOS akan membuat everos.toml dan ome.toml di bawah direktori ini. EverOS juga akan menulis memori yang diekstrak di sini.
Konfigurasikan OpenAI dan Milvus
Tetapkan kunci API OpenAI dan konfigurasikan EverOS melalui variabel lingkungan:
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 menggunakan OpenAI baik untuk ekstraksi memori maupun embedding. Secara default, text-embedding-3-small mengembalikan dimensi 1536, tetapi EverOS meneruskan nilai dimensions yang telah dikonfigurasi ke OpenAI. Tutorial ini meminta dimensi 1024 agar sesuai dengan skema Milvus yang dikelola oleh EverOS.
Mode memori chat membuat contoh ini tetap berfokus pada memori pengguna. EverOS mengelola koleksi Milvus dan skemanya, sehingga Anda tidak perlu membuatnya sendiri.
Jalankan EverOS
Jalankan server HTTP EverOS:
uv run everos server start --root "$EVEROS_ROOT"
Biarkan terminal ini tetap terbuka. EverOS terhubung ke Milvus dan membuat tujuh koleksi indeks turunan dengan awalan yang telah dikonfigurasi saat startup.
Buka terminal lain di direktori proyek yang sama dan periksa layanan:
curl http://127.0.0.1:8000/health
Contoh keluaran:
{
"status": "ok",
"version": "1.3.0",
"capabilities": {
"llm": true,
"embed": true,
"rerank": false,
"multimodal_llm": false,
"parser": true
},
"cascade": {
"healthy": true,
"pending": 0
}
}
Respons tersebut berisi bidang status tambahan. Nilai-nilai penting untuk tutorial ini adalah status: "ok", llm: true, embed: true, dan cascade.healthy: true.
Tambahkan percakapan proyek
Program Python berikut mengirimkan sepuluh percakapan independen ke EverOS. Atlas memiliki diskusi terpisah untuk peluncuran dan pembatalan. Delapan percakapan tentang proyek lain berfungsi sebagai pengalih perhatian sehingga pencarian selanjutnya harus mengidentifikasi ingatan proyek yang benar.
Simpan kode berikut sebagai 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']}")
Jalankan dari direktori proyek:
uv run python add_memories.py
Output referensi:
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
Menyetel ` defer_extraction ` ke ` true ` menyimpan setiap percakapan dalam buffer yang tahan lama tanpa meminta LLM untuk mendeteksi batas. Panggilan ` /flush ` berikut menandai akhir sesi tersebut dan memicu satu ekstraksi. EverOS kemudian menulis episode yang diekstraksi ke Markdown dan menyematkannya secara asinkron ke indeks Milvus.
Periksa memori Markdown
Berkas episode yang dihasilkan disimpan di bawah cakupan aplikasi, proyek, dan pengguna:
find "$EVEROS_ROOT/project-assistant/launch-planning/users/maya/episodes" \
-type f -name "*.md"
Output referensi (tanggal pada nama file mencerminkan waktu saat Anda menjalankan contoh ini):
everos-data/project-assistant/launch-planning/users/maya/episodes/episode-2026-09-08.md
Buka berkas tersebut untuk melihat memori yang diekstraksi oleh LLM. Cuplikan singkatnya terlihat seperti ini:
## 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.
Kata-kata, pengenal, dan cap waktu yang tepat dapat bervariasi karena memori diekstraksi oleh LLM. File Markdown asli tetap menjadi sumber kebenaran yang dapat diandalkan; indeks Milvus dapat dibangun kembali dari file tersebut.
Cari kenangan
Gunakan pencarian hibrida untuk menanyakan apa yang harus diingat sebelum Atlas diluncurkan. Simpan kode berikut sebagai 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']}")
Jalankan pencarian:
uv run python search_memories.py
Hasil referensi (skor dan kata-katanya mungkin berbeda):
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
Kedua percakapan Atlas ditampilkan di atas delapan percakapan yang tidak terkait. EverOS mengirimkan kueri ke titik akhir embedding OpenAI, meminta Milvus untuk kandidat BM25 dan vektor dalam lingkup aplikasi dan proyek Maya, serta menggabungkan kedua daftar hasil tersebut.
Periksa koleksi Milvus
EverOS membuat satu koleksi untuk setiap jenis memori turunan yang didukung. Gunakan MilvusClient untuk mencantumkan jumlah barisnya:
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()
Lihat hasil dari proses yang telah divalidasi:
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
Jumlah pasti fakta atomik dapat bervariasi tergantung pada keluaran LLM. Sepuluh baris episode sesuai dengan sepuluh percakapan yang telah disimpan. Koleksi lainnya tersedia untuk mode memori dan fitur EverOS yang tidak diuji dalam contoh terfokus ini.
Gunakan penyebaran Milvus lainnya
Untuk menggunakan titik akhir Milvus Server lain atau Zilliz Cloud, perbarui EVEROS_MILVUS__URI. Tetapkan EVEROS_MILVUS__TOKEN jika titik akhir memerlukan otentikasi. Kode pengambilan data dan pencarian tetap tidak berubah.
Kesimpulan
Dengan menggabungkan EverOS dan Milvus, Anda dapat mengubah percakapan menjadi memori yang tahan lama dan mengaksesnya melalui kata kunci serta sinyal semantik. Anda dapat menyesuaikan pola yang sama untuk memberikan memori jangka panjang kepada asisten dan aplikasi agen lainnya bagi pengguna, proyek, serta alur kerja Anda sendiri.