Implement AI orchestration wedding demo
This commit is contained in:
3
backend/app/memory/README.md
Normal file
3
backend/app/memory/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Memory
|
||||
|
||||
Context builder and semantic memory manager. Redis and Qdrant are represented behind replaceable service boundaries with deterministic local behavior for tests.
|
||||
1
backend/app/memory/__init__.py
Normal file
1
backend/app/memory/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Short- and long-term memory services."""
|
||||
33
backend/app/memory/context_builder.py
Normal file
33
backend/app/memory/context_builder.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from collections import deque
|
||||
from typing import Any
|
||||
|
||||
from app.domain.schemas import TranscriptChunk
|
||||
|
||||
|
||||
class ContextBuilder:
|
||||
"""Maintains a Redis-compatible meeting context; uses memory for offline tests."""
|
||||
|
||||
def __init__(self, window_size: int = 12) -> None:
|
||||
self.window_size = window_size
|
||||
self._transcript: deque[TranscriptChunk] = deque(maxlen=window_size)
|
||||
self._participants: set[str] = set()
|
||||
self._recent_decisions: deque[dict[str, Any]] = deque(maxlen=10)
|
||||
|
||||
def add_chunk(self, chunk: TranscriptChunk) -> None:
|
||||
self._transcript.append(chunk)
|
||||
self._participants.add(chunk.speaker)
|
||||
|
||||
def add_decision(self, decision: dict[str, Any]) -> None:
|
||||
self._recent_decisions.append(decision)
|
||||
|
||||
def snapshot(self, app_state: dict[str, Any] | None = None) -> dict[str, Any]:
|
||||
return {
|
||||
"meeting": {"id": "demo-meeting", "title": "Wedding Planning Review"},
|
||||
"participants": sorted(self._participants),
|
||||
"transcript_window": [chunk.model_dump(mode="json") for chunk in self._transcript],
|
||||
"recent_ai_decisions": list(self._recent_decisions),
|
||||
"application_state": app_state or {},
|
||||
}
|
||||
|
||||
def transcript(self) -> list[TranscriptChunk]:
|
||||
return list(self._transcript)
|
||||
47
backend/app/memory/memory_manager.py
Normal file
47
backend/app/memory/memory_manager.py
Normal file
@@ -0,0 +1,47 @@
|
||||
from hashlib import sha256
|
||||
from math import sqrt
|
||||
from typing import Any
|
||||
|
||||
|
||||
def embed_text(text: str, size: int = 32) -> list[float]:
|
||||
"""Deterministic local embedding used for offline demo and tests."""
|
||||
|
||||
buckets = [0.0] * size
|
||||
for word in text.lower().split():
|
||||
digest = sha256(word.encode()).digest()
|
||||
buckets[digest[0] % size] += 1.0
|
||||
norm = sqrt(sum(v * v for v in buckets)) or 1.0
|
||||
return [v / norm for v in buckets]
|
||||
|
||||
|
||||
class MemoryManager:
|
||||
"""Stores semantic memories behind a Qdrant-like interface with local fallback."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._items: list[dict[str, Any]] = []
|
||||
|
||||
def upsert(self, memory_type: str, text: str, metadata: dict[str, Any] | None = None) -> dict[str, Any]:
|
||||
item = {
|
||||
"id": sha256(f"{memory_type}:{text}".encode()).hexdigest()[:16],
|
||||
"type": memory_type,
|
||||
"text": text,
|
||||
"metadata": metadata or {},
|
||||
"embedding": embed_text(text),
|
||||
}
|
||||
self._items = [existing for existing in self._items if existing["id"] != item["id"]]
|
||||
self._items.append(item)
|
||||
return item
|
||||
|
||||
def retrieve(self, query: str, limit: int = 5) -> list[dict[str, Any]]:
|
||||
query_vec = embed_text(query)
|
||||
|
||||
def score(item: dict[str, Any]) -> float:
|
||||
return sum(a * b for a, b in zip(query_vec, item["embedding"], strict=True))
|
||||
|
||||
ranked = sorted(self._items, key=score, reverse=True)
|
||||
return [{k: v for k, v in item.items() if k != "embedding"} | {"score": score(item)} for item in ranked[:limit]]
|
||||
|
||||
def seed_demo(self) -> None:
|
||||
self.upsert("customer_preference", "Customer prefers white flowers and a classic minimal arrangement.", {"customer": "Demo"})
|
||||
self.upsert("vendor", "Aegean Blooms is the preferred florist for white flower arrangements.", {"vendor": "Aegean Blooms"})
|
||||
self.upsert("meeting", "Previous meeting agreed to compare venues before reserving July dates.", {"meeting_id": "prior-1"})
|
||||
Reference in New Issue
Block a user