Implemented a Persistance agent (#77)

# Pull Request Description
It adds a Postgres database to have a persistent agent that remembers
chats even when shut and opened again.
## Related Issue
Fixes #76 

## Changes Made
This PR adds the following changes:
- Added an example of how we can integrate a Postgres db so that the
agent remembers its previous chats.
  
## Implementation Details
- included a postgres db to store chat for future reference
- 

## Transaction executed by agent 
NA

## Prompt Used
<img width="936" alt="image"
src="https://github.com/user-attachments/assets/0aa1bed2-e895-41bf-ad37-a52f679db7cf"
/>

see it remebers my name even when i closed it and ran again .

## Additional Notes

https://langchain-ai.github.io/langgraphjs/reference/classes/checkpoint_postgres.PostgresSaver.html

I used this above link to implement the changes.

## Checklist
- [ x ] I have tested these changes locally
This commit is contained in:
ARYAN
2024-12-28 02:02:08 +05:30
committed by GitHub
5 changed files with 3401 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
OPENAI_API_KEY=
RPC_URL=
SOLANA_PRIVATE_KEY=
POSTGRES_DB_URL=

View File

@@ -0,0 +1,66 @@
# Persistent Agent with PostgreSQL
This example showcases a persistent agent that retains memory across sessions using a PostgreSQL database. It ensures that the agent can remember previous conversations even after being restarted, enhancing the user experience in applications requiring long-term context retention.
[Reference](https://langchain-ai.github.io/langgraphjs/reference/classes/checkpoint_postgres.PostgresSaver.html)
## Key Features
- **Persistent Memory**: The agent stores chat history in a PostgreSQL database, allowing it to remember past interactions across sessions.
- **Seamless Integration**: Designed to integrate smoothly with existing setups.
- **Scalable Solution**: Ideal for applications requiring long-term memory capabilities.
## Prerequisites
To use this feature, ensure you have the following:
1. **PostgreSQL Database URL**: Create and host ur PostgreSQL databse and enter the URL. It will be of the format "postgresql://user:password@localhost:5432/db"
## Before applying persistance
```
Available modes:
1. chat
- Interactive chat mode
2. auto
- Autonomous action mode
Choose a mode (enter number or name: 1
Starting chat mode... Type 'exit' to end.
Prompt: i am arpit
Hello Arpit! How can I assist you today?
Prompt: ^С
® arpitsingh Mac persistance-agent & ts-node index.ts
Starting Agent...
Available modes:
1. chat
- Interactive chat mode
2. auto
- Autonomous action mode
Choose a mode (enter number or name): 1
Starting chat mode... Type 'exit' to end.
Prompt: do u know my name
I don't know your name yet. If you'd like, you can share it.
```
## After applying persistence
```
Available modes:
1. chat
- Interactive chat mode
2. auto
- Autonomous action mode
Choose a mode (enter number or name: 1
Starting chat mode... Type 'exit' to end.
Prompt: i am arpit
Hello Arpit! How can I assist you today?
Prompt: ^С
® arpitsingh Mac persistance-agent & ts-node index.ts
Starting Agent...
Available modes:
1. chat
- Interactive chat mode
2. auto
- Autonomous action mode
Choose a mode (enter number or name): 1
Starting chat mode... Type 'exit' to end.
Prompt: do u know my name
Yes, you mentioned that your name is Arpit. How can I help you today?
```

View File

@@ -0,0 +1,220 @@
import { SolanaAgentKit, createSolanaTools } from "solana-agent-kit";
import { HumanMessage } from "@langchain/core/messages";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
import { ChatOpenAI } from "@langchain/openai";
import * as dotenv from "dotenv";
import * as fs from "fs";
import * as readline from "readline";
import { PostgresSaver } from "@langchain/langgraph-checkpoint-postgres";
dotenv.config();
const checkpointer = PostgresSaver.fromConnString(
process.env.POSTGRES_DB_URL!
);
function validateEnvironment(): void {
const missingVars: string[] = [];
const requiredVars = ["OPENAI_API_KEY", "RPC_URL", "SOLANA_PRIVATE_KEY", "POSTGRES_DB_URL"];
requiredVars.forEach((varName) => {
if (!process.env[varName]) {
missingVars.push(varName);
}
});
if (missingVars.length > 0) {
console.error("Error: Required environment variables are not set");
missingVars.forEach((varName) => {
console.error(`${varName}=your_${varName.toLowerCase()}_here`);
});
process.exit(1);
}
}
validateEnvironment();
const WALLET_DATA_FILE = "wallet_data.txt";
async function initializeAgent() {
try {
const llm = new ChatOpenAI({
modelName: "gpt-4o-mini",
temperature: 0.7,
});
let walletDataStr: string | null = null;
if (fs.existsSync(WALLET_DATA_FILE)) {
try {
walletDataStr = fs.readFileSync(WALLET_DATA_FILE, "utf8");
} catch (error) {
console.error("Error reading wallet data:", error);
}
}
const solanaAgent = new SolanaAgentKit(
process.env.SOLANA_PRIVATE_KEY!,
process.env.RPC_URL,
process.env.OPENAI_API_KEY!,
);
const tools = createSolanaTools(solanaAgent);
await checkpointer.setup();
const config = { configurable: { thread_id: "Solana Agent Kit!" } };
const agent = createReactAgent({
llm,
tools,
checkpointSaver: checkpointer,
messageModifier: `
You are a helpful agent that can interact onchain using the Solana Agent Kit. You are
empowered to interact onchain using your tools. If you ever need funds, you can request them from the
faucet. If not, you can provide your wallet details and request funds from the user. If there is a 5XX
(internal) HTTP error code, ask the user to try again later. If someone asks you to do something you
can't do with your currently available tools, you must say so, and encourage them to implement it
themselves using the Solana Agent Kit, recommend they go to https://www.solanaagentkit.xyz for more information. Be
concise and helpful with your responses. Refrain from restating your tools' descriptions unless it is explicitly requested.
`,
});
if (walletDataStr) {
fs.writeFileSync(WALLET_DATA_FILE, walletDataStr);
}
return { agent, config };
} catch (error) {
console.error("Failed to initialize agent:", error);
throw error;
}
}
async function runAutonomousMode(agent: any, config: any, interval = 10) {
console.log("Starting autonomous mode...");
while (true) {
try {
const thought =
"Be creative and do something interesting on the blockchain. " +
"Choose an action or set of actions and execute it that highlights your abilities.";
const stream = await agent.stream(
{ messages: [new HumanMessage(thought)] },
config,
);
for await (const chunk of stream) {
if ("agent" in chunk) {
console.log(chunk.agent.messages[0].content);
} else if ("tools" in chunk) {
console.log(chunk.tools.messages[0].content);
}
console.log("-------------------");
}
await new Promise((resolve) => setTimeout(resolve, interval * 1000));
} catch (error) {
if (error instanceof Error) {
console.error("Error:", error.message);
}
process.exit(1);
}
}
}
async function runChatMode(agent: any, config: any) {
console.log("Starting chat mode... Type 'exit' to end.");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const question = (prompt: string): Promise<string> =>
new Promise((resolve) => rl.question(prompt, resolve));
try {
while (true) {
const userInput = await question("\nPrompt: ");
if (userInput.toLowerCase() === "exit") {
break;
}
const stream = await agent.stream(
{ messages: [new HumanMessage(userInput)] },
config,
);
for await (const chunk of stream) {
if ("agent" in chunk) {
console.log(chunk.agent.messages[0].content);
} else if ("tools" in chunk) {
console.log(chunk.tools.messages[0].content);
}
console.log("-------------------");
}
}
} catch (error) {
if (error instanceof Error) {
console.error("Error:", error.message);
}
process.exit(1);
} finally {
rl.close();
}
}
async function chooseMode(): Promise<"chat" | "auto"> {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const question = (prompt: string): Promise<string> =>
new Promise((resolve) => rl.question(prompt, resolve));
while (true) {
console.log("\nAvailable modes:");
console.log("1. chat - Interactive chat mode");
console.log("2. auto - Autonomous action mode");
const choice = (await question("\nChoose a mode (enter number or name): "))
.toLowerCase()
.trim();
rl.close();
if (choice === "1" || choice === "chat") {
return "chat";
} else if (choice === "2" || choice === "auto") {
return "auto";
}
console.log("Invalid choice. Please try again.");
}
}
async function main() {
try {
console.log("Starting Agent...");
const { agent, config } = await initializeAgent();
const mode = await chooseMode();
if (mode === "chat") {
await runChatMode(agent, config);
} else {
await runAutonomousMode(agent, config);
}
} catch (error) {
if (error instanceof Error) {
console.error("Error:", error.message);
}
process.exit(1);
}
}
if (require.main === module) {
main().catch((error) => {
console.error("Fatal error:", error);
process.exit(1);
});
}

View File

@@ -0,0 +1,16 @@
{
"name": "persistance-agent",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@langchain/langgraph-checkpoint-postgres": "^0.0.2",
"solana-agent-kit": "^1.3.0"
}
}

3095
examples/persistance-agent/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff