headroom/examples/inline_memory_demo.py
chopratejas 9c9bb30ded Add persistent memory system with zero-latency inline extraction
Features:
- with_fast_memory(): Zero-latency inline extraction (Letta-style)
  - Memory extracted as part of LLM response, no extra API calls
  - Semantic retrieval with local embeddings (sub-50ms)
- with_memory(): Background extraction for non-blocking memory
- SQLite + FTS5 storage with vector similarity search
- Multi-user isolation by user_id

Memory enables temporal compression - extract key facts instead of
carrying full conversation history (4000 tokens → 50 tokens).

Includes:
- Comprehensive test suite (71 new tests)
- Documentation (docs/memory.md)
- Benchmark examples comparing approaches
- E2E test with LLM-as-judge evaluation
2026-01-14 21:32:09 -08:00

209 lines
6.6 KiB
Python

#!/usr/bin/env python3
"""Demo: Zero-Latency Inline Memory Extraction (Letta-style).
This demonstrates the Letta/MemGPT approach where the LLM outputs
memories as part of its response - ZERO extra latency!
Comparison:
- OLD: Main LLM call (500ms) + Extraction LLM call (500ms) = 1000ms total
- NEW: Main LLM call with inline extraction (500ms) = 500ms total
The memory is extracted from the SAME tokens the LLM is already generating.
Usage:
export OPENAI_API_KEY="sk-..."
python examples/inline_memory_demo.py
"""
from __future__ import annotations
import os
import sys
import time
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
from openai import OpenAI
from headroom.memory.inline_extractor import (
InlineMemoryWrapper,
)
def demo_inline_extraction():
"""Demonstrate inline memory extraction."""
print("=" * 60)
print("ZERO-LATENCY INLINE MEMORY EXTRACTION")
print("=" * 60)
api_key = os.environ.get("OPENAI_API_KEY")
if not api_key:
print("ERROR: OPENAI_API_KEY not set")
sys.exit(1)
client = OpenAI(api_key=api_key)
wrapper = InlineMemoryWrapper(client)
# Test conversations with memory-worthy content
test_conversations = [
{
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{
"role": "user",
"content": "I'm a Python developer working on a fintech startup. We use PostgreSQL for our database.",
},
],
"description": "User shares background info",
},
{
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello! How are you today?"},
],
"description": "Simple greeting (should have no memories)",
},
{
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{
"role": "user",
"content": "I prefer async/await over callbacks, and I always use type hints in my code.",
},
],
"description": "User shares preferences",
},
]
total_latency = 0
total_memories = 0
for i, test in enumerate(test_conversations, 1):
print(f"\n{'' * 60}")
print(f"Test {i}: {test['description']}")
print(f"{'' * 60}")
user_msg = test["messages"][-1]["content"]
print(f"User: {user_msg[:80]}...")
start = time.perf_counter()
response, memories = wrapper.chat(
messages=test["messages"],
model="gpt-4o-mini",
)
elapsed = time.perf_counter() - start
total_latency += elapsed
total_memories += len(memories)
print(f"\nAssistant: {response[:150]}...")
print(f"\nLatency: {elapsed * 1000:.0f}ms")
print(f"Memories extracted: {len(memories)}")
if memories:
for mem in memories:
print(f" - [{mem.get('category', 'unknown')}] {mem.get('content', '')}")
print(f"\n{'=' * 60}")
print("SUMMARY")
print(f"{'=' * 60}")
print(f"Total conversations: {len(test_conversations)}")
print(f"Total memories extracted: {total_memories}")
print(f"Average latency: {total_latency / len(test_conversations) * 1000:.0f}ms")
print("\n✓ ZERO extra latency - memories extracted from same response!")
def benchmark_vs_separate_extraction():
"""Compare inline vs separate LLM extraction."""
print("\n" + "=" * 60)
print("BENCHMARK: Inline vs Separate Extraction")
print("=" * 60)
api_key = os.environ.get("OPENAI_API_KEY")
if not api_key:
print("ERROR: OPENAI_API_KEY not set")
sys.exit(1)
client = OpenAI(api_key=api_key)
wrapper = InlineMemoryWrapper(client)
test_message = "I'm a senior backend engineer at Netflix. I prefer Go for microservices but Python for ML. I always use Docker and Kubernetes."
# Measure inline extraction
print("\n1. INLINE EXTRACTION (Letta-style)")
print(" Single LLM call with memory instruction")
inline_latencies = []
for i in range(3):
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": test_message},
]
start = time.perf_counter()
response, memories = wrapper.chat(messages, model="gpt-4o-mini")
elapsed = time.perf_counter() - start
inline_latencies.append(elapsed * 1000)
print(f" Run {i + 1}: {elapsed * 1000:.0f}ms ({len(memories)} memories)")
# Measure separate extraction (simulated)
print("\n2. SEPARATE EXTRACTION (Traditional)")
print(" Main LLM call + Extraction LLM call")
separate_latencies = []
for i in range(3):
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": test_message},
]
start = time.perf_counter()
# First call: Main response
response1 = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
)
main_response = response1.choices[0].message.content
# Second call: Extract memories
extraction_prompt = f"""Extract memories from this conversation:
User: {test_message}
Assistant: {main_response}
Return JSON: {{"memories": [{{"content": "...", "category": "preference|fact|context"}}]}}"""
client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": extraction_prompt}],
)
elapsed = time.perf_counter() - start
separate_latencies.append(elapsed * 1000)
print(f" Run {i + 1}: {elapsed * 1000:.0f}ms")
# Summary
inline_avg = sum(inline_latencies) / len(inline_latencies)
separate_avg = sum(separate_latencies) / len(separate_latencies)
print(f"\n{'' * 60}")
print(f"{'Approach':<30} {'Avg Latency':<15} {'Savings':<15}")
print(f"{'' * 60}")
print(f"{'Inline (Letta-style)':<30} {inline_avg:>10.0f}ms {'baseline':>15}")
print(
f"{'Separate extraction':<30} {separate_avg:>10.0f}ms {f'+{separate_avg - inline_avg:.0f}ms':>15}"
)
print(f"{'' * 60}")
savings = separate_avg - inline_avg
print(
f"\n✓ Inline extraction saves {savings:.0f}ms ({savings / separate_avg * 100:.0f}% faster)"
)
print("✓ This is the latency of an ENTIRE extra LLM call - now FREE!")
if __name__ == "__main__":
demo_inline_extraction()
benchmark_vs_separate_extraction()