mirror of
https://github.com/d0zingcat/solana-agent-kit.git
synced 2026-05-14 23:26:44 +00:00
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:
4
examples/persistance-agent/.env.example
Normal file
4
examples/persistance-agent/.env.example
Normal file
@@ -0,0 +1,4 @@
|
||||
OPENAI_API_KEY=
|
||||
RPC_URL=
|
||||
SOLANA_PRIVATE_KEY=
|
||||
POSTGRES_DB_URL=
|
||||
66
examples/persistance-agent/README.md
Normal file
66
examples/persistance-agent/README.md
Normal 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?
|
||||
```
|
||||
|
||||
220
examples/persistance-agent/index.ts
Normal file
220
examples/persistance-agent/index.ts
Normal 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);
|
||||
});
|
||||
}
|
||||
16
examples/persistance-agent/package.json
Normal file
16
examples/persistance-agent/package.json
Normal 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
3095
examples/persistance-agent/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user