mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-08-27 17:32:24 +00:00
#### RE-RAISING Pipecat live speech PR ### Added native speech-to-speech model support ### Summary: - Speech-to-speech support - Auto-detect audio frames and inject memories to system prompt for native audio models (Gemini Live, etc.) - Fix memory bloating - Replace memories each turn using XML tags instead of accumulating - Add temporal context - Show recency on search results ([2d ago], [15 Jan]) - New inject_mode param - auto (default), system, or user ### Docs update - Update the docs for native speech-2-speech models
134 lines
4.1 KiB
Python
134 lines
4.1 KiB
Python
"""Utility functions for Supermemory Pipecat integration."""
|
|
|
|
from datetime import datetime, timezone
|
|
from typing import Any, Dict, List, Union
|
|
|
|
|
|
def get_last_user_message(messages: List[Dict[str, str]]) -> str | None:
|
|
"""Extract the last user message content from a list of messages."""
|
|
for msg in reversed(messages):
|
|
if msg["role"] == "user":
|
|
return msg["content"]
|
|
return None
|
|
|
|
|
|
def format_relative_time(iso_timestamp: str) -> str:
|
|
"""Convert ISO timestamp to relative time string.
|
|
|
|
Format rules:
|
|
- [just now] - within 30 minutes
|
|
- [Xmins ago] - 30-60 minutes
|
|
- [X hrs ago] - less than 1 day
|
|
- [Xd ago] - less than 1 week
|
|
- [X Jul] - more than 1 week, same year
|
|
- [X Jul, 2023] - different year
|
|
"""
|
|
try:
|
|
dt = datetime.fromisoformat(iso_timestamp.replace("Z", "+00:00"))
|
|
now = datetime.now(timezone.utc)
|
|
diff = now - dt
|
|
|
|
seconds = diff.total_seconds()
|
|
minutes = seconds / 60
|
|
hours = seconds / 3600
|
|
days = seconds / 86400
|
|
|
|
if minutes < 30:
|
|
return "just now"
|
|
elif minutes < 60:
|
|
return f"{int(minutes)}mins ago"
|
|
elif hours < 24:
|
|
return f"{int(hours)} hrs ago"
|
|
elif days < 7:
|
|
return f"{int(days)}d ago"
|
|
elif dt.year == now.year:
|
|
return f"{dt.day} {dt.strftime('%b')}"
|
|
else:
|
|
return f"{dt.day} {dt.strftime('%b')}, {dt.year}"
|
|
except Exception:
|
|
return ""
|
|
|
|
|
|
def deduplicate_memories(
|
|
static: List[str],
|
|
dynamic: List[str],
|
|
search_results: List[Dict[str, Any]],
|
|
) -> Dict[str, Union[List[str], List[Dict[str, Any]]]]:
|
|
"""Deduplicate memories. Priority: static > dynamic > search.
|
|
|
|
Args:
|
|
static: List of static memory strings.
|
|
dynamic: List of dynamic memory strings.
|
|
search_results: List of search result dicts with 'memory' and 'updatedAt'.
|
|
"""
|
|
seen = set()
|
|
|
|
def unique_strings(memories: List[str]) -> List[str]:
|
|
out = []
|
|
for m in memories:
|
|
if m not in seen:
|
|
seen.add(m)
|
|
out.append(m)
|
|
return out
|
|
|
|
def unique_search(results: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
out = []
|
|
for r in results:
|
|
memory = r.get("memory", "")
|
|
if memory and memory not in seen:
|
|
seen.add(memory)
|
|
out.append(r)
|
|
return out
|
|
|
|
return {
|
|
"static": unique_strings(static),
|
|
"dynamic": unique_strings(dynamic),
|
|
"search_results": unique_search(search_results),
|
|
}
|
|
|
|
|
|
def format_memories_to_text(
|
|
memories: Dict[str, Union[List[str], List[Dict[str, Any]]]],
|
|
system_prompt: str = "Based on previous conversations, I recall:\n\n",
|
|
include_static: bool = True,
|
|
include_dynamic: bool = True,
|
|
include_search: bool = True,
|
|
) -> str:
|
|
"""Format deduplicated memories into a text string for injection.
|
|
|
|
Search results include temporal context (e.g., '3d ago') from updatedAt.
|
|
"""
|
|
sections = []
|
|
|
|
static = memories["static"]
|
|
dynamic = memories["dynamic"]
|
|
search_results = memories["search_results"]
|
|
|
|
if include_static and static:
|
|
sections.append("## User Profile (Persistent)")
|
|
sections.append("\n".join(f"- {item}" for item in static))
|
|
|
|
if include_dynamic and dynamic:
|
|
sections.append("## Recent Context")
|
|
sections.append("\n".join(f"- {item}" for item in dynamic))
|
|
|
|
if include_search and search_results:
|
|
sections.append("## Relevant Memories")
|
|
lines = []
|
|
for item in search_results:
|
|
if isinstance(item, dict):
|
|
memory = item.get("memory", "")
|
|
updated_at = item.get("updatedAt", "")
|
|
time_str = format_relative_time(updated_at) if updated_at else ""
|
|
if time_str:
|
|
lines.append(f"- [{time_str}] {memory}")
|
|
else:
|
|
lines.append(f"- {memory}")
|
|
else:
|
|
lines.append(f"- {item}")
|
|
sections.append("\n".join(lines))
|
|
|
|
if not sections:
|
|
return ""
|
|
|
|
return f"{system_prompt}\n" + "\n\n".join(sections)
|