mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-08-18 04:54:04 +00:00
merge: resolve conflicts with fix/openai-sdk-python-v4-api
Keep 7-tool parity while adopting singular container_tag, include.documents forwarding, and v2.0.0 package metadata from the base branch. Co-authored-by: Dhravya Shah <dhravya@supermemory.com>
This commit is contained in:
commit
0a3ff69f4c
5 changed files with 76 additions and 38 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
10
packages/openai-sdk-python/uv.lock
generated
10
packages/openai-sdk-python/uv.lock
generated
|
|
@ -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"]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue