Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions packages/openai-sdk-python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,8 +245,7 @@ tools = SupermemoryTools(
# Search memories
result = await tools.search_memories(
information_to_get="user preferences",
limit=10,
include_full_docs=True
limit=10
)

# Add memory
Expand All @@ -260,6 +259,10 @@ result = await tools.fetch_memory(
)
```

`include_full_docs` is retained as a deprecated Python argument for compatibility,
but v4 search returns relevant memories and chunks instead of full source documents.
It is no longer exposed in the OpenAI tool schema.

### Individual Tools

```python
Expand Down Expand Up @@ -408,7 +411,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
Expand Down
9 changes: 4 additions & 5 deletions packages/openai-sdk-python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "supermemory-openai-sdk"
version = "1.0.5"
version = "1.0.6"
description = "Memory tools for OpenAI function calling with supermemory"
readme = "README.md"
license = "MIT"
Expand All @@ -15,18 +15,17 @@ classifiers = [
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Scientific/Engineering :: Artificial Intelligence",
]
requires-python = ">=3.8.1"
requires-python = ">=3.9"
dependencies = [
"openai>=1.102.0",
"supermemory>=3.1.0,<3.5.0",
"supermemory>=3.50.0",
"typing-extensions>=4.0.0",
"requests>=2.25.0",
]
Expand Down Expand Up @@ -62,7 +61,7 @@ multi_line_output = 3
line_length = 88

[tool.mypy]
python_version = "3.8"
python_version = "3.9"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
Expand Down
18 changes: 9 additions & 9 deletions packages/openai-sdk-python/src/supermemory_openai/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,15 +222,15 @@ async def add_memory_tool(
) -> None:
"""Add a new memory to the SuperMemory system."""
try:
add_params = {
"content": content,
"container_tags": [container_tag],
}
if custom_id is not None:
add_params["custom_id"] = custom_id

# Handle both sync and async supermemory clients
result = client.memories.add(**add_params)
if custom_id is None:
result = client.add(content=content, container_tag=container_tag)
else:
result = client.add(
content=content,
container_tag=container_tag,
custom_id=custom_id,
)
if inspect.isawaitable(result):
response = await result
else:
Expand All @@ -242,7 +242,7 @@ async def add_memory_tool(
"container_tag": container_tag,
"custom_id": custom_id,
"content_length": len(content),
"memory_id": response.id,
"memory_id": getattr(response, "id", None),
},
)
except (OSError, ConnectionError) as network_error:
Expand Down
64 changes: 31 additions & 33 deletions packages/openai-sdk-python/src/supermemory_openai/tools.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
"""Supermemory tools for OpenAI function calling."""

import json
from typing import Dict, List, Optional, TypedDict, Union
import warnings
from typing import Dict, List, Optional, TypedDict

import supermemory
from openai.types.chat import (
ChatCompletionFunctionToolParam,
ChatCompletionMessageToolCall,
ChatCompletionToolMessageParam,
)
from supermemory.types import (
MemoryAddResponse,
MemoryGetResponse,
SearchExecuteResponse,
)
from supermemory.types.search_execute_response import Result
from supermemory.types import AddResponse, SearchMemoriesResponse

from .exceptions import (
SupermemoryConfigurationError,
Expand All @@ -27,6 +23,8 @@ class SupermemoryToolsConfig(TypedDict, total=False):
"""Configuration for Supermemory tools.

Only one of `project_id` or `container_tags` can be provided.
The first container tag is the primary v4 search scope; all configured tags
are applied when adding a memory.
"""

base_url: Optional[str]
Expand All @@ -35,14 +33,14 @@ class SupermemoryToolsConfig(TypedDict, total=False):


# Type aliases using inferred types from supermemory package
MemoryObject = Union[MemoryGetResponse, MemoryAddResponse]
MemoryObject = AddResponse


class MemorySearchResult(TypedDict, total=False):
"""Result type for memory search operations."""

success: bool
results: Optional[List[Result]]
results: Optional[List[Dict[str, object]]]
count: Optional[int]
error: Optional[str]

Expand All @@ -51,7 +49,7 @@ class MemoryAddResult(TypedDict, total=False):
"""Result type for memory add operations."""

success: bool
memory: Optional[MemoryAddResponse]
memory: Optional[Dict[str, object]]
error: Optional[str]


Expand All @@ -69,14 +67,6 @@ class MemoryAddResult(TypedDict, total=False):
"type": "string",
"description": "Terms to search for in the user's memories",
},
"include_full_docs": {
"type": "boolean",
"description": (
"Whether to include the full document content in the response. "
"Defaults to true for better AI context."
),
"default": True,
},
"limit": {
"type": "number",
"description": "Maximum number of results to return",
Expand Down Expand Up @@ -173,32 +163,42 @@ async def execute_tool_call(self, tool_call: ChatCompletionMessageToolCall) -> s
async def search_memories(
self,
information_to_get: str,
include_full_docs: bool = True,
include_full_docs: Optional[bool] = None,
limit: int = 10,
) -> MemorySearchResult:
"""Search memories.

Args:
information_to_get: Terms to search for
include_full_docs: Whether to include full document content
include_full_docs: Deprecated compatibility argument. V4 search
returns relevant memories and chunks, not full source documents.
limit: Maximum number of results

Returns:
MemorySearchResult
"""
if include_full_docs is not None:
warnings.warn(
"include_full_docs is deprecated and ignored because v4 search "
"does not return full source documents",
DeprecationWarning,
stacklevel=2,
)

try:
response: SearchExecuteResponse = await self.client.search.execute(
response: SearchMemoriesResponse = await self.client.search.memories(
q=information_to_get,
container_tags=self.container_tags,
container_tag=self.container_tags[0],
limit=limit,
chunk_threshold=0.6,
include_full_docs=include_full_docs,
threshold=0.6,
search_mode="hybrid",
Comment thread
cursor[bot] marked this conversation as resolved.
)

results = response.results or []
return MemorySearchResult(
success=True,
results=[r.model_dump() for r in response.results],
count=len(response.results),
results=[r.model_dump() for r in results],
count=len(results),
)
except (OSError, ConnectionError) as network_error:
return MemorySearchResult(
Expand All @@ -221,12 +221,10 @@ async def add_memory(self, memory: str) -> MemoryAddResult:
MemoryAddResult
"""
try:
add_params = {
"content": memory,
"container_tags": self.container_tags,
}

response: MemoryAddResponse = await self.client.memories.add(**add_params)
response: AddResponse = await self.client.add(
content=memory,
container_tags=self.container_tags,
)
Comment thread
cursor[bot] marked this conversation as resolved.

return MemoryAddResult(
success=True,
Expand Down Expand Up @@ -322,7 +320,7 @@ def __init__(self, api_key: str, config: Optional[SupermemoryToolsConfig] = None
async def execute(
self,
information_to_get: str,
include_full_docs: bool = True,
include_full_docs: Optional[bool] = None,
limit: int = 10,
) -> MemorySearchResult:
"""Execute search memories."""
Expand Down
9 changes: 7 additions & 2 deletions packages/openai-sdk-python/src/supermemory_openai/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,14 +212,19 @@ def deduplicate_memories(
def extract_memory_text(item: Any) -> Optional[str]:
if item is None:
return None
if isinstance(item, str):
trimmed = item.strip()
return trimmed if trimmed else None
if isinstance(item, dict):
memory = item.get("memory")
if isinstance(memory, str):
trimmed = memory.strip()
return trimmed if trimmed else None
return None
if isinstance(item, str):
trimmed = item.strip()
# Stainless SDK returns pydantic models (attribute access, snake_case).
memory = getattr(item, "memory", None)
if isinstance(memory, str):
trimmed = memory.strip()
return trimmed if trimmed else None
return None

Expand Down
63 changes: 63 additions & 0 deletions packages/openai-sdk-python/tests/test_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@ def test_return_proper_openai_function_definitions(self):
assert search_tool is not None
assert search_tool["type"] == "function"
assert "information_to_get" in search_tool["function"]["parameters"]["required"]
assert (
"include_full_docs"
not in search_tool["function"]["parameters"]["properties"]
)

# Check addMemory
add_tool = next(
Expand All @@ -177,6 +181,65 @@ def test_consistent_tool_definitions_from_class_and_helper(self, test_api_key: s
assert class_definitions == helper_definitions


class TestMemoryOperationsUnit:
"""Unit tests for memory operations (no live API)."""

@pytest.mark.asyncio
async def test_add_memory_uses_client_add(self):
"""add_memory must call client.add (memories.add was removed in supermemory 3.50)."""
from types import SimpleNamespace
from unittest.mock import AsyncMock

tools = SupermemoryTools("test-key", {"container_tags": ["unit-tag"]})
tools.client.add = AsyncMock(
return_value=SimpleNamespace(
id="doc_123",
status="queued",
model_dump=lambda: {"id": "doc_123", "status": "queued"},
)
)

result = await tools.add_memory("User likes tea")

assert result["success"] is True
assert result["memory"]["id"] == "doc_123"
tools.client.add.assert_awaited_once_with(
content="User likes tea",
container_tags=["unit-tag"],
)

@pytest.mark.asyncio
async def test_search_memories_uses_search_memories_hybrid(self):
"""V4 search must use the primary singular tag and hybrid mode."""
from types import SimpleNamespace
from unittest.mock import AsyncMock

tools = SupermemoryTools(
"test-key", {"container_tags": ["primary-tag", "secondary-tag"]}
)
tools.client.search.memories = AsyncMock(
return_value=SimpleNamespace(
results=[SimpleNamespace(model_dump=lambda: {"memory": "likes tea"})]
)
)

with pytest.warns(DeprecationWarning, match="include_full_docs"):
result = await tools.search_memories(
"tea", include_full_docs=False, limit=3
)

assert result["success"] is True
assert result["count"] == 1
tools.client.search.memories.assert_awaited_once()
kwargs = tools.client.search.memories.await_args.kwargs
assert kwargs["q"] == "tea"
assert kwargs["container_tag"] == "primary-tag"
assert "container_tags" not in kwargs
assert "include_full_docs" not in kwargs
assert kwargs["limit"] == 3
assert kwargs["search_mode"] == "hybrid"


class TestMemoryOperations:
"""Test memory operations."""

Expand Down
Loading
Loading