mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-10-26 06:04:08 +00:00
skill: rewrite supermemory skill for the v5 API and 5.0 SDKs
Namespace in the path, client.add/search/list with namespace first, documents/memories/namespaces/profiles/connectors groups, v5 error types. No v3/v4 names or routes remain in the skill.
This commit is contained in:
parent
58ef43ba5c
commit
4caf812253
7 changed files with 666 additions and 782 deletions
|
|
@ -115,17 +115,17 @@ Ready-to-use code snippets for TypeScript and Python showing the basic workflow:
|
|||
|
||||
### 2. Complete SDK Documentation
|
||||
Full reference for all SDK methods:
|
||||
- `add()` - Store memories
|
||||
- `profile()` - Retrieve user context
|
||||
- `search.memories()` - Semantic search
|
||||
- `documents.list()` - List documents
|
||||
- `documents.delete()` - Delete documents
|
||||
- `add(namespace, …)` - Store memories
|
||||
- `profile(namespace)` - Retrieve user context
|
||||
- `search(namespace, …)` - Semantic search
|
||||
- `list(namespace, "documents", …)` - List documents
|
||||
- `documents.delete(namespace, { ids })` - Delete documents
|
||||
|
||||
### 3. REST API Reference
|
||||
Complete endpoint documentation with cURL examples:
|
||||
- `POST /v3/documents` - Add documents
|
||||
- `POST /v3/search` - Search memories
|
||||
- `POST /v4/memories` - Create direct memories
|
||||
- `POST /ns/{namespace}/document` - Add documents
|
||||
- `POST /ns/{namespace}/search` - Search memories
|
||||
- `POST /ns/{namespace}/profile` - Get a user profile
|
||||
|
||||
### 4. Architecture Deep Dive
|
||||
Understand how Supermemory works:
|
||||
|
|
@ -149,10 +149,10 @@ Understand how Supermemory works:
|
|||
|
||||
The skill emphasizes:
|
||||
|
||||
- **Container Tags**: Proper isolation and organization
|
||||
- **Namespaces**: Proper isolation and organization
|
||||
- **Metadata**: Rich metadata for advanced filtering
|
||||
- **Thresholds**: Balancing precision and recall
|
||||
- **Static vs Dynamic Memories**: When to mark memories as permanent
|
||||
- **Static vs Dynamic Memories**: How profiles split long-lived facts from recent context
|
||||
- **Error Handling**: Graceful handling of API errors
|
||||
- **Integration Patterns**: Works with Vercel AI SDK, LangChain, CrewAI, etc.
|
||||
|
||||
|
|
|
|||
|
|
@ -46,25 +46,23 @@ const client = new Supermemory({
|
|||
apiKey: process.env.SUPERMEMORY_API_KEY
|
||||
});
|
||||
|
||||
// 1. Retrieve personalized context
|
||||
const context = await client.profile({
|
||||
containerTag: "user_123",
|
||||
q: "What are my preferences?"
|
||||
});
|
||||
// 1. Retrieve personalized context (profile takes no query; search separately)
|
||||
const [{ profile }, { results }] = await Promise.all([
|
||||
client.profile("user_123"),
|
||||
client.search("user_123", { query: "What are my preferences?", searchMode: "memories" }),
|
||||
]);
|
||||
|
||||
// 2. Enrich your prompt with context
|
||||
const profileText = [
|
||||
...context.profile.static,
|
||||
...context.profile.dynamic,
|
||||
].join('\n');
|
||||
const relevantMemories = JSON.stringify(context.searchResults?.results ?? []);
|
||||
const profileText = [...profile.static, ...profile.dynamic]
|
||||
.map((m) => m.memory)
|
||||
.join('\n');
|
||||
const relevantMemories = results.map((r) => r.memory ?? r.chunk).join('\n');
|
||||
const systemMessage = `User Profile:\n${profileText}\n\nRelevant Memories:\n${relevantMemories}`;
|
||||
|
||||
// 3. Store new memories after conversation
|
||||
const conversationText = "User: I prefer dark mode.\nAssistant: I'll remember that.";
|
||||
await client.add({
|
||||
await client.add("user_123", {
|
||||
content: conversationText,
|
||||
containerTag: "user_123",
|
||||
metadata: { type: "conversation" }
|
||||
});
|
||||
```
|
||||
|
|
@ -77,26 +75,20 @@ from supermemory import Supermemory
|
|||
|
||||
client = Supermemory(api_key=os.environ["SUPERMEMORY_API_KEY"])
|
||||
|
||||
# Retrieve context
|
||||
context = client.profile(
|
||||
container_tag="user_123",
|
||||
q="What are my preferences?"
|
||||
)
|
||||
# Retrieve context: the namespace comes first, then keyword arguments
|
||||
context = client.profile("user_123")
|
||||
search = client.search("user_123", query="What are my preferences?", search_mode="memories")
|
||||
|
||||
profile_text = "\n".join(
|
||||
(context.profile.static or []) + (context.profile.dynamic or [])
|
||||
)
|
||||
relevant_memories = "\n".join(
|
||||
result.memory
|
||||
for result in (context.search_results.results if context.search_results else [])
|
||||
if result.memory
|
||||
m.memory for m in context.profile.static + context.profile.dynamic
|
||||
)
|
||||
relevant_memories = "\n".join(r.memory for r in search.results if r.memory)
|
||||
|
||||
# Add memories
|
||||
conversation_text = "User: I prefer dark mode.\nAssistant: I'll remember that."
|
||||
client.add(
|
||||
"user_123",
|
||||
content=conversation_text,
|
||||
container_tag="user_123",
|
||||
metadata={"type": "conversation"}
|
||||
)
|
||||
```
|
||||
|
|
@ -125,7 +117,6 @@ Supermemory builds a **living knowledge graph** rather than static document stor
|
|||
- **TypeScript/JavaScript**: `npm install supermemory` ([npm](https://www.npmjs.com/package/supermemory))
|
||||
- **Python**: `pip install supermemory` ([PyPI](https://pypi.org/project/supermemory/))
|
||||
- **TypeScript agent tools/middleware**: `npm install @supermemory/tools`
|
||||
- **Python OpenAI tools/middleware**: `pip install supermemory-openai-sdk`
|
||||
|
||||
Discover all available SDKs and community integrations at [supermemory.ai/docs](https://supermemory.ai/docs)
|
||||
3. **Set Environment Variable**: `export SUPERMEMORY_API_KEY="your_key"`
|
||||
|
|
@ -140,20 +131,22 @@ Supermemory supports two complementary integration styles:
|
|||
|
||||
| Path | When to use | Packages |
|
||||
|------|-------------|----------|
|
||||
| **Tools** | Model explicitly decides when to search, add, list, or forget | `@supermemory/tools/ai-sdk` (TypeScript), `supermemory-openai-sdk` (Python) |
|
||||
| **Middleware** | Auto-inject profile context before each request and save conversations after | `@supermemory/tools/ai-sdk` (Vercel AI SDK), `@supermemory/tools/openai` (OpenAI), `supermemory-openai-sdk` (Python) |
|
||||
| **Tools** | Model explicitly decides when to search, add, list, or forget | `@supermemory/tools/ai-sdk` (Vercel AI SDK), `@supermemory/tools/openai` (OpenAI) |
|
||||
| **Middleware** | Auto-inject profile context before each request and save conversations after | `@supermemory/tools/ai-sdk` (Vercel AI SDK), `@supermemory/tools/openai` (OpenAI) |
|
||||
|
||||
Both read and write one `namespace` (usually one per end user). Middleware also takes an `id` that keeps one conversation in one document.
|
||||
|
||||
**Tools (7 canonical operations):**
|
||||
|
||||
| Tool | Use when |
|
||||
|------|----------|
|
||||
| `searchMemories` / `search_memories` | Proactive hybrid recall before answering when user-specific context could help — not only when explicitly asked. Hybrid returns both memory entries and source-document chunks. |
|
||||
| `addMemory` / `add_memory` | Store a single generalizable fact the user stated |
|
||||
| `getProfile` / `get_profile` | Load static + dynamic profile; pass `query` to scope search results to the current topic |
|
||||
| `documentList` / `document_list` | Browse stored **source documents** (conversations, URLs, files); returns **document IDs** |
|
||||
| `documentAdd` / `document_add` | Ingest raw content (text blob, conversation transcript, URL, notes) for **background processing** — memories are extracted automatically; use for substantial content, not single facts (`addMemory`) |
|
||||
| `documentDelete` / `document_delete` | Permanently delete a source document and soft-forget memories extracted from it |
|
||||
| `memoryForget` / `memory_forget` | **Soft delete** one learned profile fact by memory ID or exact content match |
|
||||
| `searchMemories` | Proactive hybrid recall before answering when user-specific context could help — not only when explicitly asked. Hybrid returns both memory entries and source-document chunks. |
|
||||
| `addMemory` | Store a single generalizable fact the user stated |
|
||||
| `getProfile` | Load static + dynamic profile; pass `query` to also get `searchResults` for the current topic |
|
||||
| `documentList` | Browse stored **source documents** (conversations, URLs, files); returns **document IDs** |
|
||||
| `documentAdd` | Ingest raw content (text blob, conversation transcript, URL, notes) for **background processing** — memories are extracted automatically; use for substantial content, not single facts (`addMemory`) |
|
||||
| `documentDelete` | Permanently delete a source document and soft-forget memories extracted from it |
|
||||
| `memoryForget` | **Soft delete** one learned profile fact by memory ID or exact content match |
|
||||
|
||||
### Removing information — three different mechanisms
|
||||
|
||||
|
|
@ -161,11 +154,11 @@ Agents must pick the right removal path:
|
|||
|
||||
| User intent | Tool | What it removes | ID source |
|
||||
|-------------|------|-----------------|-----------|
|
||||
| "Forget that I like tea" / correct a wrong fact | `memoryForget` | One extracted profile memory (soft delete) | Memory ID from a search result that contains `memory`, or query-backed `getProfile.searchResults` / `get_profile.search_results` |
|
||||
| "Forget that I like tea" / correct a wrong fact | `memoryForget` | One extracted profile memory (soft delete) | Memory ID from a `getProfile` entry, or from a search result that contains `memory` |
|
||||
| "Delete that conversation" / remove a whole file or URL | `documentDelete` | Source document permanently; extracted memories are soft-forgotten | `documentId` from `documentList` |
|
||||
| User is vague ("forget what you know about my job") | `searchMemories` first → then `memoryForget` | Same as memoryForget | Search first, then use `memoryId` |
|
||||
|
||||
**Do not confuse IDs:** `memoryId` ≠ `documentId`. In hybrid results, only an item containing `memory` has a forgettable memory ID; an item containing `chunk` has a chunk ID. Static/dynamic profile entries are plain text, so use query-backed profile search results when you need an ID.
|
||||
**Do not confuse IDs:** `memoryId` ≠ `documentId`. In hybrid results, only an item containing `memory` has a forgettable memory ID; an item containing `chunk` has a chunk ID. Static/dynamic profile entries are `{ id, memory }`, so their `id` is a forgettable memory ID.
|
||||
|
||||
**Soft vs hard delete:** `memoryForget` hides a fact from profile/search but leaves source documents. `documentDelete` permanently removes the underlying source and soft-forgets its extracted memories.
|
||||
|
||||
|
|
@ -174,35 +167,22 @@ Agents must pick the right removal path:
|
|||
- **`search()` / `searchMemories`**: Targeted recall — use proactively before answering when memory could improve the response, not only when the user says "search" or "what do you remember". Hybrid mode returns both extracted memories and source-document chunks.
|
||||
- **`documents.*`**: Source management — list, add, or delete documents throughout their lifecycle.
|
||||
|
||||
With multiple configured container tags, `searchMemories`, `getProfile`, and `memoryForget` use the first tag because v4 memory operations are single-space. Add, list, and delete operations use the broader configured scope where supported.
|
||||
Every tool reads and writes only the one configured `namespace`. A v5 document lives in exactly one namespace, so there is no multi-namespace tool config; create one tool set per namespace if an agent needs several.
|
||||
|
||||
**TypeScript (Vercel AI SDK):**
|
||||
```typescript
|
||||
import { supermemoryTools } from "@supermemory/tools/ai-sdk"
|
||||
|
||||
const tools = supermemoryTools(process.env.SUPERMEMORY_API_KEY!, {
|
||||
containerTags: ["user_123"],
|
||||
namespace: "user_123",
|
||||
})
|
||||
```
|
||||
|
||||
The aggregate includes destructive tools. Select only the operations the agent needs, and expose `documentDelete` or `memoryForget` only when the agent is authorized to remove data.
|
||||
|
||||
**Python (OpenAI function calling):**
|
||||
```python
|
||||
import os
|
||||
**Python:** there is no v5 Python tools package yet. Call the Python SDK (`pip install supermemory`) from your own function-calling handlers.
|
||||
|
||||
from supermemory_openai import SupermemoryTools
|
||||
|
||||
tools = SupermemoryTools(
|
||||
os.environ["SUPERMEMORY_API_KEY"],
|
||||
{"container_tags": ["user_123"]},
|
||||
)
|
||||
definitions = tools.get_tool_definitions() # all 7 tools
|
||||
```
|
||||
|
||||
Filter `definitions` before passing them to a model if it should not be able to call `document_delete` or `memory_forget`.
|
||||
|
||||
**For Chatbots**: Use middleware (`withSupermemory` / `with_supermemory`) for automatic context injection, or pass tools to the model for explicit memory control
|
||||
**For Chatbots**: Use middleware (`withSupermemory`) for automatic context injection, or pass tools to the model for explicit memory control
|
||||
|
||||
**For Knowledge Bases (RAG)**: Use `add()` / `documentAdd` for text or URL ingestion, the SDK file-upload method for local files, then `searchMemories` with hybrid mode for retrieval
|
||||
|
||||
|
|
@ -220,9 +200,9 @@ Filter `definitions` before passing them to a model if it should not be able to
|
|||
|
||||
## Best Practices
|
||||
|
||||
1. **Container Tags**: Use consistent user/project IDs as containerTags for proper isolation
|
||||
1. **Namespaces**: Use one consistent user/project ID as the namespace for proper isolation; each document belongs to exactly one namespace
|
||||
2. **Metadata**: Add custom metadata for advanced filtering (source, type, timestamp)
|
||||
3. **Thresholds**: The v4 search default is `0.6`; tune it only after checking retrieval quality
|
||||
3. **Search defaults**: v5 search defaults to `searchMode: "hybrid"` and `threshold: 0.3`; set both explicitly when you need memories-only or stricter results
|
||||
4. **Batch Operations**: Use bulk endpoints for multiple documents
|
||||
|
||||
## Integration Ecosystem
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ Complete REST API documentation for Supermemory.
|
|||
https://api.supermemory.ai
|
||||
```
|
||||
|
||||
Application routes are unversioned: there is no `/v5` path prefix. [`/v5/reference`](https://api.supermemory.ai/v5/reference) is the interactive v5 reference, not an API path.
|
||||
|
||||
## Authentication
|
||||
|
||||
All requests require authentication via Bearer token in the Authorization header:
|
||||
|
|
@ -18,15 +20,25 @@ Authorization: Bearer YOUR_API_KEY
|
|||
|
||||
Get your API key at [console.supermemory.ai](https://console.supermemory.ai).
|
||||
|
||||
## Namespaces and Parameter Placement
|
||||
|
||||
Every content route is scoped to one namespace in the path: `/ns/{namespace}/...`. A document belongs to exactly one namespace; never send several namespaces in one request.
|
||||
|
||||
- `GET` options go in the query string.
|
||||
- `POST`, `PATCH`, and `PUT` options go in the JSON body (or as form fields on file uploads). List pagination (`page`, `limit`, `sort`, `order`) is the exception and stays in the query string.
|
||||
- `DELETE` options such as `moveTo` go in the query string; bulk deletes send `ids` in the JSON body.
|
||||
|
||||
Options sent in the wrong place return `400`.
|
||||
|
||||
## Endpoints
|
||||
|
||||
### POST /v3/documents
|
||||
### POST /ns/{namespace}/document
|
||||
|
||||
Add a document for processing and memory extraction.
|
||||
|
||||
**Endpoint:**
|
||||
```
|
||||
POST https://api.supermemory.ai/v3/documents
|
||||
POST https://api.supermemory.ai/ns/{namespace}/document
|
||||
```
|
||||
|
||||
**Headers:**
|
||||
|
|
@ -39,22 +51,26 @@ Content-Type: application/json
|
|||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `content` | string | Yes | The content to process. Can be a URL, text, PDF path, image, or video |
|
||||
| `containerTag` | string | No | Identifier for organizing documents (max 100 chars, alphanumeric with hyphens/underscores) |
|
||||
| `entityContext` | string | No | Context guidance for memory extraction (max 1500 chars) |
|
||||
| `customId` | string | No | Your custom identifier (max 100 chars, alphanumeric with hyphens/underscores) |
|
||||
| `content` | string | Yes | The content to process: text or a URL. Upload files with `POST /ns/{namespace}/document/file` |
|
||||
| `id` | string | No | Your stable document ID. Repeating it appends or diffs into the same document |
|
||||
| `supportingContext` | string | No | Context guidance for memory extraction |
|
||||
| `metadata` | object | No | Custom key-value pairs (strings, numbers, booleans, or string arrays) |
|
||||
| `group` | object | No | Grouping values for the document |
|
||||
| `date` | string | No | Document date |
|
||||
| `taskType` | string | No | `"memory"` extracts long-term memories; `"superrag"` indexes without memory generation |
|
||||
| `dreaming` | string | No | `"dynamic"` (default) groups related documents; `"instant"` processes each document on its own and bills one extra operation |
|
||||
|
||||
Ingest routes take no query parameters.
|
||||
|
||||
**Example Request:**
|
||||
|
||||
```bash
|
||||
curl -X POST https://api.supermemory.ai/v3/documents \
|
||||
curl -X POST https://api.supermemory.ai/ns/user_123/document \
|
||||
-H "Authorization: Bearer YOUR_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"content": "https://example.com/article",
|
||||
"containerTag": "user_123",
|
||||
"entityContext": "Technical blog post about API design",
|
||||
"supportingContext": "Technical blog post about API design",
|
||||
"metadata": {
|
||||
"source": "blog",
|
||||
"category": "technical",
|
||||
|
|
@ -72,6 +88,8 @@ curl -X POST https://api.supermemory.ai/v3/documents \
|
|||
}
|
||||
```
|
||||
|
||||
`status` is the document's processing state after the request: `queued` when new work was queued, otherwise the current state (for example `done` for an unchanged duplicate).
|
||||
|
||||
**Response (401 Unauthorized):**
|
||||
|
||||
```json
|
||||
|
|
@ -81,32 +99,36 @@ curl -X POST https://api.supermemory.ai/v3/documents \
|
|||
}
|
||||
```
|
||||
|
||||
**Response (500 Internal Server Error):**
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "Internal Server Error",
|
||||
"details": "Failed to process document"
|
||||
}
|
||||
```
|
||||
|
||||
**Processing Statuses:**
|
||||
- `unknown`: State not yet known
|
||||
- `queued`: Document awaiting processing
|
||||
- `extracting`: Content extraction in progress
|
||||
- `chunking`: Breaking into semantic segments
|
||||
- `embedding`: Generating vector embeddings
|
||||
- `indexing`: Building relationships
|
||||
- `done`: Processing complete, searchable
|
||||
- `failed`: Processing failed
|
||||
|
||||
Related document routes:
|
||||
|
||||
| Route | Purpose |
|
||||
|-------|---------|
|
||||
| `POST /ns/{namespace}/document/batch` | Add 1–600 documents: `{"documents":[{"content":"...","id":"doc_1"}]}` |
|
||||
| `POST /ns/{namespace}/document/file` | Upload a file as `multipart/form-data` (`metadata` and `group` are JSON-encoded strings) |
|
||||
| `GET /ns/{namespace}/document/{id}?include=chunks,memories` | Read a document; lifecycle fields are under `system` |
|
||||
| `PATCH /ns/{namespace}/document/{id}` | Replace content and/or update `supportingContext`, `metadata`, `group`, `date` |
|
||||
| `DELETE /ns/{namespace}/document` | Delete 1–100 documents: `{"ids":["doc_1"]}` |
|
||||
| `POST /ns/{namespace}/list/{type}` | List `documents`, `chunks`, or `memories` |
|
||||
|
||||
---
|
||||
|
||||
### POST /v4/search
|
||||
### POST /ns/{namespace}/search
|
||||
|
||||
Search memories using semantic understanding with advanced filtering.
|
||||
Search one namespace using semantic understanding with typed filtering. Search has no query-string parameters.
|
||||
|
||||
**Endpoint:**
|
||||
```
|
||||
POST https://api.supermemory.ai/v4/search
|
||||
POST https://api.supermemory.ai/ns/{namespace}/search
|
||||
```
|
||||
|
||||
**Headers:**
|
||||
|
|
@ -120,62 +142,45 @@ Content-Type: application/json
|
|||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `query` | string | Yes | The search query |
|
||||
| `containerTags` | string[] | No | Filter by container tags |
|
||||
| `chunkThreshold` | number | No | Threshold for chunk selection (0-1). 0 = least sensitive (more results), 1 = most sensitive (fewer, accurate results). Default: 0 |
|
||||
| `searchMode` | string | No | Search mode: "semantic" (default) or "hybrid" (semantic + keyword). Use "hybrid" for RAG applications for better accuracy |
|
||||
| `docId` | string | No | Search within specific document (max 255 chars) |
|
||||
| `filters` | object | No | Advanced filtering with AND/OR logic (up to 5 nesting levels) |
|
||||
| `searchMode` | string | No | `"hybrid"` (default, memories + chunks), `"memories"`, or `"chunks"` |
|
||||
| `limit` | number | No | Maximum results |
|
||||
| `threshold` | number | No | Similarity threshold (0-1). 0 = more results, 1 = fewer, more accurate results. Default: `0.3` |
|
||||
| `filter` | object | No | Typed filter with `and`/`or` groups (up to 5 nesting levels) |
|
||||
| `include` | object | No | Booleans: `documents`, `related`, `forgotten` (each default `false`) |
|
||||
| `rerank` | string | No | `"none"` (default), `"order"`, or `"aggregate"` |
|
||||
| `rewriteQuery` | boolean | No | Retrieval-oriented query rewriting. Default: `false` |
|
||||
|
||||
**Filter Types:**
|
||||
**Filter Shape:**
|
||||
|
||||
```typescript
|
||||
{
|
||||
"filters": {
|
||||
// Metadata filtering
|
||||
"metadata": {
|
||||
"key": "value"
|
||||
},
|
||||
|
||||
// Numeric comparisons
|
||||
"numeric": {
|
||||
"field": { "$gte": 4.0 } // >, <, >=, <=, =
|
||||
},
|
||||
|
||||
// Array contains
|
||||
"array_contains": {
|
||||
"tags": "value"
|
||||
},
|
||||
|
||||
// String contains
|
||||
"string_contains": {
|
||||
"content": "substring"
|
||||
},
|
||||
|
||||
// Logical operators
|
||||
"$and": [{ /* filters */ }],
|
||||
"$or": [{ /* filters */ }]
|
||||
}
|
||||
}
|
||||
type Filter =
|
||||
| { field: string; operator: "eq" | "neq"; value: string; caseSensitive?: boolean }
|
||||
| { field: string; operator: "eq" | "neq"; value: number | boolean }
|
||||
| { field: string; operator: "gt" | "gte" | "lt" | "lte"; value: number }
|
||||
| { field: string; operator: "contains" | "notContains"; value: string; caseSensitive?: boolean }
|
||||
| { field: string; operator: "arrayContains" | "arrayNotContains"; value: string }
|
||||
| { operator: "and" | "or"; operands: Filter[] };
|
||||
```
|
||||
|
||||
Fields may contain letters, numbers, `_`, `.`, and `-`. Groups allow up to 200 operands. The same `filter` works on profile and list routes.
|
||||
|
||||
**Example Request:**
|
||||
|
||||
```bash
|
||||
curl -X POST https://api.supermemory.ai/v4/search \
|
||||
curl -X POST https://api.supermemory.ai/ns/documentation/search \
|
||||
-H "Authorization: Bearer YOUR_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"query": "How do I authenticate users?",
|
||||
"searchMode": "hybrid",
|
||||
"chunkThreshold": 0.5,
|
||||
"filters": {
|
||||
"metadata": {
|
||||
"type": "documentation",
|
||||
"category": "security"
|
||||
},
|
||||
"numeric": {
|
||||
"rating": { "$gte": 4.0 }
|
||||
}
|
||||
"threshold": 0.5,
|
||||
"filter": {
|
||||
"operator": "and",
|
||||
"operands": [
|
||||
{ "field": "type", "operator": "eq", "value": "documentation" },
|
||||
{ "field": "category", "operator": "eq", "value": "security" },
|
||||
{ "field": "rating", "operator": "gte", "value": 4.0 }
|
||||
]
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
|
@ -186,32 +191,30 @@ curl -X POST https://api.supermemory.ai/v4/search \
|
|||
{
|
||||
"results": [
|
||||
{
|
||||
"content": "Authentication can be done using JWT tokens...",
|
||||
"score": 0.89,
|
||||
"docId": "doc_123",
|
||||
"id": "chunk_456",
|
||||
"chunk": "Authentication can be done using JWT tokens...",
|
||||
"metadata": {
|
||||
"type": "documentation",
|
||||
"category": "security",
|
||||
"rating": 4.5
|
||||
},
|
||||
"chunkId": "chunk_456"
|
||||
"system": { "createdAt": "...", "updatedAt": "..." }
|
||||
},
|
||||
{
|
||||
"content": "OAuth 2.0 is a standard protocol for authorization...",
|
||||
"score": 0.82,
|
||||
"docId": "doc_789",
|
||||
"metadata": {
|
||||
"type": "documentation",
|
||||
"category": "security",
|
||||
"rating": 5.0
|
||||
},
|
||||
"chunkId": "chunk_789"
|
||||
"id": "mem_789",
|
||||
"memory": "The API supports OAuth 2.0 for authorization",
|
||||
"metadata": {},
|
||||
"isLatest": true,
|
||||
"isInference": false,
|
||||
"system": { "createdAt": "...", "updatedAt": "..." }
|
||||
}
|
||||
],
|
||||
"total": 2
|
||||
"searchTime": 42
|
||||
}
|
||||
```
|
||||
|
||||
Each result carries `memory`, `chunk`, or both; branch on field presence. With `include.documents`, the source is under `result.included.document`; with `include.related`, related memories are under `result.included.related.{parents,children,siblings}`.
|
||||
|
||||
**Response (401 Unauthorized):**
|
||||
|
||||
```json
|
||||
|
|
@ -223,99 +226,80 @@ curl -X POST https://api.supermemory.ai/v4/search \
|
|||
|
||||
---
|
||||
|
||||
### POST /v4/memories
|
||||
### POST /ns/{namespace}/profile
|
||||
|
||||
Create memories directly, bypassing document ingestion. Generates embeddings and makes them immediately searchable.
|
||||
Get the maintained profile for a namespace. Profile takes no query; call search separately for query-ranked results.
|
||||
|
||||
**Endpoint:**
|
||||
```
|
||||
POST https://api.supermemory.ai/v4/memories
|
||||
```
|
||||
|
||||
**Headers:**
|
||||
```http
|
||||
Authorization: Bearer YOUR_API_KEY
|
||||
Content-Type: application/json
|
||||
POST https://api.supermemory.ai/ns/{namespace}/profile
|
||||
```
|
||||
|
||||
**Request Body:**
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `memories` | array | Yes | Array of 1-100 memory objects |
|
||||
| `memories[].content` | string | Yes | Memory text (1-10,000 chars). Preferably entity-centric (e.g., "John prefers dark mode") |
|
||||
| `memories[].isStatic` | boolean | No | Marks permanent traits like name or profession. Default: false |
|
||||
| `memories[].metadata` | object | No | Custom key-value pairs (strings, numbers, booleans, or string arrays) |
|
||||
| `containerTag` | string | Yes | Identifier for the space/container these memories belong to |
|
||||
| `filter` | object | No | Typed filter |
|
||||
| `buckets` | string[] | No | Narrow the custom bucket section (up to 50 names). Omit to return every bucket |
|
||||
|
||||
**Example Request:**
|
||||
|
||||
```bash
|
||||
curl -X POST https://api.supermemory.ai/v4/memories \
|
||||
curl -X POST https://api.supermemory.ai/ns/user_123/profile \
|
||||
-H "Authorization: Bearer YOUR_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"containerTag": "user_123",
|
||||
"memories": [
|
||||
{
|
||||
"content": "User prefers dark mode",
|
||||
"isStatic": true,
|
||||
"metadata": {
|
||||
"category": "preferences",
|
||||
"source": "settings"
|
||||
}
|
||||
},
|
||||
{
|
||||
"content": "User mentioned working on a React project yesterday",
|
||||
"isStatic": false,
|
||||
"metadata": {
|
||||
"category": "activity",
|
||||
"timestamp": "2026-02-20T15:30:00Z"
|
||||
}
|
||||
}
|
||||
]
|
||||
}'
|
||||
-d '{}'
|
||||
```
|
||||
|
||||
**Response (201 Created):**
|
||||
**Response (200 OK):**
|
||||
|
||||
```json
|
||||
{
|
||||
"documentId": "doc_abc123",
|
||||
"memories": [
|
||||
{
|
||||
"id": "mem_xyz789",
|
||||
"memory": "User prefers dark mode",
|
||||
"isStatic": true,
|
||||
"createdAt": "2026-02-21T10:00:00Z"
|
||||
},
|
||||
{
|
||||
"id": "mem_def456",
|
||||
"memory": "User mentioned working on a React project yesterday",
|
||||
"isStatic": false,
|
||||
"createdAt": "2026-02-21T10:00:00Z"
|
||||
}
|
||||
]
|
||||
"profile": {
|
||||
"static": [{ "id": "mem_1", "memory": "The user works in design" }],
|
||||
"dynamic": [{ "id": "mem_2", "memory": "The user is preparing a launch" }],
|
||||
"buckets": { "work": [{ "id": "mem_3", "memory": "Prefers concise project updates" }] }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response (400 Bad Request):**
|
||||
`static`, `dynamic`, and `buckets` are always returned. Bucket definitions live at `GET/PUT/DELETE /ns/{namespace}/profile/buckets`.
|
||||
|
||||
---
|
||||
|
||||
### DELETE /ns/{namespace}/memories
|
||||
|
||||
Forget memories by exact ID (1–500 IDs).
|
||||
|
||||
```bash
|
||||
curl -X DELETE https://api.supermemory.ai/ns/user_123/memories \
|
||||
-H "Authorization: Bearer YOUR_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"ids":["mem_1","mem_2"]}'
|
||||
```
|
||||
|
||||
To find memories by meaning, call `DELETE /ns/{namespace}/memories/semantic` with `{"query":"...","dryRun":true}`, review the matched IDs, then submit them to the exact-ID route. Both return:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "Bad Request",
|
||||
"details": "Invalid request parameters: memories array must contain 1-100 items"
|
||||
"count": 1,
|
||||
"matches": [{ "id": "mem_1", "memory": "Old address" }],
|
||||
"errors": [{ "id": "mem_2", "error": "Memory not found" }]
|
||||
}
|
||||
```
|
||||
|
||||
**Response (404 Not Found):**
|
||||
---
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "Not Found",
|
||||
"details": "Space not found for given containerTag"
|
||||
}
|
||||
```
|
||||
### Namespaces and Organization
|
||||
|
||||
| Route | Purpose |
|
||||
|-------|---------|
|
||||
| `GET /namespaces` | List namespaces with `documentCount` and `memoryCount` |
|
||||
| `GET /ns/{namespace}` | Read a namespace's `supportingContext` |
|
||||
| `PATCH /ns/{namespace}` | Set `supportingContext` (send `null` to clear) |
|
||||
| `DELETE /ns/{namespace}` | Permanently delete a namespace and its content |
|
||||
| `DELETE /ns/{namespace}?moveTo={target}` | Move content to another namespace, then remove the source (`202`, returns `operationId`) |
|
||||
| `GET/PATCH /organization` | Read or set `organizationalContext` (PATCH needs an org admin) |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -326,10 +310,12 @@ curl -X POST https://api.supermemory.ai/v4/memories \
|
|||
| Code | Meaning | Description |
|
||||
|------|---------|-------------|
|
||||
| 200 | OK | Request successful |
|
||||
| 201 | Created | Resource created successfully |
|
||||
| 400 | Bad Request | Invalid request parameters |
|
||||
| 202 | Accepted | Async operation queued (for example a namespace move) |
|
||||
| 400 | Bad Request | Invalid request parameters, or an option in the wrong place |
|
||||
| 401 | Unauthorized | Missing or invalid API key |
|
||||
| 404 | Not Found | Resource not found |
|
||||
| 403 | Forbidden | Caller lacks permission (for example a non-admin updating `/organization`) |
|
||||
| 404 | Not Found | Resource not found in this namespace |
|
||||
| 409 | Conflict | Document still processing, or a sync already running |
|
||||
| 429 | Too Many Requests | Rate limit exceeded |
|
||||
| 500 | Internal Server Error | Server error occurred |
|
||||
|
||||
|
|
@ -383,18 +369,17 @@ Check your plan details in the [console](https://console.supermemory.ai) for spe
|
|||
|
||||
## Best Practices
|
||||
|
||||
### 1. Use Idempotent IDs
|
||||
### 1. Use Stable IDs
|
||||
|
||||
Use `customId` for idempotency to prevent duplicate processing:
|
||||
Send your own `id` so repeated writes land in one document instead of creating duplicates:
|
||||
|
||||
```bash
|
||||
curl -X POST https://api.supermemory.ai/v3/documents \
|
||||
curl -X POST https://api.supermemory.ai/ns/user_123/document \
|
||||
-H "Authorization: Bearer YOUR_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"content": "Important document",
|
||||
"customId": "doc_2026_02_21_001",
|
||||
"containerTag": "user_123"
|
||||
"id": "doc_2026_02_21_001"
|
||||
}'
|
||||
```
|
||||
|
||||
|
|
@ -403,13 +388,13 @@ curl -X POST https://api.supermemory.ai/v3/documents \
|
|||
Always check status codes and handle errors gracefully:
|
||||
|
||||
```javascript
|
||||
const response = await fetch('https://api.supermemory.ai/v3/documents', {
|
||||
const response = await fetch('https://api.supermemory.ai/ns/user_123/document', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${API_KEY}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ content: "...", containerTag: "user_123" })
|
||||
body: JSON.stringify({ content: "..." })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
|
|
@ -421,18 +406,18 @@ if (!response.ok) {
|
|||
const data = await response.json();
|
||||
```
|
||||
|
||||
### 3. Use Container Tags Consistently
|
||||
### 3. Use Namespaces Consistently
|
||||
|
||||
Maintain consistent naming for container tags:
|
||||
Maintain consistent naming for namespaces:
|
||||
|
||||
```bash
|
||||
# Good
|
||||
containerTag: "user_123"
|
||||
containerTag: "user_456"
|
||||
/ns/user_123/...
|
||||
/ns/user_456/...
|
||||
|
||||
# Avoid inconsistency
|
||||
containerTag: "user_123"
|
||||
containerTag: "123" # Different format
|
||||
/ns/user_123/...
|
||||
/ns/123/... # Different format
|
||||
```
|
||||
|
||||
### 4. Rich Metadata
|
||||
|
|
@ -442,7 +427,6 @@ Add comprehensive metadata for better filtering:
|
|||
```json
|
||||
{
|
||||
"content": "Product review",
|
||||
"containerTag": "reviews",
|
||||
"metadata": {
|
||||
"product": "iPhone 15",
|
||||
"rating": 4.5,
|
||||
|
|
@ -453,14 +437,15 @@ Add comprehensive metadata for better filtering:
|
|||
}
|
||||
```
|
||||
|
||||
### 5. Optimize Search Thresholds
|
||||
### 5. Set Search Defaults Explicitly
|
||||
|
||||
Start with default (0) and adjust based on results:
|
||||
v5 defaults to `searchMode: "hybrid"` and `threshold: 0.3`. Set them explicitly and adjust based on results:
|
||||
|
||||
```json
|
||||
{
|
||||
"query": "authentication methods",
|
||||
"chunkThreshold": 0.5 // Balanced precision/recall
|
||||
"searchMode": "memories",
|
||||
"threshold": 0.5
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -470,14 +455,15 @@ For large documents, check processing status:
|
|||
|
||||
```bash
|
||||
# Add document
|
||||
curl -X POST https://api.supermemory.ai/v3/documents \
|
||||
curl -X POST https://api.supermemory.ai/ns/docs/document \
|
||||
-H "Authorization: Bearer YOUR_API_KEY" \
|
||||
-d '{ "content": "large-document.pdf", "containerTag": "docs" }'
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{ "content": "https://example.com/large-report.pdf", "id": "report_2026" }'
|
||||
|
||||
# Returns: { "id": "doc_123", "status": "queued" }
|
||||
# Returns: { "id": "report_2026", "status": "queued" }
|
||||
|
||||
# Later, list documents to check status
|
||||
curl -X GET https://api.supermemory.ai/v3/documents?containerTag=docs \
|
||||
# Later, read the document and check system.status
|
||||
curl https://api.supermemory.ai/ns/docs/document/report_2026 \
|
||||
-H "Authorization: Bearer YOUR_API_KEY"
|
||||
```
|
||||
|
||||
|
|
@ -500,12 +486,11 @@ curl -X GET https://api.supermemory.ai/v3/documents?containerTag=docs \
|
|||
### Add Text Content
|
||||
|
||||
```bash
|
||||
curl -X POST https://api.supermemory.ai/v3/documents \
|
||||
curl -X POST https://api.supermemory.ai/ns/user_123/document \
|
||||
-H "Authorization: Bearer YOUR_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"content": "User mentioned they prefer TypeScript over JavaScript for type safety",
|
||||
"containerTag": "user_123",
|
||||
"metadata": {
|
||||
"source": "chat",
|
||||
"timestamp": "2026-02-21T10:00:00Z"
|
||||
|
|
@ -516,13 +501,12 @@ curl -X POST https://api.supermemory.ai/v3/documents \
|
|||
### Add URL
|
||||
|
||||
```bash
|
||||
curl -X POST https://api.supermemory.ai/v3/documents \
|
||||
curl -X POST https://api.supermemory.ai/ns/knowledge_base/document \
|
||||
-H "Authorization: Bearer YOUR_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"content": "https://blog.example.com/best-practices",
|
||||
"containerTag": "knowledge_base",
|
||||
"entityContext": "Software development best practices article",
|
||||
"supportingContext": "Software development best practices article",
|
||||
"metadata": {
|
||||
"type": "article",
|
||||
"category": "best-practices"
|
||||
|
|
@ -533,50 +517,35 @@ curl -X POST https://api.supermemory.ai/v3/documents \
|
|||
### Search with Filters (Hybrid Mode for RAG)
|
||||
|
||||
```bash
|
||||
curl -X POST https://api.supermemory.ai/v4/search \
|
||||
curl -X POST https://api.supermemory.ai/ns/knowledge_base/search \
|
||||
-H "Authorization: Bearer YOUR_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"query": "React performance optimization",
|
||||
"searchMode": "hybrid",
|
||||
"chunkThreshold": 0.6,
|
||||
"filters": {
|
||||
"$and": [
|
||||
{
|
||||
"metadata": {
|
||||
"type": "tutorial"
|
||||
}
|
||||
},
|
||||
{
|
||||
"numeric": {
|
||||
"rating": { "$gte": 4.0 }
|
||||
}
|
||||
}
|
||||
"threshold": 0.6,
|
||||
"filter": {
|
||||
"operator": "and",
|
||||
"operands": [
|
||||
{ "field": "type", "operator": "eq", "value": "tutorial" },
|
||||
{ "field": "rating", "operator": "gte", "value": 4.0 }
|
||||
]
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
### Create Direct Memories
|
||||
### Add Facts With Fast Memory Formation
|
||||
|
||||
There is no direct memory write in v5; memories come from documents. Use `dreaming: "instant"` when facts must appear quickly:
|
||||
|
||||
```bash
|
||||
curl -X POST https://api.supermemory.ai/v4/memories \
|
||||
curl -X POST https://api.supermemory.ai/ns/user_789/document \
|
||||
-H "Authorization: Bearer YOUR_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"containerTag": "user_789",
|
||||
"memories": [
|
||||
{
|
||||
"content": "User name is Alice Johnson",
|
||||
"isStatic": true,
|
||||
"metadata": { "type": "profile" }
|
||||
},
|
||||
{
|
||||
"content": "Alice completed the React tutorial today",
|
||||
"isStatic": false,
|
||||
"metadata": { "type": "activity", "date": "2026-02-21" }
|
||||
}
|
||||
]
|
||||
"content": "User name is Alice Johnson. Alice completed the React tutorial today.",
|
||||
"metadata": { "type": "profile" },
|
||||
"dreaming": "instant"
|
||||
}'
|
||||
```
|
||||
|
||||
|
|
@ -588,4 +557,5 @@ Coming soon: Webhooks for document processing status updates.
|
|||
|
||||
- **API Issues**: Check [status.supermemory.ai](https://status.supermemory.ai)
|
||||
- **Documentation**: [supermemory.ai/docs](https://supermemory.ai/docs)
|
||||
- **API Reference**: [api.supermemory.ai/v5/reference](https://api.supermemory.ai/v5/reference)
|
||||
- **Console**: [console.supermemory.ai](https://console.supermemory.ai)
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ Document enters the processing queue. The system validates the content type and
|
|||
|
||||
**What happens:**
|
||||
- Content type detection (PDF, image, video, URL, text)
|
||||
- Validation of metadata and container tags
|
||||
- Validation of metadata and the namespace
|
||||
- Assignment to processing queue
|
||||
|
||||
### 2. Extracting
|
||||
|
|
@ -171,13 +171,13 @@ Processing complete. Content is now fully searchable and integrated into the kno
|
|||
|
||||
### Static vs. Dynamic Memories
|
||||
|
||||
**Static Memories** (`isStatic: true`):
|
||||
**Static Memories** (`profile.static`):
|
||||
- Permanent facts that don't change
|
||||
- Examples: name, profession, birthday
|
||||
- Not subject to temporal updates
|
||||
- High priority in retrieval
|
||||
|
||||
**Dynamic Memories** (`isStatic: false`):
|
||||
**Dynamic Memories** (`profile.dynamic`):
|
||||
- Contextual, episodic information
|
||||
- Examples: recent conversations, activities
|
||||
- Can be updated or superseded
|
||||
|
|
@ -195,10 +195,7 @@ Memory v2: "User prefers React" (isLatest: false)
|
|||
Memory v3: "User prefers React with TypeScript" (isLatest: true)
|
||||
```
|
||||
|
||||
When querying, you can choose:
|
||||
- Latest version only (default)
|
||||
- Full version history
|
||||
- Specific version
|
||||
Each search result reports `isLatest`. To see version history, read the memory with `GET /ns/{namespace}/memories/{id}?include=related`: `parents` are earlier versions and `children` are newer ones.
|
||||
|
||||
## Retrieval Mechanism
|
||||
|
||||
|
|
@ -223,7 +220,7 @@ Chunk 4: similarity = 0.12 // "Database schemas"
|
|||
|
||||
**3. Threshold Filtering**
|
||||
```javascript
|
||||
chunkThreshold: 0.5
|
||||
threshold: 0.5
|
||||
Results: [Chunk 1, Chunk 2] // Only >= 0.5
|
||||
```
|
||||
|
||||
|
|
@ -258,9 +255,9 @@ Metadata:
|
|||
Results: Semantically similar + metadata match
|
||||
```
|
||||
|
||||
## Container Tag Isolation
|
||||
## Namespace Isolation
|
||||
|
||||
Container tags create isolated "spaces" within Supermemory:
|
||||
Namespaces create isolated spaces within Supermemory. Every request names one namespace in its path (`/ns/{namespace}/...`), and a document belongs to exactly one namespace:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
|
|
@ -292,7 +289,7 @@ Container tags create isolated "spaces" within Supermemory:
|
|||
|
||||
User profiles are dynamically generated from memories:
|
||||
|
||||
**Static Profile** (from `isStatic: true` memories):
|
||||
**Static Profile** (`profile.static`):
|
||||
```
|
||||
Name: John Doe
|
||||
Role: Senior Software Engineer
|
||||
|
|
@ -300,7 +297,7 @@ Preferences: Dark mode, TypeScript, Vim keybindings
|
|||
Timezone: UTC-8 (PST)
|
||||
```
|
||||
|
||||
**Dynamic Context** (from recent memories):
|
||||
**Dynamic Context** (`profile.dynamic`):
|
||||
```
|
||||
Recent Activity:
|
||||
- Working on React project (last 3 days)
|
||||
|
|
@ -312,19 +309,17 @@ Recent Activity:
|
|||
**Combined Profile:**
|
||||
```javascript
|
||||
{
|
||||
"profile": "John Doe, Senior Software Engineer who prefers TypeScript and dark mode",
|
||||
"memories": [
|
||||
{
|
||||
"content": "Currently working on React authentication",
|
||||
"score": 0.95,
|
||||
"timestamp": "2 hours ago"
|
||||
},
|
||||
{
|
||||
"content": "Completed advanced TypeScript course",
|
||||
"score": 0.87,
|
||||
"timestamp": "yesterday"
|
||||
}
|
||||
]
|
||||
"profile": {
|
||||
"static": [
|
||||
{ "id": "mem_1", "memory": "John Doe is a Senior Software Engineer" },
|
||||
{ "id": "mem_2", "memory": "Prefers TypeScript and dark mode" }
|
||||
],
|
||||
"dynamic": [
|
||||
{ "id": "mem_3", "memory": "Currently working on React authentication" },
|
||||
{ "id": "mem_4", "memory": "Completed advanced TypeScript course" }
|
||||
],
|
||||
"buckets": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -463,8 +458,8 @@ messages = [
|
|||
┌──────────────────────────────────┐
|
||||
│ Supermemory API Layer │
|
||||
│ ┌──────────┐ ┌──────────────┐ │
|
||||
│ │ /documents│ │ /search │ │
|
||||
│ │ /memories │ │ /profile │ │
|
||||
│ │ /document │ │ /search │ │
|
||||
│ │ /list │ │ /profile │ │
|
||||
│ └──────────┘ └──────────────┘ │
|
||||
└──────────┬───────────────────────┘
|
||||
↓
|
||||
|
|
@ -488,7 +483,7 @@ messages = [
|
|||
```
|
||||
[PDF Document]
|
||||
↓
|
||||
[API: POST /v3/documents]
|
||||
[API: POST /ns/{namespace}/document]
|
||||
↓
|
||||
[Queue: Document ID returned, status: "queued"]
|
||||
↓
|
||||
|
|
@ -510,11 +505,11 @@ messages = [
|
|||
```
|
||||
[User Query: "authentication methods"]
|
||||
↓
|
||||
[API: POST /v4/search]
|
||||
[API: POST /ns/{namespace}/search]
|
||||
↓
|
||||
[Embed: Query → Vector]
|
||||
↓
|
||||
[Search: Vector similarity in container]
|
||||
[Search: Vector similarity in namespace]
|
||||
↓
|
||||
[Filter: Apply metadata filters]
|
||||
↓
|
||||
|
|
@ -538,7 +533,7 @@ Supermemory is designed for scale:
|
|||
## Security & Privacy
|
||||
|
||||
- **Encryption**: AES-256 at rest, TLS 1.3 in transit
|
||||
- **Isolation**: Container tags enforce strict boundaries
|
||||
- **Isolation**: Namespaces enforce strict boundaries
|
||||
- **Access Control**: API key-based authentication
|
||||
- **Compliance**: SOC 2, GDPR compliant
|
||||
- **Data Residency**: Regional storage options
|
||||
|
|
|
|||
|
|
@ -23,8 +23,6 @@ npm install supermemory
|
|||
### Python
|
||||
```bash
|
||||
pip install supermemory
|
||||
# Or for async support with aiohttp
|
||||
pip install 'supermemory[aiohttp]'
|
||||
```
|
||||
|
||||
📦 View on PyPI: [https://pypi.org/project/supermemory/](https://pypi.org/project/supermemory/)
|
||||
|
|
@ -58,33 +56,33 @@ const client = new Supermemory({
|
|||
});
|
||||
|
||||
async function main() {
|
||||
// 1. Retrieve context for personalization
|
||||
const response = await client.profile({
|
||||
containerTag: "user_123", // Unique user identifier
|
||||
q: "What does the user prefer?"
|
||||
});
|
||||
// 1. Retrieve context for personalization ("user_123" is the namespace)
|
||||
const { profile } = await client.profile("user_123");
|
||||
|
||||
console.log("Static Profile:", response.profile.static);
|
||||
console.log("Dynamic Profile:", response.profile.dynamic);
|
||||
if (response.searchResults) {
|
||||
console.log("Search Results:", response.searchResults.results);
|
||||
}
|
||||
console.log("Static Profile:", profile.static);
|
||||
console.log("Dynamic Profile:", profile.dynamic);
|
||||
|
||||
// Profile takes no query; search separately for query-ranked results
|
||||
const { results } = await client.search("user_123", {
|
||||
query: "What does the user prefer?",
|
||||
searchMode: "memories"
|
||||
});
|
||||
console.log("Search Results:", results);
|
||||
|
||||
// 2. Enrich your LLM prompt
|
||||
const systemMessage = `
|
||||
Static Profile:
|
||||
${response.profile.static.map(f => `- ${f}`).join('\n')}
|
||||
${profile.static.map(m => `- ${m.memory}`).join('\n')}
|
||||
|
||||
Recent Context:
|
||||
${response.profile.dynamic.map(f => `- ${f}`).join('\n')}
|
||||
${profile.dynamic.map(m => `- ${m.memory}`).join('\n')}
|
||||
`;
|
||||
|
||||
// Send systemMessage to your LLM...
|
||||
|
||||
// 3. Store new memories from the conversation
|
||||
await client.add({
|
||||
await client.add("user_123", {
|
||||
content: "User mentioned they prefer dark mode and TypeScript",
|
||||
containerTag: "user_123",
|
||||
metadata: {
|
||||
source: "chat",
|
||||
timestamp: new Date().toISOString()
|
||||
|
|
@ -99,6 +97,8 @@ main();
|
|||
|
||||
### Python Example
|
||||
|
||||
The Python SDK takes the namespace first, then keyword arguments.
|
||||
|
||||
```python
|
||||
import os
|
||||
from supermemory import Supermemory
|
||||
|
|
@ -107,19 +107,14 @@ client = Supermemory(api_key=os.environ["SUPERMEMORY_API_KEY"])
|
|||
|
||||
def main():
|
||||
# 1. Retrieve context
|
||||
response = client.profile(
|
||||
container_tag="user_123",
|
||||
q="What does the user prefer?"
|
||||
)
|
||||
response = client.profile("user_123")
|
||||
|
||||
print("Static Profile:", response["profile"]["static"])
|
||||
print("Dynamic Profile:", response["profile"]["dynamic"])
|
||||
if "searchResults" in response:
|
||||
print("Search Results:", response["searchResults"]["results"])
|
||||
print("Static Profile:", response.profile.static)
|
||||
print("Dynamic Profile:", response.profile.dynamic)
|
||||
|
||||
# 2. Enrich your LLM prompt
|
||||
static_facts = "\n".join(f"- {fact}" for fact in response["profile"]["static"])
|
||||
dynamic_facts = "\n".join(f"- {fact}" for fact in response["profile"]["dynamic"])
|
||||
static_facts = "\n".join(f"- {m.memory}" for m in response.profile.static)
|
||||
dynamic_facts = "\n".join(f"- {m.memory}" for m in response.profile.dynamic)
|
||||
|
||||
system_message = f"""
|
||||
Static Profile:
|
||||
|
|
@ -133,8 +128,8 @@ def main():
|
|||
|
||||
# 3. Store new memories
|
||||
client.add(
|
||||
"user_123",
|
||||
content="User mentioned they prefer dark mode and TypeScript",
|
||||
container_tag="user_123",
|
||||
metadata={
|
||||
"source": "chat",
|
||||
"timestamp": "2026-02-21T10:00:00Z"
|
||||
|
|
@ -147,37 +142,6 @@ if __name__ == "__main__":
|
|||
main()
|
||||
```
|
||||
|
||||
### Python Async Example
|
||||
|
||||
```python
|
||||
import os
|
||||
import asyncio
|
||||
from supermemory import AsyncSupermemory
|
||||
|
||||
async def main():
|
||||
client = AsyncSupermemory(api_key=os.environ["SUPERMEMORY_API_KEY"])
|
||||
|
||||
# 1. Retrieve context
|
||||
response = await client.profile(
|
||||
container_tag="user_123",
|
||||
q="What does the user prefer?"
|
||||
)
|
||||
|
||||
print("User facts:", response["profile"]["static"])
|
||||
|
||||
# 2. Store new memories
|
||||
await client.add(
|
||||
content="User mentioned they prefer dark mode and TypeScript",
|
||||
container_tag="user_123",
|
||||
metadata={"source": "chat"}
|
||||
)
|
||||
|
||||
print("Memory stored successfully!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## Core Workflow Pattern
|
||||
|
||||
The standard Supermemory workflow follows three steps:
|
||||
|
|
@ -188,24 +152,23 @@ The standard Supermemory workflow follows three steps:
|
|||
|
||||
This pattern ensures your AI agent has perfect recall and becomes more personalized over time.
|
||||
|
||||
## Understanding Container Tags
|
||||
## Understanding Namespaces
|
||||
|
||||
Container tags are identifiers that isolate memories:
|
||||
A namespace is the identifier that isolates memories. It goes first in every SDK call and in the URL (`/ns/{namespace}/...`):
|
||||
|
||||
- Use **user IDs** for per-user personalization: `"user_123"`
|
||||
- Use **project IDs** for project-specific context: `"project_abc"`
|
||||
- Use **session IDs** for temporary context: `"session_xyz"`
|
||||
- Use **organization IDs** for shared knowledge: `"org_acme"`
|
||||
|
||||
Memories with the same containerTag are grouped together and can be searched independently.
|
||||
A document belongs to exactly one namespace, and each request reads or writes one namespace. To read across several, run one call per namespace and merge the results.
|
||||
|
||||
## Advanced: Threshold Filtering
|
||||
|
||||
Control relevance strictness with the `threshold` parameter:
|
||||
Control relevance strictness with the `threshold` search option (v5 default `0.3`):
|
||||
|
||||
```typescript
|
||||
const context = await client.profile({
|
||||
containerTag: "user_123",
|
||||
const { results } = await client.search("user_123", {
|
||||
query: "user preferences",
|
||||
threshold: 0.7 // 0-1: higher = stricter matching
|
||||
});
|
||||
|
|
@ -227,31 +190,29 @@ const context = await client.profile({
|
|||
### Chatbot with Memory
|
||||
```typescript
|
||||
// Before generating response
|
||||
const context = await client.profile({
|
||||
containerTag: userId,
|
||||
query: userMessage
|
||||
});
|
||||
const [{ profile }, { results }] = await Promise.all([
|
||||
client.profile(userId),
|
||||
client.search(userId, { query: userMessage, searchMode: "memories" })
|
||||
]);
|
||||
|
||||
// After receiving LLM response
|
||||
await client.add({
|
||||
// After receiving LLM response; a stable id keeps one conversation in one document
|
||||
await client.add(userId, {
|
||||
content: `User: ${userMessage}\nAssistant: ${llmResponse}`,
|
||||
containerTag: userId
|
||||
id: conversationId
|
||||
});
|
||||
```
|
||||
|
||||
### Document Knowledge Base
|
||||
```typescript
|
||||
// Add documents
|
||||
await client.add({
|
||||
await client.add("knowledge_base", {
|
||||
content: "https://example.com/documentation",
|
||||
containerTag: "knowledge_base",
|
||||
metadata: { type: "documentation" }
|
||||
});
|
||||
|
||||
// Search documents (use hybrid mode for RAG)
|
||||
const response = await client.search({
|
||||
q: "How do I authenticate?",
|
||||
containerTag: "knowledge_base",
|
||||
// Search documents (hybrid is the v5 default: memories + source chunks)
|
||||
const response = await client.search("knowledge_base", {
|
||||
query: "How do I authenticate?",
|
||||
searchMode: "hybrid",
|
||||
limit: 10
|
||||
});
|
||||
|
|
@ -260,10 +221,7 @@ const response = await client.search({
|
|||
### Personalized Recommendations
|
||||
```typescript
|
||||
// Get user profile
|
||||
const profile = await client.profile({
|
||||
containerTag: userId,
|
||||
query: "user interests and preferences"
|
||||
});
|
||||
const { profile } = await client.profile(userId);
|
||||
|
||||
// Use profile to personalize recommendations
|
||||
const recommendations = generateRecommendations(profile);
|
||||
|
|
@ -277,14 +235,14 @@ const recommendations = generateRecommendations(profile);
|
|||
- Verify you're using the correct key (not accidentally using a test key)
|
||||
|
||||
**No Results from Search**
|
||||
- Try lowering the `threshold` parameter
|
||||
- Ensure the containerTag matches what you used during `add()`
|
||||
- Wait 1-2 minutes after adding content for processing to complete
|
||||
- Try lowering the `threshold` option
|
||||
- Ensure the namespace matches what you used during `add()`
|
||||
- Wait for processing to finish. With the default `dreaming: "dynamic"`, a fresh namespace can show zero memories and an empty profile for several minutes; pass `dreaming: "instant"` to `add()` for quickstarts and tests
|
||||
|
||||
**Slow Processing**
|
||||
- Large PDFs (100 pages) take 1-2 minutes
|
||||
- Videos take 5-10 minutes
|
||||
- Check document status with `documents.list()`
|
||||
- Check document status with `client.list(namespace, "documents")` and read `system.status` on each item
|
||||
|
||||
## Support
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ yarn add supermemory
|
|||
# or
|
||||
pnpm add supermemory
|
||||
|
||||
# Agent tools and Vercel AI SDK middleware
|
||||
# Agent tools and Vercel AI SDK / OpenAI middleware
|
||||
npm install @supermemory/tools
|
||||
```
|
||||
|
||||
|
|
@ -23,11 +23,6 @@ npm install @supermemory/tools
|
|||
### Python
|
||||
```bash
|
||||
pip install supermemory
|
||||
# Or for async support with aiohttp
|
||||
pip install 'supermemory[aiohttp]'
|
||||
|
||||
# OpenAI function tools and middleware
|
||||
pip install supermemory-openai-sdk
|
||||
```
|
||||
|
||||
📦 View on PyPI: [https://pypi.org/project/supermemory/](https://pypi.org/project/supermemory/)
|
||||
|
|
@ -44,30 +39,30 @@ import { Supermemory } from 'supermemory';
|
|||
|
||||
const client = new Supermemory({
|
||||
apiKey: process.env.SUPERMEMORY_API_KEY, // Optional if env var is set
|
||||
baseURL: 'https://api.supermemory.ai' // Optional, defaults to this
|
||||
baseUrl: 'https://api.supermemory.ai' // Optional, defaults to this
|
||||
});
|
||||
```
|
||||
|
||||
Other client options: `timeoutInSeconds`, `headers`, and `maxRetries` (default 2).
|
||||
|
||||
### Python
|
||||
```python
|
||||
import os
|
||||
|
||||
from supermemory import Supermemory
|
||||
|
||||
# Synchronous client
|
||||
client = Supermemory(
|
||||
api_key=os.environ["SUPERMEMORY_API_KEY"], # Optional if env var is set
|
||||
base_url="https://api.supermemory.ai" # Optional, defaults to this
|
||||
)
|
||||
|
||||
# Asynchronous client
|
||||
from supermemory import AsyncSupermemory
|
||||
|
||||
async_client = AsyncSupermemory(
|
||||
api_key=os.environ["SUPERMEMORY_API_KEY"]
|
||||
)
|
||||
```
|
||||
|
||||
## Namespaces
|
||||
|
||||
Every content call is scoped to one **namespace**, passed first. A namespace is the isolation boundary (usually one per end user or project). A document belongs to exactly one namespace; to read across several, call once per namespace and merge the results.
|
||||
|
||||
In TypeScript, URL values (`namespace`, then `id` where there is one) are positional and everything else goes in one object. In Python, the namespace comes first and everything else is a keyword argument.
|
||||
|
||||
## Core Methods
|
||||
|
||||
### `add()` - Store Memories
|
||||
|
|
@ -76,35 +71,40 @@ Add content to Supermemory for processing and memory extraction.
|
|||
|
||||
#### TypeScript
|
||||
```typescript
|
||||
await client.add({
|
||||
content: string, // Required: plaintext or a URL string
|
||||
containerTag?: string, // Optional: isolation identifier
|
||||
entityContext?: string, // Optional: context for memory extraction
|
||||
customId?: string, // Optional: your custom identifier
|
||||
metadata?: Record<string, any> // Optional: custom key-value pairs
|
||||
await client.add(namespace, {
|
||||
content: string, // Required: plaintext or a URL string
|
||||
id?: string, // Optional: your stable document ID
|
||||
supportingContext?: string, // Optional: context for memory extraction
|
||||
metadata?: Record<string, any>, // Optional: custom key-value pairs
|
||||
date?: string, // Optional: document date
|
||||
taskType?: "memory" | "superrag", // Optional: "superrag" indexes without memory generation
|
||||
dreaming?: "dynamic" | "instant" // Optional: "dynamic" (default) or "instant"
|
||||
});
|
||||
|
||||
// Returns: { id: string, status: "queued" | "done" | ... }
|
||||
```
|
||||
|
||||
#### Python
|
||||
```python
|
||||
client.add(
|
||||
namespace, # Required: positional
|
||||
content=str, # Required: plaintext or a URL string
|
||||
container_tag=str, # Optional: isolation identifier
|
||||
entity_context=str, # Optional: context for memory extraction
|
||||
custom_id=str, # Optional: your custom identifier
|
||||
metadata=dict # Optional: custom key-value pairs
|
||||
)
|
||||
```
|
||||
|
||||
`add()` does not read a local file path. Upload local files with `client.documents.uploadFile({ file })` in TypeScript or `client.documents.upload_file(file=...)` in Python; `filepath` is metadata, not a file upload.
|
||||
Repeating `add()` with the same `id` appends or diffs the new content into that document; it does not replace it. Use `documents.update()` to replace content.
|
||||
|
||||
`dreaming: "instant"` makes memories appear quickly and bills one extra operation per document. Under the default `"dynamic"`, a fresh namespace can show zero memories and an empty profile for several minutes, so use `"instant"` for quickstarts and tests.
|
||||
|
||||
`add()` does not read a local file path. Upload local files with `client.documents.uploadFile(namespace, { file })`; on that multipart call, `metadata` is a JSON string.
|
||||
|
||||
#### Examples
|
||||
|
||||
**Add text content:**
|
||||
```typescript
|
||||
await client.add({
|
||||
await client.add("user_123", {
|
||||
content: "User prefers dark mode and TypeScript over JavaScript",
|
||||
containerTag: "user_123",
|
||||
metadata: {
|
||||
source: "preferences",
|
||||
timestamp: new Date().toISOString()
|
||||
|
|
@ -114,269 +114,284 @@ await client.add({
|
|||
|
||||
**Add URL for processing:**
|
||||
```typescript
|
||||
await client.add({
|
||||
await client.add("knowledge_base", {
|
||||
content: "https://example.com/blog/article",
|
||||
containerTag: "knowledge_base",
|
||||
entityContext: "technical documentation",
|
||||
supportingContext: "technical documentation",
|
||||
metadata: { type: "documentation", category: "api" }
|
||||
});
|
||||
```
|
||||
|
||||
**Add with custom ID:**
|
||||
**Add with your own ID:**
|
||||
```typescript
|
||||
await client.add({
|
||||
await client.add("project_abc", {
|
||||
content: "Project requirements document...",
|
||||
containerTag: "project_abc",
|
||||
customId: "requirements_v1",
|
||||
id: "requirements_v1",
|
||||
metadata: { version: "1.0", author: "john@example.com" }
|
||||
});
|
||||
```
|
||||
|
||||
**Add several documents at once:**
|
||||
```typescript
|
||||
await client.documents.batchAdd("project_abc", {
|
||||
documents: [
|
||||
{ content: "first", id: "doc_1" },
|
||||
{ content: "second", id: "doc_2" },
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
**Upload a file:**
|
||||
```typescript
|
||||
await client.documents.uploadFile("project_abc", {
|
||||
file,
|
||||
metadata: JSON.stringify({ source: "upload" }),
|
||||
});
|
||||
```
|
||||
|
||||
### `profile()` - Retrieve User Context
|
||||
|
||||
Get personalized context including static profile data and relevant dynamic memories.
|
||||
Get the maintained profile for a namespace: static (long-lived) and dynamic (recent) memories. Profile takes no query; call `search()` separately when you need query-ranked results.
|
||||
|
||||
#### TypeScript
|
||||
```typescript
|
||||
const response = await client.profile({
|
||||
containerTag: string, // Required: user/project identifier
|
||||
q?: string, // Optional: search query to include search results
|
||||
threshold?: number // Optional: relevance threshold (0-1, default 0.5)
|
||||
const response = await client.profile(namespace, {
|
||||
buckets?: string[], // Optional: narrow the custom bucket section (max 50 names)
|
||||
filter?: Filter // Optional: typed filter (see Metadata Filtering)
|
||||
});
|
||||
|
||||
// Returns:
|
||||
// {
|
||||
// profile: {
|
||||
// static: string[], // Array of long-lived profile facts
|
||||
// dynamic: string[] // Array of dynamic memories (recent context)
|
||||
// },
|
||||
// searchResults?: { // Only included if q parameter was provided
|
||||
// results: Array<{ // Search results
|
||||
// id: string,
|
||||
// memory?: string,
|
||||
// similarity: number,
|
||||
// metadata: object | null
|
||||
// }>,
|
||||
// total: number,
|
||||
// timing: number
|
||||
// static: Array<{ id: string, memory: string }>, // Long-lived profile facts
|
||||
// dynamic: Array<{ id: string, memory: string }>, // Recent context
|
||||
// buckets: Record<string, Array<{ id: string, memory: string }>>
|
||||
// }
|
||||
// }
|
||||
```
|
||||
|
||||
#### Python
|
||||
```python
|
||||
response = client.profile(
|
||||
container_tag=str, # Required: user/project identifier
|
||||
q=str, # Optional: search query to include search results
|
||||
threshold=float # Optional: relevance threshold (0-1, default 0.5)
|
||||
)
|
||||
response = client.profile(namespace)
|
||||
|
||||
# Returns a ProfileResponse model:
|
||||
# response.profile.static / response.profile.dynamic
|
||||
# response.search_results.results # only when q was provided
|
||||
# response.profile.static / response.profile.dynamic: lists of entries with .id and .memory
|
||||
```
|
||||
|
||||
#### Examples
|
||||
|
||||
**Get user profile:**
|
||||
```typescript
|
||||
const response = await client.profile({
|
||||
containerTag: "user_123",
|
||||
q: "What are the user's preferences and settings?"
|
||||
});
|
||||
const { profile } = await client.profile("user_123");
|
||||
|
||||
console.log(response.profile.static); // ["User John Doe", "Prefers dark mode", ...]
|
||||
console.log(response.profile.dynamic); // ["Recently mentioned...", "Last conversation..."]
|
||||
console.log(response.searchResults); // Search results for the query (if provided)
|
||||
console.log(profile.static.map(m => m.memory)); // ["User John Doe", "Prefers dark mode", ...]
|
||||
console.log(profile.dynamic.map(m => m.memory)); // ["Recently mentioned...", "Last conversation..."]
|
||||
```
|
||||
|
||||
**Profile without search (just get stored memories):**
|
||||
**Profile plus query-ranked memories:**
|
||||
```typescript
|
||||
const response = await client.profile({
|
||||
containerTag: "user_456"
|
||||
// No q parameter = only returns profile.static and profile.dynamic
|
||||
});
|
||||
|
||||
console.log(response.profile.static); // All static facts
|
||||
console.log(response.profile.dynamic); // Recent dynamic memories
|
||||
// response.searchResults will be undefined
|
||||
const [{ profile }, { results }] = await Promise.all([
|
||||
client.profile("user_456"),
|
||||
client.search("user_456", {
|
||||
query: "What are the user's preferences and settings?",
|
||||
searchMode: "memories"
|
||||
})
|
||||
]);
|
||||
```
|
||||
|
||||
Every profile entry carries an `id`, so it can be passed to `memories.forget()`.
|
||||
|
||||
### `search()` - Semantic Search
|
||||
|
||||
Search across memories using semantic retrieval. `client.search()` is the current TypeScript v4 call. Python uses `client.search.memories()` for the same v4 endpoint; the TypeScript `client.search.documents()` method is the legacy v3 document response.
|
||||
Search one namespace. Every option goes in one object; omitting `searchMode` gives `hybrid`.
|
||||
|
||||
#### TypeScript
|
||||
```typescript
|
||||
const response = await client.search({
|
||||
q: string, // Required: search query
|
||||
containerTag?: string, // Optional: filter by container tag
|
||||
limit?: number, // Optional: max results (default 10, max 100)
|
||||
threshold?: number, // Optional: similarity threshold (0-1, default 0.6)
|
||||
searchMode?: "memories" | "hybrid" | "documents", // Optional: "memories" (default), "hybrid" (memories + document chunks), or "documents" (chunks only)
|
||||
filters?: FilterObject // Optional: advanced filtering
|
||||
const response = await client.search(namespace, {
|
||||
query: string, // Required: search query
|
||||
searchMode?: "hybrid" | "memories" | "chunks", // Optional: "hybrid" (default), "memories", or "chunks"
|
||||
limit?: number, // Optional: max results
|
||||
threshold?: number, // Optional: similarity threshold (0-1, default 0.3)
|
||||
filter?: Filter, // Optional: typed filter
|
||||
include?: { documents?: boolean, related?: boolean, forgotten?: boolean },
|
||||
rerank?: "none" | "order" | "aggregate", // Optional: default "none"
|
||||
rewriteQuery?: boolean // Optional: default false
|
||||
});
|
||||
|
||||
// Returns:
|
||||
// {
|
||||
// results: Array<{
|
||||
// id: string,
|
||||
// memory?: string, // Memory content (for memory results)
|
||||
// chunk?: string, // Chunk content (for chunk results in hybrid mode)
|
||||
// metadata: object | null,
|
||||
// updatedAt: string,
|
||||
// similarity: number,
|
||||
// version?: number | null
|
||||
// memory?: string, // Present on memory results
|
||||
// chunk?: string, // Present on chunk results
|
||||
// metadata: object,
|
||||
// isLatest?: boolean,
|
||||
// isInference?: boolean,
|
||||
// system: { ... }, // Lifecycle fields
|
||||
// included?: { document?, related?: { parents, children, siblings } }
|
||||
// }>,
|
||||
// total: number,
|
||||
// timing: number // Search time in milliseconds
|
||||
// searchTime: number
|
||||
// }
|
||||
```
|
||||
|
||||
Branch on whether a result has `memory` or `chunk` instead of assuming one shape.
|
||||
|
||||
#### Python
|
||||
```python
|
||||
response = client.search.memories(
|
||||
q=str, # Required: search query
|
||||
container_tag=str, # Optional: filter by container tag
|
||||
threshold=float, # Optional: similarity threshold (0-1, default 0.6)
|
||||
limit=int, # Optional: max results (default 10, max 100)
|
||||
search_mode=str, # Optional: "memories" (default), "hybrid", or "documents"
|
||||
filters=dict # Optional: advanced filtering
|
||||
response = client.search(
|
||||
namespace,
|
||||
query=str, # Required: search query
|
||||
)
|
||||
|
||||
# Returns a SearchMemoriesResponse model:
|
||||
# response.results, response.total, response.timing
|
||||
# response.results
|
||||
```
|
||||
|
||||
#### Examples
|
||||
|
||||
**Basic semantic search:**
|
||||
```typescript
|
||||
const response = await client.search({
|
||||
q: "How do I authenticate users?",
|
||||
containerTag: "documentation",
|
||||
const { results } = await client.search("documentation", {
|
||||
query: "How do I authenticate users?",
|
||||
limit: 10
|
||||
});
|
||||
|
||||
response.results.forEach(result => {
|
||||
console.log(`Similarity: ${result.similarity}`);
|
||||
results.forEach(result => {
|
||||
console.log(`Content: ${result.memory ?? result.chunk}`);
|
||||
});
|
||||
```
|
||||
|
||||
**Hybrid search for RAG (memories + source chunks):**
|
||||
**Memories only, with v4-style strictness:**
|
||||
```typescript
|
||||
const response = await client.search({
|
||||
q: "authentication methods",
|
||||
containerTag: "docs",
|
||||
searchMode: "hybrid", // Returns both extracted memories and document chunks
|
||||
threshold: 0.3,
|
||||
const { results } = await client.search("user_123", {
|
||||
query: "What did the user decide?",
|
||||
searchMode: "memories",
|
||||
threshold: 0.6,
|
||||
limit: 10
|
||||
});
|
||||
```
|
||||
|
||||
**Source chunks only (document search):**
|
||||
```typescript
|
||||
const { results } = await client.search("docs", {
|
||||
query: "authentication methods",
|
||||
searchMode: "chunks"
|
||||
});
|
||||
```
|
||||
|
||||
**Search with metadata filters:**
|
||||
```typescript
|
||||
const response = await client.search({
|
||||
q: "authentication methods",
|
||||
containerTag: "docs",
|
||||
threshold: 0.3,
|
||||
filters: {
|
||||
AND: [
|
||||
{ key: "type", value: "tutorial" },
|
||||
{ key: "category", value: "security" }
|
||||
const { results } = await client.search("docs", {
|
||||
query: "authentication methods",
|
||||
filter: {
|
||||
operator: "and",
|
||||
operands: [
|
||||
{ field: "type", operator: "eq", value: "tutorial" },
|
||||
{ field: "category", operator: "eq", value: "security" }
|
||||
]
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
**Search within a filepath:**
|
||||
```typescript
|
||||
const response = await client.search({
|
||||
q: "rate limiting configuration",
|
||||
containerTag: "specific_project",
|
||||
filepath: "/docs/api.md"
|
||||
});
|
||||
```
|
||||
### `list()` - List Documents, Chunks, or Memories
|
||||
|
||||
### `documents.list()` - List Documents
|
||||
|
||||
Retrieve stored documents with optional filtering and pagination.
|
||||
Retrieve stored items in one namespace with optional filtering and pagination.
|
||||
|
||||
#### TypeScript
|
||||
```typescript
|
||||
const docs = await client.documents.list({
|
||||
containerTags?: string[], // Optional: filter by one or more containers
|
||||
limit?: number, // Optional: items per page (default 10)
|
||||
page?: number, // Optional: 1-based page number (default 1)
|
||||
includeContent?: boolean, // Optional: include source content (default false)
|
||||
sort?: "createdAt" | "updatedAt",
|
||||
order?: "asc" | "desc"
|
||||
const response = await client.list(namespace, "documents" | "chunks" | "memories", {
|
||||
page?: number, // Optional: 1-based page number (default 1)
|
||||
limit?: number, // Optional: items per page (default 10, max 100)
|
||||
sort?: "createdAt" | "updatedAt", // Optional: default "createdAt"
|
||||
order?: "asc" | "desc", // Optional: default "desc"
|
||||
filter?: Filter // Optional: typed filter
|
||||
});
|
||||
|
||||
// Returns:
|
||||
// {
|
||||
// memories: Array<{
|
||||
// id: string,
|
||||
// status: string,
|
||||
// metadata: object,
|
||||
// createdAt: string,
|
||||
// content?: string // only when includeContent=true
|
||||
// }>,
|
||||
// pagination: { currentPage, totalItems, totalPages, limit? }
|
||||
// documents: Array<{ id, metadata, system: { status, createdAt, updatedAt }, ... }>,
|
||||
// chunks: [...],
|
||||
// memories: [...],
|
||||
// pagination: { currentPage, totalPages, ... }
|
||||
// }
|
||||
```
|
||||
|
||||
#### Python
|
||||
```python
|
||||
docs = client.documents.list(
|
||||
container_tags=[str], # Optional: filter by one or more containers
|
||||
limit=int, # Optional: items per page (default 10)
|
||||
page=int, # Optional: 1-based page number (default 1)
|
||||
include_content=bool # Optional: include source content (default False)
|
||||
)
|
||||
// Only the array for the requested type is populated.
|
||||
```
|
||||
|
||||
#### Examples
|
||||
|
||||
**List all documents for a user:**
|
||||
```typescript
|
||||
const docs = await client.documents.list({
|
||||
containerTags: ["user_123"],
|
||||
limit: 50
|
||||
});
|
||||
const { documents } = await client.list("user_123", "documents", { limit: 50 });
|
||||
|
||||
docs.memories.forEach(doc => {
|
||||
console.log(`${doc.id}: ${doc.status}`);
|
||||
documents.forEach(doc => {
|
||||
console.log(`${doc.id}: ${doc.system.status}`);
|
||||
});
|
||||
```
|
||||
|
||||
There is no separate processing list: read `system.status` on each document (`queued`, `extracting`, `chunking`, `embedding`, `indexing`, `done`, `failed`).
|
||||
|
||||
**Paginated listing:**
|
||||
```typescript
|
||||
const page1 = await client.documents.list({ limit: 20, page: 1 });
|
||||
const page2 = await client.documents.list({ limit: 20, page: 2 });
|
||||
const page1 = await client.list("user_123", "documents", { limit: 20, page: 1 });
|
||||
const page2 = await client.list("user_123", "documents", { limit: 20, page: 2 });
|
||||
```
|
||||
|
||||
### `documents.delete()` - Delete Document
|
||||
**List memories:**
|
||||
```typescript
|
||||
const { memories } = await client.list("user_123", "memories");
|
||||
```
|
||||
|
||||
Permanently remove a source document. Memories extracted from that source are soft-forgotten so they no longer appear in profile or search.
|
||||
### `documents.get()` / `documents.update()`
|
||||
|
||||
```typescript
|
||||
const doc = await client.documents.get("user_123", "doc_1", {
|
||||
include: ["chunks", "memories"]
|
||||
});
|
||||
|
||||
// Replaces the canonical content and reprocesses it
|
||||
await client.documents.update("user_123", "doc_1", {
|
||||
content: "corrected source",
|
||||
metadata: { revision: 2 }
|
||||
});
|
||||
```
|
||||
|
||||
The document ID may be the Supermemory ID or your own `id`; it is resolved only inside that namespace.
|
||||
|
||||
### `documents.delete()` - Delete Documents
|
||||
|
||||
Permanently remove source documents. Memories extracted from them are soft-forgotten so they no longer appear in profile or search.
|
||||
|
||||
#### TypeScript
|
||||
```typescript
|
||||
await client.documents.delete(documentId);
|
||||
```
|
||||
|
||||
#### Python
|
||||
```python
|
||||
client.documents.delete(document_id)
|
||||
const { count, errors } = await client.documents.delete(namespace, {
|
||||
ids: string[] // 1-100 Supermemory or caller-defined IDs
|
||||
});
|
||||
```
|
||||
|
||||
#### Example
|
||||
|
||||
```typescript
|
||||
await client.documents.delete("doc_abc123");
|
||||
const { count, errors } = await client.documents.delete("user_123", {
|
||||
ids: ["doc_abc123"]
|
||||
});
|
||||
```
|
||||
|
||||
HTTP success can include partial failures; check `errors`.
|
||||
|
||||
### `memories.forget()` / `memories.forgetMatching()` - Forget Memories
|
||||
|
||||
```typescript
|
||||
// Preview memories that match by meaning (dryRun is required)
|
||||
const preview = await client.memories.forgetMatching("user_123", {
|
||||
query: "outdated home address",
|
||||
dryRun: true
|
||||
});
|
||||
|
||||
// Forget the reviewed IDs
|
||||
await client.memories.forget("user_123", {
|
||||
ids: preview.matches.map((m) => m.id)
|
||||
});
|
||||
```
|
||||
|
||||
Both return `{ count, matches, errors }`.
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Metadata Filtering
|
||||
|
|
@ -384,9 +399,8 @@ await client.documents.delete("doc_abc123");
|
|||
Add rich metadata to enable advanced filtering:
|
||||
|
||||
```typescript
|
||||
await client.add({
|
||||
await client.add("reviews", {
|
||||
content: "Product review of iPhone 15",
|
||||
containerTag: "reviews",
|
||||
metadata: {
|
||||
product: "iPhone 15",
|
||||
rating: 4.5,
|
||||
|
|
@ -395,65 +409,77 @@ await client.add({
|
|||
}
|
||||
});
|
||||
|
||||
// Search with metadata filters
|
||||
const results = await client.search({
|
||||
q: "phone reviews",
|
||||
containerTag: "reviews",
|
||||
filters: {
|
||||
AND: [
|
||||
{ key: "rating", value: "4.0", filterType: "numeric", numericOperator: ">=" },
|
||||
{ key: "verified", value: "true" },
|
||||
{ key: "tags", value: "apple", filterType: "array_contains" }
|
||||
// Search with a typed filter
|
||||
const { results } = await client.search("reviews", {
|
||||
query: "phone reviews",
|
||||
filter: {
|
||||
operator: "and",
|
||||
operands: [
|
||||
{ field: "rating", operator: "gte", value: 4.0 },
|
||||
{ field: "verified", operator: "eq", value: true },
|
||||
{ field: "tags", operator: "arrayContains", value: "apple" }
|
||||
]
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Entity Context for Better Extraction
|
||||
Filter operators: `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `contains`, `notContains`, `arrayContains`, `arrayNotContains`, and `and` / `or` groups with `operands`. String comparisons accept `caseSensitive`. `filter` is a typed object, not a JSON string, and the same shape works in `search`, `list`, and `profile`. Keys are literal: `customer.plan` is one field name, not a nested path.
|
||||
|
||||
### Supporting Context for Better Extraction
|
||||
|
||||
Provide context to guide memory extraction:
|
||||
|
||||
```typescript
|
||||
await client.add({
|
||||
await client.add("user_123", {
|
||||
content: "User mentioned preferring React over Vue",
|
||||
containerTag: "user_123",
|
||||
entityContext: "This is a conversation about frontend framework preferences"
|
||||
supportingContext: "This is a conversation about frontend framework preferences"
|
||||
});
|
||||
```
|
||||
|
||||
The `entityContext` helps Supermemory understand what type of information to extract and prioritize.
|
||||
The `supportingContext` helps Supermemory understand what type of information to extract and prioritize. A namespace can also carry its own context:
|
||||
|
||||
### Container Tag Patterns
|
||||
```typescript
|
||||
await client.namespaces.update("user_123", {
|
||||
supportingContext: "Personal assistant memory for one user"
|
||||
});
|
||||
```
|
||||
|
||||
### Namespace Patterns
|
||||
|
||||
**Per-User Isolation:**
|
||||
```typescript
|
||||
const userId = "user_123";
|
||||
await client.add({ content: "...", containerTag: userId });
|
||||
const context = await client.profile({ containerTag: userId, q: "..." });
|
||||
await client.add(userId, { content: "..." });
|
||||
const { profile } = await client.profile(userId);
|
||||
```
|
||||
|
||||
**Multi-Tenant Applications:**
|
||||
```typescript
|
||||
const orgTag = `org_${organizationId}`;
|
||||
const userTag = `org_${organizationId}_user_${userId}`;
|
||||
const orgNamespace = `org_${organizationId}`;
|
||||
const userNamespace = `org_${organizationId}_user_${userId}`;
|
||||
|
||||
// Org-wide knowledge
|
||||
await client.add({ content: "...", containerTag: orgTag });
|
||||
await client.add(orgNamespace, { content: "..." });
|
||||
|
||||
// User-specific within org
|
||||
await client.add({ content: "...", containerTag: userTag });
|
||||
await client.add(userNamespace, { content: "..." });
|
||||
```
|
||||
|
||||
**Project-Based Organization:**
|
||||
```typescript
|
||||
const projectTag = `project_${projectId}`;
|
||||
await client.add({
|
||||
const projectNamespace = `project_${projectId}`;
|
||||
await client.add(projectNamespace, {
|
||||
content: "Project requirements...",
|
||||
containerTag: projectTag,
|
||||
metadata: { type: "requirements", version: "1.0" }
|
||||
});
|
||||
```
|
||||
|
||||
**Manage namespaces:**
|
||||
```typescript
|
||||
const { namespaces } = await client.namespaces.list();
|
||||
await client.namespaces.delete("project_alpha", { moveTo: "project_archive" });
|
||||
```
|
||||
|
||||
## Integration with AI Frameworks
|
||||
|
||||
### Vercel AI SDK
|
||||
|
|
@ -468,7 +494,7 @@ import { openai } from "@ai-sdk/openai"
|
|||
import { supermemoryTools } from "@supermemory/tools/ai-sdk"
|
||||
|
||||
const allTools = supermemoryTools(process.env.SUPERMEMORY_API_KEY!, {
|
||||
containerTags: ["user_123"],
|
||||
namespace: "user_123",
|
||||
})
|
||||
|
||||
// Select the operations this agent is allowed to call.
|
||||
|
|
@ -492,7 +518,7 @@ Tools: `searchMemories`, `addMemory`, `getProfile`, `documentList`, `documentAdd
|
|||
|
||||
Use `searchMemories` for targeted hybrid recall; `getProfile` for broad static/dynamic user context; `documentList`, `documentAdd`, and `documentDelete` for source management. Hybrid search returns both extracted memories and source-document chunks.
|
||||
|
||||
If you configure multiple container tags, `searchMemories`, `getProfile`, and `memoryForget` use the first tag because v4 memory operations are single-space. Add, list, and delete operations use the broader configured scope where supported.
|
||||
Every tool reads and writes only the configured `namespace`. Create one tool set per namespace if an agent needs more than one.
|
||||
|
||||
`supermemoryTools()` includes destructive operations. Expose `documentDelete` and `memoryForget` only when the agent is authorized to remove data, and require user confirmation when appropriate. `stopWhen` allows the model to consume tool results and produce a final answer instead of stopping immediately after the first tool call.
|
||||
|
||||
|
|
@ -505,11 +531,13 @@ import { withSupermemory } from "@supermemory/tools/ai-sdk"
|
|||
import { openai } from "@ai-sdk/openai"
|
||||
|
||||
const modelWithMemory = withSupermemory(openai("gpt-4o"), {
|
||||
containerTag: "user_123",
|
||||
customId: "conversation_456",
|
||||
namespace: "user_123",
|
||||
id: "conversation_456",
|
||||
})
|
||||
```
|
||||
|
||||
`id` groups a conversation's messages into one document. For the OpenAI SDK, import `withSupermemory` from `@supermemory/tools/openai` and pass the same options.
|
||||
|
||||
#### Manual SDK integration
|
||||
|
||||
```typescript
|
||||
|
|
@ -519,17 +547,16 @@ import { generateText } from 'ai';
|
|||
|
||||
const memory = new Supermemory();
|
||||
|
||||
async function chat(userId: string, message: string) {
|
||||
async function chat(userId: string, conversationId: string, message: string) {
|
||||
// 1. Get context
|
||||
const context = await memory.profile({
|
||||
containerTag: userId,
|
||||
q: message
|
||||
});
|
||||
const profileText = [
|
||||
...context.profile.static,
|
||||
...context.profile.dynamic,
|
||||
].join('\n');
|
||||
const searchText = JSON.stringify(context.searchResults?.results ?? []);
|
||||
const [{ profile }, { results }] = await Promise.all([
|
||||
memory.profile(userId),
|
||||
memory.search(userId, { query: message, searchMode: "memories" })
|
||||
]);
|
||||
const profileText = [...profile.static, ...profile.dynamic]
|
||||
.map(m => m.memory)
|
||||
.join('\n');
|
||||
const searchText = results.map(r => r.memory ?? r.chunk).join('\n');
|
||||
|
||||
// 2. Generate response with context
|
||||
const { text } = await generateText({
|
||||
|
|
@ -538,10 +565,10 @@ async function chat(userId: string, message: string) {
|
|||
prompt: message
|
||||
});
|
||||
|
||||
// 3. Store conversation
|
||||
await memory.add({
|
||||
// 3. Store conversation; the same id keeps it in one document
|
||||
await memory.add(userId, {
|
||||
content: `User: ${message}\nAssistant: ${text}`,
|
||||
containerTag: userId
|
||||
id: conversationId
|
||||
});
|
||||
|
||||
return text;
|
||||
|
|
@ -560,23 +587,22 @@ const llm = new ChatOpenAI({ model: 'gpt-4' });
|
|||
|
||||
async function chatWithMemory(userId: string, userMessage: string) {
|
||||
// Retrieve context
|
||||
const context = await memory.profile({
|
||||
containerTag: userId,
|
||||
q: userMessage
|
||||
});
|
||||
const [{ profile }, { results }] = await Promise.all([
|
||||
memory.profile(userId),
|
||||
memory.search(userId, { query: userMessage, searchMode: "memories" })
|
||||
]);
|
||||
|
||||
// Create messages with context
|
||||
const messages = [
|
||||
new SystemMessage(`Context: ${JSON.stringify(context)}`),
|
||||
new SystemMessage(`Context: ${JSON.stringify({ profile, results })}`),
|
||||
new HumanMessage(userMessage)
|
||||
];
|
||||
|
||||
const response = await llm.invoke(messages);
|
||||
|
||||
// Store interaction
|
||||
await memory.add({
|
||||
content: `${userMessage}\n${response.content}`,
|
||||
containerTag: userId
|
||||
await memory.add(userId, {
|
||||
content: `${userMessage}\n${response.content}`
|
||||
});
|
||||
|
||||
return response.content;
|
||||
|
|
@ -593,19 +619,13 @@ memory = Supermemory()
|
|||
|
||||
def create_memory_enhanced_agent(user_id: str):
|
||||
# Get user context
|
||||
context = memory.profile(
|
||||
container_tag=user_id,
|
||||
q="user preferences and history"
|
||||
)
|
||||
context = memory.profile(user_id)
|
||||
search = memory.search(user_id, query="user preferences and history")
|
||||
|
||||
profile_text = "\n".join(
|
||||
(context.profile.static or []) + (context.profile.dynamic or [])
|
||||
)
|
||||
search_text = "\n".join(
|
||||
result.memory
|
||||
for result in (context.search_results.results if context.search_results else [])
|
||||
if result.memory
|
||||
m.memory for m in context.profile.static + context.profile.dynamic
|
||||
)
|
||||
search_text = "\n".join(r.memory for r in search.results if r.memory)
|
||||
|
||||
agent = Agent(
|
||||
role="Personal Assistant",
|
||||
|
|
@ -619,11 +639,11 @@ def create_memory_enhanced_agent(user_id: str):
|
|||
|
||||
## Best Practices
|
||||
|
||||
### 1. Consistent Container Tags
|
||||
Always use the same format for container tags:
|
||||
### 1. Consistent Namespaces
|
||||
Always use the same format for namespaces:
|
||||
```typescript
|
||||
// Good
|
||||
const tag = `user_${userId}`;
|
||||
const namespace = `user_${userId}`;
|
||||
|
||||
// Avoid inconsistency
|
||||
// Sometimes: "user_123"
|
||||
|
|
@ -633,9 +653,8 @@ const tag = `user_${userId}`;
|
|||
### 2. Rich Metadata
|
||||
Add metadata for better filtering and organization:
|
||||
```typescript
|
||||
await client.add({
|
||||
await client.add("user_123", {
|
||||
content: "...",
|
||||
containerTag: "user_123",
|
||||
metadata: {
|
||||
source: "chat",
|
||||
timestamp: new Date().toISOString(),
|
||||
|
|
@ -645,58 +664,61 @@ await client.add({
|
|||
});
|
||||
```
|
||||
|
||||
### 3. Meaningful Custom IDs
|
||||
Use custom IDs for idempotency and reference:
|
||||
### 3. Meaningful IDs
|
||||
Use your own `id` for idempotency and reference. Repeating an `id` appends to that document instead of creating a new one:
|
||||
```typescript
|
||||
await client.add({
|
||||
await client.add("feedback", {
|
||||
content: "...",
|
||||
customId: `feedback_${userId}_${Date.now()}`,
|
||||
containerTag: "feedback"
|
||||
id: `feedback_${userId}_${Date.now()}`
|
||||
});
|
||||
```
|
||||
|
||||
### 4. Appropriate Thresholds
|
||||
Start with the v4 search default (`0.6`) and adjust based on results:
|
||||
v5 search defaults to `threshold: 0.3` and `searchMode: "hybrid"`. Set them explicitly and adjust based on results:
|
||||
- **0.3-0.5**: Broader recall, good for discovery
|
||||
- **0.5-0.7**: Balanced precision and recall; `0.6` is the default
|
||||
- **0.5-0.7**: Balanced precision and recall
|
||||
- **0.7-1.0**: High precision, fewer but more relevant results
|
||||
|
||||
### 5. Error Handling
|
||||
Always handle errors gracefully:
|
||||
```typescript
|
||||
import { SupermemoryError } from 'supermemory';
|
||||
|
||||
try {
|
||||
await client.add({ content: "...", containerTag: "user_123" });
|
||||
await client.add("user_123", { content: "..." });
|
||||
} catch (error) {
|
||||
if (error.status === 401) {
|
||||
if (error instanceof SupermemoryError && error.statusCode === 401) {
|
||||
console.error("Invalid API key");
|
||||
} else if (error.status === 429) {
|
||||
} else if (error instanceof SupermemoryError && error.statusCode === 429) {
|
||||
console.error("Rate limit exceeded");
|
||||
} else {
|
||||
console.error("Failed to add memory:", error.message);
|
||||
console.error("Failed to add memory:", error);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`NotFoundError`, `UnauthorizedError`, and `ConflictError` cover common statuses; `SupermemoryTimeoutError` covers timeouts.
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
### TypeScript (camelCase)
|
||||
- `containerTag`
|
||||
- `entityContext`
|
||||
- `customId`
|
||||
- `namespace` (first positional argument)
|
||||
- `id`
|
||||
- `supportingContext`
|
||||
- `group`
|
||||
- `filter`
|
||||
- `query`
|
||||
- `threshold`
|
||||
- `q`
|
||||
|
||||
### Python (snake_case)
|
||||
- `container_tag`
|
||||
- `entity_context`
|
||||
- `custom_id`
|
||||
- `threshold`
|
||||
- `namespace` (first positional argument)
|
||||
- `content`, `metadata`, `query` as keyword arguments
|
||||
|
||||
## Performance Tips
|
||||
|
||||
1. **Batch Operations**: Add multiple documents in quick succession if needed
|
||||
1. **Batch Operations**: Use `documents.batchAdd()` for up to 600 documents per call
|
||||
2. **Async/Await**: Always use async operations to avoid blocking
|
||||
3. **Pagination**: Use `limit` and 1-based `page` for large document lists
|
||||
3. **Pagination**: Use `limit` (max 100) and 1-based `page` for large lists
|
||||
4. **Caching**: Cache profile() results for short periods if making multiple calls
|
||||
|
||||
## Support
|
||||
|
|
|
|||
|
|
@ -28,17 +28,13 @@ import { generateText } from 'ai';
|
|||
|
||||
const memory = new Supermemory();
|
||||
|
||||
async function chat(userId: string, message: string) {
|
||||
async function chat(userId: string, conversationId: string, message: string) {
|
||||
// 1. Retrieve user context
|
||||
const response = await memory.profile({
|
||||
containerTag: userId,
|
||||
q: message,
|
||||
threshold: 0.6
|
||||
});
|
||||
const { profile } = await memory.profile(userId);
|
||||
|
||||
// 2. Build system prompt with personalization
|
||||
const staticFacts = response.profile.static.map(f => `- ${f}`).join('\n');
|
||||
const dynamicFacts = response.profile.dynamic.map(f => `- ${f}`).join('\n');
|
||||
const staticFacts = profile.static.map(m => `- ${m.memory}`).join('\n');
|
||||
const dynamicFacts = profile.dynamic.map(m => `- ${m.memory}`).join('\n');
|
||||
|
||||
const systemPrompt = `
|
||||
You are a helpful assistant with perfect memory.
|
||||
|
|
@ -59,13 +55,12 @@ Use this context to provide personalized, contextually aware responses.
|
|||
prompt: message
|
||||
});
|
||||
|
||||
// 4. Store the interaction
|
||||
await memory.add({
|
||||
// 4. Store the interaction; the same id keeps the conversation in one document
|
||||
await memory.add(userId, {
|
||||
content: `User: ${message}\nAssistant: ${text}`,
|
||||
containerTag: userId,
|
||||
id: conversationId,
|
||||
metadata: {
|
||||
timestamp: new Date().toISOString(),
|
||||
messageId: crypto.randomUUID(),
|
||||
type: 'conversation'
|
||||
}
|
||||
});
|
||||
|
|
@ -74,7 +69,7 @@ Use this context to provide personalized, contextually aware responses.
|
|||
}
|
||||
|
||||
// Usage
|
||||
const response = await chat('user_123', 'What did I tell you about my preferences?');
|
||||
const response = await chat('user_123', 'conv_1', 'What did I tell you about my preferences?');
|
||||
console.log(response); // Uses stored context to answer accurately
|
||||
```
|
||||
|
||||
|
|
@ -90,15 +85,11 @@ openai_client = OpenAI()
|
|||
|
||||
def chat(user_id: str, message: str) -> str:
|
||||
# 1. Retrieve context
|
||||
response = memory.profile(
|
||||
container_tag=user_id,
|
||||
q=message,
|
||||
threshold=0.6
|
||||
)
|
||||
profile = memory.profile(user_id).profile
|
||||
|
||||
# 2. Build system prompt
|
||||
static_facts = "\n".join(f"- {fact}" for fact in response['profile']['static'])
|
||||
dynamic_facts = "\n".join(f"- {fact}" for fact in response['profile']['dynamic'])
|
||||
static_facts = "\n".join(f"- {m.memory}" for m in profile.static)
|
||||
dynamic_facts = "\n".join(f"- {m.memory}" for m in profile.dynamic)
|
||||
|
||||
system_prompt = f"""
|
||||
You are a helpful assistant with perfect memory.
|
||||
|
|
@ -124,8 +115,8 @@ Use this context to provide personalized responses.
|
|||
|
||||
# 4. Store interaction
|
||||
memory.add(
|
||||
user_id,
|
||||
content=f"User: {message}\nAssistant: {text}",
|
||||
container_tag=user_id,
|
||||
metadata={
|
||||
"timestamp": datetime.datetime.now().isoformat(),
|
||||
"type": "conversation"
|
||||
|
|
@ -165,14 +156,10 @@ interface Task {
|
|||
|
||||
async function taskAssistant(userId: string, query: string) {
|
||||
// Get task-related context
|
||||
const response = await memory.profile({
|
||||
containerTag: `${userId}_tasks`,
|
||||
q: query,
|
||||
threshold: 0.5
|
||||
});
|
||||
const { profile } = await memory.profile(`${userId}_tasks`);
|
||||
|
||||
// Build context from profile
|
||||
const tasks = response.profile.dynamic.map(f => `- ${f}`).join('\n');
|
||||
const tasks = profile.dynamic.map(m => `- ${m.memory}`).join('\n');
|
||||
|
||||
// Generate intelligent response
|
||||
const { text } = await generateText({
|
||||
|
|
@ -192,10 +179,9 @@ Help the user track, prioritize, and complete their tasks.
|
|||
}
|
||||
|
||||
async function addTask(userId: string, task: Task) {
|
||||
await memory.add({
|
||||
await memory.add(`${userId}_tasks`, {
|
||||
content: `Task: ${task.title} (Status: ${task.status}, Priority: ${task.priority})`,
|
||||
containerTag: `${userId}_tasks`,
|
||||
customId: task.id,
|
||||
id: task.id,
|
||||
metadata: {
|
||||
status: task.status,
|
||||
priority: task.priority,
|
||||
|
|
@ -205,15 +191,13 @@ async function addTask(userId: string, task: Task) {
|
|||
}
|
||||
|
||||
async function updateTask(userId: string, taskId: string, status: Task['status']) {
|
||||
// Add update (Supermemory will create relationship)
|
||||
await memory.add({
|
||||
// Same id appends the update to the task's document (Supermemory will create relationship)
|
||||
await memory.add(`${userId}_tasks`, {
|
||||
content: `Task ${taskId} updated to status: ${status}`,
|
||||
containerTag: `${userId}_tasks`,
|
||||
id: taskId,
|
||||
metadata: {
|
||||
taskId,
|
||||
status,
|
||||
updatedAt: new Date().toISOString(),
|
||||
type: 'update'
|
||||
updatedAt: new Date().toISOString()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -262,9 +246,8 @@ async function indexDocumentation() {
|
|||
];
|
||||
|
||||
for (const doc of docs) {
|
||||
await memory.add({
|
||||
await memory.add('documentation', {
|
||||
content: doc.url,
|
||||
containerTag: 'documentation',
|
||||
metadata: {
|
||||
category: doc.category,
|
||||
type: 'documentation',
|
||||
|
|
@ -276,22 +259,18 @@ async function indexDocumentation() {
|
|||
|
||||
// 2. Search documentation
|
||||
async function searchDocs(query: string, category?: string) {
|
||||
const filters = category ? {
|
||||
metadata: { category }
|
||||
} : undefined;
|
||||
|
||||
const results = await memory.search.memories({
|
||||
q: query,
|
||||
containerTag: 'documentation',
|
||||
const { results } = await memory.search('documentation', {
|
||||
query,
|
||||
searchMode: 'hybrid', // Use hybrid search for better RAG accuracy
|
||||
threshold: 0.3,
|
||||
limit: 10,
|
||||
filters
|
||||
filter: category
|
||||
? { field: 'category', operator: 'eq', value: category }
|
||||
: undefined
|
||||
});
|
||||
|
||||
return results.map(r => ({
|
||||
content: r.content,
|
||||
relevance: r.score,
|
||||
content: r.memory ?? r.chunk,
|
||||
metadata: r.metadata
|
||||
}));
|
||||
}
|
||||
|
|
@ -368,9 +347,8 @@ interface Ticket {
|
|||
|
||||
// 1. Store customer profile
|
||||
async function createCustomer(customer: Customer) {
|
||||
await memory.add({
|
||||
await memory.add(customer.id, {
|
||||
content: `Customer: ${customer.name} (${customer.email}), Plan: ${customer.plan}`,
|
||||
containerTag: customer.id,
|
||||
metadata: {
|
||||
type: 'profile',
|
||||
plan: customer.plan,
|
||||
|
|
@ -381,10 +359,9 @@ async function createCustomer(customer: Customer) {
|
|||
|
||||
// 2. Log support ticket
|
||||
async function createTicket(ticket: Ticket) {
|
||||
await memory.add({
|
||||
await memory.add(ticket.customerId, {
|
||||
content: `Ticket ${ticket.id}: ${ticket.subject}\n${ticket.description}`,
|
||||
containerTag: ticket.customerId,
|
||||
customId: ticket.id,
|
||||
id: ticket.id,
|
||||
metadata: {
|
||||
type: 'ticket',
|
||||
status: ticket.status,
|
||||
|
|
@ -396,12 +373,13 @@ async function createTicket(ticket: Ticket) {
|
|||
|
||||
// 3. Resolve ticket
|
||||
async function resolveTicket(customerId: string, ticketId: string, resolution: string) {
|
||||
await memory.add({
|
||||
// Same id appends the resolution to the ticket's document
|
||||
await memory.add(customerId, {
|
||||
content: `Ticket ${ticketId} resolved: ${resolution}`,
|
||||
containerTag: customerId,
|
||||
id: ticketId,
|
||||
metadata: {
|
||||
type: 'resolution',
|
||||
ticketId,
|
||||
type: 'ticket',
|
||||
status: 'resolved',
|
||||
resolvedAt: new Date().toISOString()
|
||||
}
|
||||
});
|
||||
|
|
@ -409,14 +387,10 @@ async function resolveTicket(customerId: string, ticketId: string, resolution: s
|
|||
|
||||
// 4. Support agent assistant
|
||||
async function supportAssistant(customerId: string, query: string) {
|
||||
const response = await memory.profile({
|
||||
containerTag: customerId,
|
||||
q: query,
|
||||
threshold: 0.5
|
||||
});
|
||||
const { profile } = await memory.profile(customerId);
|
||||
|
||||
const staticInfo = response.profile.static.map(f => `- ${f}`).join('\n');
|
||||
const recentTickets = response.profile.dynamic.map(f => `- ${f}`).join('\n');
|
||||
const staticInfo = profile.static.map(m => `- ${m.memory}`).join('\n');
|
||||
const recentTickets = profile.dynamic.map(m => `- ${m.memory}`).join('\n');
|
||||
|
||||
const { text } = await generateText({
|
||||
model: openai('gpt-4'),
|
||||
|
|
@ -493,10 +467,10 @@ async function indexCodebase(projectId: string, directory: string) {
|
|||
const content = fs.readFileSync(file, 'utf-8');
|
||||
const relativePath = path.relative(directory, file);
|
||||
|
||||
await memory.add({
|
||||
// Re-indexing a changed file: use documents.update() to replace its content
|
||||
await memory.add(`${projectId}_codebase`, {
|
||||
content: `File: ${relativePath}\n\n${content}`,
|
||||
containerTag: `${projectId}_codebase`,
|
||||
customId: relativePath,
|
||||
id: relativePath.replace(/[^A-Za-z0-9_-]/g, '_'),
|
||||
metadata: {
|
||||
type: 'source_file',
|
||||
language: path.extname(file).slice(1),
|
||||
|
|
@ -509,10 +483,9 @@ async function indexCodebase(projectId: string, directory: string) {
|
|||
|
||||
// 2. Index pull requests and reviews
|
||||
async function indexPR(projectId: string, prNumber: number, diff: string, comments: string[]) {
|
||||
await memory.add({
|
||||
await memory.add(`${projectId}_reviews`, {
|
||||
content: `PR #${prNumber}\n\nDiff:\n${diff}\n\nComments:\n${comments.join('\n')}`,
|
||||
containerTag: `${projectId}_reviews`,
|
||||
customId: `pr_${prNumber}`,
|
||||
id: `pr_${prNumber}`,
|
||||
metadata: {
|
||||
type: 'pull_request',
|
||||
number: prNumber,
|
||||
|
|
@ -524,17 +497,16 @@ async function indexPR(projectId: string, prNumber: number, diff: string, commen
|
|||
// 3. Review code with context
|
||||
async function reviewCode(projectId: string, code: string, fileName: string) {
|
||||
// Search for similar code patterns
|
||||
const similarCode = await memory.search.memories({
|
||||
q: code,
|
||||
containerTag: `${projectId}_codebase`,
|
||||
const { results: similarCode } = await memory.search(`${projectId}_codebase`, {
|
||||
query: code,
|
||||
searchMode: 'chunks',
|
||||
threshold: 0.3,
|
||||
limit: 5
|
||||
});
|
||||
|
||||
// Get past review comments
|
||||
const pastReviews = await memory.search.memories({
|
||||
q: `code review comments for ${fileName}`,
|
||||
containerTag: `${projectId}_reviews`,
|
||||
const { results: pastReviews } = await memory.search(`${projectId}_reviews`, {
|
||||
query: `code review comments for ${fileName}`,
|
||||
threshold: 0.3,
|
||||
limit: 5
|
||||
});
|
||||
|
|
@ -545,10 +517,10 @@ async function reviewCode(projectId: string, code: string, fileName: string) {
|
|||
You are a code review assistant familiar with this codebase.
|
||||
|
||||
Similar Code Patterns:
|
||||
${similarCode.map(c => c.content).slice(0, 3).join('\n\n---\n\n')}
|
||||
${similarCode.map(c => c.chunk).slice(0, 3).join('\n\n---\n\n')}
|
||||
|
||||
Past Review Patterns:
|
||||
${pastReviews.map(p => p.content).slice(0, 3).join('\n\n---\n\n')}
|
||||
${pastReviews.map(p => p.memory ?? p.chunk).slice(0, 3).join('\n\n---\n\n')}
|
||||
|
||||
Provide a thoughtful code review, considering existing patterns and past feedback.
|
||||
`,
|
||||
|
|
@ -608,14 +580,13 @@ interface LearningSession {
|
|||
}
|
||||
|
||||
async function recordLearningSession(session: LearningSession) {
|
||||
await memory.add({
|
||||
await memory.add(session.studentId, {
|
||||
content: `
|
||||
Topic: ${session.topic}
|
||||
Understanding: ${session.understanding}
|
||||
Content covered: ${session.content}
|
||||
Questions asked: ${session.questions.join(', ')}
|
||||
`,
|
||||
containerTag: session.studentId,
|
||||
metadata: {
|
||||
type: 'learning_session',
|
||||
topic: session.topic,
|
||||
|
|
@ -626,14 +597,10 @@ Questions asked: ${session.questions.join(', ')}
|
|||
}
|
||||
|
||||
async function adaptiveTutor(studentId: string, question: string) {
|
||||
const response = await memory.profile({
|
||||
containerTag: studentId,
|
||||
q: question,
|
||||
threshold: 0.5
|
||||
});
|
||||
|
||||
// Get learning history from search results (if available)
|
||||
const searchResults = response.searchResults?.results || [];
|
||||
const [{ profile }, { results: searchResults }] = await Promise.all([
|
||||
memory.profile(studentId),
|
||||
memory.search(studentId, { query: question, searchMode: 'chunks', threshold: 0.5 })
|
||||
]);
|
||||
|
||||
// Analyze learning patterns from metadata
|
||||
const weakTopics = searchResults
|
||||
|
|
@ -644,8 +611,8 @@ async function adaptiveTutor(studentId: string, question: string) {
|
|||
.filter(r => r.metadata?.understanding === 'high')
|
||||
.map(r => r.metadata?.topic);
|
||||
|
||||
const staticInfo = response.profile.static.map(f => `- ${f}`).join('\n');
|
||||
const recentLearning = response.profile.dynamic.slice(0, 5).map(f => f).join('\n\n');
|
||||
const staticInfo = profile.static.map(m => `- ${m.memory}`).join('\n');
|
||||
const recentLearning = profile.dynamic.slice(0, 5).map(m => m.memory).join('\n\n');
|
||||
|
||||
const { text } = await generateText({
|
||||
model: openai('gpt-4'),
|
||||
|
|
@ -724,8 +691,8 @@ interface User {
|
|||
name: string;
|
||||
}
|
||||
|
||||
// Container tag strategy
|
||||
function getContainerTags(orgId: string, userId: string) {
|
||||
// Namespace strategy: one namespace per isolation boundary
|
||||
function getNamespaces(orgId: string, userId: string) {
|
||||
return {
|
||||
org: `org_${orgId}`,
|
||||
user: `org_${orgId}_user_${userId}`,
|
||||
|
|
@ -735,11 +702,10 @@ function getContainerTags(orgId: string, userId: string) {
|
|||
|
||||
// 1. Store organization-wide knowledge
|
||||
async function addOrgKnowledge(orgId: string, content: string) {
|
||||
const tags = getContainerTags(orgId, '');
|
||||
const namespaces = getNamespaces(orgId, '');
|
||||
|
||||
await memory.add({
|
||||
await memory.add(namespaces.shared, {
|
||||
content,
|
||||
containerTag: tags.shared,
|
||||
metadata: {
|
||||
type: 'org_knowledge',
|
||||
visibility: 'organization'
|
||||
|
|
@ -749,11 +715,10 @@ async function addOrgKnowledge(orgId: string, content: string) {
|
|||
|
||||
// 2. Store user-specific data
|
||||
async function addUserData(orgId: string, userId: string, content: string) {
|
||||
const tags = getContainerTags(orgId, userId);
|
||||
const namespaces = getNamespaces(orgId, userId);
|
||||
|
||||
await memory.add({
|
||||
await memory.add(namespaces.user, {
|
||||
content,
|
||||
containerTag: tags.user,
|
||||
metadata: {
|
||||
type: 'user_data',
|
||||
visibility: 'private'
|
||||
|
|
@ -763,20 +728,20 @@ async function addUserData(orgId: string, userId: string, content: string) {
|
|||
|
||||
// 3. Search with proper isolation
|
||||
async function search(orgId: string, userId: string, query: string, includeShared: boolean = true) {
|
||||
const tags = getContainerTags(orgId, userId);
|
||||
const namespaces = getNamespaces(orgId, userId);
|
||||
|
||||
const containerTags = includeShared
|
||||
? [tags.user, tags.shared] // User + org shared
|
||||
: [tags.user]; // User only
|
||||
const targets = includeShared
|
||||
? [namespaces.user, namespaces.shared] // User + org shared
|
||||
: [namespaces.user]; // User only
|
||||
|
||||
const results = await memory.search.memories({
|
||||
q: query,
|
||||
containerTag: containerTags[0], // Use first tag
|
||||
threshold: 0.3,
|
||||
limit: 10
|
||||
});
|
||||
// One search per namespace, then merge
|
||||
const responses = await Promise.all(
|
||||
targets.map(namespace =>
|
||||
memory.search(namespace, { query, threshold: 0.3, limit: 10 })
|
||||
)
|
||||
);
|
||||
|
||||
return results;
|
||||
return responses.flatMap(r => r.results);
|
||||
}
|
||||
|
||||
// Usage
|
||||
|
|
@ -833,7 +798,7 @@ interface Paper {
|
|||
}
|
||||
|
||||
async function addPaper(userId: string, paper: Paper) {
|
||||
await memory.add({
|
||||
await memory.add(`${userId}_research`, {
|
||||
content: `
|
||||
Title: ${paper.title}
|
||||
Authors: ${paper.authors.join(', ')}
|
||||
|
|
@ -841,8 +806,7 @@ Year: ${paper.year}
|
|||
Abstract: ${paper.abstract}
|
||||
URL: ${paper.url}
|
||||
`,
|
||||
containerTag: `${userId}_research`,
|
||||
customId: paper.url,
|
||||
id: paper.url.replace(/[^A-Za-z0-9_-]/g, '_'),
|
||||
metadata: {
|
||||
type: 'paper',
|
||||
year: paper.year,
|
||||
|
|
@ -853,9 +817,8 @@ URL: ${paper.url}
|
|||
}
|
||||
|
||||
async function addResearchNote(userId: string, note: string, relatedPapers: string[]) {
|
||||
await memory.add({
|
||||
await memory.add(`${userId}_research`, {
|
||||
content: note,
|
||||
containerTag: `${userId}_research`,
|
||||
metadata: {
|
||||
type: 'note',
|
||||
relatedPapers,
|
||||
|
|
@ -865,9 +828,9 @@ async function addResearchNote(userId: string, note: string, relatedPapers: stri
|
|||
}
|
||||
|
||||
async function findRelatedResearch(userId: string, topic: string) {
|
||||
const results = await memory.search.memories({
|
||||
q: topic,
|
||||
containerTag: `${userId}_research`,
|
||||
const { results } = await memory.search(`${userId}_research`, {
|
||||
query: topic,
|
||||
searchMode: 'chunks',
|
||||
threshold: 0.3,
|
||||
limit: 20
|
||||
});
|
||||
|
|
@ -884,9 +847,9 @@ async function synthesizeInsights(userId: string, research_question: string) {
|
|||
|
||||
const context = [
|
||||
'=== Related Papers ===',
|
||||
...related.papers.map(p => p.content),
|
||||
...related.papers.map(p => p.chunk),
|
||||
'\n=== Your Notes ===',
|
||||
...related.notes.map(n => n.content)
|
||||
...related.notes.map(n => n.chunk)
|
||||
].join('\n\n');
|
||||
|
||||
const { text } = await generateText({
|
||||
|
|
@ -951,14 +914,11 @@ const synthesis = await synthesizeInsights(
|
|||
|
||||
```typescript
|
||||
// Always retrieve context first
|
||||
const response = await memory.profile({
|
||||
containerTag: userId,
|
||||
q: userMessage
|
||||
});
|
||||
const { profile } = await memory.profile(userId);
|
||||
|
||||
// Then use in generation
|
||||
const staticFacts = response.profile.static.join('\n');
|
||||
const dynamicFacts = response.profile.dynamic.join('\n');
|
||||
const staticFacts = profile.static.map(m => m.memory).join('\n');
|
||||
const dynamicFacts = profile.dynamic.map(m => m.memory).join('\n');
|
||||
|
||||
const llmResponse = await generateText({
|
||||
system: `User Profile:\n${staticFacts}\n\nRecent Context:\n${dynamicFacts}`,
|
||||
|
|
@ -970,9 +930,8 @@ const llmResponse = await generateText({
|
|||
|
||||
```typescript
|
||||
// Always store the result
|
||||
await memory.add({
|
||||
await memory.add(userId, {
|
||||
content: `Input: ${input}\nOutput: ${output}`,
|
||||
containerTag: userId,
|
||||
metadata: { timestamp: new Date().toISOString() }
|
||||
});
|
||||
```
|
||||
|
|
@ -980,9 +939,8 @@ await memory.add({
|
|||
### Pattern 3: Rich Metadata for Filtering
|
||||
|
||||
```typescript
|
||||
await memory.add({
|
||||
await memory.add(userId, {
|
||||
content: data,
|
||||
containerTag: userId,
|
||||
metadata: {
|
||||
type: 'conversation',
|
||||
category: 'support',
|
||||
|
|
@ -993,35 +951,36 @@ await memory.add({
|
|||
});
|
||||
|
||||
// Later filter by metadata
|
||||
const results = await memory.search.memories({
|
||||
q: 'billing issues',
|
||||
containerTag: 'user_123',
|
||||
filters: {
|
||||
metadata: { priority: 'high', type: 'conversation' }
|
||||
const { results } = await memory.search('user_123', {
|
||||
query: 'billing issues',
|
||||
filter: {
|
||||
operator: 'and',
|
||||
operands: [
|
||||
{ field: 'priority', operator: 'eq', value: 'high' },
|
||||
{ field: 'type', operator: 'eq', value: 'conversation' }
|
||||
]
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Pattern 4: Hierarchical Container Tags
|
||||
### Pattern 4: Hierarchical Namespaces
|
||||
|
||||
```typescript
|
||||
// Organization → Team → User hierarchy
|
||||
const tags = {
|
||||
// Organization → Team → User hierarchy; each level is its own namespace
|
||||
const namespaces = {
|
||||
org: `org_${orgId}`,
|
||||
team: `org_${orgId}_team_${teamId}`,
|
||||
user: `org_${orgId}_team_${teamId}_user_${userId}`
|
||||
};
|
||||
|
||||
// Search at appropriate level
|
||||
const orgWide = await memory.search.memories({
|
||||
q: 'company policies',
|
||||
containerTag: tags.org,
|
||||
const orgWide = await memory.search(namespaces.org, {
|
||||
query: 'company policies',
|
||||
limit: 10
|
||||
});
|
||||
|
||||
const teamSpecific = await memory.search.memories({
|
||||
q: 'team resources',
|
||||
containerTag: tags.team,
|
||||
const teamSpecific = await memory.search(namespaces.team, {
|
||||
query: 'team resources',
|
||||
limit: 10
|
||||
});
|
||||
```
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue