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(