mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-09-11 22:51:05 +00:00
Merge upstream changes from supermemoryai/supermemory
This commit is contained in:
commit
6db8deb09b
43 changed files with 1464 additions and 149 deletions
|
|
@ -291,7 +291,7 @@ For feature requests, please provide:
|
|||
- **Discord**: [Join our Discord server](https://supermemory.link/discord)
|
||||
- **GitHub Discussions**: For questions and ideas
|
||||
- **Issues**: For bug reports and feature requests
|
||||
- **Email**: [support@supermemory.com](mailto:support@supermemory.com)
|
||||
- **Email**: [support@supermemory.ai](mailto:support@supermemory.ai)
|
||||
|
||||
## 📄 License
|
||||
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ Go to [app.supermemory.ai](https://app.supermemory.ai) and sign in with your acc
|
|||
|
||||
Have questions or feedback? We're here to help:
|
||||
|
||||
- Email: [support@supermemory.com](mailto:support@supermemory.com)
|
||||
- Email: [support@supermemory.ai](mailto:support@supermemory.ai)
|
||||
- Discord: [Join our Discord server](https://supermemory.link/discord)
|
||||
- Documentation: [docs.supermemory.ai](https://docs.supermemory.ai)
|
||||
|
||||
|
|
|
|||
|
|
@ -679,7 +679,7 @@ function App() {
|
|||
<button
|
||||
className="bg-transparent border-none text-blue-500 cursor-pointer underline text-sm p-0 hover:text-blue-700"
|
||||
onClick={() => {
|
||||
window.open("mailto:support@supermemory.com", "_blank")
|
||||
window.open("mailto:support@supermemory.ai", "_blank")
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -245,7 +245,7 @@ curl -X PATCH "https://api.supermemory.ai/v3/documents/doc_id" \
|
|||
|
||||
## Next Steps
|
||||
|
||||
- [Track Processing Status](/api/track-progress) - Monitor document processing
|
||||
- [Track Processing Status](/memory-api/track-progress) - Monitor document processing
|
||||
- [Search Memories](/search/overview) - Search your content
|
||||
- [List Memories](/list-memories/overview) - Browse stored memories
|
||||
- [Update & Delete](/update-delete-memories/overview) - Manage memories
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ The Supermemory Cookbook provides complete, production-ready examples that show
|
|||
|
||||
## Coming Soon
|
||||
|
||||
We're working on more comprehensive recipes. Have a suggestion? [Let us know!](mailto:support@supermemory.com)
|
||||
We're working on more comprehensive recipes. Have a suggestion? [Let us know!](mailto:support@supermemory.ai)
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Research Assistant" icon="search" color="#gray">
|
||||
|
|
@ -55,10 +55,10 @@ Can't find what you're looking for?
|
|||
|
||||
- Browse [Search Examples](/search/examples/document-search) for specific feature usage
|
||||
- Check the [AI SDK Examples](/cookbook/ai-sdk-integration) for complete implementations
|
||||
- Reach out to [support](mailto:support@supermemory.com) for help
|
||||
- Reach out to [support](mailto:support@supermemory.ai) for help
|
||||
|
||||
## Contributing Recipes
|
||||
|
||||
Have a great Supermemory use case? We'd love to add it to the cookbook!
|
||||
|
||||
[Suggest a recipe →](mailto:support@supermemory.com?subject=Cookbook%20Recipe%20Suggestion)
|
||||
[Suggest a recipe →](mailto:support@supermemory.ai?subject=Cookbook%20Recipe%20Suggestion)
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@
|
|||
"navbar": {
|
||||
"links": [
|
||||
{
|
||||
"href": "mailto:support@supermemory.com",
|
||||
"href": "mailto:support@supermemory.ai",
|
||||
"label": "Support"
|
||||
}
|
||||
],
|
||||
|
|
@ -174,7 +174,7 @@
|
|||
},
|
||||
{
|
||||
"group": "Migration Guides",
|
||||
"pages": ["migration/from-mem0"]
|
||||
"pages": ["migration/from-mem0", "migration/from-zep"]
|
||||
},
|
||||
{
|
||||
"group": "Deployment",
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ Along with the user context, developers can also choose to do a search on the ra
|
|||
- Works well with the memory engine
|
||||
|
||||
<Info>
|
||||
You can reference the full API reference for the Memory API [here](/api-reference/manage-documents/add-document).
|
||||
You can reference the full API reference for the Memory API in the API Reference tab.
|
||||
</Info>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -373,4 +373,4 @@ requests.patch(
|
|||
|
||||
## Next Steps
|
||||
|
||||
Explore more advanced features in our [API Reference](/api-reference/manage-memories/add-memory)
|
||||
Explore more advanced features in our API Reference tab.
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ Check out the following resources to get started:
|
|||
<Card title="Quickstart" icon="zap" href="/memory-api/overview">
|
||||
Get started in 5 minutes
|
||||
</Card>
|
||||
<Card title="API Reference" icon="unplug" href="/api-reference">
|
||||
<Card title="API Reference" icon="unplug">
|
||||
Learn more about the API
|
||||
</Card>
|
||||
<Card title="Use Cases" icon="brain" href="/overview/use-cases">
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ client.memory.add(
|
|||
|
||||
This will add a new memory to your supermemory account.
|
||||
|
||||
Try it out in the [API Playground](/api-reference/manage-memories/add-memory).
|
||||
Try it out in the API Reference tab.
|
||||
|
||||
## Content Processing
|
||||
|
||||
|
|
@ -132,7 +132,7 @@ client.search.execute(
|
|||
|
||||
</CodeGroup>
|
||||
|
||||
Try it out in the [API Playground](/api-reference/search-memories/search-memories).
|
||||
Try it out in the API Reference tab.
|
||||
|
||||
You can do a lot more with supermemory, and we will walk through everything you need to.
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ title: "Overview"
|
|||
Use supermemory with the python and javascript OpenAI SDKs
|
||||
</Card>
|
||||
|
||||
<Card title="Request more plugins" icon="life-buoy" href="mailto:support@supermemory.com">
|
||||
<Card title="Request more plugins" icon="life-buoy" href="mailto:support@supermemory.ai">
|
||||
We will add support for your favorite SDKs asap.
|
||||
</Card>
|
||||
</Columns>
|
||||
|
|
|
|||
|
|
@ -178,4 +178,4 @@ Forgotten memories are memories that have been explicitly forgotten using the fo
|
|||
|
||||
## Next Steps
|
||||
|
||||
Explore more advanced features in our [API Reference](/api-reference/search-memories/search-memories).
|
||||
Explore more advanced features in our API Reference tab.
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ curl -X GET "https://api.supermemory.ai/v3/documents/doc_abc123" \
|
|||
}
|
||||
```
|
||||
|
||||
For more comprehensive information on the get documents by ID endpoint, refer to the [API reference.](/api-reference/manage-documents/get-document)
|
||||
For more comprehensive information on the get documents by ID endpoint, refer to the API Reference tab.
|
||||
|
||||
## Status Values
|
||||
|
||||
|
|
|
|||
|
|
@ -292,7 +292,7 @@ response = client.chat.completions.create(
|
|||
</CodeGroup>
|
||||
|
||||
<Note>
|
||||
For enterprise migrations, [contact us](mailto:support@supermemory.com) for assistance.
|
||||
For enterprise migrations, [contact us](mailto:support@supermemory.ai) for assistance.
|
||||
</Note>
|
||||
|
||||
## Next Steps
|
||||
|
|
|
|||
372
apps/docs/migration/from-zep.mdx
Normal file
372
apps/docs/migration/from-zep.mdx
Normal file
|
|
@ -0,0 +1,372 @@
|
|||
---
|
||||
title: "Migrating from Zep to Supermemory"
|
||||
description: "Quick guide to migrate from Zep to Supermemory"
|
||||
sidebarTitle: "From Zep"
|
||||
---
|
||||
|
||||
## Key Differences
|
||||
|
||||
| Zep AI | Supermemory |
|
||||
|--------|-------------|
|
||||
| Sessions & Messages | Documents & Container Tags |
|
||||
| `session.create()` | Use `containerTags` parameter |
|
||||
| `memory.add(session_id, ...)` | `add({containerTag: [...]})` |
|
||||
| `memory.search(session_id, {text: ...})` | `search.execute({q: ..., containerTags: [...]})` |
|
||||
|
||||
## Installation
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```bash Python
|
||||
pip install supermemory
|
||||
```
|
||||
|
||||
```bash TypeScript
|
||||
npm install supermemory
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python Python
|
||||
from supermemory import Supermemory
|
||||
client = Supermemory(api_key="your-api-key")
|
||||
```
|
||||
|
||||
```typescript TypeScript
|
||||
import { Supermemory } from "supermemory";
|
||||
const client = new Supermemory({ apiKey: "your-api-key" });
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## API Mapping
|
||||
|
||||
### Session Management
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python Zep AI
|
||||
session = client.session.create(
|
||||
session_id="user_123",
|
||||
user_id="user_123"
|
||||
)
|
||||
```
|
||||
|
||||
```python Supermemory
|
||||
# No explicit session creation - use containerTag
|
||||
containerTag = ["user_123"]
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
### Adding Memories
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python Zep AI
|
||||
client.memory.add(
|
||||
session_id="user_123",
|
||||
memory={"content": "User prefers dark mode"}
|
||||
)
|
||||
```
|
||||
|
||||
```python Supermemory
|
||||
client.add({
|
||||
"content": "User prefers dark mode",
|
||||
"containerTag": ["user_123"]
|
||||
})
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
### Searching
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python Zep AI
|
||||
results = client.memory.search(
|
||||
session_id="user_123",
|
||||
search_payload={"text": "preferences", "limit": 5}
|
||||
)
|
||||
```
|
||||
|
||||
```python Supermemory
|
||||
results = client.search.execute({
|
||||
"q": "preferences",
|
||||
"containerTag": ["user_123"],
|
||||
"limit": 5
|
||||
})
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
### Getting All Memories
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python Zep AI
|
||||
memories = client.memory.get(session_id="user_123")
|
||||
```
|
||||
|
||||
```python Supermemory
|
||||
documents = client.memories.list({
|
||||
"containerTag": ["user_123"],
|
||||
"limit": 100
|
||||
})
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Migration Steps
|
||||
|
||||
1. **Replace client initialization** - Use Supermemory client instead of Zep
|
||||
2. **Map sessions to container tags** - Replace `session_id="user_123"` with `containerTag: ["user_123"]`
|
||||
3. **Update method calls** - Use `add()` and `search.execute()` instead of `memory.add()` and `memory.search()`
|
||||
4. **Change search parameter** - Use `q` instead of `text`
|
||||
5. **Handle async processing** - Documents process asynchronously (status: `queued` → `done`)
|
||||
|
||||
## Complete Example
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python Zep AI
|
||||
from zep_python import ZepClient
|
||||
|
||||
client = ZepClient(api_key="...")
|
||||
session = client.session.create(session_id="user_123", user_id="user_123")
|
||||
|
||||
client.memory.add("user_123", {
|
||||
"content": "I love Python",
|
||||
"role": "user"
|
||||
})
|
||||
|
||||
results = client.memory.search("user_123", {
|
||||
"text": "programming",
|
||||
"limit": 3
|
||||
})
|
||||
```
|
||||
|
||||
```python Supermemory
|
||||
from supermemory import Supermemory
|
||||
|
||||
client = Supermemory(api_key="...")
|
||||
containerTag = ["user_123"]
|
||||
|
||||
client.add({
|
||||
"content": "I love Python",
|
||||
"containerTag": containerTag,
|
||||
"metadata": {"role": "user"}
|
||||
})
|
||||
|
||||
results = client.search.execute({
|
||||
"q": "programming",
|
||||
"containerTag": containerTag,
|
||||
"limit": 3
|
||||
})
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Important Notes
|
||||
|
||||
- **No session creation needed** - Just use `containerTag` in requests
|
||||
- **Messages are documents** - Store with `metadata.role` and `metadata.type`
|
||||
- **Async processing** - Documents may take a moment to be searchable
|
||||
- **Response structure** - Supermemory returns chunks with scores, not direct memory content
|
||||
|
||||
## Migrating Existing Data
|
||||
|
||||
### Quick Migration (All-in-One)
|
||||
|
||||
Complete migration in one script:
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```typescript TypeScript
|
||||
import { ZepClient } from "@getzep/zep-js";
|
||||
import { Supermemory } from "supermemory";
|
||||
|
||||
// Initialize clients
|
||||
const zep = new ZepClient({ apiKey: "your_zep_api_key" });
|
||||
const supermemory = new Supermemory({ apiKey: "your_supermemory_api_key" });
|
||||
|
||||
// Export from Zep and import to Supermemory
|
||||
const sessionIds = ["session_1", "session_2"]; // Add your session IDs
|
||||
|
||||
for (const sessionId of sessionIds) {
|
||||
const memory = await zep.memory.get(sessionId);
|
||||
const memories = memory?.memories || [];
|
||||
|
||||
for (const mem of memories) {
|
||||
if (mem.content) {
|
||||
await supermemory.add({
|
||||
content: mem.content,
|
||||
containerTag: [`session_${sessionId}`, `user_${memory.user_id || "unknown"}`],
|
||||
metadata: {
|
||||
role: mem.role,
|
||||
type: "message",
|
||||
original_uuid: mem.uuid,
|
||||
...mem.metadata
|
||||
}
|
||||
});
|
||||
console.log(`✅ Imported: ${mem.content.substring(0, 50)}...`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Migration complete!");
|
||||
```
|
||||
|
||||
```python Python
|
||||
from zep_python import ZepClient
|
||||
from supermemory import Supermemory
|
||||
|
||||
# Initialize clients
|
||||
zep = ZepClient(api_key="your_zep_api_key")
|
||||
supermemory = Supermemory(api_key="your_supermemory_api_key")
|
||||
|
||||
# Export from Zep and import to Supermemory
|
||||
session_ids = ["session_1", "session_2"] # Add your session IDs
|
||||
|
||||
for session_id in session_ids:
|
||||
memory = zep.memory.get(session_id)
|
||||
memories = memory.memories if memory else []
|
||||
|
||||
for mem in memories:
|
||||
if mem.content:
|
||||
supermemory.add({
|
||||
"content": mem.content,
|
||||
"containerTag": [f"session_{session_id}", f"user_{memory.user_id or 'unknown'}"],
|
||||
"metadata": {
|
||||
"role": mem.role,
|
||||
"type": "message",
|
||||
"original_uuid": mem.uuid,
|
||||
**(mem.metadata or {})
|
||||
}
|
||||
})
|
||||
print(f"✅ Imported: {mem.content[:50]}...")
|
||||
|
||||
print("Migration complete!")
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
### Full Migration Script
|
||||
|
||||
For a complete migration script with error handling, verification, and progress tracking, copy this TypeScript script:
|
||||
|
||||
```typescript
|
||||
import { ZepClient } from "@getzep/zep-js";
|
||||
import { Supermemory } from "supermemory";
|
||||
import * as dotenv from "dotenv";
|
||||
import * as fs from "fs";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
interface MigrationStats {
|
||||
imported: number;
|
||||
failed: number;
|
||||
skipped: number;
|
||||
}
|
||||
|
||||
async function migrateFromZep(
|
||||
zepApiKey: string,
|
||||
supermemoryApiKey: string,
|
||||
sessionIds: string[]
|
||||
) {
|
||||
const zep = new ZepClient({ apiKey: zepApiKey });
|
||||
const supermemory = new Supermemory({ apiKey: supermemoryApiKey });
|
||||
|
||||
const stats: MigrationStats = { imported: 0, failed: 0, skipped: 0 };
|
||||
const exportedData: any = {};
|
||||
|
||||
console.log("🔄 Starting migration...");
|
||||
|
||||
// Export from Zep
|
||||
for (const sessionId of sessionIds) {
|
||||
try {
|
||||
const session = await zep.session.get(sessionId);
|
||||
const memory = await zep.memory.get(sessionId);
|
||||
const memories = memory?.memories || [];
|
||||
|
||||
exportedData[sessionId] = {
|
||||
session: { session_id: sessionId, user_id: session?.user_id },
|
||||
memories: memories.map((m: any) => ({
|
||||
content: m.content,
|
||||
role: m.role,
|
||||
metadata: m.metadata,
|
||||
uuid: m.uuid,
|
||||
})),
|
||||
};
|
||||
|
||||
console.log(`✅ Exported ${memories.length} memories from ${sessionId}`);
|
||||
} catch (error: any) {
|
||||
console.log(`❌ Error exporting ${sessionId}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Save backup
|
||||
const backupFile = `zep_export_${Date.now()}.json`;
|
||||
fs.writeFileSync(backupFile, JSON.stringify(exportedData, null, 2));
|
||||
console.log(`💾 Backup saved to: ${backupFile}`);
|
||||
|
||||
// Import to Supermemory
|
||||
let totalMemories = 0;
|
||||
for (const [sessionId, data] of Object.entries(exportedData) as any) {
|
||||
const containerTag = ["imported_from_zep", `session_${sessionId}`];
|
||||
if (data.session.user_id) {
|
||||
containerTag.push(`user_${data.session.user_id}`);
|
||||
}
|
||||
|
||||
for (const memory of data.memories) {
|
||||
totalMemories++;
|
||||
try {
|
||||
if (!memory.content?.trim()) {
|
||||
stats.skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
await supermemory.add({
|
||||
content: memory.content,
|
||||
containerTag: containerTag,
|
||||
metadata: {
|
||||
source: "zep_migration",
|
||||
role: memory.role,
|
||||
type: "message",
|
||||
original_uuid: memory.uuid,
|
||||
...memory.metadata,
|
||||
},
|
||||
});
|
||||
|
||||
stats.imported++;
|
||||
console.log(`✅ [${stats.imported}/${totalMemories}] Imported`);
|
||||
} catch (error: any) {
|
||||
stats.failed++;
|
||||
console.log(`❌ Failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log("\n📊 Migration Summary:");
|
||||
console.log(`✅ Imported: ${stats.imported}`);
|
||||
console.log(`⚠️ Skipped: ${stats.skipped}`);
|
||||
console.log(`❌ Failed: ${stats.failed}`);
|
||||
}
|
||||
|
||||
// Usage
|
||||
const sessionIds = ["session_1", "session_2"]; // Add your session IDs
|
||||
migrateFromZep(
|
||||
process.env.ZEP_API_KEY!,
|
||||
process.env.SUPERMEMORY_API_KEY!,
|
||||
sessionIds
|
||||
).catch(console.error);
|
||||
```
|
||||
|
||||
|
||||
## Resources
|
||||
|
||||
- [Supermemory SDKs](/memory-api/sdks/overview)
|
||||
- [API Reference](/memory-api/overview)
|
||||
- [Search Documentation](/search/overview)
|
||||
|
|
@ -50,7 +50,7 @@ Follow these steps to build a workflow that captures and stores your Gmail messa
|
|||
|
||||
1. **Add an HTTP Request node** after the Gmail Trigger
|
||||
2. **Method**: `POST`
|
||||
3. **URL**: [`https://api.supermemory.ai/v3/documents`](/api-reference/manage-documents/add-document)
|
||||
3. **URL**: `https://api.supermemory.ai/v3/documents`
|
||||
4. Select your auth credentials you created with the Supermemory API Key.
|
||||
|
||||
#### Step 3: Format Email Data for Supermemory
|
||||
|
|
@ -89,4 +89,4 @@ If you want to process attachments:
|
|||
3. Check the execution to ensure the email was captured
|
||||
4. Verify in Supermemory that the email appears in search results
|
||||
|
||||
Refer to the [API reference](/api-reference/manage-documents/add-document) to learn more about other supermemory API endpoints.
|
||||
Refer to the API Reference tab to learn more about other supermemory API endpoints.
|
||||
|
|
@ -464,9 +464,232 @@ Combining features for comprehensive results:
|
|||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Comon Use Cases
|
||||
## Hybrid Search Mode
|
||||
|
||||
Hybrid search mode allows you to search both memories and document chunks in a single request. When `searchMode="hybrid"`, results contain objects with either a `memory` key (for memory results) or a `chunk` key (for chunk results).
|
||||
|
||||
### Basic Hybrid Search
|
||||
|
||||
<Tabs>
|
||||
<Tab title="TypeScript">
|
||||
```typescript
|
||||
const results = await client.search.memories({
|
||||
q: "machine learning best practices",
|
||||
searchMode: "hybrid", // Search memories + chunks
|
||||
limit: 10
|
||||
});
|
||||
|
||||
// Handle mixed results
|
||||
results.results.forEach(result => {
|
||||
if ('memory' in result) {
|
||||
console.log('Memory:', result.memory);
|
||||
} else if ('chunk' in result) {
|
||||
console.log('Chunk:', result.chunk);
|
||||
console.log('From document:', result.documents?.[0]?.title);
|
||||
}
|
||||
});
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Python">
|
||||
```python
|
||||
results = client.search.memories(
|
||||
q="machine learning best practices",
|
||||
search_mode="hybrid", # Search memories + chunks
|
||||
limit=10
|
||||
)
|
||||
|
||||
# Handle mixed results
|
||||
for result in results.results:
|
||||
if 'memory' in result:
|
||||
print('Memory:', result['memory'])
|
||||
elif 'chunk' in result:
|
||||
print('Chunk:', result['chunk'])
|
||||
print('From document:', result.get('documents', [{}])[0].get('title'))
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="cURL">
|
||||
```bash
|
||||
curl -X POST "https://api.supermemory.ai/v4/search" \
|
||||
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"q": "machine learning best practices",
|
||||
"searchMode": "hybrid",
|
||||
"limit": 10
|
||||
}'
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### When to Use Hybrid Mode
|
||||
|
||||
Use hybrid mode when:
|
||||
- You want comprehensive search across both memories and documents
|
||||
- Memories might not exist for certain queries but document content is available
|
||||
- You need flexibility to get either memory or document chunk results
|
||||
- You want a single search endpoint that covers all content types
|
||||
|
||||
Use memories-only mode (`searchMode="memories"`) when:
|
||||
- You only need user memories and preferences
|
||||
- You want faster, more focused results
|
||||
- You're building a personalized chatbot that relies on user context
|
||||
|
||||
### Handling Mixed Results
|
||||
|
||||
When using hybrid mode, you'll receive mixed results. Here's how to process them:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="TypeScript">
|
||||
```typescript
|
||||
const results = await client.search.memories({
|
||||
q: "quantum computing applications",
|
||||
searchMode: "hybrid",
|
||||
limit: 10
|
||||
});
|
||||
|
||||
// Separate memory and chunk results
|
||||
const memoryResults = results.results.filter(r => 'memory' in r);
|
||||
const chunkResults = results.results.filter(r => 'chunk' in r);
|
||||
|
||||
console.log(`Found ${memoryResults.length} memories and ${chunkResults.length} chunks`);
|
||||
|
||||
// Process memories
|
||||
memoryResults.forEach(mem => {
|
||||
console.log('Memory:', mem.memory);
|
||||
console.log('Similarity:', mem.similarity);
|
||||
});
|
||||
|
||||
// Process chunks
|
||||
chunkResults.forEach(chunk => {
|
||||
console.log('Chunk:', chunk.chunk);
|
||||
console.log('Document:', chunk.documents?.[0]?.title);
|
||||
console.log('Similarity:', chunk.similarity);
|
||||
});
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Python">
|
||||
```python
|
||||
results = client.search.memories(
|
||||
q="quantum computing applications",
|
||||
search_mode="hybrid",
|
||||
limit=10
|
||||
)
|
||||
|
||||
# Separate memory and chunk results
|
||||
memory_results = [r for r in results.results if 'memory' in r]
|
||||
chunk_results = [r for r in results.results if 'chunk' in r]
|
||||
|
||||
print(f"Found {len(memory_results)} memories and {len(chunk_results)} chunks")
|
||||
|
||||
# Process memories
|
||||
for mem in memory_results:
|
||||
print('Memory:', mem['memory'])
|
||||
print('Similarity:', mem['similarity'])
|
||||
|
||||
# Process chunks
|
||||
for chunk in chunk_results:
|
||||
print('Chunk:', chunk['chunk'])
|
||||
print('Document:', chunk.get('documents', [{}])[0].get('title'))
|
||||
print('Similarity:', chunk['similarity'])
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### Hybrid Search with All Features
|
||||
|
||||
Combining hybrid mode with other features:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="TypeScript">
|
||||
```typescript
|
||||
const results = await client.search.memories({
|
||||
q: "research findings on AI",
|
||||
searchMode: "hybrid",
|
||||
containerTag: "research_team",
|
||||
threshold: 0.7,
|
||||
rerank: true,
|
||||
include: {
|
||||
documents: true,
|
||||
relatedMemories: true,
|
||||
summaries: true
|
||||
},
|
||||
limit: 10
|
||||
});
|
||||
|
||||
// Results are automatically sorted by similarity
|
||||
// Memory results have 'memory' field, chunk results have 'chunk' field
|
||||
results.results.forEach(result => {
|
||||
if ('memory' in result) {
|
||||
// Memory result
|
||||
console.log('Memory:', result.memory);
|
||||
console.log('Context:', result.context);
|
||||
} else {
|
||||
// Chunk result
|
||||
console.log('Chunk:', result.chunk);
|
||||
console.log('Document:', result.documents?.[0]);
|
||||
}
|
||||
});
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Python">
|
||||
```python
|
||||
results = client.search.memories(
|
||||
q="research findings on AI",
|
||||
search_mode="hybrid",
|
||||
container_tag="research_team",
|
||||
threshold=0.7,
|
||||
rerank=True,
|
||||
include={
|
||||
"documents": True,
|
||||
"relatedMemories": True,
|
||||
"summaries": True
|
||||
},
|
||||
limit=10
|
||||
)
|
||||
|
||||
# Results are automatically sorted by similarity
|
||||
# Memory results have 'memory' field, chunk results have 'chunk' field
|
||||
for result in results.results:
|
||||
if 'memory' in result:
|
||||
# Memory result
|
||||
print('Memory:', result['memory'])
|
||||
print('Context:', result.get('context'))
|
||||
else:
|
||||
# Chunk result
|
||||
print('Chunk:', result['chunk'])
|
||||
print('Document:', result.get('documents', [{}])[0])
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="cURL">
|
||||
```bash
|
||||
curl -X POST "https://api.supermemory.ai/v4/search" \
|
||||
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"q": "research findings on AI",
|
||||
"searchMode": "hybrid",
|
||||
"containerTag": "research_team",
|
||||
"threshold": 0.7,
|
||||
"rerank": true,
|
||||
"include": {
|
||||
"documents": true,
|
||||
"relatedMemories": true,
|
||||
"summaries": true
|
||||
},
|
||||
"limit": 10
|
||||
}'
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
<Note>
|
||||
**Important**: In hybrid mode, results are automatically merged and sorted by similarity score. Memory results and chunk results are deduplicated - if a chunk is already associated with a memory result, it won't appear as a separate chunk result.
|
||||
</Note>
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
- **Chatbots**: Basic search with container tag and low threshold
|
||||
- **Q&A Systems**: Add reranking for better relevance
|
||||
- **Knowledge Retrieval**: Include documents and summaries
|
||||
- **Real-time Search**: Skip rewriting and reranking for maximum speed
|
||||
- **Hybrid Search**: Use `searchMode="hybrid"` when you need comprehensive search across both memories and documents
|
||||
|
|
|
|||
|
|
@ -194,33 +194,65 @@ Companies like Composio [Rube.app](https://rube.app) use memories search for let
|
|||
This endpoint works best for conversational AI use cases like chatbots.
|
||||
</Info>
|
||||
|
||||
**Hybrid Search Mode:**
|
||||
|
||||
The `/v4/search` endpoint supports a `searchMode` parameter with two options:
|
||||
|
||||
- **`"memories"`** (default): Searches only memory entries. Returns results with a `memory` key containing the memory content.
|
||||
- **`"hybrid"`**: Searches memories first, then falls back to document chunks if needed. Returns mixed results where each result object has either a `memory` key (for memory results) or a `chunk` key (for chunk results from documents).
|
||||
|
||||
<Note>
|
||||
In hybrid mode, results are automatically merged by similarity score and deduplicated. Check for the presence of `memory` or `chunk` keys to distinguish result types.
|
||||
</Note>
|
||||
|
||||
<Tabs>
|
||||
<Tab title="TypeScript">
|
||||
```typescript
|
||||
// Memories search
|
||||
// Memories search (default mode)
|
||||
const results = await client.search.memories({
|
||||
q: "machine learning accuracy",
|
||||
limit: 5,
|
||||
containerTag: "research",
|
||||
threshold: 0.7,
|
||||
rerank: true
|
||||
rerank: true,
|
||||
searchMode: "memories" // Default: only search memories
|
||||
});
|
||||
|
||||
// Hybrid search (memories + chunks)
|
||||
const hybridResults = await client.search.memories({
|
||||
q: "machine learning accuracy",
|
||||
limit: 5,
|
||||
containerTag: "research",
|
||||
threshold: 0.7,
|
||||
searchMode: "hybrid" // Search memories + fallback to chunks
|
||||
});
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Python">
|
||||
```python
|
||||
# Memories search
|
||||
# Memories search (default mode)
|
||||
results = client.search.memories(
|
||||
q="machine learning accuracy",
|
||||
limit=5,
|
||||
container_tag="research",
|
||||
threshold=0.7,
|
||||
rerank=True
|
||||
rerank=True,
|
||||
search_mode="memories" # Default: only search memories
|
||||
)
|
||||
|
||||
# Hybrid search (memories + chunks)
|
||||
hybrid_results = client.search.memories(
|
||||
q="machine learning accuracy",
|
||||
limit=5,
|
||||
container_tag="research",
|
||||
threshold=0.7,
|
||||
search_mode="hybrid" # Search memories + fallback to chunks
|
||||
)
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="cURL">
|
||||
```bash
|
||||
# Memories search (default mode)
|
||||
curl -X POST "https://api.supermemory.ai/v4/search" \
|
||||
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
|
|
@ -229,7 +261,20 @@ Companies like Composio [Rube.app](https://rube.app) use memories search for let
|
|||
"limit": 5,
|
||||
"containerTag": "research",
|
||||
"threshold": 0.7,
|
||||
"rerank": true
|
||||
"rerank": true,
|
||||
}'
|
||||
|
||||
# Hybrid search (memories + chunks)
|
||||
curl -X POST "https://api.supermemory.ai/v4/search" \
|
||||
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"q": "machine learning accuracy",
|
||||
"limit": 5,
|
||||
"containerTag": "research",
|
||||
"threshold": 0.7,
|
||||
"rerank": true,
|
||||
"searchMode": "hybrid"
|
||||
}'
|
||||
```
|
||||
</Tab>
|
||||
|
|
@ -283,7 +328,7 @@ Companies like Composio [Rube.app](https://rube.app) use memories search for let
|
|||
|
||||
```
|
||||
|
||||
The `/v4/search` endpoint searches through and returns memories.
|
||||
The `/v4/search` endpoint searches through and returns memories. With `searchMode="hybrid"`, it can also return document chunks when memories aren't found, providing comprehensive search coverage.
|
||||
|
||||
## Direct Document Retrieval
|
||||
|
||||
|
|
@ -324,7 +369,7 @@ curl -X GET "https://api.supermemory.ai/v3/documents/{YOUR-DOCUMENT-ID}" \
|
|||
</CodeGroup>
|
||||
|
||||
<Note>
|
||||
This endpoint returns the complete document with all fields including content, metadata, containerTags, summary, and processing status. For more details, see the [API reference](/api-reference/manage-documents/get-document).
|
||||
This endpoint returns the complete document with all fields including content, metadata, containerTags, summary, and processing status. For more details, see the API Reference tab.
|
||||
</Note>
|
||||
|
||||
## Search Flow Architecture
|
||||
|
|
|
|||
|
|
@ -205,6 +205,28 @@ These parameters are specific to `client.search.memories()`:
|
|||
```
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="searchMode" type="string" default="memories">
|
||||
**Search mode - memories only or hybrid search**
|
||||
|
||||
Controls whether to search only memories or also include document chunks:
|
||||
- **`"memories"`** (default): Searches only memory entries. Returns results with `memory` field.
|
||||
- **`"hybrid"`**: Searches memories first, then falls back to document chunks if needed. Returns mixed results with either `memory` field (for memory results) or `chunk` field (for chunk results).
|
||||
|
||||
<Note>
|
||||
In hybrid mode, results are automatically merged and deduplicated. Results contain objects with either a `memory` key (for memory results) or a `chunk` key (for chunk results from document search).
|
||||
</Note>
|
||||
|
||||
```typescript
|
||||
searchMode: "memories" // Only search memories (default)
|
||||
searchMode: "hybrid" // Search memories + fallback to chunks
|
||||
```
|
||||
|
||||
**When to use hybrid mode:**
|
||||
- When you want comprehensive search across both memories and documents
|
||||
- When memories might not exist for certain queries but document content is available
|
||||
- When you need the flexibility to get either memory or document chunk results
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="containerTag" type="string">
|
||||
**Filter by single container tag**
|
||||
|
||||
|
|
|
|||
|
|
@ -114,6 +114,8 @@ Response from `client.search.documents()` and `client.search.execute()`:
|
|||
|
||||
Response from `client.search.memories()`:
|
||||
|
||||
When `searchMode="memories"` (default), all results are memory entries:
|
||||
|
||||
```json
|
||||
{
|
||||
"results": [
|
||||
|
|
@ -160,14 +162,101 @@ Response from `client.search.memories()`:
|
|||
}
|
||||
```
|
||||
|
||||
When `searchMode="hybrid"`, results can contain both memory entries and document chunks. **Memory results have a `memory` key, chunk results have a `chunk` key:**
|
||||
|
||||
```json
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"id": "mem_xyz789",
|
||||
"memory": "Complete memory content about quantum computing applications...",
|
||||
"similarity": 0.87,
|
||||
"metadata": {
|
||||
"category": "research",
|
||||
"topic": "quantum-computing"
|
||||
},
|
||||
"updatedAt": "2024-01-18T09:15:00Z",
|
||||
"version": 3,
|
||||
"context": {
|
||||
"parents": [],
|
||||
"children": []
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"id": "doc_quantum_paper",
|
||||
"title": "Quantum Computing Applications",
|
||||
"type": "pdf",
|
||||
"createdAt": "2024-01-10T08:00:00Z",
|
||||
"updatedAt": "2024-01-10T08:00:00Z"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "chunk_abc123",
|
||||
"chunk": "This is a chunk of content from a document about quantum computing...",
|
||||
"similarity": 0.82,
|
||||
"metadata": {
|
||||
"category": "research",
|
||||
"source": "document"
|
||||
},
|
||||
"updatedAt": "2024-01-15T10:30:00Z",
|
||||
"version": 1,
|
||||
"context": {
|
||||
"parents": [],
|
||||
"children": []
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"id": "doc_quantum_research",
|
||||
"title": "Quantum Computing Research Paper",
|
||||
"type": "pdf",
|
||||
"metadata": {
|
||||
"author": "Dr. Smith"
|
||||
},
|
||||
"createdAt": "2024-01-15T10:30:00Z",
|
||||
"updatedAt": "2024-01-15T10:30:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"timing": 198,
|
||||
"total": 2
|
||||
}
|
||||
```
|
||||
|
||||
<Note>
|
||||
**Distinguishing Memory vs Chunk Results:**
|
||||
|
||||
In hybrid mode, check which key exists on the result object:
|
||||
- **Memory results**: Have a `memory` key (no `chunk` key)
|
||||
- **Chunk results**: Have a `chunk` key (no `memory` key)
|
||||
|
||||
```typescript
|
||||
// TypeScript example
|
||||
results.results.forEach(result => {
|
||||
if ('memory' in result) {
|
||||
// This is a memory result
|
||||
console.log('Memory:', result.memory);
|
||||
} else if ('chunk' in result) {
|
||||
// This is a chunk result
|
||||
console.log('Chunk:', result.chunk);
|
||||
}
|
||||
});
|
||||
```
|
||||
</Note>
|
||||
|
||||
### Memory Result Fields
|
||||
|
||||
<ResponseField name="id" type="string">
|
||||
Unique identifier for the memory entry.
|
||||
Unique identifier for the memory entry or chunk ID. In hybrid mode, can be either a memory ID (e.g., `mem_xyz789`) or a chunk ID (e.g., `chunk_abc123`).
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="memory" type="string">
|
||||
**Complete memory content**. Unlike document search which returns chunks, memory search returns the full memory text.
|
||||
<ResponseField name="memory" type="string" optional>
|
||||
**Complete memory content**. Only present for memory results (when `searchMode="memories"` or when a memory result is returned in hybrid mode). This field is not present for chunk results.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="chunk" type="string" optional>
|
||||
**Chunk content from a document**. Only present for chunk results when `searchMode="hybrid"`. This field is not present for memory results. Contains the actual text content from the document chunk.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="similarity" type="number" range="0-1">
|
||||
|
|
@ -189,7 +278,11 @@ Response from `client.search.memories()`:
|
|||
</ResponseField>
|
||||
|
||||
<ResponseField name="version" type="number | null" optional>
|
||||
Version number of this memory entry. Used for tracking memory evolution and relationships.
|
||||
Version number of this memory entry. Used for tracking memory evolution and relationships. For chunk results, this is typically `1`.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="rootMemoryId" type="string | null" optional>
|
||||
Root memory ID for memory entries. Only present for memory results. Always `null` for chunk results.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="context" type="object" optional>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ description: "Learn how to use the code block to integrate supermemory with Zap
|
|||
With Supermemory you can now easily add memory to your Zapier workflow steps. Here's how:
|
||||
|
||||
## Prerequisites
|
||||
- A Supermemory API Key. Get yours [here](console.supermemory.ai)
|
||||
- A Supermemory API Key. Get yours [here](https://console.supermemory.ai)
|
||||
|
||||
## Step-by-step tutorial
|
||||
|
||||
|
|
@ -30,7 +30,7 @@ For this tutorial, we're building a simple flow that adds incoming emails in Gma
|
|||

|
||||
</Step>
|
||||
<Step title="Integrate Supermemory">
|
||||
Since we're ingesting data here, we'll use the [add documents endpoint.](/api-reference/manage-documents/add-document)
|
||||
Since we're ingesting data here, we'll use the add documents endpoint.
|
||||
|
||||
Add the following code block:
|
||||
|
||||
|
|
@ -61,4 +61,4 @@ For this tutorial, we're building a simple flow that adds incoming emails in Gma
|
|||
</Note>
|
||||
|
||||
|
||||
You can perform other operations like search, filtering, user profiles, etc., by using other Supermemory API endpoints which can be found in our [API Reference.](api-reference/search/search-memory-entries)
|
||||
You can perform other operations like search, filtering, user profiles, etc., by using other Supermemory API endpoints which can be found in our API Reference tab.
|
||||
|
|
@ -101,7 +101,11 @@ const DocumentCard = memo(
|
|||
(document.metadata?.website_og_image as string | undefined) ||
|
||||
document.ogImage
|
||||
}
|
||||
description={document.content && typeof document.content === "string" ? document.content : undefined}
|
||||
description={
|
||||
document.content && typeof document.content === "string"
|
||||
? document.content
|
||||
: undefined
|
||||
}
|
||||
onOpenDetails={() => onOpenDetails(document)}
|
||||
onDelete={() => onDelete(document)}
|
||||
/>
|
||||
|
|
@ -155,15 +159,13 @@ export const MasonryMemoryList = ({
|
|||
return documents
|
||||
}
|
||||
|
||||
return documents
|
||||
.map((doc) => ({
|
||||
...doc,
|
||||
memoryEntries: doc.memoryEntries.filter(
|
||||
(memory) =>
|
||||
(memory.spaceContainerTag ?? memory.spaceId) === selectedSpace,
|
||||
),
|
||||
}))
|
||||
.filter((doc) => doc.memoryEntries.length > 0)
|
||||
return documents.map((doc) => ({
|
||||
...doc,
|
||||
memoryEntries: doc.memoryEntries.filter(
|
||||
(memory) =>
|
||||
(memory.spaceContainerTag ?? memory.spaceId) === selectedSpace,
|
||||
),
|
||||
}))
|
||||
}, [documents, selectedSpace])
|
||||
|
||||
const handleOpenDetails = useCallback((document: DocumentWithMemories) => {
|
||||
|
|
|
|||
|
|
@ -239,15 +239,13 @@ export const MemoryListView = ({
|
|||
return documents
|
||||
}
|
||||
|
||||
return documents
|
||||
.map((doc) => ({
|
||||
...doc,
|
||||
memoryEntries: doc.memoryEntries.filter(
|
||||
(memory) =>
|
||||
(memory.spaceContainerTag ?? memory.spaceId) === selectedSpace,
|
||||
),
|
||||
}))
|
||||
.filter((doc) => doc.memoryEntries.length > 0)
|
||||
return documents.map((doc) => ({
|
||||
...doc,
|
||||
memoryEntries: doc.memoryEntries.filter(
|
||||
(memory) =>
|
||||
(memory.spaceContainerTag ?? memory.spaceId) === selectedSpace,
|
||||
),
|
||||
}))
|
||||
}, [documents, selectedSpace])
|
||||
|
||||
const handleOpenDetails = useCallback((document: DocumentWithMemories) => {
|
||||
|
|
|
|||
34
bun.lock
34
bun.lock
|
|
@ -194,9 +194,9 @@
|
|||
"dependencies": {
|
||||
"@ai-sdk/openai": "^2.0.22",
|
||||
"@ai-sdk/provider": "^2.0.0",
|
||||
"ai": "^5.0.26",
|
||||
"ai": "^5.0.113",
|
||||
"supermemory": "^3.0.0-alpha.26",
|
||||
"zod": "^4.1.4",
|
||||
"zod": "^4.1.8",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@total-typescript/tsconfig": "^1.0.4",
|
||||
|
|
@ -222,7 +222,7 @@
|
|||
},
|
||||
"packages/memory-graph": {
|
||||
"name": "@supermemory/memory-graph",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.7",
|
||||
"dependencies": {
|
||||
"@emotion/is-prop-valid": "^1.4.0",
|
||||
"@radix-ui/react-collapsible": "^1.1.12",
|
||||
|
|
@ -250,7 +250,7 @@
|
|||
},
|
||||
"packages/tools": {
|
||||
"name": "@supermemory/tools",
|
||||
"version": "1.3.13",
|
||||
"version": "1.3.60",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "^2.0.25",
|
||||
"@ai-sdk/openai": "^2.0.23",
|
||||
|
|
@ -609,7 +609,7 @@
|
|||
|
||||
"@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20251202.0", "", { "os": "win32", "cpu": "x64" }, "sha512-gzeU6eDydTi7ib+Q9DD/c0hpXtqPucnHk2tfGU03mljPObYxzMkkPGgB5qxpksFvub3y4K0ChjqYxGJB4F+j3g=="],
|
||||
|
||||
"@cloudflare/workers-types": ["@cloudflare/workers-types@4.20251219.0", "", {}, "sha512-qwuvc3ZDdCfcK9dJrBSFHOsX8kL72sypfBilzEWbbb+slB2NiggjsHeGMV2ZQiQc1zyBMQPjIvsVeE7Apxp7hw=="],
|
||||
"@cloudflare/workers-types": ["@cloudflare/workers-types@4.20251223.0", "", {}, "sha512-r7oxkFjbMcmzhIrzjXaiJlGFDmmeu3+GlwkLlZbUxVWrXHTCkvqu+DrWnNmF6xZEf9j+2/PpuKIS21J522xhJA=="],
|
||||
|
||||
"@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="],
|
||||
|
||||
|
|
@ -1457,7 +1457,7 @@
|
|||
|
||||
"@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="],
|
||||
|
||||
"@smithy/util-waiter": ["@smithy/util-waiter@4.2.5", "", { "dependencies": { "@smithy/abort-controller": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-Dbun99A3InifQdIrsXZ+QLcC0PGBPAdrl4cj1mTgJvyc9N2zf7QSxg8TBkzsCmGJdE3TLbO9ycwpY0EkWahQ/g=="],
|
||||
"@smithy/util-waiter": ["@smithy/util-waiter@2.2.0", "", { "dependencies": { "@smithy/abort-controller": "^2.2.0", "@smithy/types": "^2.12.0", "tslib": "^2.6.2" } }, "sha512-IHk53BVw6MPMi2Gsn+hCng8rFA3ZmR3Rk7GllxDUW9qFJl/hiSvskn7XldkECapQVkIg/1dHpMAxI9xSTaLLSA=="],
|
||||
|
||||
"@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="],
|
||||
|
||||
|
|
@ -4411,10 +4411,14 @@
|
|||
|
||||
"@aws-sdk/client-cloudfront/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
|
||||
|
||||
"@aws-sdk/client-cloudfront/@smithy/util-waiter": ["@smithy/util-waiter@2.2.0", "", { "dependencies": { "@smithy/abort-controller": "^2.2.0", "@smithy/types": "^2.12.0", "tslib": "^2.6.2" } }, "sha512-IHk53BVw6MPMi2Gsn+hCng8rFA3ZmR3Rk7GllxDUW9qFJl/hiSvskn7XldkECapQVkIg/1dHpMAxI9xSTaLLSA=="],
|
||||
|
||||
"@aws-sdk/client-cloudfront/fast-xml-parser": ["fast-xml-parser@4.2.5", "", { "dependencies": { "strnum": "^1.0.5" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g=="],
|
||||
|
||||
"@aws-sdk/client-dynamodb/@smithy/util-waiter": ["@smithy/util-waiter@4.2.5", "", { "dependencies": { "@smithy/abort-controller": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-Dbun99A3InifQdIrsXZ+QLcC0PGBPAdrl4cj1mTgJvyc9N2zf7QSxg8TBkzsCmGJdE3TLbO9ycwpY0EkWahQ/g=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@smithy/util-waiter": ["@smithy/util-waiter@4.2.5", "", { "dependencies": { "@smithy/abort-controller": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-Dbun99A3InifQdIrsXZ+QLcC0PGBPAdrl4cj1mTgJvyc9N2zf7QSxg8TBkzsCmGJdE3TLbO9ycwpY0EkWahQ/g=="],
|
||||
|
||||
"@aws-sdk/client-s3/@smithy/util-waiter": ["@smithy/util-waiter@4.2.5", "", { "dependencies": { "@smithy/abort-controller": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-Dbun99A3InifQdIrsXZ+QLcC0PGBPAdrl4cj1mTgJvyc9N2zf7QSxg8TBkzsCmGJdE3TLbO9ycwpY0EkWahQ/g=="],
|
||||
|
||||
"@aws-sdk/client-sts/@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@3.0.0", "", { "dependencies": { "@aws-crypto/ie11-detection": "^3.0.0", "@aws-crypto/sha256-js": "^3.0.0", "@aws-crypto/supports-web-crypto": "^3.0.0", "@aws-crypto/util": "^3.0.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@aws-sdk/util-utf8-browser": "^3.0.0", "tslib": "^1.11.1" } }, "sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ=="],
|
||||
|
||||
"@aws-sdk/client-sts/@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@3.0.0", "", { "dependencies": { "@aws-crypto/util": "^3.0.0", "@aws-sdk/types": "^3.222.0", "tslib": "^1.11.1" } }, "sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ=="],
|
||||
|
|
@ -4711,6 +4715,10 @@
|
|||
|
||||
"@shikijs/core/hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="],
|
||||
|
||||
"@smithy/util-waiter/@smithy/abort-controller": ["@smithy/abort-controller@2.2.0", "", { "dependencies": { "@smithy/types": "^2.12.0", "tslib": "^2.6.2" } }, "sha512-wRlta7GuLWpTqtFfGo+nZyOO1vEvewdNR1R4rTxpC8XU6vG/NDyrFBhwLZsqg1NUoR1noVaXJPC/7ZK47QCySw=="],
|
||||
|
||||
"@smithy/util-waiter/@smithy/types": ["@smithy/types@2.12.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-QwYgloJ0sVNBeBuBs65cIkTbfzV/Q6ZNPCJ99EICFEdJYG50nGIY/uYXp+TbsdJReIuPr0a0kXmCvren3MbRRw=="],
|
||||
|
||||
"@stoplight/better-ajv-errors/leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="],
|
||||
|
||||
"@stoplight/json/safe-stable-stringify": ["safe-stable-stringify@1.1.1", "", {}, "sha512-ERq4hUjKDbJfE4+XtZLFPCDi8Vb1JqaxAPTxWFLBx8XcAlf9Bda/ZJdVezs/NAfsMQScyIlUMx+Yeu7P7rx5jw=="],
|
||||
|
|
@ -4727,12 +4735,12 @@
|
|||
|
||||
"@stoplight/yaml/@stoplight/types": ["@stoplight/types@14.1.1", "", { "dependencies": { "@types/json-schema": "^7.0.4", "utility-types": "^3.10.0" } }, "sha512-/kjtr+0t0tjKr+heVfviO9FrU/uGLc+QNX3fHJc19xsCNYqU7lVhaXxDmEID9BZTjG+/r9pK9xP/xU02XGg65g=="],
|
||||
|
||||
"@supermemory/ai-sdk/ai": ["ai@5.0.113", "", { "dependencies": { "@ai-sdk/gateway": "2.0.21", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-26vivpSO/mzZj0k1Si2IpsFspp26ttQICHRySQiMrtWcRd5mnJMX2a8sG28vmZ38C+JUn1cWmfZrsLMxkSMw9g=="],
|
||||
|
||||
"@supermemory/ai-sdk/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"@supermemory/ai-sdk/zod": ["zod@4.1.13", "", {}, "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig=="],
|
||||
|
||||
"@supermemory/memory-graph/@supermemory/memory-graph": ["@supermemory/memory-graph@0.1.7", "", { "dependencies": { "@emotion/is-prop-valid": "^1.4.0", "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-slot": "^1.2.4", "@vanilla-extract/css": "^1.17.4", "@vanilla-extract/recipes": "^0.5.7", "@vanilla-extract/sprinkles": "^1.6.5", "lucide-react": "^0.552.0", "motion": "^12.23.24" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-5R4x4BeyX/8pq0yLxj/EefU2T48Dv1T0FRrN6SIRpBXbipYJFRLqKMBjBiDGI3EBv9hHBOJDnANnenvoxnNccg=="],
|
||||
|
||||
"@supermemory/memory-graph/lucide-react": ["lucide-react@0.552.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-g9WCjmfwqbexSnZE+2cl21PCfXOcqnGeWeMTNAOGEfpPbm/ZF4YIq77Z8qWrxbu660EKuLB4nSLggoKnCb+isw=="],
|
||||
|
||||
"@supermemory/memory-graph/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
|
@ -5241,8 +5249,6 @@
|
|||
|
||||
"@aws-sdk/client-cloudfront/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
|
||||
|
||||
"@aws-sdk/client-cloudfront/@smithy/util-waiter/@smithy/abort-controller": ["@smithy/abort-controller@2.2.0", "", { "dependencies": { "@smithy/types": "^2.12.0", "tslib": "^2.6.2" } }, "sha512-wRlta7GuLWpTqtFfGo+nZyOO1vEvewdNR1R4rTxpC8XU6vG/NDyrFBhwLZsqg1NUoR1noVaXJPC/7ZK47QCySw=="],
|
||||
|
||||
"@aws-sdk/client-cloudfront/fast-xml-parser/strnum": ["strnum@1.1.2", "", {}, "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA=="],
|
||||
|
||||
"@aws-sdk/client-sts/@aws-crypto/sha256-browser/@aws-crypto/supports-web-crypto": ["@aws-crypto/supports-web-crypto@3.0.0", "", { "dependencies": { "tslib": "^1.11.1" } }, "sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg=="],
|
||||
|
|
@ -5627,6 +5633,10 @@
|
|||
|
||||
"@stoplight/spectral-core/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
|
||||
|
||||
"@supermemory/ai-sdk/ai/@ai-sdk/gateway": ["@ai-sdk/gateway@2.0.21", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19", "@vercel/oidc": "3.0.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-BwV7DU/lAm3Xn6iyyvZdWgVxgLu3SNXzl5y57gMvkW4nGhAOV5269IrJzQwGt03bb107sa6H6uJwWxc77zXoGA=="],
|
||||
|
||||
"@supermemory/ai-sdk/ai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.19", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-W41Wc9/jbUVXVwCN/7bWa4IKe8MtxO3EyA0Hfhx6grnmiYlCvpI8neSYWFE0zScXJkgA/YK3BRybzgyiXuu6JA=="],
|
||||
|
||||
"@supermemory/tools/@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.18", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ypv1xXMsgGcNKUP+hglKqtdDuMg68nWHucPPAhIENrbFAI+xCHiqPVN8Zllxyv1TNZwGWUghPxJXU+Mqps0YRQ=="],
|
||||
|
||||
"@vanilla-extract/integration/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.0", "", { "os": "aix", "cpu": "ppc64" }, "sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A=="],
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@
|
|||
"dependencies": {
|
||||
"@ai-sdk/openai": "^2.0.22",
|
||||
"@ai-sdk/provider": "^2.0.0",
|
||||
"ai": "^5.0.26",
|
||||
"ai": "^5.0.113",
|
||||
"supermemory": "^3.0.0-alpha.26",
|
||||
"zod": "^4.1.4"
|
||||
"zod": "^4.1.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@total-typescript/tsconfig": "^1.0.4",
|
||||
|
|
@ -25,7 +25,7 @@
|
|||
},
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"types": "./dist/index-CITmF79o.d.ts",
|
||||
"exports": {
|
||||
".": "./dist/index.js",
|
||||
"./package.json": "./package.json"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@supermemory/memory-graph",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.7",
|
||||
"description": "Interactive graph visualization component for Supermemory - visualize and explore your memory connections",
|
||||
"type": "module",
|
||||
"main": "./dist/memory-graph.cjs",
|
||||
|
|
|
|||
|
|
@ -88,7 +88,6 @@ export function useGraphData(
|
|||
memoryEntries: memories,
|
||||
}
|
||||
})
|
||||
.filter((doc) => doc.memoryEntries.length > 0)
|
||||
|
||||
// Apply maxNodes limit using Option B (dynamic cap per document)
|
||||
if (maxNodes && maxNodes > 0) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# @supermemory/tools
|
||||
|
||||
Memory tools for AI SDK and OpenAI function calling with supermemory.
|
||||
Memory tools for AI SDK and OpenAI function calling with supermemory
|
||||
|
||||
This package provides supermemory tools for both AI SDK and OpenAI function calling through dedicated submodule exports, each with function-based architectures optimized for their respective use cases.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@supermemory/tools",
|
||||
"type": "module",
|
||||
"version": "1.3.13",
|
||||
"version": "1.3.60",
|
||||
"description": "Memory tools for AI SDK and OpenAI function calling with supermemory",
|
||||
"scripts": {
|
||||
"build": "tsdown",
|
||||
|
|
|
|||
100
packages/tools/src/conversations-client.ts
Normal file
100
packages/tools/src/conversations-client.ts
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
/**
|
||||
* Client for the Supermemory Conversations API
|
||||
*
|
||||
* This module provides a helper function to ingest conversations using the
|
||||
* /v4/conversations endpoint, which supports structured messages with smart
|
||||
* diffing and append detection on the backend.
|
||||
*/
|
||||
|
||||
export interface ConversationMessage {
|
||||
role: "user" | "assistant" | "system" | "tool"
|
||||
content: string | ContentPart[]
|
||||
name?: string
|
||||
tool_calls?: ToolCall[]
|
||||
tool_call_id?: string
|
||||
}
|
||||
|
||||
export interface ContentPart {
|
||||
type: "text" | "image_url"
|
||||
text?: string
|
||||
image_url?: { url: string }
|
||||
}
|
||||
|
||||
export interface ToolCall {
|
||||
id: string
|
||||
type: "function"
|
||||
function: {
|
||||
name: string
|
||||
arguments: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface AddConversationParams {
|
||||
conversationId: string
|
||||
messages: ConversationMessage[]
|
||||
containerTags?: string[]
|
||||
metadata?: Record<string, string | number | boolean>
|
||||
apiKey: string
|
||||
baseUrl?: string
|
||||
}
|
||||
|
||||
export interface AddConversationResponse {
|
||||
id: string
|
||||
conversationId: string
|
||||
status: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a conversation to Supermemory using the /v4/conversations endpoint
|
||||
*
|
||||
* This endpoint supports:
|
||||
* - Structured messages with roles (user, assistant, system, tool)
|
||||
* - Multi-modal content (text, images)
|
||||
* - Tool calls and responses
|
||||
*
|
||||
* @param params - Configuration for adding the conversation
|
||||
* @returns Promise resolving to the conversation response
|
||||
* @throws Error if the API request fails
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const response = await addConversation({
|
||||
* conversationId: "conv-123",
|
||||
* messages: [
|
||||
* { role: "user", content: "Hello!" },
|
||||
* { role: "assistant", content: "Hi there!" }
|
||||
* ],
|
||||
* containerTags: ["user-456"],
|
||||
* apiKey: process.env.SUPERMEMORY_API_KEY,
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export async function addConversation(
|
||||
params: AddConversationParams,
|
||||
): Promise<AddConversationResponse> {
|
||||
const baseUrl = params.baseUrl || "https://api.supermemory.ai"
|
||||
const url = `${baseUrl}/v4/conversations`
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
conversationId: params.conversationId,
|
||||
messages: params.messages,
|
||||
containerTags: params.containerTags,
|
||||
metadata: params.metadata,
|
||||
}),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text().catch(() => "Unknown error")
|
||||
throw new Error(
|
||||
`Failed to add conversation: ${response.status} ${response.statusText}. ${errorText}`,
|
||||
)
|
||||
}
|
||||
|
||||
return await response.json()
|
||||
}
|
||||
|
|
@ -71,6 +71,7 @@ export function withSupermemory(
|
|||
const verbose = options?.verbose ?? false
|
||||
const mode = options?.mode ?? "profile"
|
||||
const addMemory = options?.addMemory ?? "never"
|
||||
const baseUrl = options?.baseUrl
|
||||
|
||||
const openaiWithSupermemory = createOpenAIMiddleware(
|
||||
openaiClient,
|
||||
|
|
@ -80,6 +81,7 @@ export function withSupermemory(
|
|||
verbose,
|
||||
mode,
|
||||
addMemory,
|
||||
baseUrl,
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,22 @@
|
|||
import type OpenAI from "openai"
|
||||
import Supermemory from "supermemory"
|
||||
import { addConversation } from "../conversations-client"
|
||||
import { deduplicateMemories } from "../shared"
|
||||
import { createLogger, type Logger } from "../vercel/logger"
|
||||
import { convertProfileToMarkdown } from "../vercel/util"
|
||||
|
||||
const normalizeBaseUrl = (url?: string): string => {
|
||||
const defaultUrl = "https://api.supermemory.ai"
|
||||
if (!url) return defaultUrl
|
||||
return url.endsWith("/") ? url.slice(0, -1) : url
|
||||
}
|
||||
|
||||
export interface OpenAIMiddlewareOptions {
|
||||
conversationId?: string
|
||||
verbose?: boolean
|
||||
mode?: "profile" | "query" | "full"
|
||||
addMemory?: "always" | "never"
|
||||
baseUrl?: string
|
||||
}
|
||||
|
||||
interface SupermemoryProfileSearch {
|
||||
|
|
@ -78,6 +87,7 @@ const getLastUserMessage = (
|
|||
const supermemoryProfileSearch = async (
|
||||
containerTag: string,
|
||||
queryText: string,
|
||||
baseUrl: string,
|
||||
): Promise<SupermemoryProfileSearch> => {
|
||||
const payload = queryText
|
||||
? JSON.stringify({
|
||||
|
|
@ -89,7 +99,7 @@ const supermemoryProfileSearch = async (
|
|||
})
|
||||
|
||||
try {
|
||||
const response = await fetch("https://api.supermemory.ai/v4/profile", {
|
||||
const response = await fetch(`${baseUrl}/v4/profile`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
|
|
@ -147,19 +157,77 @@ const addSystemPrompt = async (
|
|||
containerTag: string,
|
||||
logger: Logger,
|
||||
mode: "profile" | "query" | "full",
|
||||
baseUrl: string,
|
||||
) => {
|
||||
const systemPromptExists = messages.some((msg) => msg.role === "system")
|
||||
|
||||
const queryText = mode !== "profile" ? getLastUserMessage(messages) : ""
|
||||
|
||||
const memories = await searchAndFormatMemories(
|
||||
queryText,
|
||||
const memoriesResponse = await supermemoryProfileSearch(
|
||||
containerTag,
|
||||
logger,
|
||||
mode,
|
||||
"chat",
|
||||
queryText,
|
||||
baseUrl,
|
||||
)
|
||||
|
||||
const memoryCountStatic = memoriesResponse.profile.static?.length || 0
|
||||
const memoryCountDynamic = memoriesResponse.profile.dynamic?.length || 0
|
||||
|
||||
logger.info("Memory search completed for chat API", {
|
||||
containerTag,
|
||||
memoryCountStatic,
|
||||
memoryCountDynamic,
|
||||
queryText:
|
||||
queryText.substring(0, 100) + (queryText.length > 100 ? "..." : ""),
|
||||
mode,
|
||||
})
|
||||
|
||||
const deduplicated = deduplicateMemories({
|
||||
static: memoriesResponse.profile.static,
|
||||
dynamic: memoriesResponse.profile.dynamic,
|
||||
searchResults: memoriesResponse.searchResults.results,
|
||||
})
|
||||
|
||||
logger.debug("Memory deduplication completed for chat API", {
|
||||
static: {
|
||||
original: memoryCountStatic,
|
||||
deduplicated: deduplicated.static.length,
|
||||
},
|
||||
dynamic: {
|
||||
original: memoryCountDynamic,
|
||||
deduplicated: deduplicated.dynamic.length,
|
||||
},
|
||||
searchResults: {
|
||||
original: memoriesResponse.searchResults.results.length,
|
||||
deduplicated: deduplicated.searchResults.length,
|
||||
},
|
||||
})
|
||||
|
||||
const profileData =
|
||||
mode !== "query"
|
||||
? convertProfileToMarkdown({
|
||||
profile: {
|
||||
static: deduplicated.static,
|
||||
dynamic: deduplicated.dynamic,
|
||||
},
|
||||
searchResults: { results: [] },
|
||||
})
|
||||
: ""
|
||||
const searchResultsMemories =
|
||||
mode !== "profile"
|
||||
? `Search results for user's recent message: \n${deduplicated.searchResults
|
||||
.map((memory) => `- ${memory}`)
|
||||
.join("\n")}`
|
||||
: ""
|
||||
|
||||
const memories = `${profileData}\n${searchResultsMemories}`.trim()
|
||||
|
||||
if (memories) {
|
||||
logger.debug("Memory content preview for chat API", {
|
||||
content: memories,
|
||||
fullLength: memories.length,
|
||||
})
|
||||
}
|
||||
|
||||
if (systemPromptExists) {
|
||||
logger.debug("Added memories to existing system prompt")
|
||||
return messages.map((msg) =>
|
||||
|
|
@ -215,11 +283,17 @@ const getConversationContent = (
|
|||
* Saves the provided content as a memory with the specified container tag and
|
||||
* optional custom ID. Logs success or failure information for debugging.
|
||||
*
|
||||
* If customId starts with "conversation:" and messages are provided, uses the
|
||||
* /v4/conversations endpoint with structured messages instead of the memories endpoint.
|
||||
*
|
||||
* @param client - SuperMemory client instance
|
||||
* @param containerTag - The container tag/identifier for the memory
|
||||
* @param content - The content to save as a memory
|
||||
* @param customId - Optional custom ID for the memory (e.g., conversation ID)
|
||||
* @param content - The content to save as a memory (used for fallback)
|
||||
* @param customId - Optional custom ID for the memory (e.g., conversation:456)
|
||||
* @param logger - Logger instance for debugging and info output
|
||||
* @param messages - Optional OpenAI messages array (for conversation endpoint)
|
||||
* @param apiKey - API key for direct conversation endpoint calls
|
||||
* @param baseUrl - Base URL for API calls
|
||||
* @returns Promise that resolves when memory is saved (or fails silently)
|
||||
*
|
||||
* @example
|
||||
|
|
@ -227,9 +301,12 @@ const getConversationContent = (
|
|||
* await addMemoryTool(
|
||||
* supermemoryClient,
|
||||
* "user-123",
|
||||
* "User prefers React with TypeScript",
|
||||
* "conversation-456",
|
||||
* logger
|
||||
* "User: Hello\n\nAssistant: Hi!",
|
||||
* "conversation:456",
|
||||
* logger,
|
||||
* messages, // OpenAI messages array
|
||||
* apiKey,
|
||||
* baseUrl
|
||||
* )
|
||||
* ```
|
||||
*/
|
||||
|
|
@ -239,8 +316,51 @@ const addMemoryTool = async (
|
|||
content: string,
|
||||
customId: string | undefined,
|
||||
logger: Logger,
|
||||
messages?: OpenAI.Chat.Completions.ChatCompletionMessageParam[],
|
||||
apiKey?: string,
|
||||
baseUrl?: string,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
if (customId && messages && apiKey) {
|
||||
const conversationId = customId.replace("conversation:", "")
|
||||
|
||||
// Convert OpenAI messages to conversation format
|
||||
const conversationMessages = messages.map((msg) => ({
|
||||
role: msg.role as "user" | "assistant" | "system" | "tool",
|
||||
content:
|
||||
typeof msg.content === "string"
|
||||
? msg.content
|
||||
: Array.isArray(msg.content)
|
||||
? msg.content
|
||||
.filter((c) => c.type === "text")
|
||||
.map((c) => ({
|
||||
type: "text" as const,
|
||||
text: (c as { type: "text"; text: string }).text,
|
||||
}))
|
||||
: "",
|
||||
...((msg as any).name && { name: (msg as any).name }),
|
||||
...((msg as any).tool_calls && { tool_calls: (msg as any).tool_calls }),
|
||||
...((msg as any).tool_call_id && { tool_call_id: (msg as any).tool_call_id }),
|
||||
}))
|
||||
|
||||
const response = await addConversation({
|
||||
conversationId,
|
||||
messages: conversationMessages,
|
||||
containerTags: [containerTag],
|
||||
apiKey,
|
||||
baseUrl,
|
||||
})
|
||||
|
||||
logger.info("Conversation saved successfully via /v4/conversations", {
|
||||
containerTag,
|
||||
conversationId,
|
||||
messageCount: messages.length,
|
||||
responseId: response.id,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Fallback to old behavior for non-conversation memories
|
||||
const response = await client.memories.add({
|
||||
content,
|
||||
containerTags: [containerTag],
|
||||
|
|
@ -293,8 +413,10 @@ export function createOpenAIMiddleware(
|
|||
options?: OpenAIMiddlewareOptions,
|
||||
) {
|
||||
const logger = createLogger(options?.verbose ?? false)
|
||||
const baseUrl = normalizeBaseUrl(options?.baseUrl)
|
||||
const client = new Supermemory({
|
||||
apiKey: process.env.SUPERMEMORY_API_KEY,
|
||||
...(baseUrl !== "https://api.supermemory.ai" ? { baseURL: baseUrl } : {}),
|
||||
})
|
||||
|
||||
const conversationId = options?.conversationId
|
||||
|
|
@ -327,6 +449,7 @@ export function createOpenAIMiddleware(
|
|||
const memoriesResponse = await supermemoryProfileSearch(
|
||||
containerTag,
|
||||
queryText,
|
||||
baseUrl,
|
||||
)
|
||||
|
||||
const memoryCountStatic = memoriesResponse.profile.static?.length || 0
|
||||
|
|
@ -341,26 +464,41 @@ export function createOpenAIMiddleware(
|
|||
mode,
|
||||
})
|
||||
|
||||
const deduplicated = deduplicateMemories({
|
||||
static: memoriesResponse.profile.static,
|
||||
dynamic: memoriesResponse.profile.dynamic,
|
||||
searchResults: memoriesResponse.searchResults.results,
|
||||
})
|
||||
|
||||
logger.debug(`Memory deduplication completed for ${context} API`, {
|
||||
static: {
|
||||
original: memoryCountStatic,
|
||||
deduplicated: deduplicated.static.length,
|
||||
},
|
||||
dynamic: {
|
||||
original: memoryCountDynamic,
|
||||
deduplicated: deduplicated.dynamic.length,
|
||||
},
|
||||
searchResults: {
|
||||
original: memoriesResponse.searchResults.results.length,
|
||||
deduplicated: deduplicated.searchResults.length,
|
||||
},
|
||||
})
|
||||
|
||||
const profileData =
|
||||
mode !== "query"
|
||||
? convertProfileToMarkdown({
|
||||
profile: {
|
||||
static: memoriesResponse.profile.static?.map((item) => item.memory),
|
||||
dynamic: memoriesResponse.profile.dynamic?.map(
|
||||
(item) => item.memory,
|
||||
),
|
||||
},
|
||||
searchResults: {
|
||||
results: memoriesResponse.searchResults.results.map((item) => ({
|
||||
memory: item.memory,
|
||||
})) as [{ memory: string }],
|
||||
static: deduplicated.static,
|
||||
dynamic: deduplicated.dynamic,
|
||||
},
|
||||
searchResults: { results: [] },
|
||||
})
|
||||
: ""
|
||||
const searchResultsMemories =
|
||||
mode !== "profile"
|
||||
? `Search results for user's ${context === "chat" ? "recent message" : "input"}: \n${memoriesResponse.searchResults.results
|
||||
.map((result) => `- ${result.memory}`)
|
||||
? `Search results for user's ${context === "chat" ? "recent message" : "input"}: \n${deduplicated.searchResults
|
||||
.map((memory) => `- ${memory}`)
|
||||
.join("\n")}`
|
||||
: ""
|
||||
|
||||
|
|
@ -380,7 +518,9 @@ export function createOpenAIMiddleware(
|
|||
params: Parameters<typeof originalResponsesCreate>[0],
|
||||
) => {
|
||||
if (!originalResponsesCreate) {
|
||||
throw new Error("Responses API is not available in this OpenAI client version")
|
||||
throw new Error(
|
||||
"Responses API is not available in this OpenAI client version",
|
||||
)
|
||||
}
|
||||
|
||||
const input = typeof params.input === "string" ? params.input : ""
|
||||
|
|
@ -399,24 +539,26 @@ export function createOpenAIMiddleware(
|
|||
const operations: Promise<any>[] = []
|
||||
|
||||
if (addMemory === "always" && input?.trim()) {
|
||||
const content = conversationId
|
||||
? `Input: ${input}`
|
||||
: input
|
||||
const content = conversationId ? `Input: ${input}` : input
|
||||
const customId = conversationId
|
||||
? `conversation:${conversationId}`
|
||||
: undefined
|
||||
|
||||
operations.push(addMemoryTool(client, containerTag, content, customId, logger))
|
||||
operations.push(
|
||||
addMemoryTool(client, containerTag, content, customId, logger),
|
||||
)
|
||||
}
|
||||
|
||||
const queryText = mode !== "profile" ? input : ""
|
||||
operations.push(searchAndFormatMemories(
|
||||
queryText,
|
||||
containerTag,
|
||||
logger,
|
||||
mode,
|
||||
"responses",
|
||||
))
|
||||
operations.push(
|
||||
searchAndFormatMemories(
|
||||
queryText,
|
||||
containerTag,
|
||||
logger,
|
||||
mode,
|
||||
"responses",
|
||||
),
|
||||
)
|
||||
|
||||
const results = await Promise.all(operations)
|
||||
const memories = results[results.length - 1] // Memory search result is always last
|
||||
|
|
@ -462,16 +604,24 @@ export function createOpenAIMiddleware(
|
|||
? `conversation:${conversationId}`
|
||||
: undefined
|
||||
|
||||
operations.push(addMemoryTool(client, containerTag, content, customId, logger))
|
||||
operations.push(
|
||||
addMemoryTool(
|
||||
client,
|
||||
containerTag,
|
||||
content,
|
||||
customId,
|
||||
logger,
|
||||
messages,
|
||||
process.env.SUPERMEMORY_API_KEY,
|
||||
baseUrl,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
operations.push(addSystemPrompt(
|
||||
messages,
|
||||
containerTag,
|
||||
logger,
|
||||
mode,
|
||||
))
|
||||
operations.push(
|
||||
addSystemPrompt(messages, containerTag, logger, mode, baseUrl),
|
||||
)
|
||||
|
||||
const results = await Promise.all(operations)
|
||||
const enhancedMessages = results[results.length - 1] // Enhanced messages result is always last
|
||||
|
|
|
|||
|
|
@ -45,3 +45,102 @@ export function getContainerTags(config?: {
|
|||
}
|
||||
return config?.containerTags ?? CONTAINER_TAG_CONSTANTS.defaultTags
|
||||
}
|
||||
|
||||
/**
|
||||
* Memory item interface representing a single memory with optional metadata
|
||||
*/
|
||||
export interface MemoryItem {
|
||||
memory: string
|
||||
metadata?: Record<string, unknown>
|
||||
}
|
||||
|
||||
/**
|
||||
* Profile data structure containing memory items from different sources
|
||||
*/
|
||||
export interface ProfileWithMemories {
|
||||
static?: Array<MemoryItem>
|
||||
dynamic?: Array<MemoryItem>
|
||||
searchResults?: Array<MemoryItem>
|
||||
}
|
||||
|
||||
/**
|
||||
* Deduplicated memory strings organized by source
|
||||
*/
|
||||
export interface DeduplicatedMemories {
|
||||
static: string[]
|
||||
dynamic: string[]
|
||||
searchResults: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Deduplicates memory items across static, dynamic, and search result sources.
|
||||
* Priority: Static > Dynamic > Search Results
|
||||
*
|
||||
* @param data - Profile data with memory items from different sources
|
||||
* @returns Deduplicated memory strings for each source
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const deduplicated = deduplicateMemories({
|
||||
* static: [{ memory: "User likes TypeScript" }],
|
||||
* dynamic: [{ memory: "User likes TypeScript" }, { memory: "User works remotely" }],
|
||||
* searchResults: [{ memory: "User prefers async/await" }]
|
||||
* });
|
||||
* // Returns:
|
||||
* // {
|
||||
* // static: ["User likes TypeScript"],
|
||||
* // dynamic: ["User works remotely"],
|
||||
* // searchResults: ["User prefers async/await"]
|
||||
* // }
|
||||
* ```
|
||||
*/
|
||||
export function deduplicateMemories(
|
||||
data: ProfileWithMemories,
|
||||
): DeduplicatedMemories {
|
||||
const staticItems = data.static ?? []
|
||||
const dynamicItems = data.dynamic ?? []
|
||||
const searchItems = data.searchResults ?? []
|
||||
|
||||
const getMemoryString = (item: MemoryItem): string | null => {
|
||||
if (!item || typeof item.memory !== "string") return null
|
||||
const trimmed = item.memory.trim()
|
||||
return trimmed.length > 0 ? trimmed : null
|
||||
}
|
||||
|
||||
const staticMemories: string[] = []
|
||||
const seenMemories = new Set<string>()
|
||||
|
||||
for (const item of staticItems) {
|
||||
const memory = getMemoryString(item)
|
||||
if (memory !== null) {
|
||||
staticMemories.push(memory)
|
||||
seenMemories.add(memory)
|
||||
}
|
||||
}
|
||||
|
||||
const dynamicMemories: string[] = []
|
||||
|
||||
for (const item of dynamicItems) {
|
||||
const memory = getMemoryString(item)
|
||||
if (memory !== null && !seenMemories.has(memory)) {
|
||||
dynamicMemories.push(memory)
|
||||
seenMemories.add(memory)
|
||||
}
|
||||
}
|
||||
|
||||
const searchMemories: string[] = []
|
||||
|
||||
for (const item of searchItems) {
|
||||
const memory = getMemoryString(item)
|
||||
if (memory !== null && !seenMemories.has(memory)) {
|
||||
searchMemories.push(memory)
|
||||
seenMemories.add(memory)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
static: staticMemories,
|
||||
dynamic: dynamicMemories,
|
||||
searchResults: searchMemories,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ interface WrapVercelLanguageModelOptions {
|
|||
mode?: "profile" | "query" | "full";
|
||||
addMemory?: "always" | "never";
|
||||
apiKey?: string;
|
||||
baseUrl?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -26,6 +27,7 @@ interface WrapVercelLanguageModelOptions {
|
|||
* @param options.mode - Optional mode for memory search: "profile", "query", or "full" (default: "profile")
|
||||
* @param options.addMemory - Optional mode for memory search: "always", "never" (default: "never")
|
||||
* @param options.apiKey - Optional Supermemory API key to use instead of the environment variable
|
||||
* @param options.baseUrl - Optional base URL for the Supermemory API (default: "https://api.supermemory.ai")
|
||||
*
|
||||
* @returns A wrapped language model that automatically includes relevant memories in prompts
|
||||
*
|
||||
|
|
@ -64,10 +66,11 @@ const wrapVercelLanguageModel = (
|
|||
const verbose = options?.verbose ?? false
|
||||
const mode = options?.mode ?? "profile"
|
||||
const addMemory = options?.addMemory ?? "never"
|
||||
const baseUrl = options?.baseUrl
|
||||
|
||||
const wrappedModel = wrapLanguageModel({
|
||||
model,
|
||||
middleware: createSupermemoryMiddleware(containerTag, providedApiKey, conversationId, verbose, mode, addMemory),
|
||||
middleware: createSupermemoryMiddleware(containerTag, providedApiKey, conversationId, verbose, mode, addMemory, baseUrl),
|
||||
})
|
||||
|
||||
return wrappedModel
|
||||
|
|
|
|||
|
|
@ -1,10 +1,18 @@
|
|||
import type { LanguageModelV2CallOptions } from "@ai-sdk/provider"
|
||||
import { deduplicateMemories } from "../shared"
|
||||
import type { Logger } from "./logger"
|
||||
import { convertProfileToMarkdown, type ProfileStructure } from "./util"
|
||||
|
||||
export const normalizeBaseUrl = (url?: string): string => {
|
||||
const defaultUrl = "https://api.supermemory.ai"
|
||||
if (!url) return defaultUrl
|
||||
return url.endsWith("/") ? url.slice(0, -1) : url
|
||||
}
|
||||
|
||||
const supermemoryProfileSearch = async (
|
||||
containerTag: string,
|
||||
queryText: string,
|
||||
baseUrl: string,
|
||||
): Promise<ProfileStructure> => {
|
||||
const payload = queryText
|
||||
? JSON.stringify({
|
||||
|
|
@ -16,7 +24,7 @@ const supermemoryProfileSearch = async (
|
|||
})
|
||||
|
||||
try {
|
||||
const response = await fetch("https://api.supermemory.ai/v4/profile", {
|
||||
const response = await fetch(`${baseUrl}/v4/profile`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
|
|
@ -46,6 +54,7 @@ export const addSystemPrompt = async (
|
|||
containerTag: string,
|
||||
logger: Logger,
|
||||
mode: "profile" | "query" | "full",
|
||||
baseUrl = "https://api.supermemory.ai",
|
||||
) => {
|
||||
const systemPromptExists = params.prompt.some(
|
||||
(prompt) => prompt.role === "system",
|
||||
|
|
@ -65,6 +74,7 @@ export const addSystemPrompt = async (
|
|||
const memoriesResponse = await supermemoryProfileSearch(
|
||||
containerTag,
|
||||
queryText,
|
||||
baseUrl,
|
||||
)
|
||||
|
||||
const memoryCountStatic = memoriesResponse.profile.static?.length || 0
|
||||
|
|
@ -79,12 +89,41 @@ export const addSystemPrompt = async (
|
|||
mode,
|
||||
})
|
||||
|
||||
const deduplicated = deduplicateMemories({
|
||||
static: memoriesResponse.profile.static,
|
||||
dynamic: memoriesResponse.profile.dynamic,
|
||||
searchResults: memoriesResponse.searchResults.results,
|
||||
})
|
||||
|
||||
logger.debug("Memory deduplication completed", {
|
||||
static: {
|
||||
original: memoryCountStatic,
|
||||
deduplicated: deduplicated.static.length,
|
||||
},
|
||||
dynamic: {
|
||||
original: memoryCountDynamic,
|
||||
deduplicated: deduplicated.dynamic.length,
|
||||
},
|
||||
searchResults: {
|
||||
original: memoriesResponse.searchResults.results.length,
|
||||
deduplicated: deduplicated.searchResults.length,
|
||||
},
|
||||
})
|
||||
|
||||
const profileData =
|
||||
mode !== "query" ? convertProfileToMarkdown(memoriesResponse) : ""
|
||||
mode !== "query"
|
||||
? convertProfileToMarkdown({
|
||||
profile: {
|
||||
static: deduplicated.static,
|
||||
dynamic: deduplicated.dynamic,
|
||||
},
|
||||
searchResults: { results: [] },
|
||||
})
|
||||
: ""
|
||||
const searchResultsMemories =
|
||||
mode !== "profile"
|
||||
? `Search results for user's recent message: \n${memoriesResponse.searchResults.results
|
||||
.map((result) => `- ${result.memory}`)
|
||||
? `Search results for user's recent message: \n${deduplicated.searchResults
|
||||
.map((memory) => `- ${memory}`)
|
||||
.join("\n")}`
|
||||
: ""
|
||||
|
||||
|
|
|
|||
|
|
@ -4,13 +4,17 @@ import type {
|
|||
LanguageModelV2StreamPart,
|
||||
} from "@ai-sdk/provider"
|
||||
import Supermemory from "supermemory"
|
||||
import {
|
||||
addConversation,
|
||||
type ConversationMessage,
|
||||
} from "../conversations-client"
|
||||
import { createLogger, type Logger } from "./logger"
|
||||
import {
|
||||
type OutputContentItem,
|
||||
getLastUserMessage,
|
||||
filterOutSupermemories,
|
||||
} from "./util"
|
||||
import { addSystemPrompt } from "./memory-prompt"
|
||||
import { addSystemPrompt, normalizeBaseUrl } from "./memory-prompt"
|
||||
|
||||
const getConversationContent = (params: LanguageModelV2CallOptions) => {
|
||||
return params.prompt
|
||||
|
|
@ -31,6 +35,66 @@ const getConversationContent = (params: LanguageModelV2CallOptions) => {
|
|||
.join("\n\n")
|
||||
}
|
||||
|
||||
const convertToConversationMessages = (
|
||||
params: LanguageModelV2CallOptions,
|
||||
assistantResponseText: string,
|
||||
): ConversationMessage[] => {
|
||||
const messages: ConversationMessage[] = []
|
||||
|
||||
for (const msg of params.prompt) {
|
||||
if (typeof msg.content === "string") {
|
||||
const filteredContent = filterOutSupermemories(msg.content)
|
||||
if (filteredContent) {
|
||||
messages.push({
|
||||
role: msg.role as "user" | "assistant" | "system" | "tool",
|
||||
content: filteredContent,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
const contentParts = msg.content
|
||||
.map((c) => {
|
||||
if (c.type === "text") {
|
||||
const filteredText = filterOutSupermemories(c.text)
|
||||
if (filteredText) {
|
||||
return {
|
||||
type: "text" as const,
|
||||
text: filteredText,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (
|
||||
c.type === "file" &&
|
||||
typeof c.data === "string" &&
|
||||
c.mediaType.startsWith("image/")
|
||||
) {
|
||||
return {
|
||||
type: "image_url" as const,
|
||||
image_url: { url: c.data },
|
||||
}
|
||||
}
|
||||
return null
|
||||
})
|
||||
.filter((part) => part !== null)
|
||||
|
||||
if (contentParts.length > 0) {
|
||||
messages.push({
|
||||
role: msg.role as "user" | "assistant" | "system" | "tool",
|
||||
content: contentParts,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (assistantResponseText) {
|
||||
messages.push({
|
||||
role: "assistant",
|
||||
content: assistantResponseText,
|
||||
})
|
||||
}
|
||||
|
||||
return messages
|
||||
}
|
||||
|
||||
const addMemoryTool = async (
|
||||
client: Supermemory,
|
||||
containerTag: string,
|
||||
|
|
@ -38,21 +102,47 @@ const addMemoryTool = async (
|
|||
assistantResponseText: string,
|
||||
params: LanguageModelV2CallOptions,
|
||||
logger: Logger,
|
||||
apiKey: string,
|
||||
baseUrl: string,
|
||||
): Promise<void> => {
|
||||
const userMessage = getLastUserMessage(params)
|
||||
const content = conversationId
|
||||
? `${getConversationContent(params)} \n\n Assistant: ${assistantResponseText}`
|
||||
: `User: ${userMessage} \n\n Assistant: ${assistantResponseText}`
|
||||
const customId = conversationId ? `conversation:${conversationId}` : undefined
|
||||
|
||||
try {
|
||||
if (customId && conversationId) {
|
||||
const conversationMessages = convertToConversationMessages(
|
||||
params,
|
||||
assistantResponseText,
|
||||
)
|
||||
|
||||
const response = await addConversation({
|
||||
conversationId,
|
||||
messages: conversationMessages,
|
||||
containerTags: [containerTag],
|
||||
apiKey,
|
||||
baseUrl,
|
||||
})
|
||||
|
||||
logger.info("Conversation saved successfully via /v4/conversations", {
|
||||
containerTag,
|
||||
conversationId,
|
||||
messageCount: conversationMessages.length,
|
||||
responseId: response.id,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const userMessage = getLastUserMessage(params)
|
||||
const content = conversationId
|
||||
? `${getConversationContent(params)} \n\n Assistant: ${assistantResponseText}`
|
||||
: `User: ${userMessage} \n\n Assistant: ${assistantResponseText}`
|
||||
|
||||
const response = await client.memories.add({
|
||||
content,
|
||||
containerTags: [containerTag],
|
||||
customId,
|
||||
})
|
||||
|
||||
logger.info("Memory saved successfully", {
|
||||
logger.info("Memory saved successfully via /v3/documents", {
|
||||
containerTag,
|
||||
customId,
|
||||
content,
|
||||
|
|
@ -73,11 +163,16 @@ export const createSupermemoryMiddleware = (
|
|||
verbose = false,
|
||||
mode: "profile" | "query" | "full" = "profile",
|
||||
addMemory: "always" | "never" = "never",
|
||||
baseUrl?: string,
|
||||
): LanguageModelV2Middleware => {
|
||||
const logger = createLogger(verbose)
|
||||
const normalizedBaseUrl = normalizeBaseUrl(baseUrl)
|
||||
|
||||
const client = new Supermemory({
|
||||
apiKey,
|
||||
...(normalizedBaseUrl !== "https://api.supermemory.ai"
|
||||
? { baseURL: normalizedBaseUrl }
|
||||
: {}),
|
||||
})
|
||||
|
||||
return {
|
||||
|
|
@ -102,6 +197,7 @@ export const createSupermemoryMiddleware = (
|
|||
containerTag,
|
||||
logger,
|
||||
mode,
|
||||
normalizedBaseUrl,
|
||||
)
|
||||
return transformedParams
|
||||
},
|
||||
|
|
@ -123,6 +219,8 @@ export const createSupermemoryMiddleware = (
|
|||
assistantResponseText,
|
||||
params,
|
||||
logger,
|
||||
apiKey,
|
||||
normalizedBaseUrl,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -168,6 +266,8 @@ export const createSupermemoryMiddleware = (
|
|||
generatedText,
|
||||
params,
|
||||
logger,
|
||||
apiKey,
|
||||
normalizedBaseUrl,
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,16 +1,22 @@
|
|||
import type { LanguageModelV2CallOptions, LanguageModelV2Message } from "@ai-sdk/provider"
|
||||
|
||||
export interface ProfileStructure {
|
||||
profile: {
|
||||
static?: Array<{ memory: string; metadata?: Record<string, unknown> }>
|
||||
dynamic?: Array<{ memory: string; metadata?: Record<string, unknown> }>
|
||||
}
|
||||
searchResults: {
|
||||
results: Array<{ memory: string; metadata?: Record<string, unknown> }>
|
||||
}
|
||||
}
|
||||
|
||||
export interface ProfileMarkdownData {
|
||||
profile: {
|
||||
static?: string[]
|
||||
dynamic?: string[]
|
||||
}
|
||||
searchResults: {
|
||||
results: [
|
||||
{
|
||||
memory: string
|
||||
},
|
||||
]
|
||||
results: Array<{ memory: string }>
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -32,12 +38,11 @@ export type OutputContentItem =
|
|||
}
|
||||
|
||||
/**
|
||||
* Convert ProfileStructure to markdown
|
||||
* based on profile.static and profile.dynamic properties
|
||||
* @param data ProfileStructure
|
||||
* @returns Markdown string
|
||||
* Convert profile data to markdown format
|
||||
* @param data Profile data with string arrays for static and dynamic memories
|
||||
* @returns Markdown string with profile sections
|
||||
*/
|
||||
export function convertProfileToMarkdown(data: ProfileStructure): string {
|
||||
export function convertProfileToMarkdown(data: ProfileMarkdownData): string {
|
||||
const sections: string[] = []
|
||||
|
||||
if (data.profile.static && data.profile.static.length > 0) {
|
||||
|
|
|
|||
53
packages/tools/test-supermemory.ts
Normal file
53
packages/tools/test-supermemory.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import { OpenAI } from "openai"
|
||||
import { withSupermemory } from "./src/openai"
|
||||
|
||||
// Make sure to set these environment variables:
|
||||
// OPENAI_API_KEY=your_openai_api_key
|
||||
// SUPERMEMORY_API_KEY=your_supermemory_api_key
|
||||
|
||||
const openai = new OpenAI({
|
||||
apiKey: process.env.OPENAI_API_KEY,
|
||||
})
|
||||
|
||||
// Wrap OpenAI client with supermemory
|
||||
const openaiWithSupermemory = withSupermemory(openai, "test_user_123", {
|
||||
verbose: true, // Enable logging to see what's happening
|
||||
mode: "full", // Search both profile and query memories
|
||||
addMemory: "always", // Auto-save conversations as memories
|
||||
})
|
||||
|
||||
// async function testChatCompletion() {
|
||||
// console.log("\n=== Testing Chat Completion ===")
|
||||
// const response = await openaiWithSupermemory.chat.completions.create({
|
||||
// model: "gpt-4o-mini",
|
||||
// messages: [
|
||||
// { role: "user", content: "My favorite color is blue" },
|
||||
// ],
|
||||
// })
|
||||
|
||||
// console.log("Response:", response.choices[0]?.message.content)
|
||||
// }
|
||||
|
||||
async function testResponses() {
|
||||
console.log("\n=== Testing Responses API ===")
|
||||
const response = await openaiWithSupermemory.chat.completions.create({
|
||||
model: "gpt-4o",
|
||||
messages: [
|
||||
{ role: "user", content: "what's my favoritge color?" },
|
||||
],
|
||||
})
|
||||
|
||||
console.log("Response:", JSON.stringify(response.choices[0]?.message.content, null, 2))
|
||||
}
|
||||
|
||||
// Run tests
|
||||
async function main() {
|
||||
try {
|
||||
// await testChatCompletion()
|
||||
await testResponses()
|
||||
} catch (error) {
|
||||
console.error("Error:", error)
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
1
packages/tools/test/chatapp/.gitignore
vendored
1
packages/tools/test/chatapp/.gitignore
vendored
|
|
@ -1,5 +1,4 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ export async function POST(req: Request) {
|
|||
mode: "full",
|
||||
addMemory: "always",
|
||||
verbose: true,
|
||||
baseUrl: process.env.SUPERMEMORY_BASE_URL,
|
||||
})
|
||||
|
||||
const completion = await openaiWithSupermemory.chat.completions.create({
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ const model = withSupermemory(openai("gpt-4"), "user-123", {
|
|||
addMemory: "always",
|
||||
conversationId: "chat-session",
|
||||
verbose: true,
|
||||
baseUrl: process.env.SUPERMEMORY_BASE_URL,
|
||||
})
|
||||
|
||||
export async function POST(req: Request) {
|
||||
|
|
|
|||
|
|
@ -17,4 +17,5 @@ export default defineConfig({
|
|||
sourcemap: false,
|
||||
},
|
||||
exports: true,
|
||||
unbundle: true,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -29,19 +29,17 @@ export function useGraphData(
|
|||
const allEdges: GraphEdge[] = [];
|
||||
|
||||
// Filter documents that have memories in selected space
|
||||
const filteredDocuments = data.documents
|
||||
.map((doc) => ({
|
||||
...doc,
|
||||
memoryEntries:
|
||||
selectedSpace === "all"
|
||||
? doc.memoryEntries
|
||||
: doc.memoryEntries.filter(
|
||||
(memory) =>
|
||||
(memory.spaceContainerTag ?? memory.spaceId ?? "default") ===
|
||||
selectedSpace,
|
||||
),
|
||||
}))
|
||||
.filter((doc) => doc.memoryEntries.length > 0);
|
||||
const filteredDocuments = data.documents.map((doc) => ({
|
||||
...doc,
|
||||
memoryEntries:
|
||||
selectedSpace === "all"
|
||||
? doc.memoryEntries
|
||||
: doc.memoryEntries.filter(
|
||||
(memory) =>
|
||||
(memory.spaceContainerTag ?? memory.spaceId ?? "default") ===
|
||||
selectedSpace,
|
||||
),
|
||||
}));
|
||||
|
||||
// Group documents by space for better clustering
|
||||
const documentsBySpace = new Map<string, typeof filteredDocuments>();
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue