diff --git a/packages/openai-sdk-python/README.md b/packages/openai-sdk-python/README.md index 7e5b68aa..3405dc2f 100644 --- a/packages/openai-sdk-python/README.md +++ b/packages/openai-sdk-python/README.md @@ -4,6 +4,22 @@ Memory tools and middleware for OpenAI with Supermemory integration. This package provides both **automatic memory injection middleware** and **manual memory tools** for the official [OpenAI Python SDK](https://github.com/openai/openai-python) using [Supermemory](https://supermemory.ai) capabilities. +## Breaking changes (v2.0) + +Aligned with Supermemory v4 APIs: + +- `SupermemoryToolsConfig.container_tags` (list) is **removed**. Use a single `container_tag` string instead. +- Search/add tool calls send `container_tag` (singular) to the API. +- `search_memories(..., include_full_docs=True)` maps to `include={"documents": True}` on `client.search.memories`. + +```python +# Before (v1) +config = {"container_tags": ["user-123"]} + +# After (v2) +config = {"container_tag": "user-123"} +``` + ## Installation Install using uv (recommended): @@ -237,7 +253,7 @@ from supermemory_openai import SupermemoryTools tools = SupermemoryTools( api_key="your-supermemory-api-key", config={ - "project_id": "my-project", # or use container_tags + "project_id": "my-project", # or use container_tag="user-123" "base_url": "https://custom-endpoint.com", # optional } ) @@ -408,7 +424,7 @@ Optional for testing: ### Required - `openai>=1.102.0` - Official OpenAI Python SDK -- `supermemory>=3.1.0` - Supermemory client +- `supermemory>=3.50.0` - Supermemory client - `requests>=2.25.0` - HTTP requests (fallback) ### Optional diff --git a/packages/openai-sdk-python/pyproject.toml b/packages/openai-sdk-python/pyproject.toml index 3aa3d9d7..d4f9ba17 100644 --- a/packages/openai-sdk-python/pyproject.toml +++ b/packages/openai-sdk-python/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "supermemory-openai-sdk" -version = "1.0.6" +version = "2.0.0" description = "Memory tools for OpenAI function calling with supermemory" readme = "README.md" license = "MIT" @@ -25,7 +25,7 @@ classifiers = [ requires-python = ">=3.9" dependencies = [ "openai>=1.102.0", - "supermemory>=3.16.0", + "supermemory>=3.50.0", "typing-extensions>=4.0.0", "requests>=2.25.0", ] diff --git a/packages/openai-sdk-python/src/supermemory_openai/tools.py b/packages/openai-sdk-python/src/supermemory_openai/tools.py index cdbd2ef7..be53a858 100644 --- a/packages/openai-sdk-python/src/supermemory_openai/tools.py +++ b/packages/openai-sdk-python/src/supermemory_openai/tools.py @@ -130,11 +130,14 @@ ALL_TOOL_NAMES = ( class SupermemoryToolsConfig(TypedDict, total=False): """Configuration for Supermemory tools. - Only one of `project_id` or `container_tags` can be provided. + Only one of `project_id` or `container_tag` can be provided. + + Breaking change: `container_tags` (list) was removed in favor of a single + `container_tag` string, matching the Supermemory v4 API. """ base_url: Optional[str] - container_tags: Optional[List[str]] + container_tag: Optional[str] project_id: Optional[str] @@ -347,16 +350,16 @@ MEMORY_TOOL_SCHEMAS: Dict[str, ChatCompletionFunctionToolParam] = { } -def _resolve_container_tags(config: SupermemoryToolsConfig) -> List[str]: - if config.get("project_id") is not None and config.get("container_tags") is not None: +def _resolve_container_tag(config: SupermemoryToolsConfig) -> str: + if config.get("project_id") is not None and config.get("container_tag") is not None: raise SupermemoryConfigurationError( - "Supermemory tools config accepts either project_id or container_tags, not both." + "Supermemory tools config accepts either project_id or container_tag, not both." ) if config.get("project_id"): - return [f"sm_project_{config['project_id']}"] - if config.get("container_tags"): - return config["container_tags"] - return ["sm_project_default"] + return f"sm_project_{config['project_id']}" + if config.get("container_tag"): + return config["container_tag"] + return "sm_project_default" def _tool_definition(name: str) -> ChatCompletionToolParam: @@ -386,10 +389,10 @@ class SupermemoryTools: client_kwargs["base_url"] = config["base_url"] self.client = supermemory.AsyncSupermemory(**client_kwargs) - self.container_tags = _resolve_container_tags(config) + self.container_tag = _resolve_container_tag(config) def _primary_container_tag(self, container_tag: Optional[str] = None) -> str: - return container_tag or self.container_tags[0] + return container_tag or self.container_tag def get_tool_definitions(self) -> List[ChatCompletionFunctionToolParam]: """Get OpenAI function definitions for all memory tools.""" @@ -431,10 +434,11 @@ class SupermemoryTools: try: response: SearchMemoriesResponse = await self.client.search.memories( q=information_to_get, - container_tags=self.container_tags, + container_tag=self.container_tag, limit=limit, threshold=DEFAULT_CHUNK_THRESHOLD, search_mode="hybrid", + include={"documents": include_full_docs}, ) results = response.results or [] @@ -459,7 +463,7 @@ class SupermemoryTools: try: response: AddResponse = await self.client.add( content=memory, - container_tags=self.container_tags, + container_tag=self.container_tag, ) return MemoryAddResult( @@ -582,7 +586,7 @@ class SupermemoryTools: kwargs: Dict[str, Any] = { "content": content, - "container_tags": self.container_tags, + "container_tag": self.container_tag, } if metadata: kwargs["metadata"] = metadata diff --git a/packages/openai-sdk-python/tests/test_tools.py b/packages/openai-sdk-python/tests/test_tools.py index 3a5e34f9..c4e55588 100644 --- a/packages/openai-sdk-python/tests/test_tools.py +++ b/packages/openai-sdk-python/tests/test_tools.py @@ -125,14 +125,15 @@ class TestToolInitialization: assert tools is not None assert len(tools.get_tool_definitions()) == EXPECTED_TOOL_COUNT - def test_create_tools_with_custom_container_tags(self, test_api_key: str): - """Test creating tools with custom container tags.""" + def test_create_tools_with_custom_container_tag(self, test_api_key: str): + """Test creating tools with a custom container tag.""" config: SupermemoryToolsConfig = { - "container_tags": ["custom-tag-1", "custom-tag-2"], + "container_tag": "custom-tag-1", } tools = SupermemoryTools(test_api_key, config) assert tools is not None + assert tools.container_tag == "custom-tag-1" assert len(tools.get_tool_definitions()) == EXPECTED_TOOL_COUNT @@ -180,7 +181,7 @@ class TestMemoryOperationsUnit: from types import SimpleNamespace from unittest.mock import AsyncMock - tools = SupermemoryTools("test-key", {"container_tags": ["unit-tag"]}) + tools = SupermemoryTools("test-key", {"container_tag": "unit-tag"}) tools.client.add = AsyncMock( return_value=SimpleNamespace( id="doc_123", @@ -195,7 +196,7 @@ class TestMemoryOperationsUnit: assert result["memory"]["id"] == "doc_123" tools.client.add.assert_awaited_once_with( content="User likes tea", - container_tags=["unit-tag"], + container_tag="unit-tag", ) @pytest.mark.asyncio @@ -204,7 +205,7 @@ class TestMemoryOperationsUnit: from types import SimpleNamespace from unittest.mock import AsyncMock - tools = SupermemoryTools("test-key", {"container_tags": ["unit-tag"]}) + tools = SupermemoryTools("test-key", {"container_tag": "unit-tag"}) tools.client.search.memories = AsyncMock( return_value=SimpleNamespace( results=[SimpleNamespace(model_dump=lambda: {"memory": "likes tea"})] @@ -218,9 +219,26 @@ class TestMemoryOperationsUnit: tools.client.search.memories.assert_awaited_once() kwargs = tools.client.search.memories.await_args.kwargs assert kwargs["q"] == "tea" - assert kwargs["container_tags"] == ["unit-tag"] + assert kwargs["container_tag"] == "unit-tag" assert kwargs["limit"] == 3 assert kwargs["search_mode"] == "hybrid" + assert kwargs["include"] == {"documents": True} + + @pytest.mark.asyncio + async def test_search_memories_forwards_include_full_docs_false(self): + """include_full_docs=False must set include.documents to False.""" + from types import SimpleNamespace + from unittest.mock import AsyncMock + + tools = SupermemoryTools("test-key", {"container_tag": "unit-tag"}) + tools.client.search.memories = AsyncMock( + return_value=SimpleNamespace(results=[]) + ) + + await tools.search_memories("tea", include_full_docs=False) + + kwargs = tools.client.search.memories.await_args.kwargs + assert kwargs["include"] == {"documents": False} @pytest.mark.asyncio async def test_get_profile_uses_client_profile(self): @@ -228,7 +246,7 @@ class TestMemoryOperationsUnit: from types import SimpleNamespace from unittest.mock import AsyncMock - tools = SupermemoryTools("test-key", {"container_tags": ["unit-tag"]}) + tools = SupermemoryTools("test-key", {"container_tag": "unit-tag"}) tools.client.profile = AsyncMock( return_value=SimpleNamespace( profile={"static": ["likes tea"], "dynamic": []}, @@ -250,7 +268,7 @@ class TestMemoryOperationsUnit: from types import SimpleNamespace from unittest.mock import AsyncMock - tools = SupermemoryTools("test-key", {"container_tags": ["unit-tag"]}) + tools = SupermemoryTools("test-key", {"container_tag": "unit-tag"}) tools.client.documents.list = AsyncMock( return_value=SimpleNamespace( memories=[{"id": "doc_1"}], @@ -270,14 +288,14 @@ class TestMemoryOperationsUnit: @pytest.mark.asyncio async def test_memory_forget_requires_id_or_content(self): """memory_forget must reject calls without memory_id or memory_content.""" - tools = SupermemoryTools("test-key", {"container_tags": ["unit-tag"]}) + tools = SupermemoryTools("test-key", {"container_tag": "unit-tag"}) result = await tools.memory_forget() assert result["success"] is False assert "memory_id or memory_content" in result["error"] - def test_rejects_project_id_and_container_tags(self): - """Config must reject both project_id and container_tags.""" + def test_rejects_project_id_and_container_tag(self): + """Config must reject both project_id and container_tag.""" from supermemory_openai.exceptions import SupermemoryConfigurationError with pytest.raises(SupermemoryConfigurationError): @@ -285,7 +303,7 @@ class TestMemoryOperationsUnit: "test-key", { "project_id": "abc", - "container_tags": ["tag-a"], + "container_tag": "tag-a", }, ) @@ -324,7 +342,7 @@ class TestMemoryOperations: async def test_add_memory(self, test_api_key: str, test_base_url: str): """Test adding memory.""" config: SupermemoryToolsConfig = { - "container_tags": ["test-add-memory"], + "container_tag": "test-add-memory", } if test_base_url: config["base_url"] = test_base_url @@ -460,7 +478,7 @@ class TestOpenAIIntegration: ): """Test handling multiple tool calls.""" tools_config: SupermemoryToolsConfig = { - "container_tags": ["test-multi-tools"], + "container_tag": "test-multi-tools", } if test_base_url: tools_config["base_url"] = test_base_url diff --git a/packages/openai-sdk-python/uv.lock b/packages/openai-sdk-python/uv.lock index 72db4dd2..401b6a5c 100644 --- a/packages/openai-sdk-python/uv.lock +++ b/packages/openai-sdk-python/uv.lock @@ -377,7 +377,7 @@ resolution-markers = [ "python_full_version < '3.10'", ] dependencies = [ - { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } wheels = [ @@ -392,7 +392,7 @@ resolution-markers = [ "python_full_version >= '3.10'", ] dependencies = [ - { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } wheels = [ @@ -422,7 +422,7 @@ name = "exceptiongroup" version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } wheels = [ @@ -1372,7 +1372,7 @@ wheels = [ [[package]] name = "supermemory-openai-sdk" -version = "1.0.6" +version = "2.0.0" source = { editable = "." } dependencies = [ { name = "openai" }, @@ -1402,7 +1402,7 @@ requires-dist = [ { name = "aiohttp", marker = "extra == 'async'", specifier = ">=3.8.0" }, { name = "openai", specifier = ">=1.102.0" }, { name = "requests", specifier = ">=2.25.0" }, - { name = "supermemory", specifier = ">=3.16.0" }, + { name = "supermemory", specifier = ">=3.50.0" }, { name = "typing-extensions", specifier = ">=4.0.0" }, ] provides-extras = ["async"]