From 9a1af251ec1bece4ca9d2d6d9050220c0cc84920 Mon Sep 17 00:00:00 2001 From: Harish Kotra Date: Thu, 6 Mar 2025 12:23:18 -0700 Subject: [PATCH 1/4] Added integration: Use your own Gaia node with XMTP --- integrations/gaia/.env.example | 4 + integrations/gaia/README.md | 149 +++++++++++++++++++++++++++++++++ integrations/gaia/index.ts | 120 ++++++++++++++++++++++++++ integrations/gaia/package.json | 20 +++++ yarn.lock | 11 +++ 5 files changed, 304 insertions(+) create mode 100644 integrations/gaia/.env.example create mode 100644 integrations/gaia/README.md create mode 100644 integrations/gaia/index.ts create mode 100644 integrations/gaia/package.json diff --git a/integrations/gaia/.env.example b/integrations/gaia/.env.example new file mode 100644 index 0000000..58ab873 --- /dev/null +++ b/integrations/gaia/.env.example @@ -0,0 +1,4 @@ +#GAIA Integration +GAIA_NODE_URL= # the URL of the Gaia node (Ex: https://llama8b.gaia.domains/v1) +GAIA_API_KEY= # the API key for the Gaia node (Ex: gaia-xxxxxxxxxx) +GAIA_MODEL_NAME= # the name of model running on your Gaia node (Ex: llama) \ No newline at end of file diff --git a/integrations/gaia/README.md b/integrations/gaia/README.md new file mode 100644 index 0000000..278c7fd --- /dev/null +++ b/integrations/gaia/README.md @@ -0,0 +1,149 @@ +# GPT agent example + +This example uses a [Gaia](https://docs.gaianet.ai) API for AI based responses and [XMTP](https://xmtp.org) for secure messaging. You can test your agent on [xmtp.chat](https://xmtp.chat) or any other XMTP-compatible client. + +Using Gaia, you can also run your own [node](https://docs.gaianet.ai/getting-started/quick-start) and use the OpenAI compatible API in this library. + +## Environment variables + +Add the following keys to a `.env` file: + +```bash +WALLET_KEY= # the private key of the wallet +ENCRYPTION_KEY= # a second random 32 bytes encryption key for local db encryption +GAIA_API_KEY= # Your API key from https://gaianet.ai +GAIA_NODE_URL= # Your custom Gaia node URL or a public node, ex: https://llama8b.gaia.domains/v1 +GAIA_MODEL_NAME= # Model name running in your Gaia node or a public node, ex: llama +``` + +You can generate random keys with the following command: + +```bash +yarn gen:keys +``` + +> [!WARNING] +> Running the `gen:keys` script will overwrite the existing `.env` file. + +## Usage + +```tsx +import { Client, type XmtpEnv } from "@xmtp/node-sdk"; +import OpenAI from "openai"; +import { createSigner, getEncryptionKeyFromHex } from "@/helpers"; + +const { WALLET_KEY, ENCRYPTION_KEY, GAIA_NODE_URL, GAIA_API_KEY, GAIA_MODEL_NAME } = process.env; + +if (!WALLET_KEY) { + throw new Error("WALLET_KEY must be set"); +} + +if (!ENCRYPTION_KEY) { + throw new Error("ENCRYPTION_KEY must be set"); +} + +/* Check if the Gaia API key is set */ +if (!GAIA_API_KEY) { + throw new Error("GAIA_API_KEY must be set"); +} + +/* Check if the Gaia node's base URL is set */ +if (!GAIA_NODE_URL) { + throw new Error("GAIA_NODE_URL must be set"); +} + +/* Check if the the model name for the Gaia node is set */ +if (!GAIA_MODEL_NAME) { + throw new Error("GAIA_MODEL_NAME must be set"); +} + +const signer = createSigner(WALLET_KEY); +const encryptionKey = getEncryptionKeyFromHex(ENCRYPTION_KEY); +const openai = new OpenAI({ + baseURL: GAIA_NODE_URL, + apiKey: GAIA_API_KEY +}); + +/* Set the environment to dev or production */ +const env: XmtpEnv = "dev"; + +async function main() { + console.log(`Creating client on the '${env}' network...`); + const client = await Client.create(signer, encryptionKey, { + env, + }); + + console.log("Syncing conversations..."); + /* Sync the conversations from the network to update the local db */ + await client.conversations.sync(); + + console.log( + `Agent initialized on ${client.accountAddress}\nSend a message on http://xmtp.chat/dm/${client.accountAddress}`, + ); + + console.log("Waiting for messages..."); + const stream = client.conversations.streamAllMessages(); + + for await (const message of await stream) { + /* Ignore messages from the same agent or non-text messages */ + if ( + message?.senderInboxId.toLowerCase() === client.inboxId.toLowerCase() || + message?.contentType?.typeId !== "text" + ) { + continue; + } + + console.log( + `Received message: ${message.content as string} by ${message.senderInboxId}`, + ); + + const conversation = client.conversations.getConversationById( + message.conversationId, + ); + + if (!conversation) { + console.log("Unable to find conversation, skipping"); + continue; + } + + try { + const completion = await openai.chat.completions.create({ + messages: [{ role: "user", content: message.content as string }], + model: GAIA_MODEL_NAME, + }); + + /* Get the AI response */ + const response = + completion.choices[0]?.message?.content || + "I'm not sure how to respond to that."; + + console.log(`Sending AI response: ${response}`); + await conversation.send(response); + } catch (error) { + console.error("Error getting AI response:", error); + await conversation.send( + "Sorry, I encountered an error processing your message.", + ); + } + + console.log("Waiting for messages..."); + } +} + +main().catch(console.error); +``` + +## Run the agent + +```bash +# git clone repo +git clone https://github.com/ephemeraHQ/xmtp-agent-examples.git +# go to the folder +cd xmtp-agent-examples +# install packages +yarn +# generate random keys (optional) +yarn gen:keys +# run the example +yarn examples:gpt +``` diff --git a/integrations/gaia/index.ts b/integrations/gaia/index.ts new file mode 100644 index 0000000..8b43bc4 --- /dev/null +++ b/integrations/gaia/index.ts @@ -0,0 +1,120 @@ +import { Client, type XmtpEnv } from "@xmtp/node-sdk"; +import OpenAI from "openai"; +import { createSigner, getEncryptionKeyFromHex } from "@/helpers"; + +/* Get the wallet key associated to the public key of + * the agent and the encryption key for the local db + * that stores your agent's messages */ +const { WALLET_KEY, ENCRYPTION_KEY, GAIA_NODE_URL, GAIA_API_KEY, GAIA_MODEL_NAME } = process.env; + +/* Check if the environment variables are set */ +if (!WALLET_KEY) { + throw new Error("WALLET_KEY must be set"); +} + +/* Check if the encryption key is set */ +if (!ENCRYPTION_KEY) { + throw new Error("ENCRYPTION_KEY must be set"); +} + +/* Check if the OpenAI API key is set */ +if (!GAIA_API_KEY) { + throw new Error("GAIA_API_KEY must be set"); +} + +/* Check if the Gaia node's base URL is set */ +if (!GAIA_NODE_URL) { + throw new Error("GAIA_NODE_URL must be set"); +} + +/* Check if the the model name for the Gaia node is set */ +if (!GAIA_MODEL_NAME) { + throw new Error("GAIA_MODEL_NAME must be set"); +} + +/* Create the signer using viem and parse the encryption key for the local db */ +const signer = createSigner(WALLET_KEY); +const encryptionKey = getEncryptionKeyFromHex(ENCRYPTION_KEY); + +/* Initialize the OpenAI client */ +const openai = new OpenAI({ + baseURL: GAIA_NODE_URL, + apiKey: GAIA_API_KEY +}); + +/* Set the environment to dev or production */ +const env: XmtpEnv = "dev"; + +/** + * Main function to run the agent + */ +async function main() { + console.log(`Creating client on the '${env}' network...`); + /* Initialize the xmtp client */ + const client = await Client.create(signer, encryptionKey, { + env, + }); + + console.log("Syncing conversations..."); + /* Sync the conversations from the network to update the local db */ + await client.conversations.sync(); + + console.log( + `Agent initialized on ${client.accountAddress}\nSend a message on http://xmtp.chat/dm/${client.accountAddress}`, + ); + + console.log("Waiting for messages..."); + /* Stream all messages from the network */ + const stream = client.conversations.streamAllMessages(); + + for await (const message of await stream) { + /* Ignore messages from the same agent or non-text messages */ + if ( + message?.senderInboxId.toLowerCase() === client.inboxId.toLowerCase() || + message?.contentType?.typeId !== "text" + ) { + continue; + } + + console.log( + `Received message: ${message.content as string} by ${message.senderInboxId}`, + ); + + /* Get the conversation from the local db */ + const conversation = client.conversations.getConversationById( + message.conversationId, + ); + + /* If the conversation is not found, skip the message */ + if (!conversation) { + console.log("Unable to find conversation, skipping"); + continue; + } + + try { + /* Get the AI response */ + const completion = await openai.chat.completions.create({ + messages: [{ role: "user", content: message.content as string }], + model: GAIA_MODEL_NAME, + }); + + /* Get the AI response */ + const response = + completion.choices[0]?.message?.content || + "I'm not sure how to respond to that."; + + console.log(`Sending AI response: ${response}`); + /* Send the AI response to the conversation */ + await conversation.send(response); + } catch (error) { + console.error("Error getting AI response:", error); + await conversation.send( + "Sorry, I encountered an error processing your message.", + ); + } + + console.log("Waiting for messages..."); + } +} + +main().catch(console.error); diff --git a/integrations/gaia/package.json b/integrations/gaia/package.json new file mode 100644 index 0000000..f733b29 --- /dev/null +++ b/integrations/gaia/package.json @@ -0,0 +1,20 @@ +{ + "name": "@integrations/gaia", + "version": "0.0.1", + "private": true, + "type": "module", + "scripts": { + "dev": "tsx --env-file=.env index.ts", + "gen:keys": "tsx ../../scripts/generateKeys.ts" + }, + "dependencies": { + "@xmtp/node-sdk": "0.0.42", + "tsx": "^4.19.2", + "uint8arrays": "^5.1.0", + "viem": "^2.22.17" + }, + "packageManager": "yarn@4.6.0", + "engines": { + "node": ">=20" + } +} diff --git a/yarn.lock b/yarn.lock index c1e12f0..81598df 100644 --- a/yarn.lock +++ b/yarn.lock @@ -808,6 +808,17 @@ __metadata: languageName: node linkType: hard +"@integrations/gaia@workspace:integrations/gaia": + version: 0.0.0-use.local + resolution: "@integrations/gaia@workspace:integrations/gaia" + dependencies: + "@xmtp/node-sdk": "npm:0.0.42" + tsx: "npm:^4.19.2" + uint8arrays: "npm:^5.1.0" + viem: "npm:^2.22.17" + languageName: unknown + linkType: soft + "@integrations/gpt@workspace:integrations/grok": version: 0.0.0-use.local resolution: "@integrations/gpt@workspace:integrations/grok" From f6e55a3d26b9a32bdabee6b04dd08541f38b7093 Mon Sep 17 00:00:00 2001 From: Harish Kotra Date: Sat, 8 Mar 2025 07:21:18 +0530 Subject: [PATCH 2/4] Update README.md Updated details to match with `integrations/gaia` --- integrations/gaia/README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/integrations/gaia/README.md b/integrations/gaia/README.md index 278c7fd..b3bdbca 100644 --- a/integrations/gaia/README.md +++ b/integrations/gaia/README.md @@ -1,4 +1,4 @@ -# GPT agent example +# Gaia agent example This example uses a [Gaia](https://docs.gaianet.ai) API for AI based responses and [XMTP](https://xmtp.org) for secure messaging. You can test your agent on [xmtp.chat](https://xmtp.chat) or any other XMTP-compatible client. @@ -142,8 +142,7 @@ git clone https://github.com/ephemeraHQ/xmtp-agent-examples.git cd xmtp-agent-examples # install packages yarn -# generate random keys (optional) -yarn gen:keys -# run the example -yarn examples:gpt + +cd integrations/gaia +yarn dev ``` From a506e25f51dff39590a27a7a9c463070606f6e1b Mon Sep 17 00:00:00 2001 From: fabri Date: Mon, 10 Mar 2025 20:47:23 -0300 Subject: [PATCH 3/4] update linter --- integrations/gaia/index.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/integrations/gaia/index.ts b/integrations/gaia/index.ts index 8b43bc4..22b97c4 100644 --- a/integrations/gaia/index.ts +++ b/integrations/gaia/index.ts @@ -5,7 +5,13 @@ import { createSigner, getEncryptionKeyFromHex } from "@/helpers"; /* Get the wallet key associated to the public key of * the agent and the encryption key for the local db * that stores your agent's messages */ -const { WALLET_KEY, ENCRYPTION_KEY, GAIA_NODE_URL, GAIA_API_KEY, GAIA_MODEL_NAME } = process.env; +const { + WALLET_KEY, + ENCRYPTION_KEY, + GAIA_NODE_URL, + GAIA_API_KEY, + GAIA_MODEL_NAME, +} = process.env; /* Check if the environment variables are set */ if (!WALLET_KEY) { @@ -37,9 +43,9 @@ const signer = createSigner(WALLET_KEY); const encryptionKey = getEncryptionKeyFromHex(ENCRYPTION_KEY); /* Initialize the OpenAI client */ -const openai = new OpenAI({ - baseURL: GAIA_NODE_URL, - apiKey: GAIA_API_KEY +const openai = new OpenAI({ + baseURL: GAIA_NODE_URL, + apiKey: GAIA_API_KEY, }); /* Set the environment to dev or production */ From 0e23caa9f284373a8888f97d7c372a0ae3240e10 Mon Sep 17 00:00:00 2001 From: fabri Date: Mon, 10 Mar 2025 20:55:32 -0300 Subject: [PATCH 4/4] update pr --- integrations/gaia/index.ts | 2 +- integrations/gaia/package.json | 5 ++++- integrations/gaia/tsconfig.json | 9 +++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 integrations/gaia/tsconfig.json diff --git a/integrations/gaia/index.ts b/integrations/gaia/index.ts index 22b97c4..d062eaa 100644 --- a/integrations/gaia/index.ts +++ b/integrations/gaia/index.ts @@ -101,7 +101,7 @@ async function main() { /* Get the AI response */ const completion = await openai.chat.completions.create({ messages: [{ role: "user", content: message.content as string }], - model: GAIA_MODEL_NAME, + model: GAIA_MODEL_NAME as string, }); /* Get the AI response */ diff --git a/integrations/gaia/package.json b/integrations/gaia/package.json index f733b29..9fe6a66 100644 --- a/integrations/gaia/package.json +++ b/integrations/gaia/package.json @@ -4,8 +4,11 @@ "private": true, "type": "module", "scripts": { + "build": "cd ../.. && yarn tsc", "dev": "tsx --env-file=.env index.ts", - "gen:keys": "tsx ../../scripts/generateKeys.ts" + "gen:keys": "tsx ../../scripts/generateKeys.ts", + "lint": "cd ../.. && yarn eslint integrations/gaia", + "typecheck": "cd ../.. && yarn typecheck" }, "dependencies": { "@xmtp/node-sdk": "0.0.42", diff --git a/integrations/gaia/tsconfig.json b/integrations/gaia/tsconfig.json new file mode 100644 index 0000000..a13356c --- /dev/null +++ b/integrations/gaia/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "paths": { + "@/helpers": ["./helpers/index.ts"] + } + }, + "include": ["**/*.ts"] +}