Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add zod for config format - clean coding and refactoring #97

Merged
merged 1 commit into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions auto-agents-framework/.env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,29 @@ MAX_MENTIONS=20
MAX_THREAD_LENGTH=20
MAX_MY_RECENT_TWEETS=10
POST_TWEETS=false
RESPONSE_INTERVAL_MINUTES=26
POST_INTERVAL_MINUTES=30
RESPONSE_INTERVAL_MS=26
POST_INTERVAL_MS=30

# LLM Configuration
LARGE_LLM_MODEL=<large_llm_model>
SMALL_LLM_MODEL=<small_llm_model>
OPENAI_API_KEY=<openai_api_key>

# AutoDrive Configuration
AUTO_DRIVE_API_KEY=<auto_drive_api_key>
AUTO_DRIVE_ENCRYPTION_PASSWORD=<auto_drive_encryption_password>
AUTO_DRIVE_UPLOAD=false

# SC Configuration
# Blockchain Configuration
RPC_URL=<rpc_url>
CONTRACT_ADDRESS=<contract_address>
PRIVATE_KEY=<private_key>

# LLM Configuration
SMALL_LLM_MODEL=gpt-4o-mini
LARGE_LLM_MODEL=<large_llm_model>
OPENAI_API_KEY=<openai_api_key>

# SerpAPI Configuration
SERPAPI_API_KEY=<serpapi_api_key>

# Server Configuration
PORT=<port>

# Environment
NODE_ENV=<node_env>
NODE_ENV=<node_env>

# Retry Limit
RETRY_LIMIT=<retry_limit>
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { config } from '../../../config/index.js';
import { wallet } from './agentWallet.js';
import { cidFromBlakeHash, cidToString } from '@autonomys/auto-dag-data';

const CONTRACT_ADDRESS = config.autoDriveConfig.CONTRACT_ADDRESS as `0x${string}`;
const CONTRACT_ADDRESS = config.blockchainConfig.CONTRACT_ADDRESS as `0x${string}`;

const contract = new ethers.Contract(CONTRACT_ADDRESS, MEMORY_ABI, wallet);

Expand Down
4 changes: 2 additions & 2 deletions auto-agents-framework/src/agents/tools/utils/agentWallet.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { ethers } from 'ethers';
import { config } from '../../../config/index.js';

const provider = new ethers.JsonRpcProvider(config.autoDriveConfig.RPC_URL);
const provider = new ethers.JsonRpcProvider(config.blockchainConfig.RPC_URL);

export const wallet = new ethers.Wallet(config.autoDriveConfig.PRIVATE_KEY as string, provider);
export const wallet = new ethers.Wallet(config.blockchainConfig.PRIVATE_KEY as string, provider);

export async function signMessage(data: object): Promise<string> {
const message = JSON.stringify(data);
Expand Down
110 changes: 63 additions & 47 deletions auto-agents-framework/src/config/index.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,73 @@
import { z } from 'zod';
import dotenv from 'dotenv';
import { configSchema } from './schema.js';
import path from 'path';
import { fileURLToPath } from 'url';
import { dirname } from 'path';

// Get the equivalent of __dirname in ESM
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const __dirname = path.dirname(__filename);

dotenv.config();
dotenv.config({ path: path.resolve(__dirname, '../../.env') });

const twitterConfig = {
USERNAME: process.env.TWITTER_USERNAME || '',
PASSWORD: process.env.TWITTER_PASSWORD || '',
COOKIES_PATH: process.env.TWITTER_COOKIES_PATH || 'cookies.json',
NUM_TIMELINE_TWEETS: Number(process.env.NUM_TIMELINE_TWEETS) || 10,
NUM_FOLLOWING_RECENT_TWEETS: Number(process.env.NUM_FOLLOWING_RECENT_TWEETS) || 10,
NUM_RANDOM_FOLLOWERS: Number(process.env.NUM_RANDOM_FOLLOWERS) || 5,
MAX_MENTIONS: Number(process.env.MAX_MENTIONS) || 5,
MAX_THREAD_LENGTH: Number(process.env.MAX_THREAD_LENGTH) || 20,
MAX_MY_RECENT_TWEETS: Number(process.env.MAX_MY_RECENT_TWEETS) || 10,
POST_TWEETS: process.env.POST_TWEETS === 'true',
RESPONSE_INTERVAL_MS: Number(process.env.RESPONSE_INTERVAL_MINUTES) * 60 * 1000 || 26 * 60 * 1000,
POST_INTERVAL_MS: Number(process.env.POST_INTERVAL_MINUTES) * 60 * 1000 || 30 * 60 * 1000,
};
function formatZodError(error: z.ZodError) {
const missingVars = error.issues.map(issue => {
const path = issue.path.join('.');
return `- ${path}: ${issue.message}`;
});
return `Missing or invalid environment variables:
\n${missingVars.join('\n')}
\nPlease check your .env file and ensure all required variables are set correctly.`;
}

const llmConfig = {
LARGE_LLM_MODEL: process.env.LARGE_LLM_MODEL || 'gpt-4o',
SMALL_LLM_MODEL: process.env.SMALL_LLM_MODEL || 'gpt-4o-mini',
OPENAI_API_KEY: process.env.OPENAI_API_KEY || '',
};
export const config = (() => {
try {
const rawConfig = {
twitterConfig: {
USERNAME: process.env.TWITTER_USERNAME || '',
PASSWORD: process.env.TWITTER_PASSWORD || '',
COOKIES_PATH: process.env.TWITTER_COOKIES_PATH || 'cookies.json',
NUM_TIMELINE_TWEETS: Number(process.env.NUM_TIMELINE_TWEETS) || 10,
NUM_FOLLOWING_RECENT_TWEETS: Number(process.env.NUM_FOLLOWING_RECENT_TWEETS) || 10,
NUM_RANDOM_FOLLOWERS: Number(process.env.NUM_RANDOM_FOLLOWERS) || 5,
MAX_MENTIONS: Number(process.env.MAX_MENTIONS) || 5,
MAX_THREAD_LENGTH: Number(process.env.MAX_THREAD_LENGTH) || 20,
MAX_MY_RECENT_TWEETS: Number(process.env.MAX_MY_RECENT_TWEETS) || 10,
POST_TWEETS: process.env.POST_TWEETS === 'true',
RESPONSE_INTERVAL_MS: (Number(process.env.RESPONSE_INTERVAL_MS) || 26) * 60 * 1000,
POST_INTERVAL_MS: (Number(process.env.POST_INTERVAL_MS) || 30) * 60 * 1000,
},
llmConfig: {
LARGE_LLM_MODEL: process.env.LARGE_LLM_MODEL || 'gpt-4',
SMALL_LLM_MODEL: process.env.SMALL_LLM_MODEL || 'gpt-4-mini',
OPENAI_API_KEY: process.env.OPENAI_API_KEY || '',
},
autoDriveConfig: {
AUTO_DRIVE_API_KEY: process.env.AUTO_DRIVE_API_KEY,
AUTO_DRIVE_ENCRYPTION_PASSWORD: process.env.AUTO_DRIVE_ENCRYPTION_PASSWORD,
AUTO_DRIVE_UPLOAD: process.env.AUTO_DRIVE_UPLOAD === 'true',
},
blockchainConfig: {
RPC_URL: process.env.RPC_URL || undefined,
CONTRACT_ADDRESS: process.env.CONTRACT_ADDRESS || undefined,
PRIVATE_KEY: process.env.PRIVATE_KEY || undefined,
},
SERPAPI_API_KEY: process.env.SERPAPI_API_KEY || '',
NODE_ENV: process.env.NODE_ENV || 'development',
RETRY_LIMIT: Number(process.env.RETRY_LIMIT) || 2,
};

const autoDriveConfig = {
AUTO_DRIVE_API_KEY: process.env.AUTO_DRIVE_API_KEY,
AUTO_DRIVE_ENCRYPTION_PASSWORD: process.env.AUTO_DRIVE_ENCRYPTION_PASSWORD,
AUTO_DRIVE_UPLOAD: process.env.AUTO_DRIVE_UPLOAD === 'true',
// SC Configuration
RPC_URL: process.env.RPC_URL,
CONTRACT_ADDRESS: process.env.CONTRACT_ADDRESS,
PRIVATE_KEY: process.env.PRIVATE_KEY,
WALLET_ADDRESS: process.env.WALLET_ADDRESS,
};
return configSchema.parse(rawConfig);
} catch (error) {
if (error instanceof z.ZodError) {
console.error('\x1b[31m%s\x1b[0m', formatZodError(error));
console.info(
'\x1b[36m%s\x1b[0m',
'\nTip: Copy .env.sample to .env and fill in the required values.',
);
process.exit(1);
}
throw error;
}
})();

export const config = {
twitterConfig,
llmConfig,
autoDriveConfig,

// Server Configuration
PORT: process.env.PORT || 3001,

// Environment
NODE_ENV: process.env.NODE_ENV || 'development',

// RESPONSE CONFIG
RETRY_LIMIT: process.env.RETRY_LIMIT || 2,
};
export type Config = z.infer<typeof configSchema>;
72 changes: 72 additions & 0 deletions auto-agents-framework/src/config/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { z } from 'zod';

const twitterConfigSchema = z.object({
USERNAME: z.string().min(1, 'Twitter username is required'),
PASSWORD: z.string().min(1, 'Twitter password is required'),
COOKIES_PATH: z.string(),
NUM_TIMELINE_TWEETS: z.number().int().positive(),
NUM_FOLLOWING_RECENT_TWEETS: z.number().int().positive(),
NUM_RANDOM_FOLLOWERS: z.number().int().positive(),
MAX_MENTIONS: z.number().int().positive(),
MAX_THREAD_LENGTH: z.number().int().positive(),
MAX_MY_RECENT_TWEETS: z.number().int().positive(),
POST_TWEETS: z.boolean(),
RESPONSE_INTERVAL_MS: z.number().int().positive(),
POST_INTERVAL_MS: z.number().int().positive(),
});

const llmConfigSchema = z.object({
LARGE_LLM_MODEL: z.string().min(1),
SMALL_LLM_MODEL: z.string().min(1),
OPENAI_API_KEY: z.string().min(1, 'OpenAI API key is required'),
});

const autoDriveConfigSchema = z.object({
AUTO_DRIVE_API_KEY: z.string().optional(),
AUTO_DRIVE_ENCRYPTION_PASSWORD: z.string().optional(),
AUTO_DRIVE_UPLOAD: z.boolean(),
});

const blockchainConfigSchema = z.object({
RPC_URL: z.string().optional(),
CONTRACT_ADDRESS: z.union([
z
.string()
.regex(
/^0x[0-9a-fA-F]{40}$/,
"Contract address must be a 42-character string: '0x' prefix followed by 40 hex characters",
)
.refine(
val => val.length === 42,
'Contract address must be exactly 42 characters (0x + 40 hex characters)',
),
z.literal(''),
z.literal(undefined),
]),
PRIVATE_KEY: z.union([
z
.string()
.regex(
/^0x[0-9a-fA-F]{64}$/,
"Private key must be a 66-character string: '0x' prefix followed by 64 hex characters",
)
.refine(
val => val.length === 66,
'Private key must be exactly 66 characters (0x + 64 hex characters)',
),
z.literal(''),
z.literal(undefined),
]),
});

const SERPAPI_API_KEY = z.string().optional();

export const configSchema = z.object({
twitterConfig: twitterConfigSchema,
llmConfig: llmConfigSchema,
autoDriveConfig: autoDriveConfigSchema,
blockchainConfig: blockchainConfigSchema,
SERPAPI_API_KEY: SERPAPI_API_KEY,
NODE_ENV: z.enum(['development', 'production', 'test']),
RETRY_LIMIT: z.number().int().nonnegative(),
});
Loading