diff --git a/auto-agents-framework/.env.sample b/auto-agents-framework/.env.sample index 913f257d..86bcfbf6 100644 --- a/auto-agents-framework/.env.sample +++ b/auto-agents-framework/.env.sample @@ -36,4 +36,7 @@ SERPAPI_API_KEY= NODE_ENV= # Retry Limit -RETRY_LIMIT= \ No newline at end of file +RETRY_LIMIT= + +# Agent Version +AGENT_VERSION= \ No newline at end of file diff --git a/auto-agents-framework/.gitignore b/auto-agents-framework/.gitignore index 94d8f122..3a17a8a7 100644 --- a/auto-agents-framework/.gitignore +++ b/auto-agents-framework/.gitignore @@ -1,3 +1,4 @@ characters/ !characters/character.example.ts .cookies/ +dsn-kol-schemas.json \ No newline at end of file diff --git a/auto-agents-framework/package.json b/auto-agents-framework/package.json index 1b21cd66..102ab659 100644 --- a/auto-agents-framework/package.json +++ b/auto-agents-framework/package.json @@ -10,7 +10,8 @@ "dev": "tsx watch src/index.ts", "format": "prettier --write \"src/**/*.ts\"", "format:check": "prettier --check \"src/**/*.ts\"", - "example:twitter": "tsx examples/twitter.ts" + "example:twitter": "tsx examples/twitter.ts", + "extract-kol-dsn-schemas": "tsx src/agents/workflows/kol/cli/extractDsnSchemas.ts" }, "dependencies": { "@autonomys/auto-dag-data": "1.2.1", @@ -21,9 +22,10 @@ "@langchain/openai": "0.3.16", "agent-twitter-client": "0.0.18", "dotenv": "^16.3.1", - "ethers": "^6.13.4", + "ethers": "^6.13.4", "winston": "^3.11.0", - "zod": "^3.22.4" + "zod": "^3.22.4", + "zod-to-json-schema": "^3.24.1" }, "devDependencies": { "@tsconfig/node20": "^20.1.4", @@ -33,4 +35,4 @@ "tsx": "^4.7.1", "typescript": "^5.3.3" } -} \ No newline at end of file +} diff --git a/auto-agents-framework/src/agents/tools/utils/dsnUpload.ts b/auto-agents-framework/src/agents/tools/utils/dsnUpload.ts index ba4afa80..51c50fa9 100644 --- a/auto-agents-framework/src/agents/tools/utils/dsnUpload.ts +++ b/auto-agents-framework/src/agents/tools/utils/dsnUpload.ts @@ -2,7 +2,7 @@ import { createLogger } from '../../../utils/logger.js'; import { hexlify } from 'ethers'; import { createAutoDriveApi, uploadFile } from '@autonomys/auto-drive'; import { stringToCid, blake3HashFromCid } from '@autonomys/auto-dag-data'; -import { config } from '../../../config/index.js'; +import { config, agentVersion } from '../../../config/index.js'; import { wallet, signMessage } from './agentWallet.js'; import { setLastMemoryHash, getLastMemoryCid } from './agentMemoryContract.js'; @@ -68,6 +68,7 @@ export async function uploadToDsn(data: object) { data: data, previousCid: previousCid, timestamp: timestamp, + agentVersion: agentVersion, }); const dsnData = { @@ -75,6 +76,7 @@ export async function uploadToDsn(data: object) { previousCid: previousCid, signature: signature, timestamp: timestamp, + agentVersion: agentVersion, }; logger.info('Upload to Dsn - DSN Data', { dsnData }); @@ -93,6 +95,7 @@ export async function uploadToDsn(data: object) { compression: true, password: config.autoDriveConfig.AUTO_DRIVE_ENCRYPTION_PASSWORD || undefined, }); + logger.info('Upload to Dsn - Uploaded CID', { uploadedCid }, stringToCid(uploadedCid)); const blake3hash = blake3HashFromCid(stringToCid(uploadedCid)); logger.info('Setting last memory hash', { diff --git a/auto-agents-framework/src/agents/workflows/kol/cli/extractDsnSchemas.ts b/auto-agents-framework/src/agents/workflows/kol/cli/extractDsnSchemas.ts new file mode 100644 index 00000000..bf8c27b1 --- /dev/null +++ b/auto-agents-framework/src/agents/workflows/kol/cli/extractDsnSchemas.ts @@ -0,0 +1,64 @@ +import { z } from 'zod'; +import { zodToJsonSchema } from 'zod-to-json-schema'; +import { DsnDataType } from '../types.js'; +import { engagementSchema, responseSchema, skippedEngagementSchema, dsnTweet } from '../schemas.js'; +import { writeFileSync } from 'fs'; +import { join } from 'path'; + +const dsnCommonFields = z.object({ + previousCid: z.string().optional(), + signature: z.string(), + timestamp: z.string(), + agentVersion: z.string(), +}); + +const extractDsnResponseSchema = () => { + const schema = z + .object({ + type: z.literal(DsnDataType.RESPONSE), + tweet: dsnTweet, + decision: engagementSchema, + }) + .merge(responseSchema) + .merge(dsnCommonFields); + + return zodToJsonSchema(schema); +}; + +const extractDsnSkippedEngagementSchema = () => { + const schema = z + .object({ + type: z.literal(DsnDataType.SKIPPED_ENGAGEMENT), + tweet: dsnTweet, + }) + .merge(skippedEngagementSchema) + .merge(dsnCommonFields); + + return zodToJsonSchema(schema); +}; + +const extractDsnGeneratedTweetSchema = () => { + const schema = z + .object({ + type: z.literal(DsnDataType.GENERATED_TWEET), + content: z.string(), + tweetId: z.string().nullable(), + }) + .merge(dsnCommonFields); + + return zodToJsonSchema(schema); +}; + +const main = () => { + const schemas = { + response: extractDsnResponseSchema(), + skipped_engagement: extractDsnSkippedEngagementSchema(), + generated_tweet: extractDsnGeneratedTweetSchema(), + }; + + const outputPath = join(process.cwd(), 'dsn-kol-schemas.json'); + writeFileSync(outputPath, JSON.stringify(schemas, null, 2)); + console.log(`Schemas written to ${outputPath}`); +}; + +main(); diff --git a/auto-agents-framework/src/agents/workflows/kol/nodes/engagementNode.ts b/auto-agents-framework/src/agents/workflows/kol/nodes/engagementNode.ts index eabe844e..a21b8d7e 100644 --- a/auto-agents-framework/src/agents/workflows/kol/nodes/engagementNode.ts +++ b/auto-agents-framework/src/agents/workflows/kol/nodes/engagementNode.ts @@ -50,6 +50,17 @@ export const createEngagementNode = (config: WorkflowConfig) => { text: tweet.text!, username: tweet.username!, timeParsed: tweet.timeParsed!, + thread: + tweet.thread && tweet.thread.length > 0 + ? Array.from( + tweet.thread.map(t => ({ + id: t.id, + text: t.text, + username: t.username, + timeParsed: t.timeParsed, + })), + ) + : 'No thread', }, decision, }; diff --git a/auto-agents-framework/src/agents/workflows/kol/nodes/generateTweetNode.ts b/auto-agents-framework/src/agents/workflows/kol/nodes/generateTweetNode.ts index 4fe0b7cc..67410451 100644 --- a/auto-agents-framework/src/agents/workflows/kol/nodes/generateTweetNode.ts +++ b/auto-agents-framework/src/agents/workflows/kol/nodes/generateTweetNode.ts @@ -1,4 +1,12 @@ -import { EngagementDecision, WorkflowConfig } from '../types.js'; +import { + DsnData, + DsnGeneratedTweetData, + DsnResponseData, + DsnSkippedEngagementData, + EngagementDecision, + WorkflowConfig, + DsnDataType, +} from '../types.js'; import { createLogger } from '../../../../utils/logger.js'; import { State } from '../workflow.js'; import { invokePostTweetTool } from '../../../tools/postTweetTool.js'; @@ -13,26 +21,30 @@ const postResponse = async ( decision: EngagementDecision, summary: z.infer, ) => { - const thread = - decision.tweet.thread && decision.tweet.thread.length > 0 - ? decision.tweet.thread.map(t => ({ text: t.text, username: t.username })) - : 'No thread'; - const decisionInfo = { tweet: decision.tweet.text, reason: decision.decision.reason }; + const engagementDecision = { + tweetText: decision.tweet.text, + reason: decision.decision.reason, + }; const response = await config.prompts.responsePrompt .pipe(config.llms.generation) .pipe(responseParser) .invoke({ - decision: decisionInfo, - thread, + decision: engagementDecision, + thread: decision.tweet.thread, patterns: summary.patterns, commonWords: summary.commonWords, }); //TODO: After sending the tweet, we need to get the latest tweet, ensure it is the same as we sent and return it //This has not been working as expected, so we need to investigate this later - const tweet = await invokePostTweetTool(config.toolNode, response.content, decision.tweet.id); + const postedResponse = await invokePostTweetTool( + config.toolNode, + response.content, + decision.tweet.id, + ); return { ...response, - decisionInfo: decisionInfo, + tweet: decision.tweet, + engagementDecision, //tweetId: tweet ? tweet.id : null }; }; @@ -63,9 +75,7 @@ export const createGenerateTweetNode = id: d.tweet.id, text: d.tweet.text, username: d.tweet.username, - thread: d.tweet.thread - ? d.tweet.thread.map(t => ({ id: t.id, text: t.text, username: t.username })) - : 'No thread', + thread: d.tweet.thread, }, })); logger.info('Engagement Decisions', { @@ -90,24 +100,33 @@ export const createGenerateTweetNode = const postedTweet = await invokePostTweetTool(config.toolNode, generatedTweet.tweet); // Transform the data into an array format expected by DSN - const formattedDsnData = [ - ...postedResponses.map(response => ({ - type: 'response', - content: response.content, - decisionInfo: response.decisionInfo, - //tweetId: response.tweetId, - strategy: response.strategy, - })), - ...shouldNotEngage.map(item => ({ - type: 'skipped_engagement', - decision: item.decision, - tweet: item.tweet, - })), + const formattedDsnData: DsnData[] = [ + ...postedResponses.map( + response => + ({ + type: DsnDataType.RESPONSE, + tweet: response.tweet, + content: response.content, + strategy: response.strategy, + decision: { + shouldEngage: true, + reason: response.engagementDecision.reason, + }, + }) as DsnResponseData, + ), + ...shouldNotEngage.map( + item => + ({ + type: DsnDataType.SKIPPED_ENGAGEMENT, + decision: item.decision, + tweet: item.tweet, + }) as DsnSkippedEngagementData, + ), { - type: 'generated_tweet', + type: DsnDataType.GENERATED_TWEET, content: generatedTweet.tweet, tweetId: postedTweet ? postedTweet.postedTweetId : null, - }, + } as DsnGeneratedTweetData, ]; return { diff --git a/auto-agents-framework/src/agents/workflows/kol/schemas.ts b/auto-agents-framework/src/agents/workflows/kol/schemas.ts index 44a8b26e..528ba3a6 100644 --- a/auto-agents-framework/src/agents/workflows/kol/schemas.ts +++ b/auto-agents-framework/src/agents/workflows/kol/schemas.ts @@ -1,27 +1,31 @@ import { z } from 'zod'; +export const dsnTweet: z.ZodType = z.object({ + id: z.string(), + text: z.string(), + username: z.string(), + timeParsed: z.string(), + thread: z.array(z.lazy(() => dsnTweet)).optional(), +}); + export const engagementSchema = z.object({ shouldEngage: z.boolean(), - reason: z.string(), + reason: z.string().optional(), }); export const responseSchema = z.object({ content: z.string().describe('The response to the tweet'), strategy: z.string().describe('The strategy used to generate the response'), - thread: z - .array( - z.object({ - id: z.string(), - text: z.string(), - username: z.string(), - }), - ) - .optional(), +}); + +export const skippedEngagementSchema = z.object({ + decision: engagementSchema, }); export const dsnUploadSchema = z.object({ - previousCid: z.string().optional(), data: z.any(), + signature: z.string(), + previousCid: z.string().optional(), }); export const trendSchema = z.object({ diff --git a/auto-agents-framework/src/agents/workflows/kol/types.ts b/auto-agents-framework/src/agents/workflows/kol/types.ts index b397d9ed..73a28cfb 100644 --- a/auto-agents-framework/src/agents/workflows/kol/types.ts +++ b/auto-agents-framework/src/agents/workflows/kol/types.ts @@ -5,9 +5,14 @@ import { Tweet, TwitterApi } from '../../../services/twitter/types.js'; import { ToolNode } from '@langchain/langgraph/prebuilt'; import { ChatOpenAI } from '@langchain/openai'; import { Runnable } from '@langchain/core/runnables'; -import { engagementSchema } from './schemas.js'; +import { engagementSchema, responseSchema, skippedEngagementSchema, dsnTweet } from './schemas.js'; import { ChatPromptTemplate } from '@langchain/core/prompts'; +export enum DsnDataType { + RESPONSE = 'response', + SKIPPED_ENGAGEMENT = 'skipped', + GENERATED_TWEET = 'posted', +} export type WorkflowConfig = Readonly<{ twitterApi: TwitterApi; toolNode: ToolNode; @@ -30,3 +35,22 @@ export type EngagementDecision = { decision: z.infer; tweet: Tweet; }; + +export type DsnResponseData = { + type: DsnDataType.RESPONSE; + tweet: z.infer; + decision: z.infer; +} & z.infer; + +export type DsnSkippedEngagementData = { + type: DsnDataType.SKIPPED_ENGAGEMENT; + tweet: z.infer; +} & z.infer; + +export type DsnGeneratedTweetData = { + type: DsnDataType.GENERATED_TWEET; + content: string; + tweetId: string | null; +}; + +export type DsnData = DsnResponseData | DsnSkippedEngagementData | DsnGeneratedTweetData; diff --git a/auto-agents-framework/src/config/index.ts b/auto-agents-framework/src/config/index.ts index 55d16183..4197a210 100644 --- a/auto-agents-framework/src/config/index.ts +++ b/auto-agents-framework/src/config/index.ts @@ -28,6 +28,8 @@ function formatZodError(error: z.ZodError) { \nPlease check your .env file and ensure all required variables are set correctly.`; } +export const agentVersion = process.env.AGENT_VERSION || '1.0.0'; + export const config = (() => { try { const username = process.env.TWITTER_USERNAME || ''; diff --git a/auto-agents-framework/yarn.lock b/auto-agents-framework/yarn.lock index d06cdf75..8e88dedb 100644 --- a/auto-agents-framework/yarn.lock +++ b/auto-agents-framework/yarn.lock @@ -1971,7 +1971,7 @@ yaml@^2.2.1: resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.6.1.tgz#42f2b1ba89203f374609572d5349fb8686500773" integrity sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg== -zod-to-json-schema@^3.22.3, zod-to-json-schema@^3.22.5: +zod-to-json-schema@^3.22.3, zod-to-json-schema@^3.22.5, zod-to-json-schema@^3.24.1: version "3.24.1" resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.24.1.tgz#f08c6725091aadabffa820ba8d50c7ab527f227a" integrity sha512-3h08nf3Vw3Wl3PK+q3ow/lIil81IT2Oa7YpQyUUDsEWbXveMesdfK1xBd2RhCkynwZndAxixji/7SYJJowr62w==