diff options
| author | Dhravya Shah <[email protected]> | 2026-01-18 16:55:32 -0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-01-18 16:55:32 -0800 |
| commit | 87b361c26bf5fc16049cd2727825891aa14b8e8b (patch) | |
| tree | c2f9f4f6223a7c1734578b772a16490ba2eb8b96 /packages | |
| parent | Add Claude Code GitHub Workflow (#681) (diff) | |
| download | supermemory-87b361c26bf5fc16049cd2727825891aa14b8e8b.tar.xz supermemory-87b361c26bf5fc16049cd2727825891aa14b8e8b.zip | |
docs changes (#678)
Co-authored-by: Claude Opus 4.5 <[email protected]>
Diffstat (limited to 'packages')
26 files changed, 1456 insertions, 71 deletions
diff --git a/packages/ai-sdk/package.json b/packages/ai-sdk/package.json index 5e59fa5e..deae39ad 100644 --- a/packages/ai-sdk/package.json +++ b/packages/ai-sdk/package.json @@ -25,7 +25,7 @@ }, "main": "./dist/index.js", "module": "./dist/index.js", - "types": "./dist/index-CITmF79o.d.ts", + "types": "./dist/index-h2cm3lSK.d.ts", "exports": { ".": "./dist/index.js", "./package.json": "./package.json" diff --git a/packages/ai-sdk/src/tools.ts b/packages/ai-sdk/src/tools.ts index f04df10b..f5126863 100644 --- a/packages/ai-sdk/src/tools.ts +++ b/packages/ai-sdk/src/tools.ts @@ -90,7 +90,7 @@ export function supermemoryTools( try { const metadata: Record<string, string | number | boolean> = {} - const response = await client.memories.add({ + const response = await client.add({ content: memory, containerTags, ...(Object.keys(metadata).length > 0 && { metadata }), diff --git a/packages/docs-test/.gitignore b/packages/docs-test/.gitignore new file mode 100644 index 00000000..4c49bd78 --- /dev/null +++ b/packages/docs-test/.gitignore @@ -0,0 +1 @@ +.env diff --git a/packages/docs-test/package.json b/packages/docs-test/package.json new file mode 100644 index 00000000..1c1b6674 --- /dev/null +++ b/packages/docs-test/package.json @@ -0,0 +1,26 @@ +{ + "name": "docs-test", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "test": "bun run run.ts", + "test:ts": "bun run run.ts typescript", + "test:py": "bun run run.ts python", + "test:integrations": "bun run run.ts integrations", + "test:quickstart": "bun run run.ts quickstart", + "test:sdk": "bun run run.ts sdk", + "test:search": "bun run run.ts search" + }, + "dependencies": { + "@ai-sdk/anthropic": "^3.0.15", + "@ai-sdk/openai": "latest", + "@anthropic-ai/sdk": "latest", + "@supermemory/tools": "workspace:*", + "ai": "latest", + "dotenv": "latest", + "openai": "latest", + "supermemory": "latest", + "zod": "latest" + } +} diff --git a/packages/docs-test/requirements.txt b/packages/docs-test/requirements.txt new file mode 100644 index 00000000..ad5ebad6 --- /dev/null +++ b/packages/docs-test/requirements.txt @@ -0,0 +1,5 @@ +supermemory +openai +anthropic +httpx +python-dotenv diff --git a/packages/docs-test/run.ts b/packages/docs-test/run.ts new file mode 100644 index 00000000..a98d4a1b --- /dev/null +++ b/packages/docs-test/run.ts @@ -0,0 +1,121 @@ +#!/usr/bin/env bun +import { spawn } from "child_process" +import path from "path" + +const args = process.argv.slice(2) +const filter = args[0] // e.g., "typescript", "python", "integrations", or specific file + +const TESTS_DIR = path.join(import.meta.dir, "tests") + +interface TestFile { + name: string + path: string + type: "ts" | "py" +} + +function getTests(): TestFile[] { + const tests: TestFile[] = [] + + // TypeScript tests + const tsTests = ["quickstart", "sdk", "search", "user-profiles"] + for (const t of tsTests) { + tests.push({ + name: `typescript/${t}`, + path: path.join(TESTS_DIR, "typescript", `${t}.ts`), + type: "ts", + }) + } + + // Python tests + const pyTests = ["quickstart", "sdk", "search", "user_profiles"] + for (const t of pyTests) { + tests.push({ + name: `python/${t}`, + path: path.join(TESTS_DIR, "python", `${t}.py`), + type: "py", + }) + } + + // Integration tests + const intTests = [ + { name: "ai-sdk", type: "ts" as const }, + { name: "openai-sdk", type: "ts" as const }, + { name: "openai-sdk", type: "py" as const }, + { name: "claude-memory", type: "ts" as const }, + ] + for (const t of intTests) { + tests.push({ + name: `integrations/${t.name}`, + path: path.join(TESTS_DIR, "integrations", `${t.name}.${t.type === "ts" ? "ts" : "py"}`), + type: t.type, + }) + } + + return tests +} + +async function runTest(test: TestFile): Promise<boolean> { + return new Promise((resolve) => { + console.log(`\n${"=".repeat(60)}`) + console.log(`Running: ${test.name}`) + console.log("=".repeat(60)) + + const cmd = test.type === "ts" ? "bun" : path.join(import.meta.dir, ".venv", "bin", "python3") + const proc = spawn(cmd, [test.path], { + stdio: "inherit", + env: { ...process.env }, + }) + + proc.on("close", (code) => { + resolve(code === 0) + }) + }) +} + +async function main() { + console.log("Supermemory Docs Test Runner") + console.log("============================\n") + + let tests = getTests() + + // Filter tests if specified + if (filter) { + tests = tests.filter((t) => t.name.includes(filter) || t.type === filter.replace(".", "")) + } + + if (tests.length === 0) { + console.log("No tests matched the filter:", filter) + console.log("\nAvailable tests:") + getTests().forEach((t) => console.log(` - ${t.name} (${t.type})`)) + process.exit(1) + } + + console.log(`Running ${tests.length} test(s)...\n`) + + const results: { test: string; passed: boolean }[] = [] + + for (const test of tests) { + const passed = await runTest(test) + results.push({ test: test.name, passed }) + } + + // Summary + console.log(`\n${"=".repeat(60)}`) + console.log("SUMMARY") + console.log("=".repeat(60)) + + const passed = results.filter((r) => r.passed).length + const failed = results.filter((r) => !r.passed).length + + for (const r of results) { + console.log(`${r.passed ? "✅" : "❌"} ${r.test}`) + } + + console.log(`\nTotal: ${passed} passed, ${failed} failed`) + + if (failed > 0) { + process.exit(1) + } +} + +main().catch(console.error) diff --git a/packages/docs-test/tests/integrations/ai-sdk.ts b/packages/docs-test/tests/integrations/ai-sdk.ts new file mode 100644 index 00000000..7caba72f --- /dev/null +++ b/packages/docs-test/tests/integrations/ai-sdk.ts @@ -0,0 +1,100 @@ +import "dotenv/config" +import { openai } from "@ai-sdk/openai" +import { createAnthropic } from "@ai-sdk/anthropic" +import { + withSupermemory, + supermemoryTools, + searchMemoriesTool, + addMemoryTool, + type MemoryPromptData, +} from "@supermemory/tools/ai-sdk" + +async function testMiddleware() { + console.log("=== Middleware ===") + + // Basic wrapper + const model = withSupermemory(openai("gpt-4"), "user-123") + console.log("✓ withSupermemory basic") + + // With addMemory option + const modelWithAdd = withSupermemory(openai("gpt-4"), "user-123", { + addMemory: "always", + }) + console.log("✓ withSupermemory with addMemory") + + // With verbose logging + const modelVerbose = withSupermemory(openai("gpt-4"), "user-123", { + verbose: true, + }) + console.log("✓ withSupermemory with verbose") +} + +async function testSearchModes() { + console.log("\n=== Search Modes ===") + + const profileModel = withSupermemory(openai("gpt-4"), "user-123", { mode: "profile" }) + console.log("✓ mode: profile") + + const queryModel = withSupermemory(openai("gpt-4"), "user-123", { mode: "query" }) + console.log("✓ mode: query") + + const fullModel = withSupermemory(openai("gpt-4"), "user-123", { mode: "full" }) + console.log("✓ mode: full") +} + +async function testCustomPrompt() { + console.log("\n=== Custom Prompt Template ===") + + const anthropic = createAnthropic({ apiKey: "test-key" }) + + const claudePrompt = (data: MemoryPromptData) => + ` +<context> + <user_profile>${data.userMemories}</user_profile> + <relevant_memories>${data.generalSearchMemories}</relevant_memories> +</context> +`.trim() + + const model = withSupermemory(anthropic("claude-3-sonnet-20240229"), "user-123", { + mode: "full", + promptTemplate: claudePrompt, + }) + console.log("✓ Custom prompt template") +} + +async function testTools() { + console.log("\n=== Memory Tools ===") + + // All tools + const tools = supermemoryTools("YOUR_API_KEY") + console.log("✓ supermemoryTools") + + // Individual tools + const searchTool = searchMemoriesTool("API_KEY", { projectId: "personal" }) + console.log("✓ searchMemoriesTool") + + const addTool = addMemoryTool("API_KEY") + console.log("✓ addMemoryTool") + + // Combined + const toolsObj = { + searchMemories: searchTool, + addMemory: addTool, + } + console.log("✓ Combined tools object") +} + +async function main() { + console.log("AI SDK Integration Tests") + console.log("========================\n") + + await testMiddleware() + await testSearchModes() + await testCustomPrompt() + await testTools() + + console.log("\n========================") + console.log("✅ All AI SDK tests passed!") +} + +main().catch(console.error) diff --git a/packages/docs-test/tests/integrations/claude-memory.ts b/packages/docs-test/tests/integrations/claude-memory.ts new file mode 100644 index 00000000..c4078789 --- /dev/null +++ b/packages/docs-test/tests/integrations/claude-memory.ts @@ -0,0 +1,101 @@ +import "dotenv/config" +import { createClaudeMemoryTool } from "@supermemory/tools/claude-memory" + +async function testConfiguration() { + console.log("=== Configuration ===") + + // Basic + const tool = createClaudeMemoryTool(process.env.SUPERMEMORY_API_KEY!, { + projectId: "my-app", + }) + console.log("✓ Basic config") + + // Full options + const toolFull = createClaudeMemoryTool(process.env.SUPERMEMORY_API_KEY!, { + projectId: "my-app", + containerTags: ["user-123", "project-alpha"], + memoryContainerTag: "my_memory_prefix", + }) + console.log("✓ Full config") + + return tool +} + +async function testMethods(tool: ReturnType<typeof createClaudeMemoryTool>) { + console.log("\n=== Methods ===") + + console.log(`✓ handleCommand exists: ${typeof tool.handleCommand === "function"}`) + console.log( + `✓ handleCommandForToolResult exists: ${typeof tool.handleCommandForToolResult === "function"}` + ) +} + +async function testCommands(tool: ReturnType<typeof createClaudeMemoryTool>) { + console.log("\n=== Commands ===") + + // View (list) + const listResult = await tool.handleCommand({ + command: "view", + path: "/memories/", + }) + console.log(`✓ view (list): ${listResult.success}`) + + // Create + const createResult = await tool.handleCommand({ + command: "create", + path: "/memories/test-file.txt", + file_text: "User prefers dark mode\nFavorite language: TypeScript", + }) + console.log(`✓ create: ${createResult.success}`) + + // Wait for indexing + await new Promise((r) => setTimeout(r, 3000)) + + // str_replace + const replaceResult = await tool.handleCommand({ + command: "str_replace", + path: "/memories/test-file.txt", + old_str: "dark mode", + new_str: "light mode", + }) + console.log(`✓ str_replace: ${replaceResult.success}`) + + // Delete + const deleteResult = await tool.handleCommand({ + command: "delete", + path: "/memories/test-file.txt", + }) + console.log(`✓ delete: ${deleteResult.success}`) +} + +async function testToolResultFormat(tool: ReturnType<typeof createClaudeMemoryTool>) { + console.log("\n=== Tool Result Format ===") + + const toolResult = await tool.handleCommandForToolResult( + { command: "view", path: "/memories/" }, + "test-tool-id-123" + ) + + const hasCorrectStructure = + toolResult.type === "tool_result" && + toolResult.tool_use_id === "test-tool-id-123" && + typeof toolResult.content === "string" && + typeof toolResult.is_error === "boolean" + + console.log(`✓ Tool result structure: ${hasCorrectStructure}`) +} + +async function main() { + console.log("Claude Memory Tool Tests") + console.log("========================\n") + + const tool = await testConfiguration() + await testMethods(tool) + await testCommands(tool) + await testToolResultFormat(tool) + + console.log("\n========================") + console.log("✅ All Claude Memory tests passed!") +} + +main().catch(console.error) diff --git a/packages/docs-test/tests/integrations/openai-sdk.py b/packages/docs-test/tests/integrations/openai-sdk.py new file mode 100644 index 00000000..8f8c2d40 --- /dev/null +++ b/packages/docs-test/tests/integrations/openai-sdk.py @@ -0,0 +1,90 @@ +import os +from dotenv import load_dotenv +from openai import OpenAI + +load_dotenv() + + +def test_openai_with_supermemory(): + """Test OpenAI SDK with Supermemory context""" + print("=== OpenAI SDK with Supermemory ===") + + if not os.getenv("OPENAI_API_KEY"): + print("⚠ OPENAI_API_KEY not set, skipping live tests") + return + + # This demonstrates manual integration pattern for Python + # since @supermemory/tools is TypeScript-only + + from supermemory import Supermemory + + memory_client = Supermemory() + openai_client = OpenAI() + + USER_ID = "docs-test-openai-py" + + # Get memory context + profile = memory_client.profile( + container_tag=USER_ID, + q="What are my preferences?", + ) + + context = f"""User Profile: +{chr(10).join(profile.profile.static)} + +Relevant Memories: +{chr(10).join(r.content for r in profile.search_results.results)}""" + + print("✓ Got memory context") + + # Use with OpenAI + response = openai_client.chat.completions.create( + model="gpt-4o-mini", + messages=[ + {"role": "system", "content": f"Use this context:\n{context}"}, + {"role": "user", "content": "What do you know about me?"}, + ], + max_tokens=100, + ) + print(f"✓ OpenAI response: {response.choices[0].message.content[:50]}...") + + +def test_save_conversation(): + """Test saving conversation to Supermemory""" + print("\n=== Save Conversation ===") + + if not os.getenv("OPENAI_API_KEY"): + print("⚠ Skipped (no OPENAI_API_KEY)") + return + + from supermemory import Supermemory + + memory_client = Supermemory() + USER_ID = "docs-test-openai-py" + + conversation = [ + {"role": "user", "content": "My favorite programming language is Python"}, + {"role": "assistant", "content": "That's great! Python is very versatile."}, + ] + + # Save conversation + memory_client.add( + content="\n".join(f"{m['role']}: {m['content']}" for m in conversation), + container_tag=USER_ID, + ) + print("✓ Saved conversation to memory") + + +def main(): + print("OpenAI SDK Integration Tests (Python)") + print("=====================================\n") + + test_openai_with_supermemory() + test_save_conversation() + + print("\n=====================================") + print("✅ All OpenAI SDK tests passed!") + + +if __name__ == "__main__": + main() diff --git a/packages/docs-test/tests/integrations/openai-sdk.ts b/packages/docs-test/tests/integrations/openai-sdk.ts new file mode 100644 index 00000000..c651e7dc --- /dev/null +++ b/packages/docs-test/tests/integrations/openai-sdk.ts @@ -0,0 +1,80 @@ +import "dotenv/config" +import OpenAI from "openai" +import { withSupermemory } from "@supermemory/tools/openai" + +const OPENAI_API_KEY = process.env.OPENAI_API_KEY + +async function testOpenAIWrapper() { + console.log("=== OpenAI SDK Wrapper ===") + + if (!OPENAI_API_KEY) { + console.log("⚠ OPENAI_API_KEY not set, skipping live tests") + return false + } + + const openai = new OpenAI() + + // Basic wrapper + const client = withSupermemory(openai, "docs-test-openai") + console.log("✓ withSupermemory basic") + + // With options + const clientWithOptions = withSupermemory(openai, "docs-test-openai", { + mode: "full", + addMemory: "always", + verbose: true, + }) + console.log("✓ withSupermemory with options") +} + +async function testChatCompletion() { + console.log("\n=== Chat Completion with Memory ===") + + if (!OPENAI_API_KEY) { + console.log("⚠ OPENAI_API_KEY not set, skipping") + return + } + + const openai = new OpenAI() + const client = withSupermemory(openai, "docs-test-openai", { + mode: "full", + addMemory: "always", + }) + + // Add context + const addResponse = await client.chat.completions.create({ + model: "gpt-4o-mini", + messages: [ + { role: "system", content: "You are a helpful assistant." }, + { role: "user", content: "Remember that my favorite color is blue." }, + ], + max_tokens: 100, + }) + console.log("✓ Add context:", addResponse.choices[0]?.message.content?.substring(0, 50)) + + // Retrieve context + const retrieveResponse = await client.chat.completions.create({ + model: "gpt-4o-mini", + messages: [ + { role: "system", content: "You are a helpful assistant." }, + { role: "user", content: "What do you know about my preferences?" }, + ], + max_tokens: 100, + }) + console.log("✓ Retrieve context:", retrieveResponse.choices[0]?.message.content?.substring(0, 50)) +} + +async function main() { + console.log("OpenAI SDK Integration Tests") + console.log("============================\n") + + const hasKey = await testOpenAIWrapper() + if (hasKey !== false) { + await testChatCompletion() + } + + console.log("\n============================") + console.log("✅ All OpenAI SDK tests passed!") +} + +main().catch(console.error) diff --git a/packages/docs-test/tests/python/quickstart.py b/packages/docs-test/tests/python/quickstart.py new file mode 100644 index 00000000..e0689900 --- /dev/null +++ b/packages/docs-test/tests/python/quickstart.py @@ -0,0 +1,53 @@ +import os +from dotenv import load_dotenv + +load_dotenv() + +from supermemory import Supermemory + +client = Supermemory() +USER_ID = "docs-test-user-py" + +conversation = [ + {"role": "assistant", "content": "Hello, how are you doing?"}, + {"role": "user", "content": "Hello! I am Dhravya. I am 20 years old. I love to code!"}, + {"role": "user", "content": "Can I go to the club?"}, +] + +print("Testing quickstart Python code...\n") + +# Get user profile + relevant memories for context +print("1. Getting user profile...") +profile = client.profile(container_tag=USER_ID, q=conversation[-1]["content"]) + +print(f"Profile response: {profile}") + +def get_memory(r): + if hasattr(r, 'memory'): + return r.memory + return r.get('memory', '') if isinstance(r, dict) else str(r) + +context = f"""Static profile: +{chr(10).join(profile.profile.static)} + +Dynamic profile: +{chr(10).join(profile.profile.dynamic)} + +Relevant memories: +{chr(10).join(get_memory(r) for r in profile.search_results.results)}""" + +print(f"\n2. Built context: {context}") + +# Build messages with memory-enriched context +messages = [{"role": "system", "content": f"User context:\n{context}"}, *conversation] +print("\n3. Messages built successfully") + +# Store conversation for future context +print("\n4. Adding memory...") +add_result = client.add( + content="\n".join(f"{m['role']}: {m['content']}" for m in conversation), + container_tag=USER_ID, +) + +print(f"Add result: {add_result}") +print("\n✅ Quickstart Python test passed!") diff --git a/packages/docs-test/tests/python/sdk.py b/packages/docs-test/tests/python/sdk.py new file mode 100644 index 00000000..a083ede2 --- /dev/null +++ b/packages/docs-test/tests/python/sdk.py @@ -0,0 +1,152 @@ +import os +import time +from pathlib import Path +from dotenv import load_dotenv +import httpx +import supermemory +from supermemory import Supermemory + +load_dotenv() + +client = Supermemory() + + +def test_documents_crud(): + """Test document CRUD operations""" + print("\n=== Document CRUD Operations ===") + + # Create + doc = client.documents.add(content=f"Test content - {time.time()}") + print(f"✓ documents.add: {doc.id}") + + # Read + fetched = client.documents.get(doc.id) + print(f"✓ documents.get: {fetched.id}") + + # Update + updated = client.documents.update(doc.id, content=f"Updated - {time.time()}") + print(f"✓ documents.update: {updated.id}") + + # Wait for processing + time.sleep(10) + + # Delete + client.documents.delete(doc.id) + print("✓ documents.delete") + + +def test_batch_operations(): + """Test batch operations""" + print("\n=== Batch Operations ===") + + batch = client.documents.batch_add( + documents=[ + {"content": f"Batch 1 - {time.time()}"}, + {"content": f"Batch 2 - {time.time()}"}, + ] + ) + print(f"✓ documents.batch_add: {batch}") + + +def test_search(): + """Test search""" + print("\n=== Search ===") + + results = client.search.execute(q="test content") + print(f"✓ search.execute: {len(results.results) if results.results else 0} results") + + +def test_file_uploads(): + """Test file uploads""" + print("\n=== File Uploads ===") + + test_path = Path("/tmp/test-py-upload.txt") + test_path.write_text(f"Test content {time.time()}") + client.documents.upload_file(file=test_path) + print("✓ documents.upload_file with Path") + test_path.unlink() + + +def test_error_handling(): + """Test error handling patterns""" + print("\n=== Error Handling ===") + + try: + client.documents.add(content="Test content") + print("✓ Error handling pattern works") + except supermemory.APIConnectionError: + print("APIConnectionError handled") + except supermemory.RateLimitError: + print("RateLimitError handled") + except supermemory.APIStatusError: + print("APIStatusError handled") + + +def test_client_config(): + """Test client configuration""" + print("\n=== Client Configuration ===") + + # Retries + client2 = Supermemory(max_retries=0) + print("✓ max_retries config") + + # Timeout + client3 = Supermemory(timeout=20.0) + print("✓ timeout config") + + # Granular timeout + client4 = Supermemory(timeout=httpx.Timeout(60.0, read=5.0, write=10.0, connect=2.0)) + print("✓ granular timeout config") + + # Per-request options + client.with_options(max_retries=5).documents.add(content="Test content") + print("✓ per-request options") + + +def test_raw_response(): + """Test raw response access""" + print("\n=== Raw Response ===") + + response = client.documents.with_raw_response.add(content="Test content") + print(f"✓ with_raw_response: has headers={hasattr(response, 'headers')}") + + memory = response.parse() + print(f"✓ parse(): {memory.id}") + + +def test_streaming_response(): + """Test streaming response""" + print("\n=== Streaming Response ===") + + with client.documents.with_streaming_response.add(content="Test content") as response: + print(f"✓ with_streaming_response: has headers={hasattr(response, 'headers')}") + + +def test_context_manager(): + """Test context manager""" + print("\n=== Context Manager ===") + + with Supermemory() as temp_client: + print("✓ Context manager works") + + +def main(): + print("Python SDK Tests") + print("================") + + test_documents_crud() + test_batch_operations() + test_search() + test_file_uploads() + test_error_handling() + test_client_config() + test_raw_response() + test_streaming_response() + test_context_manager() + + print("\n================") + print("✅ All Python SDK tests passed!") + + +if __name__ == "__main__": + main() diff --git a/packages/docs-test/tests/python/search.py b/packages/docs-test/tests/python/search.py new file mode 100644 index 00000000..820629eb --- /dev/null +++ b/packages/docs-test/tests/python/search.py @@ -0,0 +1,113 @@ +import os +from dotenv import load_dotenv +from supermemory import Supermemory + +load_dotenv() + +client = Supermemory() + + +def test_search_modes(): + """Test different search modes""" + print("=== Search Modes ===") + + # Hybrid search + hybrid = client.search.memories( + q="quarterly goals", + container_tag="user_123", + search_mode="hybrid", + ) + print(f"✓ hybrid search: {len(hybrid.results)} results") + + # Memories only + memories = client.search.memories( + q="user preferences", + container_tag="user_123", + search_mode="memories", + ) + print(f"✓ memories search: {len(memories.results)} results") + + +def test_filtering(): + """Test search filtering""" + print("\n=== Filtering ===") + + # Basic containerTag filter + results = client.search.memories( + q="project updates", + container_tag="user_123", + search_mode="hybrid", + ) + print(f"✓ containerTag filter: {len(results.results)} results") + + # Metadata filtering + filtered = client.search.memories( + q="meeting notes", + container_tag="user_123", + filters={ + "AND": [ + {"key": "type", "value": "meeting"}, + {"key": "year", "value": "2024"}, + ] + }, + ) + print(f"✓ metadata filter: {len(filtered.results)} results") + + +def test_reranking(): + """Test reranking""" + print("\n=== Reranking ===") + + results = client.search.memories( + q="complex technical question", + container_tag="user_123", + rerank=True, + ) + print(f"✓ reranking: {len(results.results)} results") + + +def test_threshold(): + """Test similarity threshold""" + print("\n=== Threshold ===") + + broad = client.search.memories(q="test query", threshold=0.3) + print(f"✓ broad threshold (0.3): {len(broad.results)} results") + + precise = client.search.memories(q="test query", threshold=0.8) + print(f"✓ precise threshold (0.8): {len(precise.results)} results") + + +def test_chatbot_context(): + """Test chatbot context pattern""" + print("\n=== Chatbot Context Pattern ===") + + def get_context(user_id: str, message: str) -> str: + results = client.search.memories( + q=message, + container_tag=user_id, + search_mode="hybrid", + threshold=0.6, + limit=5, + ) + return "\n\n".join(r.memory or r.chunk or "" for r in results.results) + + context = get_context("user_123", "What are the project updates?") + print(f"✓ chatbot context: {len(context)} chars") + + +def main(): + print("Search Tests") + print("============\n") + + test_search_modes() + test_filtering() + test_reranking() + test_threshold() + test_chatbot_context() + + print("\n============") + print("✅ All search tests passed!") + + +if __name__ == "__main__": + main() diff --git a/packages/docs-test/tests/python/user_profiles.py b/packages/docs-test/tests/python/user_profiles.py new file mode 100644 index 00000000..f34c1499 --- /dev/null +++ b/packages/docs-test/tests/python/user_profiles.py @@ -0,0 +1,102 @@ +import os +import time +from dotenv import load_dotenv +from supermemory import Supermemory + +load_dotenv() + +client = Supermemory() +USER_ID = "docs-test-profiles-py" + + +def test_basic_profile(): + """Test basic profile retrieval""" + print("=== Basic Profile ===") + + profile = client.profile( + container_tag=USER_ID, + q="What are my preferences?", + ) + + print(f"✓ Static profile: {len(profile.profile.static)} items") + print(f"✓ Dynamic profile: {len(profile.profile.dynamic)} items") + print(f"✓ Search results: {len(profile.search_results.results)} items") + + +def test_profile_with_memories(): + """Test profile with memory context""" + print("\n=== Profile with Memory Context ===") + + # Add some memories + client.add( + content="User prefers dark mode for all applications", + container_tag=USER_ID, + ) + client.add( + content="User is learning TypeScript and Rust", + container_tag=USER_ID, + ) + + # Wait for indexing + time.sleep(2) + + # Get profile with search + profile = client.profile( + container_tag=USER_ID, + q="What programming languages does the user know?", + ) + + print("✓ Profile retrieved with memories") + print(f" Static: {profile.profile.static[:2]}") + print(f" Dynamic: {profile.profile.dynamic[:2]}") + + +def get_memory(r): + if hasattr(r, 'memory'): + return r.memory + return r.get('memory', '') if isinstance(r, dict) else str(r) + + +def test_building_context(): + """Test building LLM context""" + print("\n=== Building LLM Context ===") + + conversation = [{"role": "user", "content": "What theme should I use for my IDE?"}] + + profile = client.profile( + container_tag=USER_ID, + q=conversation[-1]["content"], + ) + + context = f"""User Profile: +{chr(10).join(profile.profile.static)} + +Recent Context: +{chr(10).join(profile.profile.dynamic)} + +Relevant Memories: +{chr(10).join(get_memory(r) for r in profile.search_results.results)}""" + + print(f"✓ Built context: {len(context)} chars") + + messages = [ + {"role": "system", "content": f"Use this context about the user:\n{context}"}, + *conversation, + ] + print(f"✓ Messages ready for LLM: {len(messages)} messages") + + +def main(): + print("User Profiles Tests") + print("===================\n") + + test_basic_profile() + test_profile_with_memories() + test_building_context() + + print("\n===================") + print("✅ All user profile tests passed!") + + +if __name__ == "__main__": + main() diff --git a/packages/docs-test/tests/typescript/quickstart.ts b/packages/docs-test/tests/typescript/quickstart.ts new file mode 100644 index 00000000..12da131b --- /dev/null +++ b/packages/docs-test/tests/typescript/quickstart.ts @@ -0,0 +1,57 @@ +import "dotenv/config" +import Supermemory from "supermemory" + +const client = new Supermemory() +const USER_ID = "docs-test-user" + +const conversation = [ + { role: "assistant", content: "Hello, how are you doing?" }, + { + role: "user", + content: "Hello! I am Dhravya. I am 20 years old. I love to code!", + }, + { role: "user", content: "Can I go to the club?" }, +] + +async function main() { + console.log("Testing quickstart TypeScript code...\n") + + // Get user profile + relevant memories for context + console.log("1. Getting user profile...") + const profile = await client.profile({ + containerTag: USER_ID, + q: conversation.at(-1)!.content, + }) + + console.log("Profile response:", JSON.stringify(profile, null, 2)) + + const context = `Static profile: +${profile.profile.static.join("\n")} + +Dynamic profile: +${profile.profile.dynamic.join("\n")} + +Relevant memories: +${profile.searchResults?.results.map((r) => r.content).join("\n")}` + + console.log("\n2. Built context:", context) + + // Build messages with memory-enriched context + const messages = [ + { role: "system", content: `User context:\n${context}` }, + ...conversation, + ] + console.log("\n3. Messages built successfully") + + // Store conversation for future context + console.log("\n4. Adding memory...") + const addResult = await client.add({ + content: conversation.map((m) => `${m.role}: ${m.content}`).join("\n"), + containerTag: USER_ID, + }) + + console.log("Add result:", JSON.stringify(addResult, null, 2)) + console.log("\n✅ Quickstart TypeScript test passed!") +} + +main().catch(console.error) diff --git a/packages/docs-test/tests/typescript/sdk.ts b/packages/docs-test/tests/typescript/sdk.ts new file mode 100644 index 00000000..7dc477e0 --- /dev/null +++ b/packages/docs-test/tests/typescript/sdk.ts @@ -0,0 +1,146 @@ +import "dotenv/config" +import fs from "fs" +import Supermemory, { toFile } from "supermemory" + +const client = new Supermemory() + +async function testDocumentsCRUD() { + console.log("\n=== Document CRUD Operations ===") + + // Create + const doc = await client.documents.add({ + content: "Test content for TypeScript SDK - " + Date.now(), + }) + console.log("✓ documents.add:", doc.id) + + // Read + const fetched = await client.documents.get(doc.id) + console.log("✓ documents.get:", fetched.id) + + // Update + const updated = await client.documents.update(doc.id, { + content: "Updated content - " + Date.now(), + }) + console.log("✓ documents.update:", updated.id) + + // Wait for processing before delete + await new Promise((r) => setTimeout(r, 10000)) + + // Delete + await client.documents.delete(doc.id) + console.log("✓ documents.delete") +} + +async function testBatchOperations() { + console.log("\n=== Batch Operations ===") + + const batch = await client.documents.batchAdd({ + documents: [ + { content: "Batch doc 1 - " + Date.now() }, + { content: "Batch doc 2 - " + Date.now() }, + ], + }) + console.log("✓ documents.batchAdd:", batch) +} + +async function testSearch() { + console.log("\n=== Search ===") + + const results = await client.search.execute({ q: "test content" }) + console.log("✓ search.execute:", results.results?.length ?? 0, "results") +} + +async function testFileUploads() { + console.log("\n=== File Uploads ===") + + // Using fs.createReadStream + const testFile = "/tmp/test-doc-upload.txt" + fs.writeFileSync(testFile, "Test file content") + await client.documents.uploadFile({ file: fs.createReadStream(testFile) }) + console.log("✓ uploadFile with fs.createReadStream") + + // Using File API + await client.documents.uploadFile({ file: new File(["my bytes"], "file") }) + console.log("✓ uploadFile with File API") + + // Using toFile + await client.documents.uploadFile({ file: await toFile(Buffer.from("my bytes"), "file") }) + console.log("✓ uploadFile with toFile") + + fs.unlinkSync(testFile) +} + +async function testClientConfig() { + console.log("\n=== Client Configuration ===") + + // Retries + const clientWithRetries = new Supermemory({ maxRetries: 0 }) + console.log("✓ maxRetries config") + + // Timeout + const clientWithTimeout = new Supermemory({ timeout: 20 * 1000 }) + console.log("✓ timeout config") + + // Per-request options + await client.add({ content: "Test - " + Date.now() }, { maxRetries: 5, timeout: 5000 }) + console.log("✓ per-request options") +} + +async function testRawResponse() { + console.log("\n=== Raw Response ===") + + const response = await client.documents + .add({ content: "Test - " + Date.now() }) + .asResponse() + console.log("✓ .asResponse() status:", response.status) + + const { data, response: raw } = await client.documents + .add({ content: "Test - " + Date.now() }) + .withResponse() + console.log("✓ .withResponse() data:", data.id, "status:", raw.status) +} + +async function testTypes() { + console.log("\n=== Types ===") + + const params: Supermemory.AddParams = { + content: "Typed content", + } + const response: Supermemory.AddResponse = await client.add(params) + console.log("✓ TypeScript types work:", response.id) +} + +async function testErrorHandling() { + console.log("\n=== Error Handling ===") + + const response = await client.documents + .add({ content: "Test - " + Date.now() }) + .catch(async (err) => { + if (err instanceof Supermemory.APIError) { + console.log("Caught APIError:", err.status, err.name) + } else { + throw err + } + return null + }) + console.log("✓ Error handling pattern works:", response?.id) +} + +async function main() { + console.log("TypeScript SDK Tests") + console.log("====================") + + await testDocumentsCRUD() + await testBatchOperations() + await testSearch() + await testFileUploads() + await testClientConfig() + await testRawResponse() + await testTypes() + await testErrorHandling() + + console.log("\n====================") + console.log("✅ All TypeScript SDK tests passed!") +} + +main().catch(console.error) diff --git a/packages/docs-test/tests/typescript/search.ts b/packages/docs-test/tests/typescript/search.ts new file mode 100644 index 00000000..45f903eb --- /dev/null +++ b/packages/docs-test/tests/typescript/search.ts @@ -0,0 +1,110 @@ +import "dotenv/config" +import Supermemory from "supermemory" + +const client = new Supermemory() + +async function testSearchModes() { + console.log("=== Search Modes ===") + + // Hybrid: memories + document chunks (recommended) + const hybridResults = await client.search.memories({ + q: "quarterly goals", + containerTag: "user_123", + searchMode: "hybrid", + }) + console.log("✓ hybrid search:", hybridResults.results.length, "results") + + // Memories only + const memoriesResults = await client.search.memories({ + q: "user preferences", + containerTag: "user_123", + searchMode: "memories", + }) + console.log("✓ memories search:", memoriesResults.results.length, "results") +} + +async function testFiltering() { + console.log("\n=== Filtering ===") + + // Basic containerTag filter + const results = await client.search.memories({ + q: "project updates", + containerTag: "user_123", + searchMode: "hybrid", + }) + console.log("✓ containerTag filter:", results.results.length, "results") + + // Metadata-based filtering + const filteredResults = await client.search.memories({ + q: "meeting notes", + containerTag: "user_123", + filters: { + AND: [ + { key: "type", value: "meeting" }, + { key: "year", value: "2024" }, + ], + }, + }) + console.log("✓ metadata filter:", filteredResults.results.length, "results") +} + +async function testReranking() { + console.log("\n=== Reranking ===") + + const results = await client.search.memories({ + q: "complex technical question", + containerTag: "user_123", + rerank: true, + }) + console.log("✓ reranking:", results.results.length, "results") +} + +async function testThreshold() { + console.log("\n=== Threshold ===") + + const broadResults = await client.search.memories({ + q: "test query", + threshold: 0.3, + }) + console.log("✓ broad threshold (0.3):", broadResults.results.length, "results") + + const preciseResults = await client.search.memories({ + q: "test query", + threshold: 0.8, + }) + console.log("✓ precise threshold (0.8):", preciseResults.results.length, "results") +} + +async function testChatbotContext() { + console.log("\n=== Chatbot Context Pattern ===") + + async function getContext(userId: string, message: string) { + const results = await client.search.memories({ + q: message, + containerTag: userId, + searchMode: "hybrid", + threshold: 0.6, + limit: 5, + }) + return results.results.map((r) => r.memory || r.chunk).join("\n\n") + } + + const context = await getContext("user_123", "What are the project updates?") + console.log("✓ chatbot context:", context.length, "chars") +} + +async function main() { + console.log("Search Tests") + console.log("============\n") + + await testSearchModes() + await testFiltering() + await testReranking() + await testThreshold() + await testChatbotContext() + + console.log("\n============") + console.log("✅ All search tests passed!") +} + +main().catch(console.error) diff --git a/packages/docs-test/tests/typescript/user-profiles.ts b/packages/docs-test/tests/typescript/user-profiles.ts new file mode 100644 index 00000000..4643af4f --- /dev/null +++ b/packages/docs-test/tests/typescript/user-profiles.ts @@ -0,0 +1,89 @@ +import "dotenv/config" +import Supermemory from "supermemory" + +const client = new Supermemory() +const USER_ID = "docs-test-profiles" + +async function testBasicProfile() { + console.log("=== Basic Profile ===") + + const profile = await client.profile({ + containerTag: USER_ID, + q: "What are my preferences?", + }) + + console.log("✓ Static profile:", profile.profile.static.length, "items") + console.log("✓ Dynamic profile:", profile.profile.dynamic.length, "items") + console.log("✓ Search results:", profile.searchResults.results.length, "items") +} + +async function testProfileWithMemories() { + console.log("\n=== Profile with Memory Context ===") + + // First add some memories + await client.add({ + content: "User prefers dark mode for all applications", + containerTag: USER_ID, + }) + await client.add({ + content: "User is learning TypeScript and Rust", + containerTag: USER_ID, + }) + + // Wait a bit for indexing + await new Promise((r) => setTimeout(r, 2000)) + + // Now get profile with search + const profile = await client.profile({ + containerTag: USER_ID, + q: "What programming languages does the user know?", + }) + + console.log("✓ Profile retrieved with memories") + console.log(" Static:", profile.profile.static.slice(0, 2)) + console.log(" Dynamic:", profile.profile.dynamic.slice(0, 2)) +} + +async function testBuildingContext() { + console.log("\n=== Building LLM Context ===") + + const conversation = [ + { role: "user", content: "What theme should I use for my IDE?" }, + ] + + const profile = await client.profile({ + containerTag: USER_ID, + q: conversation.at(-1)!.content, + }) + + const context = `User Profile: +${profile.profile.static.join("\n")} + +Recent Context: +${profile.profile.dynamic.join("\n")} + +Relevant Memories: +${profile.searchResults.results.map((r) => r.content).join("\n")}` + + console.log("✓ Built context:", context.length, "chars") + + const messages = [ + { role: "system", content: `Use this context about the user:\n${context}` }, + ...conversation, + ] + console.log("✓ Messages ready for LLM:", messages.length, "messages") +} + +async function main() { + console.log("User Profiles Tests") + console.log("===================\n") + + await testBasicProfile() + await testProfileWithMemories() + await testBuildingContext() + + console.log("\n===================") + console.log("✅ All user profile tests passed!") +} + +main().catch(console.error) diff --git a/packages/openai-sdk-python/src/supermemory_openai/middleware.py b/packages/openai-sdk-python/src/supermemory_openai/middleware.py index e2399bb6..4f6dc8ec 100644 --- a/packages/openai-sdk-python/src/supermemory_openai/middleware.py +++ b/packages/openai-sdk-python/src/supermemory_openai/middleware.py @@ -1,31 +1,31 @@ """Supermemory middleware for OpenAI clients.""" -from dataclasses import dataclass -from typing import Optional, Union, Any, Literal, cast import asyncio import os +from dataclasses import dataclass +from typing import Any, Literal, Optional, Union, cast -from openai import OpenAI, AsyncOpenAI +import supermemory +from openai import AsyncOpenAI, OpenAI from openai.types.chat import ( ChatCompletionMessageParam, ChatCompletionSystemMessageParam, ) -import supermemory -from .utils import ( - Logger, - create_logger, - get_last_user_message, - get_conversation_content, - convert_profile_to_markdown, - deduplicate_memories, -) from .exceptions import ( - SupermemoryConfigurationError, SupermemoryAPIError, + SupermemoryConfigurationError, SupermemoryMemoryOperationError, SupermemoryNetworkError, ) +from .utils import ( + Logger, + convert_profile_to_markdown, + create_logger, + deduplicate_memories, + get_conversation_content, + get_last_user_message, +) @dataclass @@ -75,7 +75,7 @@ async def supermemory_profile_search( raise SupermemoryAPIError( "Supermemory profile search failed", status_code=response.status, - response_text=error_text + response_text=error_text, ) data = await response.json() @@ -98,7 +98,7 @@ async def supermemory_profile_search( raise SupermemoryAPIError( "Supermemory profile search failed", status_code=response.status_code, - response_text=response.text + response_text=response.text, ) return SupermemoryProfileSearch(response.json()) @@ -146,9 +146,18 @@ async def add_system_prompt( logger.debug( "Memory deduplication completed", { - "static": {"original": memory_count_static, "deduplicated": len(deduplicated.static)}, - "dynamic": {"original": memory_count_dynamic, "deduplicated": len(deduplicated.dynamic)}, - "search_results": {"original": memory_count_search, "deduplicated": len(deduplicated.search_results)}, + "static": { + "original": memory_count_static, + "deduplicated": len(deduplicated.static), + }, + "dynamic": { + "original": memory_count_dynamic, + "deduplicated": len(deduplicated.dynamic), + }, + "search_results": { + "original": memory_count_search, + "deduplicated": len(deduplicated.search_results), + }, }, ) @@ -217,10 +226,10 @@ async def add_memory_tool( # Handle both sync and async supermemory clients try: - response = await client.memories.add(**add_params) + response = await client.add(**add_params) except TypeError: # If it's not awaitable, call it synchronously - response = client.memories.add(**add_params) + response = client.add(**add_params) logger.info( "Memory saved successfully", @@ -237,18 +246,14 @@ async def add_memory_tool( {"error": str(network_error)}, ) raise SupermemoryNetworkError( - "Failed to save memory due to network error", - network_error + "Failed to save memory due to network error", network_error ) except Exception as error: logger.error( "Error saving memory", {"error": str(error)}, ) - raise SupermemoryMemoryOperationError( - "Failed to save memory", - error - ) + raise SupermemoryMemoryOperationError("Failed to save memory", error) class SupermemoryOpenAIWrapper: @@ -271,16 +276,17 @@ class SupermemoryOpenAIWrapper: if not hasattr(supermemory, "Supermemory"): raise SupermemoryConfigurationError( "supermemory package is required but not found", - ImportError("supermemory package not installed") + ImportError("supermemory package not installed"), ) api_key = self._get_api_key() try: - self._supermemory_client: supermemory.Supermemory = supermemory.Supermemory(api_key=api_key) + self._supermemory_client: supermemory.Supermemory = supermemory.Supermemory( + api_key=api_key + ) except Exception as e: raise SupermemoryConfigurationError( - f"Failed to initialize Supermemory client: {e}", - e + f"Failed to initialize Supermemory client: {e}", e ) # Wrap the chat completions create method @@ -359,15 +365,24 @@ class SupermemoryOpenAIWrapper: try: if task_obj.exception() is not None: exception = task_obj.exception() - if isinstance(exception, (SupermemoryNetworkError, SupermemoryAPIError)): + if isinstance( + exception, + (SupermemoryNetworkError, SupermemoryAPIError), + ): self._logger.warn( "Background memory storage failed", - {"error": str(exception), "type": type(exception).__name__} + { + "error": str(exception), + "type": type(exception).__name__, + }, ) else: self._logger.error( "Unexpected error in background memory storage", - {"error": str(exception), "type": type(exception).__name__} + { + "error": str(exception), + "type": type(exception).__name__, + }, ) except asyncio.CancelledError: self._logger.debug("Memory storage task was cancelled") @@ -440,7 +455,7 @@ class SupermemoryOpenAIWrapper: # We're in an async context, log warning and skip memory saving self._logger.warn( "Cannot save memory in sync client from async context", - {"error": str(e)} + {"error": str(e)}, ) else: raise @@ -454,7 +469,7 @@ class SupermemoryOpenAIWrapper: # Unexpected errors should be investigated self._logger.error( "Unexpected error saving memory", - {"error": str(e), "type": type(e).__name__} + {"error": str(e), "type": type(e).__name__}, ) # Handle memory search and injection @@ -464,11 +479,14 @@ class SupermemoryOpenAIWrapper: self._logger.debug("No user message found, skipping memory search") return original_create(**kwargs) - self._logger.info("Starting memory search", { - "container_tag": self._container_tag, - "conversation_id": self._options.conversation_id, - "mode": self._options.mode, - }) + self._logger.info( + "Starting memory search", + { + "container_tag": self._container_tag, + "conversation_id": self._options.conversation_id, + "mode": self._options.mode, + }, + ) # Use asyncio.run() for memory search and injection try: @@ -495,7 +513,7 @@ class SupermemoryOpenAIWrapper: self._logger, self._options.mode, self._get_api_key(), - ) + ), ) enhanced_messages = future.result() else: @@ -517,20 +535,24 @@ class SupermemoryOpenAIWrapper: if not self._background_tasks: return - self._logger.debug(f"Waiting for {len(self._background_tasks)} background tasks to complete") + self._logger.debug( + f"Waiting for {len(self._background_tasks)} background tasks to complete" + ) try: if timeout is not None: await asyncio.wait_for( asyncio.gather(*self._background_tasks, return_exceptions=True), - timeout=timeout + timeout=timeout, ) else: await asyncio.gather(*self._background_tasks, return_exceptions=True) self._logger.debug("All background tasks completed") except asyncio.TimeoutError: - self._logger.warn(f"Background tasks did not complete within {timeout}s timeout") + self._logger.warn( + f"Background tasks did not complete within {timeout}s timeout" + ) # Cancel remaining tasks for task in self._background_tasks: if not task.done(): @@ -580,7 +602,9 @@ class SupermemoryOpenAIWrapper: else: raise except asyncio.TimeoutError: - self._logger.warn("Some background memory tasks did not complete on exit") + self._logger.warn( + "Some background memory tasks did not complete on exit" + ) self.cancel_background_tasks() def __getattr__(self, name: str) -> Any: diff --git a/packages/openai-sdk-python/src/supermemory_openai/tools.py b/packages/openai-sdk-python/src/supermemory_openai/tools.py index 111253cd..87c19428 100644 --- a/packages/openai-sdk-python/src/supermemory_openai/tools.py +++ b/packages/openai-sdk-python/src/supermemory_openai/tools.py @@ -1,14 +1,14 @@ """Supermemory tools for OpenAI function calling.""" import json -from typing import Dict, List, Optional, Union, TypedDict +from typing import Dict, List, Optional, TypedDict, Union +import supermemory from openai.types.chat import ( + ChatCompletionFunctionToolParam, ChatCompletionMessageToolCall, ChatCompletionToolMessageParam, - ChatCompletionFunctionToolParam, ) -import supermemory from supermemory.types import ( MemoryAddResponse, MemoryGetResponse, @@ -230,7 +230,7 @@ class SupermemoryTools: if metadata: add_params["metadata"] = metadata - response: MemoryAddResponse = await self.client.memories.add(**add_params) + response: MemoryAddResponse = await self.client.add(**add_params) return MemoryAddResult( success=True, diff --git a/packages/pipecat-sdk-python/src/supermemory_pipecat/service.py b/packages/pipecat-sdk-python/src/supermemory_pipecat/service.py index ab07e672..01bc03df 100644 --- a/packages/pipecat-sdk-python/src/supermemory_pipecat/service.py +++ b/packages/pipecat-sdk-python/src/supermemory_pipecat/service.py @@ -11,12 +11,11 @@ import os from typing import Any, Dict, List, Literal, Optional from loguru import logger -from pydantic import BaseModel, Field - from pipecat.frames.frames import Frame, LLMContextFrame, LLMMessagesFrame from pipecat.processors.aggregators.llm_context import LLMContext from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContextFrame from pipecat.processors.frame_processor import FrameDirection, FrameProcessor +from pydantic import BaseModel, Field from .exceptions import ( ConfigurationError, @@ -73,7 +72,9 @@ class SupermemoryPipecatService(FrameProcessor): search_limit: int = Field(default=10, ge=1) search_threshold: float = Field(default=0.1, ge=0.0, le=1.0) - system_prompt: str = Field(default="Based on previous conversations, I recall:\n\n") + system_prompt: str = Field( + default="Based on previous conversations, I recall:\n\n" + ) mode: Literal["profile", "query", "full"] = Field(default="full") def __init__( @@ -202,7 +203,9 @@ class SupermemoryPipecatService(FrameProcessor): messages: List of message dicts with 'role' and 'content' keys. """ if self._supermemory_client is None: - logger.warning("Supermemory client not initialized, skipping memory storage") + logger.warning( + "Supermemory client not initialized, skipping memory storage" + ) return if not messages: @@ -223,7 +226,7 @@ class SupermemoryPipecatService(FrameProcessor): if self.session_id: add_params["custom_id"] = f"{self.session_id}" - await self._supermemory_client.memories.add(**add_params) + await self._supermemory_client.add(**add_params) logger.debug(f"Successfully stored {len(messages)} messages in Supermemory") except Exception as e: @@ -317,16 +320,22 @@ class SupermemoryPipecatService(FrameProcessor): if latest_user_message: # Retrieve memories from Supermemory try: - memories_data = await self._retrieve_memories(latest_user_message) + memories_data = await self._retrieve_memories( + latest_user_message + ) self._enhance_context_with_memories( context, latest_user_message, memories_data ) except MemoryRetrievalError as e: - logger.warning(f"Memory retrieval failed, continuing without memories: {e}") + logger.warning( + f"Memory retrieval failed, continuing without memories: {e}" + ) # Store unsent messages (user and assistant only, skip system) storable_messages = [ - msg for msg in context_messages if msg["role"] in ("user", "assistant") + msg + for msg in context_messages + if msg["role"] in ("user", "assistant") ] unsent_messages = storable_messages[self._messages_sent_count :] diff --git a/packages/tools/src/ai-sdk.ts b/packages/tools/src/ai-sdk.ts index 8b893cd0..0f2d5fe0 100644 --- a/packages/tools/src/ai-sdk.ts +++ b/packages/tools/src/ai-sdk.ts @@ -87,7 +87,7 @@ export const addMemoryTool = ( try { const metadata: Record<string, string | number | boolean> = {} - const response = await client.memories.add({ + const response = await client.add({ content: memory, containerTags, ...(Object.keys(metadata).length > 0 && { metadata }), diff --git a/packages/tools/src/claude-memory.ts b/packages/tools/src/claude-memory.ts index 54da77a1..2ef45f21 100644 --- a/packages/tools/src/claude-memory.ts +++ b/packages/tools/src/claude-memory.ts @@ -47,17 +47,23 @@ export class ClaudeMemoryTool { private memoryContainerPrefix: string /** - * Normalize file path to be used as customId (replace / with --) + * Normalize file path to be used as customId + * Converts /memories/file.txt -> memories_file_txt */ private normalizePathToCustomId(path: string): string { - return path.replace(/\//g, "--") + return path + .replace(/^\//, "") // Remove leading slash + .replace(/\//g, "_") // Replace / with _ + .replace(/\./g, "_") // Replace . with _ } /** - * Convert customId back to file path (replace -- with /) + * Convert customId back to file path + * Note: This is lossy since we can't distinguish _ from . or / + * We rely on metadata.file_path for accurate path reconstruction */ private customIdToPath(customId: string): string { - return customId.replace(/--/g, "/") + return "/" + customId.replace(/_/g, "/") } constructor(apiKey: string, config?: ClaudeMemoryConfig) { @@ -329,7 +335,7 @@ export class ClaudeMemoryTool { try { const normalizedId = this.normalizePathToCustomId(filePath) - const response = await this.client.memories.add({ + const response = await this.client.add({ content: fileText, customId: normalizedId, containerTags: this.containerTags, @@ -388,7 +394,7 @@ export class ClaudeMemoryTool { // Update the document const normalizedId = this.normalizePathToCustomId(filePath) - const updateResponse = await this.client.memories.add({ + const updateResponse = await this.client.add({ content: newContent, customId: normalizedId, containerTags: this.containerTags, @@ -447,7 +453,7 @@ export class ClaudeMemoryTool { // Update the document const normalizedId = this.normalizePathToCustomId(filePath) - await this.client.memories.add({ + await this.client.add({ content: newContent, customId: normalizedId, containerTags: this.containerTags, @@ -530,7 +536,7 @@ export class ClaudeMemoryTool { const newNormalizedId = this.normalizePathToCustomId(newPath) // Create new document with new path - await this.client.memories.add({ + await this.client.add({ content: originalContent, customId: newNormalizedId, containerTags: this.containerTags, diff --git a/packages/tools/src/openai/middleware.ts b/packages/tools/src/openai/middleware.ts index 4d030d0e..d390d841 100644 --- a/packages/tools/src/openai/middleware.ts +++ b/packages/tools/src/openai/middleware.ts @@ -363,7 +363,7 @@ const addMemoryTool = async ( } // Fallback to old behavior for non-conversation memories - const response = await client.memories.add({ + const response = await client.add({ content, containerTags: [containerTag], customId, diff --git a/packages/tools/src/openai/tools.ts b/packages/tools/src/openai/tools.ts index 4078df09..c0a3e25d 100644 --- a/packages/tools/src/openai/tools.ts +++ b/packages/tools/src/openai/tools.ts @@ -141,7 +141,7 @@ export function createAddMemoryFunction( try { const metadata: Record<string, string | number | boolean> = {} - const response = await client.memories.add({ + const response = await client.add({ content: memory, containerTags, ...(Object.keys(metadata).length > 0 && { metadata }), diff --git a/packages/tools/src/vercel/middleware.ts b/packages/tools/src/vercel/middleware.ts index 43306934..8336b397 100644 --- a/packages/tools/src/vercel/middleware.ts +++ b/packages/tools/src/vercel/middleware.ts @@ -137,7 +137,7 @@ export const saveMemoryAfterResponse = async ( ? `${getConversationContent(params)} \n\n Assistant: ${assistantResponseText}` : `User: ${userMessage} \n\n Assistant: ${assistantResponseText}` - const response = await client.memories.add({ + const response = await client.add({ content, containerTags: [containerTag], customId, |