mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-05-19 07:42:43 +00:00
bug fixes
This commit is contained in:
parent
e8252b1048
commit
430410f65f
5 changed files with 165 additions and 32 deletions
|
|
@ -22,6 +22,9 @@ from .middleware import (
|
|||
SupermemoryOpenAIWrapper,
|
||||
)
|
||||
|
||||
# Backward compatibility alias for renamed class
|
||||
OpenAIMiddlewareOptions = SupermemoryOpenAIOptions
|
||||
|
||||
from .utils import (
|
||||
Logger,
|
||||
create_logger,
|
||||
|
|
@ -59,6 +62,7 @@ __all__ = [
|
|||
# Middleware
|
||||
"with_supermemory",
|
||||
"SupermemoryOpenAIOptions",
|
||||
"OpenAIMiddlewareOptions", # Backward compatibility alias
|
||||
"SupermemoryOpenAIWrapper",
|
||||
# Utils
|
||||
"Logger",
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import os
|
|||
from openai import AsyncOpenAI, OpenAI
|
||||
from supermemory_openai import (
|
||||
with_supermemory,
|
||||
OpenAIMiddlewareOptions,
|
||||
SupermemoryOpenAIOptions,
|
||||
SupermemoryConfigurationError,
|
||||
SupermemoryAPIError,
|
||||
)
|
||||
|
|
@ -37,8 +37,9 @@ async def test_async_middleware():
|
|||
# Wrap with Supermemory middleware
|
||||
openai_with_memory = with_supermemory(
|
||||
openai_client,
|
||||
container_tag="test-user-123",
|
||||
options=OpenAIMiddlewareOptions(
|
||||
SupermemoryOpenAIOptions(
|
||||
container_tag="test-user-123",
|
||||
custom_id="test-conversation-async",
|
||||
mode="profile",
|
||||
verbose=True,
|
||||
add_memory="never" # Don't save test messages
|
||||
|
|
@ -87,8 +88,9 @@ def test_sync_middleware():
|
|||
# Wrap with Supermemory middleware
|
||||
openai_with_memory = with_supermemory(
|
||||
openai_client,
|
||||
container_tag="test-user-sync-123",
|
||||
options=OpenAIMiddlewareOptions(
|
||||
SupermemoryOpenAIOptions(
|
||||
container_tag="test-user-sync-123",
|
||||
custom_id="test-conversation-sync",
|
||||
mode="profile",
|
||||
verbose=True
|
||||
)
|
||||
|
|
@ -122,17 +124,35 @@ def test_error_handling():
|
|||
print("\n🔄 Testing Error Handling...")
|
||||
|
||||
try:
|
||||
# Test with missing API key
|
||||
# Test with missing API key (clear env var temporarily)
|
||||
original_key = os.environ.pop("SUPERMEMORY_API_KEY", None)
|
||||
|
||||
openai_client = OpenAI(api_key="fake-key")
|
||||
|
||||
# This should raise SupermemoryConfigurationError
|
||||
with_supermemory(openai_client, "test-user")
|
||||
with_supermemory(
|
||||
openai_client,
|
||||
SupermemoryOpenAIOptions(
|
||||
container_tag="test-user",
|
||||
custom_id="test-conv"
|
||||
)
|
||||
)
|
||||
|
||||
# Restore key if it existed
|
||||
if original_key:
|
||||
os.environ["SUPERMEMORY_API_KEY"] = original_key
|
||||
|
||||
print("❌ Should have raised SupermemoryConfigurationError")
|
||||
|
||||
except SupermemoryConfigurationError as e:
|
||||
# Restore key if it existed
|
||||
if original_key:
|
||||
os.environ["SUPERMEMORY_API_KEY"] = original_key
|
||||
print(f"✅ Correctly caught configuration error: {e}")
|
||||
except Exception as e:
|
||||
# Restore key if it existed
|
||||
if original_key:
|
||||
os.environ["SUPERMEMORY_API_KEY"] = original_key
|
||||
print(f"❌ Wrong exception type: {type(e).__name__}: {e}")
|
||||
|
||||
|
||||
|
|
@ -156,8 +176,9 @@ def test_background_tasks():
|
|||
# Wrap with memory storage enabled
|
||||
wrapped_client = with_supermemory(
|
||||
openai_client,
|
||||
container_tag="test-background-tasks",
|
||||
options=OpenAIMiddlewareOptions(
|
||||
SupermemoryOpenAIOptions(
|
||||
container_tag="test-background-tasks",
|
||||
custom_id="test-bg-tasks-conv",
|
||||
add_memory="always",
|
||||
verbose=True
|
||||
)
|
||||
|
|
|
|||
|
|
@ -241,11 +241,13 @@ export const buildMemoriesText = async (
|
|||
} = options
|
||||
|
||||
// Fetch profile data when mode includes profile (profile or full)
|
||||
// Note: We never send queryText here - profile endpoint is only for static/dynamic memories.
|
||||
// Query-based search is handled separately by the SDK search functions (performSearch).
|
||||
let profileData: ProfileStructure | null = null
|
||||
if (mode !== "query") {
|
||||
profileData = await supermemoryProfileSearch(
|
||||
containerTag,
|
||||
mode === "profile" ? "" : queryText, // Only send query for full mode
|
||||
"", // No query - profile is for static/dynamic only
|
||||
baseUrl,
|
||||
apiKey,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,11 @@
|
|||
|
||||
import { describe, it, expect, beforeEach, vi, afterEach } from "vitest"
|
||||
import { withSupermemory } from "../../src/openai"
|
||||
import { createMockProfileResponse } from "../utils/supermemory-mocks"
|
||||
import {
|
||||
createMockProfileResponse,
|
||||
createMockMemoriesSearchResponse,
|
||||
createRoutedFetchMock,
|
||||
} from "../utils/supermemory-mocks"
|
||||
|
||||
// Create a mock OpenAI client
|
||||
const createMockOpenAIClient = () => {
|
||||
|
|
@ -229,13 +233,14 @@ describe("Unit: OpenAI withSupermemory", () => {
|
|||
})
|
||||
|
||||
it("should search memories based on user message in query mode", async () => {
|
||||
fetchMock.mockResolvedValue({
|
||||
ok: true,
|
||||
json: () =>
|
||||
Promise.resolve(
|
||||
createMockProfileResponse([], [], ["TypeScript is their favorite"]),
|
||||
),
|
||||
// Use routed fetch mock to handle SDK search calls
|
||||
const routedFetch = createRoutedFetchMock({
|
||||
memoriesSearchResponse: createMockMemoriesSearchResponse([
|
||||
"TypeScript is their favorite",
|
||||
]),
|
||||
})
|
||||
fetchMock = vi.fn(routedFetch)
|
||||
globalThis.fetch = fetchMock as unknown as typeof fetch
|
||||
|
||||
const mockClient = createMockOpenAIClient()
|
||||
const originalCreate = mockClient._mockCreate
|
||||
|
|
@ -253,12 +258,14 @@ describe("Unit: OpenAI withSupermemory", () => {
|
|||
],
|
||||
})
|
||||
|
||||
// Verify fetch was called with query text
|
||||
// Verify fetch was called (SDK search)
|
||||
expect(fetchMock.mock.calls.length).toBeGreaterThan(0)
|
||||
const fetchCall = fetchMock.mock.calls[0]
|
||||
const fetchBody = JSON.parse(fetchCall?.[1]?.body)
|
||||
expect(fetchBody.q).toBe("What's my favorite programming language?")
|
||||
expect(fetchBody.containerTag).toBe("user-123")
|
||||
|
||||
// Find the /v4/search call
|
||||
const searchCall = fetchMock.mock.calls.find((call: unknown[]) =>
|
||||
(call[0] as string)?.toString().includes("/v4/search"),
|
||||
)
|
||||
expect(searchCall).toBeDefined()
|
||||
|
||||
const calledMessages = originalCreate.mock.calls[0][0].messages
|
||||
expect(calledMessages[0].content).toContain(
|
||||
|
|
@ -267,17 +274,18 @@ describe("Unit: OpenAI withSupermemory", () => {
|
|||
})
|
||||
|
||||
it("should combine profile and search in full mode", async () => {
|
||||
fetchMock.mockResolvedValue({
|
||||
ok: true,
|
||||
json: () =>
|
||||
Promise.resolve(
|
||||
createMockProfileResponse(
|
||||
["Name: Alice"],
|
||||
["Likes coffee"],
|
||||
["Recently discussed Python"],
|
||||
),
|
||||
),
|
||||
// Use routed fetch mock to handle both profile and SDK search calls
|
||||
const routedFetch = createRoutedFetchMock({
|
||||
profileResponse: createMockProfileResponse(
|
||||
["Name: Alice"],
|
||||
["Likes coffee"],
|
||||
),
|
||||
memoriesSearchResponse: createMockMemoriesSearchResponse([
|
||||
"Recently discussed Python",
|
||||
]),
|
||||
})
|
||||
fetchMock = vi.fn(routedFetch)
|
||||
globalThis.fetch = fetchMock as unknown as typeof fetch
|
||||
|
||||
const mockClient = createMockOpenAIClient()
|
||||
const originalCreate = mockClient._mockCreate
|
||||
|
|
@ -285,6 +293,7 @@ describe("Unit: OpenAI withSupermemory", () => {
|
|||
containerTag: "user-123",
|
||||
customId: "conv-456",
|
||||
mode: "full",
|
||||
addMemory: "never",
|
||||
})
|
||||
|
||||
await wrapped.chat.completions.create({
|
||||
|
|
|
|||
|
|
@ -15,3 +15,100 @@ export const createMockProfileResponse = (
|
|||
results: searchResults.map((memory) => ({ memory })),
|
||||
},
|
||||
})
|
||||
|
||||
/**
|
||||
* Creates a mock response for the SDK search.memories() endpoint (/v4/search)
|
||||
*/
|
||||
export const createMockMemoriesSearchResponse = (memories: string[] = []) => ({
|
||||
results: memories.map((memory) => ({ memory })),
|
||||
})
|
||||
|
||||
/**
|
||||
* Creates a mock response for the SDK search.documents() endpoint (/v3/search)
|
||||
*/
|
||||
export const createMockDocumentsSearchResponse = (
|
||||
documents: Array<{ content: string; isRelevant?: boolean }> = [],
|
||||
) => ({
|
||||
results: documents.map((doc, index) => ({
|
||||
id: `doc-${index}`,
|
||||
chunks: [
|
||||
{
|
||||
content: doc.content,
|
||||
isRelevant: doc.isRelevant ?? true,
|
||||
},
|
||||
],
|
||||
})),
|
||||
})
|
||||
|
||||
/**
|
||||
* Creates a mock fetch implementation that routes to different responses
|
||||
* based on the URL endpoint.
|
||||
*/
|
||||
export const createRoutedFetchMock = (options: {
|
||||
profileResponse?: ReturnType<typeof createMockProfileResponse>
|
||||
memoriesSearchResponse?: ReturnType<typeof createMockMemoriesSearchResponse>
|
||||
documentsSearchResponse?: ReturnType<typeof createMockDocumentsSearchResponse>
|
||||
conversationResponse?: { id: string; conversationId: string; status: string }
|
||||
}) => {
|
||||
return (url: string | URL | Request, _init?: RequestInit) => {
|
||||
const urlStr = typeof url === "string" ? url : url.toString()
|
||||
|
||||
// Route based on endpoint
|
||||
if (urlStr.includes("/v4/profile")) {
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
headers: new Headers({ "content-type": "application/json" }),
|
||||
json: () =>
|
||||
Promise.resolve(
|
||||
options.profileResponse ?? createMockProfileResponse(),
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
if (urlStr.includes("/v4/search")) {
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
headers: new Headers({ "content-type": "application/json" }),
|
||||
json: () =>
|
||||
Promise.resolve(
|
||||
options.memoriesSearchResponse ??
|
||||
createMockMemoriesSearchResponse(),
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
if (urlStr.includes("/v3/search")) {
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
headers: new Headers({ "content-type": "application/json" }),
|
||||
json: () =>
|
||||
Promise.resolve(
|
||||
options.documentsSearchResponse ??
|
||||
createMockDocumentsSearchResponse(),
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
if (urlStr.includes("/v4/conversations")) {
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
headers: new Headers({ "content-type": "application/json" }),
|
||||
json: () =>
|
||||
Promise.resolve(
|
||||
options.conversationResponse ?? {
|
||||
id: "mem-123",
|
||||
conversationId: "conv-456",
|
||||
status: "success",
|
||||
},
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
// Default fallback
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
headers: new Headers({ "content-type": "application/json" }),
|
||||
json: () => Promise.resolve({}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue