recallrai
Version:
Official Node.js SDK for RecallrAI - Revolutionary contextual memory system that enables AI assistants to form meaningful connections between conversations, just like human memory.
828 lines (669 loc) • 22.9 kB
Markdown
# RecallrAI Node.js SDK
Official Node.js SDK for RecallrAI – a revolutionary contextual memory system that enables AI assistants to form meaningful connections between conversations, just like human memory.
**Note:** All datetime objects returned by the SDK are JavaScript Date objects in UTC timezone.
## Installation
Install the SDK via npm or yarn:
```bash
npm install recallrai
# or
yarn add recallrai
```
## Initialization
Create a client instance with your API key and project ID:
```typescript
import { RecallrAI } from "recallrai";
const client = new RecallrAI({
apiKey: "rai_yourapikey",
projectId: "project-uuid",
baseUrl: "https://api.recallrai.com", // custom endpoint if applicable
timeout: 60, // seconds
});
```
## User Management
### Create a User
```typescript
import { UserAlreadyExistsError } from "recallrai";
try {
const user = await client.createUser("user123", { name: "John Doe" });
console.log(`Created user: ${user.userId}`);
console.log(`User metadata:`, user.metadata);
console.log(`Created at: ${user.createdAt}`);
} catch (error) {
if (error instanceof UserAlreadyExistsError) {
console.log(`Error: ${error.message}`);
}
}
```
### Get a User
```typescript
import { UserNotFoundError } from "recallrai";
try {
const user = await client.getUser("user123");
console.log(`User metadata:`, user.metadata);
console.log(`Last active: ${user.lastActiveAt}`);
} catch (error) {
if (error instanceof UserNotFoundError) {
console.log(`Error: ${error.message}`);
}
}
```
If `userId` is already trusted, you can skip the lookup request:
```typescript
const user = await client.getUser("user123", { validate: false });
```
### List Users
```typescript
const userList = await client.listUsers({
offset: 0,
limit: 10,
metadataFilter: { role: "admin" },
});
console.log(`Total users: ${userList.total}`);
console.log(`Has more users: ${userList.hasMore}`);
console.log("---");
for (const u of userList.users) {
console.log(`User ID: ${u.userId}`);
console.log(`Metadata:`, u.metadata);
console.log(`Created at: ${u.createdAt}`);
console.log(`Last active: ${u.lastActiveAt}`);
console.log("---");
}
```
### Update a User
```typescript
import { UserNotFoundError, UserAlreadyExistsError } from "recallrai";
try {
const user = await client.getUser("user123");
await user.update({
newMetadata: { name: "John Doe", role: "admin" },
newUserId: "john_doe",
mergeConflictEnabled: true, // override: always raise merge conflicts for this user
});
console.log(`Updated user ID: ${user.userId}`);
console.log(`Updated metadata:`, user.metadata);
console.log(`Merge conflict enabled: ${user.mergeConflictEnabled}`);
console.log(`Last active: ${user.lastActiveAt}`);
} catch (error) {
if (error instanceof UserNotFoundError) {
console.log(`Error: ${error.message}`);
} else if (error instanceof UserAlreadyExistsError) {
console.log(`Error: ${error.message}`);
}
}
```
### Refresh User Instance
```typescript
import { UserNotFoundError } from "recallrai";
try {
const user = await client.getUser("john_doe");
await user.refresh();
console.log(`Refreshed user metadata:`, user.metadata);
console.log(`Last active: ${user.lastActiveAt}`);
} catch (error) {
if (error instanceof UserNotFoundError) {
console.log(`Error: ${error.message}`);
}
}
```
### Delete a User
```typescript
import { UserNotFoundError } from "recallrai";
try {
const user = await client.getUser("john_doe");
await user.delete();
console.log("User deleted successfully");
} catch (error) {
if (error instanceof UserNotFoundError) {
console.log(`Error: ${error.message}`);
}
}
```
## Session Management
### Create a Session
```typescript
import { UserNotFoundError } from "recallrai";
try {
const user = await client.getUser("user123");
const session = await user.createSession({
autoProcessAfterSeconds: 600,
metadata: { type: "chat" },
});
console.log("Created session id:", session.sessionId);
} catch (error) {
if (error instanceof UserNotFoundError) {
console.log(`Error: ${error.message}`);
}
}
```
### Get an Existing Session
```typescript
import { UserNotFoundError, SessionNotFoundError } from "recallrai";
try {
const user = await client.getUser("user123");
const session = await user.getSession("session-uuid");
console.log("Session status:", session.status);
console.log("Session metadata:", session.metadata);
} catch (error) {
if (error instanceof UserNotFoundError) {
console.log(`Error: ${error.message}`);
} else if (error instanceof SessionNotFoundError) {
console.log(`Error: ${error.message}`);
}
}
```
If `sessionId` is trusted, you can skip this lookup request:
```typescript
const session = await user.getSession("session-uuid", { validate: false });
```
### Trusted IDs - Skip Validation Lookups
```typescript
import { RecallStrategy } from "recallrai";
// Skips GET /api/v1/users/{userId}
const user = await client.getUser("user123", { validate: false });
// Skips GET /api/v1/users/{userId}/sessions/{sessionId}
const session = await user.getSession("session-uuid", { validate: false });
// Goes directly to context retrieval
const context = await session.getContext({
recallStrategy: RecallStrategy.LOW_LATENCY,
});
console.log(context.context);
```
Use this only when IDs are already trusted by your system. This optimization skips SDK pre-validation calls.
When `validate: false` is used, unknown reference fields are set to `UNAVAILABLE` until you call `refresh()`.
Import it from `recallrai` when you need to check for this sentinel.
### Update a Session
```typescript
import { UserNotFoundError, SessionNotFoundError } from "recallrai";
try {
const user = await client.getUser("user123");
const session = await user.getSession("session-uuid");
await session.update({ type: "support_chat" });
console.log("Updated session metadata:", session.metadata);
} catch (error) {
if (error instanceof UserNotFoundError) {
console.log(`Error: ${error.message}`);
} else if (error instanceof SessionNotFoundError) {
console.log(`Error: ${error.message}`);
}
}
```
### Refresh a Session
```typescript
import { UserNotFoundError, SessionNotFoundError } from "recallrai";
try {
const user = await client.getUser("user123");
const session = await user.getSession("session-uuid");
await session.refresh();
console.log("Session status:", session.status);
console.log("Refreshed session metadata:", session.metadata);
} catch (error) {
if (error instanceof UserNotFoundError) {
console.log(`Error: ${error.message}`);
} else if (error instanceof SessionNotFoundError) {
console.log(`Error: ${error.message}`);
}
}
```
### Delete a Session
```typescript
import { UserNotFoundError, SessionNotFoundError } from "recallrai";
try {
const user = await client.getUser("user123");
const session = await user.getSession("session-uuid");
await session.delete();
console.log("Session deleted successfully");
} catch (error) {
if (error instanceof UserNotFoundError) {
console.log(`Error: ${error.message}`);
} else if (error instanceof SessionNotFoundError) {
console.log(`Error: ${error.message}`);
}
}
```
### List Sessions
```typescript
import { SessionStatus, UserNotFoundError } from "recallrai";
try {
const user = await client.getUser("user123");
const sessionList = await user.listSessions({
offset: 0,
limit: 10,
metadataFilter: { type: "chat" },
statusFilter: [SessionStatus.PENDING, SessionStatus.PROCESSING],
});
console.log(`Total sessions: ${sessionList.total}`);
console.log(`Has more sessions: ${sessionList.hasMore}`);
for (const s of sessionList.sessions) {
console.log(s.sessionId, s.status, s.metadata);
}
} catch (error) {
if (error instanceof UserNotFoundError) {
console.log(`Error: ${error.message}`);
}
}
```
### Session – Adding Messages
```typescript
import { UserNotFoundError, SessionNotFoundError, InvalidSessionStateError, MessageRole } from "recallrai";
try {
await session.addMessage(MessageRole.USER, "Hello! How are you?");
await session.addMessage(MessageRole.ASSISTANT, "I'm an assistant. How can I help you?");
} catch (error) {
if (error instanceof UserNotFoundError) {
console.log(`Error: ${error.message}`);
} else if (error instanceof SessionNotFoundError) {
console.log(`Error: ${error.message}`);
} else if (error instanceof InvalidSessionStateError) {
console.log(`Error: ${error.message}`);
}
}
```
### Session – Retrieving Context
```typescript
import { UserNotFoundError, SessionNotFoundError, RecallStrategy } from "recallrai";
try {
let context = await session.getContext();
console.log("Context:", context.context);
context = await session.getContext({
recallStrategy: RecallStrategy.LOW_LATENCY,
});
console.log("Context:", context.context);
context = await session.getContext({
recallStrategy: RecallStrategy.BALANCED,
minTopK: 10,
maxTopK: 100,
memoriesThreshold: 0.6,
summariesThreshold: 0.5,
lastNMessages: 20,
lastNSummaries: 5,
timezone: "America/Los_Angeles",
});
console.log("Context:", context.context);
context = await session.getContext({ includeMetadataIds: true });
if (context.metadata) {
console.log("Memory IDs:", context.metadata.memoryIds);
console.log("Session IDs:", context.metadata.sessionIds);
console.log("Vector Queries:", context.metadata.vectorSearchQueries);
console.log("Keywords:", context.metadata.keywords);
console.log("Summary Queries:", context.metadata.sessionSummariesSearchQueries);
console.log("Date Filters:", context.metadata.dateRangeFilters);
console.log("Agent Reasoning:", context.metadata.agentReasoning);
}
} catch (error) {
if (error instanceof UserNotFoundError) {
console.log(`Error: ${error.message}`);
} else if (error instanceof SessionNotFoundError) {
console.log(`Error: ${error.message}`);
}
}
```
### Session – Streaming Context
```typescript
import { UserNotFoundError, SessionNotFoundError, RecallStrategy } from "recallrai";
try {
for await (const event of session.getContextStream({
recallStrategy: RecallStrategy.BALANCED,
timezone: "America/Los_Angeles",
})) {
if (event.statusUpdateMessage) {
console.log("Status:", event.statusUpdateMessage);
}
if (event.metadata) {
console.log("Metadata:", event.metadata);
}
if (event.isFinal) {
if (event.errorMessage) {
console.log("Error:", event.errorMessage);
} else {
console.log("Final context:", event.context);
}
}
}
} catch (error) {
if (error instanceof UserNotFoundError) {
console.log(`Error: ${error.message}`);
} else if (error instanceof SessionNotFoundError) {
console.log(`Error: ${error.message}`);
}
}
```
### Session – Process Session
```typescript
import { UserNotFoundError, SessionNotFoundError, InvalidSessionStateError } from "recallrai";
try {
await session.process();
} catch (error) {
if (error instanceof UserNotFoundError) {
console.log(`Error: ${error.message}`);
} else if (error instanceof SessionNotFoundError) {
console.log(`Error: ${error.message}`);
} else if (error instanceof InvalidSessionStateError) {
console.log(`Error: ${error.message}`);
}
}
```
### Session – List Messages
```typescript
import { UserNotFoundError, SessionNotFoundError } from "recallrai";
try {
const messages = await session.getMessages(0, 50);
for (const msg of messages.messages) {
console.log(`${msg.role.toUpperCase()} (at ${msg.timestamp}): ${msg.content}`);
}
console.log(`Has more?: ${messages.hasMore}`);
console.log(`Total messages: ${messages.total}`);
} catch (error) {
if (error instanceof UserNotFoundError) {
console.log(`Error: ${error.message}`);
} else if (error instanceof SessionNotFoundError) {
console.log(`Error: ${error.message}`);
}
}
```
## User Memories
### List User Memories (with optional category filters)
```typescript
import { UserNotFoundError, InvalidCategoriesError } from "recallrai";
try {
const user = await client.getUser("user123");
const memories = await user.listMemories({
categories: ["food_preferences", "allergies"],
sessionIdFilter: ["session-uuid-1", "session-uuid-2"], // optional: filter by specific sessions
sessionMetadataFilter: { environment: "production" }, // optional: filter by session metadata
offset: 0,
limit: 20,
includePreviousVersions: true,
includeConnectedMemories: true,
});
for (const mem of memories.items) {
console.log(`Memory ID: ${mem.memoryId}`);
console.log(`Categories: ${mem.categories}`);
console.log(`Content: ${mem.content}`);
console.log(`Created at: ${mem.createdAt}`);
console.log(`Session ID: ${mem.sessionId}`);
console.log(`Version: ${mem.versionNumber} of ${mem.totalVersions}`);
console.log(`Has previous versions: ${mem.hasPreviousVersions}`);
if (mem.previousVersions) {
console.log(`Previous versions: ${mem.previousVersions.length}`);
for (const version of mem.previousVersions) {
console.log(` - Version ${version.versionNumber}: ${version.content}`);
console.log(` Created: ${version.createdAt}, Expired: ${version.expiredAt}`);
console.log(` Expiration reason: ${version.expirationReason}`);
}
}
if (mem.connectedMemories) {
console.log(`Connected memories: ${mem.connectedMemories.length}`);
for (const connected of mem.connectedMemories) {
console.log(` - ${connected.memoryId}: ${connected.content}`);
}
}
console.log(`Merge conflict in progress: ${mem.mergeConflictInProgress}`);
console.log("---");
}
console.log(`Has more?: ${memories.hasMore}`);
console.log(`Total memories: ${memories.total}`);
} catch (error) {
if (error instanceof UserNotFoundError) {
console.log(`Error: ${error.message}`);
} else if (error instanceof InvalidCategoriesError) {
console.log(`Error: ${error.message}`);
}
}
```
## User Messages
### Get Last N Messages
```typescript
import { UserNotFoundError } from "recallrai";
try {
const user = await client.getUser("user123");
const messages = await user.getLastNMessages(5);
for (const msg of messages.messages) {
console.log(`Session ID: ${msg.sessionId}`);
console.log(`${msg.role.toUpperCase()} (at ${msg.timestamp}): ${msg.content}`);
console.log("---");
}
} catch (error) {
if (error instanceof UserNotFoundError) {
console.log(`Error: ${error.message}`);
}
}
```
## Merge Conflict Management
### List Merge Conflicts
```typescript
import { UserNotFoundError, MergeConflictStatus } from "recallrai";
try {
const user = await client.getUser("user123");
const conflicts = await user.listMergeConflicts({
offset: 0,
limit: 10,
status: MergeConflictStatus.PENDING,
sortBy: "created_at",
sortOrder: "desc",
});
console.log(`Total conflicts: ${conflicts.total}`);
console.log(`Has more: ${conflicts.hasMore}`);
for (const conf of conflicts.conflicts) {
console.log(`Conflict ID: ${conf.conflictId}`);
console.log(`Status: ${conf.status}`);
console.log(`New memory: ${conf.proposedMemoryContent}`);
console.log(`Conflicting memories: ${conf.conflictingMemories.length}`);
console.log(`Questions: ${conf.clarifyingQuestions.length}`);
console.log(`Created at: ${conf.createdAt}`);
console.log("---");
}
} catch (error) {
if (error instanceof UserNotFoundError) {
console.log(`Error: ${error.message}`);
}
}
```
### Get a Specific Merge Conflict
```typescript
import { UserNotFoundError, MergeConflictNotFoundError } from "recallrai";
try {
const user = await client.getUser("user123");
const conflict = await user.getMergeConflict("conflict-uuid");
console.log(`Conflict ID: ${conflict.conflictId}`);
console.log(`Status: ${conflict.status}`);
console.log(`New memory content: ${conflict.proposedMemoryContent}`);
console.log("\nConflicting memories:");
for (const mem of conflict.conflictingMemories) {
console.log(` Content: ${mem.content}`);
console.log(` Reason: ${mem.reason}`);
console.log();
}
console.log("Clarifying questions:");
for (const ques of conflict.clarifyingQuestions) {
console.log(` Question: ${ques.question}`);
console.log(` Options: ${ques.options}`);
console.log();
}
} catch (error) {
if (error instanceof UserNotFoundError) {
console.log(`Error: ${error.message}`);
} else if (error instanceof MergeConflictNotFoundError) {
console.log(`Error: ${error.message}`);
}
}
```
### Resolve a Merge Conflict
```typescript
import {
UserNotFoundError,
MergeConflictNotFoundError,
MergeConflictAlreadyResolvedError,
MergeConflictInvalidQuestionsError,
MergeConflictMissingAnswersError,
MergeConflictInvalidAnswerError,
ValidationError,
} from "recallrai";
try {
const user = await client.getUser("user123");
const conflict = await user.getMergeConflict("conflict-uuid");
const answers = [];
for (const ques of conflict.clarifyingQuestions) {
console.log(` Question: ${ques.question}`);
console.log(` Options: ${ques.options}`);
console.log();
answers.push({
question: ques.question,
answer: ques.options[0],
message: "User prefers this option based on recent conversation",
});
}
await conflict.resolve(answers);
console.log(`Conflict resolved! Status: ${conflict.status}`);
console.log(`Resolved at: ${conflict.resolvedAt}`);
if (conflict.resolutionData) {
console.log(`Resolution data:`, conflict.resolutionData);
}
} catch (error) {
if (error instanceof UserNotFoundError) {
console.log(`Error: ${error.message}`);
} else if (error instanceof MergeConflictNotFoundError) {
console.log(`Error: ${error.message}`);
} else if (error instanceof MergeConflictAlreadyResolvedError) {
console.log(`Error: ${error.message}`);
} else if (error instanceof MergeConflictInvalidQuestionsError) {
console.log(`Error: ${error.message}`);
} else if (error instanceof MergeConflictMissingAnswersError) {
console.log(`Error: ${error.message}`);
} else if (error instanceof MergeConflictInvalidAnswerError) {
console.log(`Error: ${error.message}`);
} else if (error instanceof ValidationError) {
console.log(`Error: ${error.message}`);
}
}
```
### Refresh Merge Conflict Data
```typescript
import { UserNotFoundError, MergeConflictNotFoundError } from "recallrai";
try {
const user = await client.getUser("user123");
const conflict = await user.getMergeConflict("conflict-uuid");
await conflict.refresh();
console.log(`Current status: ${conflict.status}`);
console.log(`Last updated: ${conflict.resolvedAt}`);
} catch (error) {
if (error instanceof UserNotFoundError) {
console.log(`Error: ${error.message}`);
} else if (error instanceof MergeConflictNotFoundError) {
console.log(`Error: ${error.message}`);
}
}
```
## Example Usage with LLMs
```typescript
import OpenAI from "openai";
import { RecallrAI, UserNotFoundError, MessageRole } from "recallrai";
const raiClient = new RecallrAI({
apiKey: "rai_yourapikey",
projectId: "your-project-uuid",
});
const oaiClient = new OpenAI({
apiKey: "your-openai-api-key",
});
async function chatWithMemory(userId: string, sessionId?: string) {
let user;
try {
user = await raiClient.getUser(userId);
} catch (error) {
if (error instanceof UserNotFoundError) {
user = await raiClient.createUser(userId);
} else {
throw error;
}
}
let session;
if (sessionId) {
session = await user.getSession(sessionId);
} else {
session = await user.createSession({ autoProcessAfterSeconds: 1800 });
console.log(`Created new session: ${session.sessionId}`);
}
console.log("Chat session started. Type 'exit' to end the conversation.");
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const askQuestion = (query: string): Promise<string> => {
return new Promise((resolve) => rl.question(query, resolve));
};
while (true) {
const userMessage = await askQuestion("You: ");
if (userMessage.toLowerCase() === "exit") {
break;
}
await session.addMessage(MessageRole.USER, userMessage);
const context = await session.getContext();
const systemPrompt = "You are a helpful assistant" + context.context;
const messagesData = await session.getMessages(0, 50);
const previousMessages = messagesData.messages.map((message) => ({
role: message.role,
content: message.content,
}));
const response = await oaiClient.chat.completions.create({
model: "gpt-4o-mini",
messages: [{ role: "system", content: systemPrompt }, ...previousMessages],
temperature: 0.7,
});
const assistantMessage = response.choices[0].message.content || "";
console.log(`Assistant: ${assistantMessage}`);
await session.addMessage(MessageRole.ASSISTANT, assistantMessage);
}
rl.close();
console.log("Processing session to update memory...");
await session.process();
console.log(`Session ended. Session ID: ${session.sessionId}`);
return session.sessionId;
}
chatWithMemory("user123").then((sessionId) => {
console.log(`To continue this conversation later, use session ID: ${sessionId}`);
});
```
## Exception Handling
The RecallrAI SDK implements a comprehensive exception hierarchy to help you handle different error scenarios gracefully.
### Available Exceptions
- **RecallrAIError**: Base exception for all SDK-specific errors
- **AuthenticationError**: Authentication issues with API key or project ID
- **TimeoutError**: Request timeout
- **ConnectionError**: Connection issues to the API
- **InternalServerError**: API returns 5xx error
- **RateLimitError**: API rate limit exceeded
- **UserNotFoundError**: User doesn't exist
- **UserAlreadyExistsError**: User creation with existing ID
- **InvalidCategoriesError**: Invalid memory categories
- **SessionNotFoundError**: Session doesn't exist
- **InvalidSessionStateError**: Invalid operation for session state
- **MergeConflictNotFoundError**: Merge conflict doesn't exist
- **MergeConflictAlreadyResolvedError**: Conflict already processed
- **MergeConflictInvalidQuestionsError**: Invalid clarifying questions
- **MergeConflictMissingAnswersError**: Missing required answers
- **MergeConflictInvalidAnswerError**: Invalid answer option
- **ValidationError**: Request validation failure
### Importing Exceptions
```typescript
import {
UserNotFoundError,
SessionNotFoundError,
InvalidCategoriesError,
MergeConflictNotFoundError,
MergeConflictAlreadyResolvedError,
} from "recallrai";
```
### Best Practices for Error Handling
```typescript
try {
// SDK operation
} catch (error) {
if (error instanceof UserNotFoundError) {
// Specific handling
} else if (error instanceof RecallrAIError) {
// General fallback
}
}
```
## Conclusion
This README outlines the basic usage of the RecallrAI SDK functions for user and session management. For additional documentation and advanced usage, please see the [official documentation](https://docs.recallrai.com) or the source code repository on [GitHub](https://github.com/recallrai/sdk-node).