Skip to content

Commit

Permalink
Merge pull request #21 from autonomys/auto-chain-enhance
Browse files Browse the repository at this point in the history
Auto chain agent -> memory enabled + enhanced frontend
  • Loading branch information
Xm0onh authored Nov 22, 2024
2 parents 7eb18eb + 5f85618 commit 6128e28
Show file tree
Hide file tree
Showing 27 changed files with 1,963 additions and 1,041 deletions.
4 changes: 3 additions & 1 deletion auto-chain-agent/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ Thumbs.db
# Database
*.sqlite
*.sqlite3

summary-differences.json
summary-*.json
diffs/
# Test coverage
coverage
35 changes: 31 additions & 4 deletions auto-chain-agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,40 @@ A blockchain interaction service that provides natural language processing capab
## Features

- Natural language processing for blockchain interactions
- Balance checking
- Transaction sending
- Transaction history retrieval
- Memory-enabled conversations with context retention
- Balance checking and transaction management
- Wallet management
- Transaction history tracking

## Available Tools

The agent supports the following blockchain operations:

- `get_balance`: Check the balance of a blockchain address
- `send_transaction`: Send tokens to another address
- `send_transaction`: Send tokens to another address (supports AI3 token transfers)
- `get_transaction_history`: Retrieve transaction history and account information

The service consists of three main components:

- **Agents**: Core blockchain interaction and natural language processing
- **Backend**: API service for handling requests
- **Frontend**: User interface for interacting with the agent

## Setup

### Prerequisites

- Node.js (v18 or higher)
- Yarn package manager
- SQLite3

### Environment Variables

Create `.env` files in the respective directories following the `.env.example` file.

### Running the Services

```bash
yarn install
yarn dev
```
3 changes: 2 additions & 1 deletion auto-chain-agent/agents/.env.sample
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
OPENAI_API_KEY=<your-openai-api-key>
ANTHROPIC_API_KEY=<your-anthropic-api-key>
TEST_MNEMONIC=<test-mnemonic>
AGENT_KEY=<mnemonic>
AGENTS_PORT=3000
DSN_API_KEY=<your-dsn-api-key>
6 changes: 3 additions & 3 deletions auto-chain-agent/agents/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
"start": "node dist/index.js"
},
"dependencies": {
"@autonomys/auto-consensus": "0.7.3",
"@autonomys/auto-drive": "0.7.3",
"@autonomys/auto-utils": "0.7.3",
"@autonomys/auto-consensus": "1.0.9",
"@autonomys/auto-drive": "1.0.9",
"@autonomys/auto-utils": "1.0.9",
"@langchain/anthropic": "0.3.7",
"@langchain/community": "0.3.11",
"@langchain/core": "0.3.3",
Expand Down
17 changes: 10 additions & 7 deletions auto-chain-agent/agents/src/config/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import dotenv from 'dotenv';
import path from 'path';

dotenv.config();

export const config = {
CHECK_INTERVAL: 20 * 1000,
SUMMARY_DIR: path.join(process.cwd(), 'diffs'),
SUMMARY_FILE_PATH : path.join(process.cwd(), 'diffs', 'summary-differences.json'),
DIFF_FILE_PREFIX: 'summary-diff',
LLM_MODEL: "gpt-4o-mini",
TEMPERATURE: 0.5,
AGENT_KEY: process.env.AGENT_KEY || '//Alice',
NETWORK: 'taurus',
port: process.env.AGENTS_PORT || 3000,
anthropicApiKey: process.env.ANTHROPIC_API_KEY,
openaiApiKey: process.env.OPENAI_API_KEY,
environment: process.env.NODE_ENV || 'development',
autoConsensus: {
apiKey: process.env.AUTO_CONSENSUS_API_KEY,
},
llmConfig: {
temperature: 0.4,
maxTokens: 1500
}
dsnApiKey: process.env.DSN_API_KEY,
};
65 changes: 51 additions & 14 deletions auto-chain-agent/agents/src/services/chainAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import { ToolNode } from "@langchain/langgraph/prebuilt";
import { blockchainTools } from './tools';
import { config } from "../config/index";
import logger from "../logger";
import { createThreadStorage } from "./threadStorage";

// Define state schema for the graph
import { startWithHistory } from "./utils";
import { loadThreadSummary, startSummarySystem } from './thread/summarySystem';
import { createThreadStorage } from './thread/threadStorage';
import { ConversationState } from './thread/interface';
const StateAnnotation = Annotation.Root({
messages: Annotation<BaseMessage[]>({
reducer: (curr, prev) => [...curr, ...prev],
Expand All @@ -22,23 +23,26 @@ const StateAnnotation = Annotation.Root({
};
result?: string;
}>>({
reducer: (_, next) => next, // Only keep current interaction's tool calls
reducer: (_, next) => next,
default: () => [],
})
});

// Initialize core components
const model = new ChatOpenAI({
openAIApiKey: config.openaiApiKey,
modelName: "gpt-4o-mini",
temperature: 0.7,
modelName: config.LLM_MODEL,
temperature: config.TEMPERATURE,
}).bindTools(blockchainTools);

const threadStorage = createThreadStorage();
const checkpointer = new MemorySaver();
const toolNode = new ToolNode(blockchainTools);

// Define node functions
const conversationState: ConversationState = {
isInitialLoad: true,
needsHistoryRebuild: false
};

const agentNode = async (state: typeof StateAnnotation.State) => {
try {
const systemMessage = new SystemMessage({
Expand All @@ -47,6 +51,23 @@ const agentNode = async (state: typeof StateAnnotation.State) => {
- When blockchain operations are needed, you can check balances and perform transactions`
});

if (conversationState.needsHistoryRebuild) {
await startWithHistory().then(async () => {
const prevMessages = (await loadThreadSummary())
.map((content: string) => new HumanMessage({ content }));
state.messages = [...state.messages, ...prevMessages];
conversationState.isInitialLoad = false;
});
conversationState.needsHistoryRebuild = false;
}

if (conversationState.isInitialLoad) {
const prevMessages = (await loadThreadSummary())
.map(content => new HumanMessage({ content }));
state.messages = [...state.messages, ...prevMessages];
conversationState.isInitialLoad = false;
}

const messages = [systemMessage, ...state.messages];
const response = await model.invoke(messages);

Expand Down Expand Up @@ -108,7 +129,6 @@ const shouldContinue = (state: typeof StateAnnotation.State) => {
return 'agent';
};

// Create and initialize the graph
const createBlockchainGraph = async () => {
try {
return new StateGraph(StateAnnotation)
Expand All @@ -124,18 +144,17 @@ const createBlockchainGraph = async () => {
}
};

// Initialize graph
let agentGraph: Awaited<ReturnType<typeof createBlockchainGraph>>;
(async () => {
try {
agentGraph = await createBlockchainGraph();
logger.info('Blockchain agent initialized successfully');
await startSummarySystem();
logger.info('Blockchain agent and summary system initialized successfully');
} catch (error) {
logger.error('Failed to initialize blockchain agent:', error);
}
})();

// Export the agent interface
export const blockchainAgent = {
async handleMessage({ message, threadId }: { message: string; threadId?: string }) {
try {
Expand All @@ -145,14 +164,32 @@ export const blockchainAgent = {

const currentThreadId = threadId || `blockchain_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
const previousState = threadId ? await threadStorage.loadThread(threadId) : null;


let relevantContext: BaseMessage[] = [];
if (!previousState || previousState.messages.length <= 2) {
const summaries = await loadThreadSummary();
if (summaries.length > 0) {
relevantContext = [new SystemMessage({
content: `Previous conversations context: ${
summaries
.slice(0, 100)
.map(summary => summary.trim())
.join(' | ')
}`
})];
}
}
logger.info(`Relevant context: ${relevantContext}`);
const initialState = {
messages: previousState ? [
...previousState.messages,
new HumanMessage({ content: message })
] : [
...relevantContext,
new SystemMessage({
content: `You are a helpful AI assistant. You can engage in general conversation and also help with blockchain operations like checking balances and performing transactions.`
content: `
You are a helpful AI assistant.
You can engage in general conversation and also help with blockchain operations like checking balances and performing transactions.`
}),
new HumanMessage({ content: message })
]
Expand Down
30 changes: 30 additions & 0 deletions auto-chain-agent/agents/src/services/thread/db.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import sqlite3 from 'sqlite3';
import { open } from 'sqlite';
import logger from '../../logger';

export const initializeDb = async (dbPath: string) => {
logger.info('Initializing SQLite database at:', dbPath);
const db = await open({
filename: dbPath,
driver: sqlite3.Database
});

await db.exec(`
CREATE TABLE IF NOT EXISTS threads (
thread_id TEXT PRIMARY KEY,
messages TEXT NOT NULL,
tool_calls TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS summary_uploads (
id INTEGER PRIMARY KEY AUTOINCREMENT,
upload_id TEXT NOT NULL,
CID TEXT NULL,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
);
`);

return db;
};
3 changes: 3 additions & 0 deletions auto-chain-agent/agents/src/services/thread/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { createThreadStorage } from './threadStorage';

export type ThreadStorage = ReturnType<typeof createThreadStorage>;
33 changes: 33 additions & 0 deletions auto-chain-agent/agents/src/services/thread/interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { BaseMessage, MessageContent } from '@langchain/core/messages';

export interface ThreadState {
messages: BaseMessage[];
toolCalls: Array<{
id: string;
type: string;
function: {
name: string;
arguments: string;
};
result?: string;
}>;
}
export interface ConversationState {
isInitialLoad: boolean;
needsHistoryRebuild: boolean;
}

export interface SummaryDifference {
timestamp: string;
threadId: string;
previousSummary: string | MessageContent;
currentSummary: string | MessageContent;
difference: string | MessageContent;
previousCID?: string;
}

export interface SummaryState {
lastCheck: string;
differences: SummaryDifference[];
}

67 changes: 67 additions & 0 deletions auto-chain-agent/agents/src/services/thread/summaryState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import fs from 'fs';
import logger from '../../logger';
import { SummaryState } from './interface';
import { saveDiffFile } from './utils';
import { config } from '../../config';

export const loadSummaryState = async (): Promise<SummaryState> => {
try {
if (fs.existsSync(config.SUMMARY_FILE_PATH)) {
const data = await fs.promises.readFile(config.SUMMARY_FILE_PATH, 'utf8');
return JSON.parse(data);
}
return {
lastCheck: new Date().toISOString(),
differences: []
};
} catch (error) {
logger.error('Error loading summary state:', error);
return {
lastCheck: new Date().toISOString(),
differences: []
};
}
};

export const saveSummaryState = async (state: SummaryState) => {
try {
const existingState = await (async (): Promise<SummaryState | null> => {
if (fs.existsSync(config.SUMMARY_FILE_PATH)) {
const existingData = await fs.promises.readFile(config.SUMMARY_FILE_PATH, 'utf8');
return JSON.parse(existingData);
}
return null;
})();

// Save main summary file
await fs.promises.writeFile(
config.SUMMARY_FILE_PATH,
JSON.stringify(state, null, 2),
'utf8'
);

// Only create and upload diff file if there are actual changes
if (!existingState ||
JSON.stringify(existingState.differences) !== JSON.stringify(state.differences)) {

// Get new differences since last state
const newDifferences = existingState
? state.differences.filter(diff =>
!existingState.differences.some(
existingDiff => existingDiff.timestamp === diff.timestamp
)
)
: state.differences;

if (newDifferences.length > 0) {
await saveDiffFile(newDifferences);
}
} else {
logger.info('No changes detected in summary differences, skipping diff file creation');
}
} catch (error) {
logger.error('Error saving summary state:', error);
throw error;
}
};

Loading

0 comments on commit 6128e28

Please sign in to comment.