mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-08-28 05:25:33 +00:00
Merge 7eca7c7e53 into 29c43984fe
This commit is contained in:
commit
740053d4ae
7 changed files with 205 additions and 1351 deletions
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -15,7 +15,6 @@ 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",
|
||||
|
|
@ -23,10 +22,10 @@ classifiers = [
|
|||
"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",
|
||||
]
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
"""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 (
|
||||
|
|
@ -9,12 +10,7 @@ from openai.types.chat import (
|
|||
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,
|
||||
|
|
@ -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]
|
||||
|
|
@ -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]
|
||||
|
||||
|
|
@ -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]
|
||||
|
||||
|
||||
|
|
@ -69,14 +67,6 @@ MEMORY_TOOL_SCHEMAS: Dict[str, ChatCompletionFunctionToolParam] = {
|
|||
"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",
|
||||
|
|
@ -173,32 +163,42 @@ class SupermemoryTools:
|
|||
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
|
||||
"""
|
||||
try:
|
||||
response: SearchExecuteResponse = await self.client.search.execute(
|
||||
q=information_to_get,
|
||||
container_tags=self.container_tags,
|
||||
limit=limit,
|
||||
chunk_threshold=0.6,
|
||||
include_full_docs=include_full_docs,
|
||||
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: SearchMemoriesResponse = await self.client.search.memories(
|
||||
q=information_to_get,
|
||||
container_tag=self.container_tags[0],
|
||||
limit=limit,
|
||||
threshold=0.6,
|
||||
search_mode="hybrid",
|
||||
)
|
||||
|
||||
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(
|
||||
|
|
@ -221,12 +221,10 @@ class SupermemoryTools:
|
|||
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,
|
||||
)
|
||||
|
||||
return MemoryAddResult(
|
||||
success=True,
|
||||
|
|
@ -322,7 +320,7 @@ class SearchMemoriesTool:
|
|||
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."""
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -159,6 +159,10 @@ class TestToolDefinitions:
|
|||
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(
|
||||
|
|
@ -177,6 +181,65 @@ class TestToolDefinitions:
|
|||
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."""
|
||||
|
||||
|
|
|
|||
1378
packages/openai-sdk-python/uv.lock
generated
1378
packages/openai-sdk-python/uv.lock
generated
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue