Files
EverOS/use-cases/openher/demo/evermemos_demo.py
2026-06-06 13:59:12 +08:00

461 lines
15 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
OpenHer × EverOS Integration Demo
Demonstrates how EverOS provides long-term memory to the
AI Being persona engine. Shows session context loading, memory
storage, search, and relationship vector evolution.
Usage:
# With EverMind Cloud
export EVERMEMOS_BASE_URL=https://api.evermind.ai/v1
export EVERMEMOS_API_KEY=your_key
python demo/evermemos_demo.py
# With self-hosted EverOS
export EVERMEMOS_BASE_URL=http://localhost:1995/api/v1
python demo/evermemos_demo.py
"""
import asyncio
import os
import sys
from datetime import datetime
# ──────────────────────────────────────────────
# EverOS Client (minimal standalone version)
# ──────────────────────────────────────────────
try:
import httpx
except ImportError:
print("❌ httpx not installed. Run: pip install httpx")
sys.exit(1)
class EverOSClient:
"""Minimal EverOS client for demo purposes."""
def __init__(self, base_url: str, api_key: str = ""):
self.base_url = base_url.rstrip("/")
self.api_key = api_key
self._client = httpx.AsyncClient(timeout=10.0)
self.available = bool(base_url)
async def _headers(self) -> dict:
h = {"Content-Type": "application/json"}
if self.api_key:
h["Authorization"] = f"Bearer {self.api_key}"
return h
async def health_check(self) -> bool:
"""Check if EverOS is reachable."""
try:
# Try the health endpoint (remove /api/v1 suffix)
health_url = self.base_url.replace("/api/v1", "") + "/health"
resp = await self._client.get(health_url, headers=await self._headers())
return resp.status_code == 200
except Exception:
return False
async def store_turn(
self,
user_id: str,
persona_id: str,
persona_name: str,
user_name: str,
group_id: str,
user_message: str,
agent_reply: str,
) -> dict:
"""Store a conversation turn as memory."""
now = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S+00:00")
messages = [
{
"message_id": f"msg_{hash(user_message) & 0xFFFF:04x}_u",
"create_time": now,
"sender": user_id,
"sender_name": user_name,
"content": user_message,
},
{
"message_id": f"msg_{hash(agent_reply) & 0xFFFF:04x}_a",
"create_time": now,
"sender": persona_id,
"sender_name": persona_name,
"content": agent_reply,
},
]
resp = await self._client.post(
f"{self.base_url}/memories",
json={"messages": messages, "group_id": group_id},
headers=await self._headers(),
)
return resp.json() if resp.status_code == 200 else {"error": resp.text}
async def search(
self,
query: str,
user_id: str,
group_id: str,
top_k: int = 5,
) -> dict:
"""Search for relevant memories."""
resp = await self._client.get(
f"{self.base_url}/memories/search",
params={
"query": query,
"user_id": user_id,
"group_id": group_id,
"top_k": top_k,
"retrieve_method": "hybrid",
},
headers=await self._headers(),
)
return resp.json() if resp.status_code == 200 else {"error": resp.text}
async def get_user_profile(self, user_id: str) -> dict:
"""Get user profile (accumulated from conversations)."""
resp = await self._client.get(
f"{self.base_url}/users/{user_id}/profile",
headers=await self._headers(),
)
return resp.json() if resp.status_code == 200 else {}
async def close(self):
await self._client.aclose()
# ──────────────────────────────────────────────
# Relationship Vector (from EverOS session)
# ──────────────────────────────────────────────
def compute_relationship_vector(profile_data: dict) -> dict:
"""
Extract 4D relationship vector from EverOS profile data.
These 4 dimensions expand the persona engine's neural network
from 8D to 12D input, allowing it to differentiate behavior
between strangers and old friends.
"""
return {
"relationship_depth": min(1.0, profile_data.get("interaction_count", 0) / 50),
"emotional_valence": profile_data.get("sentiment_avg", 0.0),
"trust_level": min(1.0, profile_data.get("trust_score", 0.0)),
"pending_foresight": 1.0 if profile_data.get("foresight") else 0.0,
}
def apply_relationship_ema(
prior: dict,
delta: dict,
conversation_depth: float,
prev_ema: dict | None = None,
) -> dict:
"""
Semi-emergent relationship update (Step 2.5 of ChatAgent lifecycle).
Blends EverOS prior with LLM-judged delta through EMA:
- alpha modulated by conversation depth (deeper = trust LLM more)
- Clips to valid ranges
- Preserves momentum through prev_ema
"""
if prev_ema is None:
prev_ema = dict(prior)
alpha = max(0.15, min(0.65, 0.15 + 0.5 * conversation_depth))
ema = {}
for k in prior:
lo = -1.0 if k == "emotional_valence" else 0.0
posterior = max(lo, min(1.0, prior[k] + delta.get(k, 0.0)))
prev = prev_ema.get(k, prior[k])
ema[k] = round(alpha * posterior + (1 - alpha) * prev, 4)
return ema
# ──────────────────────────────────────────────
# Demo
# ──────────────────────────────────────────────
async def main():
base_url = os.getenv("EVERMEMOS_BASE_URL", "")
api_key = os.getenv("EVERMEMOS_API_KEY", "")
if not base_url:
print("=" * 60)
print("OpenHer × EverOS Integration Demo")
print("=" * 60)
print()
print("⚠️ EVERMEMOS_BASE_URL not set.")
print()
print("To run this demo, set up EverOS:")
print()
print(" Option A — EverMind Cloud:")
print(" export EVERMEMOS_BASE_URL=https://api.evermind.ai/v1")
print(" export EVERMEMOS_API_KEY=your_key")
print()
print(" Option B — Self-hosted:")
print(" cd vendor/EverOS && docker compose up -d")
print(" uv run python src/run.py")
print(" export EVERMEMOS_BASE_URL=http://localhost:1995/api/v1")
print()
print("Get your API key: https://console.evermind.ai/")
print()
print("Running in simulation mode...\n")
await demo_simulation()
return
client = EverOSClient(base_url, api_key)
print("=" * 60)
print("OpenHer × EverOS Integration Demo")
print("=" * 60)
print(f"\n📡 EverOS: {base_url}")
# Health check
healthy = await client.health_check()
if not healthy:
print("❌ EverOS is not reachable. Check your URL and try again.")
await client.close()
return
print("✅ EverOS is healthy\n")
# ── Demo conversation ──
user_id = "demo_user"
persona_id = "luna"
persona_name = "Luna (陆暖)"
user_name = "Demo User"
group_id = f"{persona_id}__{user_id}"
conversations = [
(
"My name is Alex, I'm a software engineer",
"Nice to meet you Alex! What kind of software do you work on?",
),
(
"I love hiking in the mountains on weekends",
"That sounds wonderful! There's something about being up high "
"that makes everything else feel small.",
),
("I drink my coffee black, no sugar", "Noted! A purist. I respect that."),
]
print("📝 Storing conversation memories...\n")
for user_msg, agent_reply in conversations:
result = await client.store_turn(
user_id=user_id,
persona_id=persona_id,
persona_name=persona_name,
user_name=user_name,
group_id=group_id,
user_message=user_msg,
agent_reply=agent_reply,
)
status = "" if "error" not in result else ""
print(f' {status} User: "{user_msg[:50]}..."')
# Wait for indexing
print("\n⏳ Waiting for memory indexing (3s)...")
await asyncio.sleep(3)
# Search
print("\n🔍 Searching for relevant memories...\n")
queries = [
"What does Alex like to do on weekends?",
"How does Alex take their coffee?",
"What is Alex's occupation?",
]
for query in queries:
result = await client.search(
query=query,
user_id=user_id,
group_id=group_id,
)
memories = result.get("result", {}).get("memories", [])
print(f' Q: "{query}"')
if memories:
for mem in memories[:2]:
content = str(mem)[:100]
print(f"{content}")
else:
print(" → (no results yet — indexing may still be in progress)")
print()
# Relationship vector
print("📊 Relationship Vector Evolution:\n")
prior = {
"relationship_depth": 0.0,
"emotional_valence": 0.0,
"trust_level": 0.0,
"pending_foresight": 0.0,
}
deltas = [
{"relationship_depth": 0.1, "emotional_valence": 0.2, "trust_level": 0.05},
{"relationship_depth": 0.05, "emotional_valence": 0.1, "trust_level": 0.1},
{"relationship_depth": 0.08, "emotional_valence": 0.15, "trust_level": 0.12},
]
ema = None
for i, delta in enumerate(deltas):
ema = apply_relationship_ema(
prior, delta, conversation_depth=0.2 * (i + 1), prev_ema=ema
)
print(
f" Turn {i + 1}: depth={ema['relationship_depth']:.3f} "
f"valence={ema['emotional_valence']:.3f} "
f"trust={ema['trust_level']:.3f}"
)
prior = ema
print(
"\n → After 3 turns: no longer a stranger "
f"(depth={ema['relationship_depth']:.3f})"
)
print(" → Neural network now produces warmer, more familiar behavioral signals\n")
await client.close()
print("✅ Demo complete!")
async def demo_simulation():
"""Run demo in simulation mode (no EverOS connection)."""
print("📊 Simulating Relationship Vector Evolution:\n")
print(" This shows how the 4D EverOS relationship vector")
print(" deepens over multiple conversation turns.\n")
prior = {
"relationship_depth": 0.0,
"emotional_valence": 0.0,
"trust_level": 0.0,
"pending_foresight": 0.0,
}
# Simulate 10 turns of conversation
simulated_deltas = [
(
0.3,
{
"relationship_depth": 0.10,
"emotional_valence": 0.15,
"trust_level": 0.05,
},
),
(
0.4,
{
"relationship_depth": 0.08,
"emotional_valence": 0.10,
"trust_level": 0.08,
},
),
(
0.5,
{
"relationship_depth": 0.05,
"emotional_valence": 0.20,
"trust_level": 0.12,
},
),
(
0.6,
{
"relationship_depth": 0.06,
"emotional_valence": -0.10,
"trust_level": 0.03,
},
),
(
0.7,
{
"relationship_depth": 0.04,
"emotional_valence": 0.08,
"trust_level": 0.10,
},
),
(
0.7,
{
"relationship_depth": 0.03,
"emotional_valence": 0.12,
"trust_level": 0.08,
},
),
(
0.8,
{
"relationship_depth": 0.02,
"emotional_valence": 0.05,
"trust_level": 0.06,
},
),
(
0.8,
{
"relationship_depth": 0.03,
"emotional_valence": 0.10,
"trust_level": 0.05,
},
),
(
0.9,
{
"relationship_depth": 0.01,
"emotional_valence": 0.08,
"trust_level": 0.04,
},
),
(
0.9,
{
"relationship_depth": 0.02,
"emotional_valence": 0.06,
"trust_level": 0.03,
},
),
]
ema = None
for i, (depth, delta) in enumerate(simulated_deltas, 1):
alpha = max(0.15, min(0.65, 0.15 + 0.5 * depth))
ema = apply_relationship_ema(
prior, delta, conversation_depth=depth, prev_ema=ema
)
bar_d = "" * int(ema["relationship_depth"] * 20)
bar_v = "" * int(max(0, ema["emotional_valence"]) * 20)
bar_t = "" * int(ema["trust_level"] * 20)
print(
f" Turn {i:2d} (α={alpha:.2f}): "
f"depth={ema['relationship_depth']:.3f} {bar_d}"
)
print(f" valence={ema['emotional_valence']:+.3f} {bar_v}")
print(f" trust={ema['trust_level']:.3f} {bar_t}")
print()
prior = ema
print(" ──────────────────────────────────")
print(
f" Final state: depth={ema['relationship_depth']:.3f}, "
f"valence={ema['emotional_valence']:+.3f}, "
f"trust={ema['trust_level']:.3f}"
)
print()
print(" Turn 4 shows a negative emotional event (valence delta = -0.10),")
print(" but the EMA smoothing prevents overreaction — the relationship")
print(" continues to deepen because trust was already building.")
print()
print(" This vector feeds into the 25D neural network input,")
print(" producing different behavioral signals for strangers vs. friends:")
print(" - Higher warmth, vulnerability, and initiative for trusted users")
print(" - More guarded, formal signals for new users")
print()
print("✅ Simulation complete!")
if __name__ == "__main__":
asyncio.run(main())