diff --git a/apps/docs/integrations/pipecat.mdx b/apps/docs/integrations/pipecat.mdx
index e54e9450..a3717476 100644
--- a/apps/docs/integrations/pipecat.mdx
+++ b/apps/docs/integrations/pipecat.mdx
@@ -15,6 +15,10 @@ To use Supermemory with Pipecat, install the required dependencies:
pip install supermemory-pipecat
```
+Version 0.2.0 uses the Supermemory Python SDK v5. Keep using `user_id=`;
+the service passes that value as the namespace for profile, search, and writes.
+`session_id` is passed as the v5 document `id` (previously `custom_id`).
+
Set up your API key as an environment variable:
```bash
@@ -66,7 +70,8 @@ When integrated with Pipecat, Supermemory provides two key functionalities:
### 1. Memory retrieval
-When a user message is detected, Supermemory retrieves relevant memories:
+When a user message is detected, Supermemory reads the v5 profile and searches
+the same namespace when query results are enabled:
- **Static Profile**: Persistent facts about the user
- **Dynamic Profile**: Recent context and preferences
@@ -142,11 +147,11 @@ from fastapi import FastAPI, WebSocket
from fastapi.middleware.cors import CORSMiddleware
from pipecat.audio.vad.silero import SileroVADAnalyzer
-from pipecat.frames.frames import LLMMessagesFrame
from pipecat.pipeline.pipeline import Pipeline
from pipecat.pipeline.runner import PipelineRunner
-from pipecat.pipeline.task import PipelineParams, PipelineTask
-from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
+from pipecat.pipeline.task import PipelineTask
+from pipecat.processors.aggregators.llm_context import LLMContext
+from pipecat.processors.aggregators.llm_response_universal import LLMContextAggregatorPair
from pipecat.serializers.protobuf import ProtobufFrameSerializer
from pipecat.services.openai.llm import OpenAILLMService
from pipecat.services.openai.tts import OpenAITTSService
@@ -193,8 +198,8 @@ async def run_bot(websocket_client, user_id: str, session_id: str):
),
)
- context = OpenAILLMContext([{"role": "system", "content": SYSTEM_PROMPT}])
- context_aggregator = llm.create_context_aggregator(context)
+ context = LLMContext([{"role": "system", "content": SYSTEM_PROMPT}])
+ context_aggregator = LLMContextAggregatorPair(context)
pipeline = Pipeline([
transport.input(),
@@ -207,7 +212,7 @@ async def run_bot(websocket_client, user_id: str, session_id: str):
context_aggregator.assistant(),
])
- task = PipelineTask(pipeline, params=PipelineParams(allow_interruptions=True))
+ task = PipelineTask(pipeline)
@transport.event_handler("on_client_disconnected")
async def on_client_disconnected(transport, client):
diff --git a/packages/pipecat-sdk-python/Agents.md b/packages/pipecat-sdk-python/Agents.md
index 0d4f687c..f218be88 100644
--- a/packages/pipecat-sdk-python/Agents.md
+++ b/packages/pipecat-sdk-python/Agents.md
@@ -20,7 +20,7 @@ Place `SupermemoryPipecatService` between context aggregator and LLM in the pipe
from supermemory_pipecat import SupermemoryPipecatService
memory = SupermemoryPipecatService(
- user_id="user-123", # Required: identifies the user
+ user_id="user-123", # Required: scopes all memory calls
session_id="session-456", # Optional: groups conversations
)
diff --git a/packages/pipecat-sdk-python/README.md b/packages/pipecat-sdk-python/README.md
index f885ed71..1d692d7d 100644
--- a/packages/pipecat-sdk-python/README.md
+++ b/packages/pipecat-sdk-python/README.md
@@ -8,6 +8,10 @@ Memory-enhanced conversational AI pipelines with [Supermemory](https://supermemo
pip install supermemory-pipecat
```
+Version 0.2.0 requires the Supermemory Python SDK v5. Keep using `user_id=`;
+the service passes that value as the namespace for profile, search, and writes.
+`session_id` is passed as the v5 document `id` (previously `custom_id`).
+
## Quick Start
```python
@@ -19,7 +23,7 @@ from supermemory_pipecat import SupermemoryPipecatService
# Create memory service
memory = SupermemoryPipecatService(
api_key=os.getenv("SUPERMEMORY_API_KEY"),
- user_id="user-123", # Required: used as container_tag
+ user_id="user-123", # Required: scopes all memory calls
session_id="conversation-456", # Optional: groups memories by session
)
@@ -45,7 +49,7 @@ pipeline = Pipeline([
| Parameter | Type | Required | Description |
| ------------ | ----------- | -------- | ---------------------------------------------------------- |
-| `user_id` | str | **Yes** | User identifier - used as container_tag for memory scoping |
+| `user_id` | str | **Yes** | User identifier passed as the v5 namespace for memory scoping |
| `session_id` | str | No | Session/conversation ID for grouping memories |
| `api_key` | str | No | Supermemory API key (or set `SUPERMEMORY_API_KEY` env var) |
| `params` | InputParams | No | Advanced configuration |
@@ -81,7 +85,7 @@ memory = SupermemoryPipecatService(
1. **Intercepts context frames** - Listens for Pipecat's universal `LLMContextFrame` (and legacy 0.x frames)
2. **Tracks conversation** - Separates real conversation messages from tagged memory context
-3. **Retrieves memories** - Queries `/v4/profile` API with user's message
+3. **Retrieves memories** - Reads the v5 profile and, in `query` or `full` mode, searches the same namespace for the user's message
4. **Injects memories** - Uses a system message for audio/system mode and a tagged user message otherwise
5. **Stores messages** - Serializes newly observed user and assistant messages through a background queue that drains during cleanup
@@ -98,13 +102,12 @@ User: What's the weather like today?
Assistant: It's sunny today.
```
-is sent to Supermemory as:
+is added to the `user-123` namespace as:
```json
{
"content": "[{\"role\": \"user\", \"content\": \"What's the weather like today?\"}, {\"role\": \"assistant\", \"content\": \"It's sunny today.\"}]",
- "container_tags": ["user-123"],
- "custom_id": "conversation-456",
+ "id": "conversation-456",
"metadata": { "platform": "pipecat" }
}
```
diff --git a/packages/pipecat-sdk-python/pyproject.toml b/packages/pipecat-sdk-python/pyproject.toml
index 4cf30adf..3ee8a4a4 100644
--- a/packages/pipecat-sdk-python/pyproject.toml
+++ b/packages/pipecat-sdk-python/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "supermemory-pipecat"
-version = "0.1.5"
+version = "0.2.0"
description = "Supermemory integration for Pipecat - memory-enhanced conversational AI pipelines"
readme = "README.md"
license = "MIT"
@@ -32,7 +32,7 @@ classifiers = [
]
dependencies = [
"pipecat-ai>=0.0.98,<2.0.0",
- "supermemory>=3.16.0,<5",
+ "supermemory>=5.0.0,<6",
"pydantic>=2.10.0",
"loguru>=0.7.3",
]
diff --git a/packages/pipecat-sdk-python/src/supermemory_pipecat/__init__.py b/packages/pipecat-sdk-python/src/supermemory_pipecat/__init__.py
index c6089e79..7377900e 100644
--- a/packages/pipecat-sdk-python/src/supermemory_pipecat/__init__.py
+++ b/packages/pipecat-sdk-python/src/supermemory_pipecat/__init__.py
@@ -48,7 +48,7 @@ try:
__version__ = version("supermemory-pipecat")
except PackageNotFoundError:
# Source-tree fallback; built wheels always use package metadata above.
- __version__ = "0.1.5"
+ __version__ = "0.2.0"
__all__ = [
# Main service
diff --git a/packages/pipecat-sdk-python/src/supermemory_pipecat/service.py b/packages/pipecat-sdk-python/src/supermemory_pipecat/service.py
index d17596c9..0a3e3f2e 100644
--- a/packages/pipecat-sdk-python/src/supermemory_pipecat/service.py
+++ b/packages/pipecat-sdk-python/src/supermemory_pipecat/service.py
@@ -23,6 +23,7 @@ from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
from .exceptions import ConfigurationError, MemoryRetrievalError, MemoryStorageError
from .utils import (
_field,
+ _memory_text,
deduplicate_memories,
escape_memory_delimiters,
format_memories_to_text,
@@ -167,13 +168,13 @@ class SupermemoryPipecatService(FrameProcessor):
Args:
api_key: Supermemory API key. Falls back to SUPERMEMORY_API_KEY env var.
- user_id: The user ID - used as container_tag for memory scoping.
+ user_id: User identifier passed as the v5 namespace for memory scoping.
session_id: Session/conversation ID for grouping memories.
params: Configuration parameters for memory retrieval.
base_url: Optional custom base URL for Supermemory API.
Raises:
- ConfigurationError: If API key is missing or user_id not provided.
+ ConfigurationError: If API key or user_id is missing.
"""
super().__init__()
@@ -226,25 +227,44 @@ class SupermemoryPipecatService(FrameProcessor):
)
try:
- # One profile call: static + dynamic, and (when mode/query allow)
- # search_results via `q`. This is the intended profile API shape.
- kwargs: Dict[str, Any] = {"container_tag": self.container_tag}
+ calls: Dict[str, Any] = {}
+ if self.params.mode != "query":
+ calls["profile"] = self._supermemory_client.profile(self.user_id)
if self.params.mode != "profile" and query:
- kwargs["q"] = query
- kwargs["threshold"] = self.params.search_threshold
+ calls["search"] = self._supermemory_client.search(
+ self.user_id,
+ query=query,
+ search_mode="memories",
+ limit=min(self.params.search_limit, 100),
+ threshold=self.params.search_threshold,
+ )
+ results = await asyncio.gather(*calls.values(), return_exceptions=True)
+ responses = {}
+ for name, result in zip(calls, results):
+ if isinstance(result, asyncio.CancelledError):
+ raise result
+ if isinstance(result, Exception):
+ logger.warning(f"Supermemory {name} failed: {result}")
+ else:
+ responses[name] = result
+ if calls and not responses:
+ raise results[0]
- response = await self._supermemory_client.profile(**kwargs)
-
- profile = _field(response, "profile")
- search_results_response = _field(response, "search_results", "searchResults")
-
- raw_search_results = _field(search_results_response, "results", default=[]) or []
- search_results = list(raw_search_results)[: self.params.search_limit]
+ profile = _field(responses.get("profile"), "profile")
+ search_results = list(_field(responses.get("search"), "results", default=[]) or [])
return {
"profile": {
- "static": list(_field(profile, "static", default=[]) or []),
- "dynamic": list(_field(profile, "dynamic", default=[]) or []),
+ "static": [
+ text
+ for item in _field(profile, "static", default=[]) or []
+ if (text := _memory_text(item))
+ ],
+ "dynamic": [
+ text
+ for item in _field(profile, "dynamic", default=[]) or []
+ if (text := _memory_text(item))
+ ],
},
"search_results": search_results,
}
@@ -264,13 +284,12 @@ class SupermemoryPipecatService(FrameProcessor):
try:
add_params: Dict[str, Any] = {
"content": json.dumps(messages),
- "container_tags": [self.container_tag],
"metadata": {"platform": "pipecat"},
}
if self.session_id:
- add_params["custom_id"] = self.session_id
+ add_params["id"] = self.session_id
- await self._supermemory_client.add(**add_params)
+ await self._supermemory_client.add(self.user_id, **add_params)
except Exception as e:
raise MemoryStorageError("Failed to store messages", e) from e
diff --git a/packages/pipecat-sdk-python/src/supermemory_pipecat/utils.py b/packages/pipecat-sdk-python/src/supermemory_pipecat/utils.py
index 2e0041d1..dc2eb02a 100644
--- a/packages/pipecat-sdk-python/src/supermemory_pipecat/utils.py
+++ b/packages/pipecat-sdk-python/src/supermemory_pipecat/utils.py
@@ -4,7 +4,6 @@ import re
from datetime import datetime, timezone
from typing import Any, Dict, List, Union
-
_DYNAMIC_DATE_PREFIX = re.compile(
r"^\s*(?:\[recent\]\s*)?(?:\[\d{4}-\d{2}-\d{2}\]\s*)?",
re.IGNORECASE,
@@ -71,11 +70,7 @@ def format_relative_time(iso_timestamp: str) -> str:
def _field(item: Any, *names: str, default: Any = None) -> Any:
- """Read a field from a dict or pydantic/SDK model.
-
- Accepts camelCase and snake_case names so helpers work with both raw JSON
- dicts and Stainless-generated response models.
- """
+ """Accept alternative field names across raw JSON and SDK response models."""
if item is None:
return default
if isinstance(item, dict):
@@ -135,7 +130,6 @@ def deduplicate_memories(
def unique_search(results: List[Any]) -> List[Any]:
out: List[Any] = []
for r in results:
- # v4 search.memories/hybrid uses `memory` or `chunk`.
memory = _memory_text(r)
key = comparison_key(memory)
if key and key not in seen:
@@ -184,7 +178,9 @@ def format_memories_to_text(
continue
memory = _memory_text(item)
- updated_at = _field(item, "updatedAt", "updated_at", default="")
+ updated_at = _field(item, "updatedAt", "updated_at") or _field(
+ _field(item, "system"), "updatedAt", "updated_at", default=""
+ )
time_str = format_relative_time(updated_at) if updated_at else ""
if time_str:
lines.append(f"- [{time_str}] {memory}")
diff --git a/packages/pipecat-sdk-python/tests/conftest.py b/packages/pipecat-sdk-python/tests/conftest.py
deleted file mode 100644
index 74c64fed..00000000
--- a/packages/pipecat-sdk-python/tests/conftest.py
+++ /dev/null
@@ -1,97 +0,0 @@
-"""Import stubs for heavy optional deps; no-ops in CI where the real ones are installed."""
-
-from __future__ import annotations
-
-import sys
-import types
-
-
-def _install_test_stubs() -> None:
- if "loguru" not in sys.modules:
- loguru_module = types.ModuleType("loguru")
-
- class _Logger:
- def warning(self, *_args, **_kwargs):
- return None
-
- def error(self, *_args, **_kwargs):
- return None
-
- loguru_module.logger = _Logger()
- sys.modules["loguru"] = loguru_module
-
- if "pydantic" not in sys.modules:
- pydantic_module = types.ModuleType("pydantic")
-
- class BaseModel:
- def __init__(self, **kwargs):
- for key, value in kwargs.items():
- setattr(self, key, value)
-
- def Field(*, default=None, **_kwargs):
- return default
-
- pydantic_module.BaseModel = BaseModel
- pydantic_module.Field = Field
- sys.modules["pydantic"] = pydantic_module
-
- if "pipecat" not in sys.modules:
- pipecat_module = types.ModuleType("pipecat")
- sys.modules["pipecat"] = pipecat_module
-
- frames_module = types.ModuleType("pipecat.frames.frames")
-
- class Frame: # pragma: no cover - import stub
- pass
-
- class InputAudioRawFrame: # pragma: no cover - import stub
- pass
-
- class LLMContextFrame: # pragma: no cover - import stub
- pass
-
- class LLMMessagesFrame: # pragma: no cover - import stub
- pass
-
- frames_module.Frame = Frame
- frames_module.InputAudioRawFrame = InputAudioRawFrame
- frames_module.LLMContextFrame = LLMContextFrame
- frames_module.LLMMessagesFrame = LLMMessagesFrame
-
- llm_context_module = types.ModuleType("pipecat.processors.aggregators.llm_context")
-
- class LLMContext: # pragma: no cover - import stub
- pass
-
- llm_context_module.LLMContext = LLMContext
-
- openai_context_module = types.ModuleType(
- "pipecat.processors.aggregators.openai_llm_context"
- )
-
- class OpenAILLMContextFrame: # pragma: no cover - import stub
- pass
-
- openai_context_module.OpenAILLMContextFrame = OpenAILLMContextFrame
-
- frame_processor_module = types.ModuleType("pipecat.processors.frame_processor")
-
- class FrameDirection: # pragma: no cover - import stub
- pass
-
- class FrameProcessor:
- def __init__(self, *args, **kwargs):
- return None
-
- frame_processor_module.FrameDirection = FrameDirection
- frame_processor_module.FrameProcessor = FrameProcessor
-
- sys.modules["pipecat.frames.frames"] = frames_module
- sys.modules["pipecat.processors.aggregators.llm_context"] = llm_context_module
- sys.modules[
- "pipecat.processors.aggregators.openai_llm_context"
- ] = openai_context_module
- sys.modules["pipecat.processors.frame_processor"] = frame_processor_module
-
-
-_install_test_stubs()
diff --git a/packages/pipecat-sdk-python/tests/test_empty_profile.py b/packages/pipecat-sdk-python/tests/test_empty_profile.py
index e0b4bf71..41155a3f 100644
--- a/packages/pipecat-sdk-python/tests/test_empty_profile.py
+++ b/packages/pipecat-sdk-python/tests/test_empty_profile.py
@@ -1,125 +1,106 @@
from __future__ import annotations
-import sys
-import types
+import json
import unittest
from types import SimpleNamespace
from unittest.mock import AsyncMock
-
-def _install_test_stubs() -> None:
- if "loguru" not in sys.modules:
- loguru_module = types.ModuleType("loguru")
-
- class _Logger:
- def warning(self, *_args, **_kwargs):
- return None
-
- def error(self, *_args, **_kwargs):
- return None
-
- loguru_module.logger = _Logger()
- sys.modules["loguru"] = loguru_module
-
- if "pydantic" not in sys.modules:
- pydantic_module = types.ModuleType("pydantic")
-
- class BaseModel:
- def __init__(self, **kwargs):
- for key, value in kwargs.items():
- setattr(self, key, value)
-
- def Field(*, default=None, **_kwargs):
- return default
-
- pydantic_module.BaseModel = BaseModel
- pydantic_module.Field = Field
- sys.modules["pydantic"] = pydantic_module
-
- if "pipecat" not in sys.modules:
- pipecat_module = types.ModuleType("pipecat")
- sys.modules["pipecat"] = pipecat_module
-
- frames_module = types.ModuleType("pipecat.frames.frames")
-
- class Frame: # pragma: no cover - import stub
- pass
-
- class InputAudioRawFrame: # pragma: no cover - import stub
- pass
-
- class LLMContextFrame: # pragma: no cover - import stub
- pass
-
- class LLMMessagesFrame: # pragma: no cover - import stub
- pass
-
- frames_module.Frame = Frame
- frames_module.InputAudioRawFrame = InputAudioRawFrame
- frames_module.LLMContextFrame = LLMContextFrame
- frames_module.LLMMessagesFrame = LLMMessagesFrame
-
- llm_context_module = types.ModuleType("pipecat.processors.aggregators.llm_context")
-
- class LLMContext: # pragma: no cover - import stub
- pass
-
- llm_context_module.LLMContext = LLMContext
-
- openai_context_module = types.ModuleType(
- "pipecat.processors.aggregators.openai_llm_context"
- )
-
- class OpenAILLMContextFrame: # pragma: no cover - import stub
- pass
-
- openai_context_module.OpenAILLMContextFrame = OpenAILLMContextFrame
-
- frame_processor_module = types.ModuleType("pipecat.processors.frame_processor")
-
- class FrameDirection: # pragma: no cover - import stub
- pass
-
- class FrameProcessor:
- def __init__(self, *args, **kwargs):
- return None
-
- frame_processor_module.FrameDirection = FrameDirection
- frame_processor_module.FrameProcessor = FrameProcessor
-
- sys.modules["pipecat.frames.frames"] = frames_module
- sys.modules["pipecat.processors.aggregators.llm_context"] = llm_context_module
- sys.modules[
- "pipecat.processors.aggregators.openai_llm_context"
- ] = openai_context_module
- sys.modules["pipecat.processors.frame_processor"] = frame_processor_module
-
-
-_install_test_stubs()
+import httpx
+from supermemory import AsyncSupermemory
from supermemory_pipecat.service import SupermemoryPipecatService
+from supermemory_pipecat.utils import format_memories_to_text
-class _MockSupermemoryClient:
- def __init__(self, response):
- self.profile = AsyncMock(return_value=response)
+class _Context:
+ def __init__(self, messages):
+ self.messages = messages
+
+ def get_messages(self):
+ return self.messages
+
+ def add_message(self, message):
+ self.messages.append(message)
-class TestSupermemoryPipecatNullProfile(unittest.IsolatedAsyncioTestCase):
+class TestSupermemoryPipecatService(unittest.IsolatedAsyncioTestCase):
+ async def test_v5_reads_and_writes_use_user_id_as_namespace(self) -> None:
+ requests = {}
+ failed = set()
+
+ def respond(request):
+ requests[request.url.path] = json.loads(request.content)
+ if request.url.path.rsplit("/", 1)[-1] in failed:
+ return httpx.Response(500)
+ if request.url.path.endswith("/profile"):
+ payload = {"profile": {"static": [{"memory": "Likes tea"}], "dynamic": []}}
+ elif request.url.path.endswith("/search"):
+ payload = {
+ "results": [
+ {
+ "id": "mem_123",
+ "memory": "Plans a trip",
+ "metadata": {},
+ "similarity": 0.9,
+ "isLatest": True,
+ "isInference": False,
+ "system": {"updatedAt": "2020-01-01T00:00:00Z"},
+ }
+ ]
+ }
+ else:
+ payload = {"id": "conversation-456"}
+ return httpx.Response(200, json=payload)
+
+ async with AsyncSupermemory(
+ api_key="mock_key",
+ base_url="https://example.test",
+ http_client=httpx.AsyncClient(transport=httpx.MockTransport(respond)),
+ ) as client:
+ service = SupermemoryPipecatService(
+ api_key="mock_key",
+ user_id="user-123",
+ session_id="conversation-456",
+ params=SupermemoryPipecatService.InputParams(search_limit=500),
+ )
+ service._supermemory_client = client
+ result = await service._retrieve_memories("Where am I going?")
+ await service._store_messages([{"role": "user", "content": "Hello"}])
+ failed.add("search")
+ partial_result = await service._retrieve_memories("Where am I going?")
+ failed.clear()
+ failed.add("profile")
+ search_only_result = await service._retrieve_memories("Where am I going?")
+
+ self.assertEqual(result["profile"]["static"], ["Likes tea"])
+ self.assertEqual(result["search_results"][0].memory, "Plans a trip")
+ self.assertEqual(
+ set(requests), {"/ns/user-123/profile", "/ns/user-123/search", "/ns/user-123/document"}
+ )
+ self.assertEqual(requests["/ns/user-123/search"]["query"], "Where am I going?")
+ self.assertEqual(requests["/ns/user-123/search"]["limit"], 100)
+ self.assertEqual(requests["/ns/user-123/document"]["id"], "conversation-456")
+ self.assertEqual(partial_result["profile"]["static"], ["Likes tea"])
+ self.assertEqual(partial_result["search_results"], [])
+ self.assertEqual(search_only_result["profile"]["static"], [])
+ self.assertEqual(search_only_result["search_results"][0].memory, "Plans a trip")
+ self.assertIn(
+ "[1 Jan, 2020] Plans a trip",
+ format_memories_to_text(
+ {"static": [], "dynamic": [], "search_results": result["search_results"]}
+ ),
+ )
+
async def test_retrieve_memories_handles_null_profile(self) -> None:
service = SupermemoryPipecatService(api_key="mock_key", user_id="new_user_123")
-
- response = SimpleNamespace(profile=None, search_results=None)
- service._supermemory_client = _MockSupermemoryClient(response)
-
- result = await service._retrieve_memories("Hello world")
+ service._supermemory_client = SimpleNamespace(
+ profile=AsyncMock(return_value=SimpleNamespace(profile=None)),
+ search=AsyncMock(return_value=SimpleNamespace(results=[])),
+ )
self.assertEqual(
- result,
- {
- "profile": {"static": [], "dynamic": []},
- "search_results": [],
- },
+ await service._retrieve_memories("Hello world"),
+ {"profile": {"static": [], "dynamic": []}, "search_results": []},
)
def test_query_mode_keeps_search_fact_also_present_in_profile(self) -> None:
@@ -130,18 +111,7 @@ class TestSupermemoryPipecatNullProfile(unittest.IsolatedAsyncioTestCase):
session_id="conversation-456",
params=SupermemoryPipecatService.InputParams(mode="query"),
)
-
- class Context:
- def __init__(self):
- self.messages = [{"role": "user", "content": "What do I like?"}]
-
- def get_messages(self):
- return self.messages
-
- def add_message(self, message):
- self.messages.append(message)
-
- context = Context()
+ context = _Context([{"role": "user", "content": "What do I like?"}])
service._enhance_context_with_memories(
context,
"What do I like?",
@@ -151,12 +121,9 @@ class TestSupermemoryPipecatNullProfile(unittest.IsolatedAsyncioTestCase):
},
)
- self.assertTrue(
- any(fact in message.get("content", "") for message in context.messages)
- )
+ self.assertTrue(any(fact in message.get("content", "") for message in context.messages))
def test_system_injection_replaces_stale_block_with_backslash_memory(self) -> None:
- """A memory containing backslashes must not be read as a regex template."""
backslash = chr(92)
fact = "User's repo is at C:" + backslash + "Users" + backslash + "alice"
service = SupermemoryPipecatService(
@@ -165,33 +132,17 @@ class TestSupermemoryPipecatNullProfile(unittest.IsolatedAsyncioTestCase):
session_id="conversation-456",
params=SupermemoryPipecatService.InputParams(inject_mode="system"),
)
- newline = chr(10)
- stale = newline.join(["", "Stale fact", ""])
-
- class Context:
- def __init__(self):
- self.messages = [
- {
- "role": "system",
- "content": "You are helpful." + newline + newline + stale,
- },
- {"role": "user", "content": "Where is my repo?"},
- ]
-
- def get_messages(self):
- return self.messages
-
- def add_message(self, message):
- self.messages.append(message)
-
- context = Context()
+ stale = "\nStale fact\n"
+ context = _Context(
+ [
+ {"role": "system", "content": "You are helpful.\n\n" + stale},
+ {"role": "user", "content": "Where is my repo?"},
+ ]
+ )
service._enhance_context_with_memories(
context,
"Where is my repo?",
- {
- "profile": {"static": [fact], "dynamic": []},
- "search_results": [],
- },
+ {"profile": {"static": [fact], "dynamic": []}, "search_results": []},
)
system_content = context.messages[0]["content"]
diff --git a/packages/pipecat-sdk-python/tests/test_public_exports.py b/packages/pipecat-sdk-python/tests/test_public_exports.py
index b20cb707..e7941192 100644
--- a/packages/pipecat-sdk-python/tests/test_public_exports.py
+++ b/packages/pipecat-sdk-python/tests/test_public_exports.py
@@ -4,10 +4,6 @@ from __future__ import annotations
import unittest
-from .test_empty_profile import _install_test_stubs
-
-_install_test_stubs()
-
import supermemory_pipecat
from supermemory_pipecat import InputParams, SupermemoryPipecatService
diff --git a/packages/pipecat-sdk-python/tests/test_utils.py b/packages/pipecat-sdk-python/tests/test_utils.py
index 2ef69893..23bef3d6 100644
--- a/packages/pipecat-sdk-python/tests/test_utils.py
+++ b/packages/pipecat-sdk-python/tests/test_utils.py
@@ -10,7 +10,7 @@ from supermemory_pipecat.utils import (
class TestSearchResultExtraction(unittest.TestCase):
- """v4 hybrid search returns chunk hits whose `memory` field is empty."""
+ """Search can return chunk hits whose `memory` field is empty."""
def test_falls_through_to_chunk_when_memory_is_empty(self) -> None:
result = deduplicate_memories(