Skip to content

Commit

Permalink
Merge pull request #51 from getzep/add-zep-memory
Browse files Browse the repository at this point in the history
Add langchain memory implementation
  • Loading branch information
paul-paliychuk authored Feb 19, 2024
2 parents 9869a41 + be9283e commit cac010b
Show file tree
Hide file tree
Showing 10 changed files with 359 additions and 79 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,19 +48,19 @@ You will also need to provide a Zep Project API key to your zep client for cloud
You can find out about zep projects in our [cloud docs](https://help.getzep.com/projects.html)

### Using langchain zep classes with `zep-js@next`:
In the pre-release version `zep-js` sdk comes with `ZepChatMessageHistory` and `ZepVectorStore`
In the pre-release version `zep-js` sdk comes with `ZepChatMessageHistory`, `ZepVectorStore` and `ZepMemory`
classes that are compatible with [`Langchain's JS expression language`](https://js.langchain.com/docs/expression_language/)

In order to use these classes in your application, you need to make sure that you have
`@langchain/core` package installed:
`langchain` package installed:

```bash
npm install @langchain/core@^0.1.23
npm install langchain@^0.1.23
```

You can import these classes in the following way:

```typescript
import { ZepChatMessageHistory, ZepVectorStore } from "@getzep/zep-js/langchain"
import { ZepChatMessageHistory, ZepVectorStore, ZepMemory } from "@getzep/zep-js/langchain"
```

1 change: 1 addition & 0 deletions examples/langchain/message_history_example.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
} from "@langchain/core/prompts";
import { RunnableWithMessageHistory } from "@langchain/core/runnables";
import { ConsoleCallbackHandler } from "@langchain/core/tracers/console";

async function main() {
const zepClient = await ZepClient.init(
process.env.ZEP_API_KEY,
Expand Down
7 changes: 2 additions & 5 deletions examples/langchain/message_history_vector_store_example.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@ import {
MessagesPlaceholder,
PromptTemplate,
} from "@langchain/core/prompts";
import {
formatDocument,
ZepChatMessageHistory,
ZepVectorStore,
} from "../../src/langchain";
import { ZepChatMessageHistory, ZepVectorStore } from "../../src/langchain";
import { Document } from "@langchain/core/documents";
import {
RunnableLambda,
Expand All @@ -20,6 +16,7 @@ import {
} from "@langchain/core/runnables";
import { StringOutputParser } from "@langchain/core/output_parsers";
import { ConsoleCallbackHandler } from "@langchain/core/tracers/console";
import { formatDocument } from "langchain/schema/prompt_template";

const DEFAULT_DOCUMENT_PROMPT = PromptTemplate.fromTemplate("{pageContent}");
interface ChainInput {
Expand Down
2 changes: 1 addition & 1 deletion examples/langchain/vector_store_example.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import {
RunnablePassthrough,
} from "@langchain/core/runnables";
import { StringOutputParser } from "@langchain/core/output_parsers";
import { formatDocument } from "../../src/langchain";
import { ConsoleCallbackHandler } from "@langchain/core/tracers/console";
import { formatDocument } from "langchain/schema/prompt_template";

const DEFAULT_DOCUMENT_PROMPT = PromptTemplate.fromTemplate("{pageContent}");

Expand Down
16 changes: 12 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"show-config": "tsc --showConfig",
"name": "@getzep/zep-js",
"version": "2.0.0-rc.2",
"version": "2.0.0-rc.3",
"description": "Zep: Fast, scalable building blocks for production LLM apps",
"private": false,
"publishConfig": {
Expand Down Expand Up @@ -55,17 +55,24 @@
"typescript": "^5.1.6"
},
"peerDependencies": {
"@langchain/core": "^0.1.23"
"langchain": "^0.1.19",
"@langchain/core": "^0.1.19"
},
"peerDependenciesMeta": {
"langchain": {
"optional": true
},
"@langchain/core": {
"optional": false
"optional": true
}
},
"resolutions": {
"@langchain/core": "^0.1.19"
},
"devDependencies": {
"@faker-js/faker": "^8.2.0",
"@langchain/core": "^0.1.23",
"@langchain/openai": "^0.0.14",
"@faker-js/faker": "^8.2.0",
"@types/jest": "^29.5.7",
"@types/node": "^20.8.9",
"@types/sax": "^1.2.7",
Expand All @@ -81,6 +88,7 @@
"jest": "^29.6.2",
"jest-fetch-mock": "^3.0.3",
"jsdoc": "^4.0.2",
"langchain": "^0.1.19",
"prettier": "^3.0.3",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.1",
Expand Down
2 changes: 1 addition & 1 deletion src/langchain/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export * from "./utils";
export * from "./message_history";
export * from "./vector_store";
export * from "./memory";
240 changes: 240 additions & 0 deletions src/langchain/memory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
/* eslint import/no-extraneous-dependencies: 0 */

import {
getInputValue,
getOutputValue,
InputValues,
MemoryVariables,
OutputValues,
} from "@langchain/core/memory";
import {
AIMessage,
BaseMessage,
ChatMessage,
getBufferString,
HumanMessage,
SystemMessage,
} from "@langchain/core/messages";
import { BaseChatMemory, BaseChatMemoryInput } from "langchain/memory";
import ZepClient from "../zep-client";
import { Memory } from "../memory_models";
import { NotFoundError } from "../errors";
import { Message } from "../message_models";

export interface ZepMemoryInput extends BaseChatMemoryInput {
humanPrefix?: string;

aiPrefix?: string;

memoryKey?: string;

baseURL?: string;

sessionId: string;

apiKey: string;
}

/**
* Class used to manage the memory of a chat session, including loading
* and saving the chat history, and clearing the memory when needed. It
* uses the ZepClient to interact with the Zep service for managing the
* chat session's memory.
* @example
* ```typescript
* const sessionId = randomUUID();
*
* // Initialize ZepMemory with session ID, base URL, and API key
* const memory = new ZepMemory({
* sessionId,
* apiKey: "change_this_key",
* });
*
* // Create a ChatOpenAI model instance with specific parameters
* const model = new ChatOpenAI({
* modelName: "gpt-3.5-turbo",
* temperature: 0,
* });
*
* // Create a ConversationChain with the model and memory
* const chain = new ConversationChain({ llm: model, memory });
*
* // Example of calling the chain with an input
* const res1 = await chain.call({ input: "Hi! I'm Jim." });
* console.log({ res1 });
*
* // Follow-up call to the chain to demonstrate memory usage
* const res2 = await chain.call({ input: "What did I just say my name was?" });
* console.log({ res2 });
*
* // Output the session ID and the current state of memory
* console.log("Session ID: ", sessionId);
* console.log("Memory: ", await memory.loadMemoryVariables({}));
*
* ```
*/
export class ZepMemory extends BaseChatMemory implements ZepMemoryInput {
humanPrefix = "Human";

aiPrefix = "AI";

memoryKey = "history";

apiKey: string;

sessionId: string;

zepClientPromise: Promise<ZepClient>;

private readonly zepInitFailMsg = "ZepClient is not initialized";

constructor(fields: ZepMemoryInput) {
super({
returnMessages: fields?.returnMessages ?? false,
inputKey: fields?.inputKey,
outputKey: fields?.outputKey,
});

this.humanPrefix = fields.humanPrefix ?? this.humanPrefix;
this.aiPrefix = fields.aiPrefix ?? this.aiPrefix;
this.memoryKey = fields.memoryKey ?? this.memoryKey;
this.apiKey = fields.apiKey;
this.sessionId = fields.sessionId;
this.zepClientPromise = ZepClient.init(fields.apiKey, fields.baseURL);
}

get memoryKeys() {
return [this.memoryKey];
}

/**
* Method that retrieves the chat history from the Zep service and formats
* it into a list of messages.
* @param values Input values for the method.
* @returns Promise that resolves with the chat history formatted into a list of messages.
*/
async loadMemoryVariables(values: InputValues): Promise<MemoryVariables> {
// use either lastN provided by developer or undefined to use the
// server preset.

// Wait for ZepClient to be initialized
const zepClient = await this.zepClientPromise;
if (!zepClient) {
throw new Error(this.zepInitFailMsg);
}

const memoryType = values.memoryType ?? "perpetual";
let memory: Memory | null = null;
try {
memory = await zepClient.memory.getMemory(this.sessionId, memoryType);
} catch (error) {
if (error instanceof NotFoundError) {
return this.returnMessages
? { [this.memoryKey]: [] }
: { [this.memoryKey]: "" };
}
throw error;
}

let messages: BaseMessage[] =
memory && memory.summary?.content
? [new SystemMessage(memory.summary.content)]
: [];

if (memory) {
messages = messages.concat(
memory.messages.map((message) => {
const { content, role } = message;
if (role === this.humanPrefix) {
return new HumanMessage(content);
}
if (role === this.aiPrefix) {
return new AIMessage(content);
}
// default to generic ChatMessage
return new ChatMessage(content, role);
}),
);
}

if (this.returnMessages) {
return {
[this.memoryKey]: messages,
};
}
return {
[this.memoryKey]: getBufferString(
messages,
this.humanPrefix,
this.aiPrefix,
),
};
}

/**
* Method that saves the input and output messages to the Zep service.
* @param inputValues Input messages to be saved.
* @param outputValues Output messages to be saved.
* @returns Promise that resolves when the messages have been saved.
*/
async saveContext(
inputValues: InputValues,
outputValues: OutputValues,
): Promise<void> {
const input = getInputValue(inputValues, this.inputKey);
const output = getOutputValue(outputValues, this.outputKey);

// Create new Memory and Message instances
const memory = new Memory({
messages: [
new Message({
role: this.humanPrefix,
content: `${input}`,
}),
new Message({
role: this.aiPrefix,
content: `${output}`,
}),
],
});

// Wait for ZepClient to be initialized
const zepClient = await this.zepClientPromise;
if (!zepClient) {
throw new Error(this.zepInitFailMsg);
}

// Add the new memory to the session using the ZepClient
if (this.sessionId) {
try {
await zepClient.memory.addMemory(this.sessionId, memory);
} catch (error) {
console.error("Error adding memory: ", error);
}
}

// Call the superclass's saveContext method
await super.saveContext(inputValues, outputValues);
}

/**
* Method that deletes the chat history from the Zep service.
* @returns Promise that resolves when the chat history has been deleted.
*/
async clear(): Promise<void> {
// Wait for ZepClient to be initialized
const zepClient = await this.zepClientPromise;
if (!zepClient) {
throw new Error(this.zepInitFailMsg);
}

try {
await zepClient.memory.deleteMemory(this.sessionId);
} catch (error) {
console.error("Error deleting session: ", error);
}

// Clear the superclass's chat history
await super.clear();
}
}
2 changes: 1 addition & 1 deletion src/langchain/message_history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import type { MemoryType } from "../interfaces";
* class. It includes properties like humanPrefix, aiPrefix, memoryKey,
* baseURL, sessionId, and apiKey.
*/
export interface ZepMemoryInput {
interface ZepMemoryInput {
sessionId: string;
client: ZepClient;
memoryType: MemoryType;
Expand Down
Loading

0 comments on commit cac010b

Please sign in to comment.