Skip to content

Commit

Permalink
0.1.0(ts): farcaster example (#206)
Browse files Browse the repository at this point in the history
  • Loading branch information
stat authored and John-peterson-coinbase committed Feb 1, 2025
1 parent bffcb86 commit 3bb28b8
Show file tree
Hide file tree
Showing 10 changed files with 1,074 additions and 1,793 deletions.
6 changes: 6 additions & 0 deletions cdp-langchain/examples/farcaster-typescript/.env-local
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
AGENT_FID=
CDP_API_KEY_NAME=
CDP_API_KEY_PRIVATE_KEY=
OPENAI_API_KEY=
NEYNAR_API_KEY=
NEYNAR_MANAGED_SIGNER=
4 changes: 4 additions & 0 deletions cdp-langchain/examples/farcaster-typescript/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"parser": "@typescript-eslint/parser",
"extends": ["../../../.eslintrc.base.json"]
}
7 changes: 7 additions & 0 deletions cdp-langchain/examples/farcaster-typescript/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
docs/
dist/
coverage/
.github/
src/client
**/**/*.json
*.md
11 changes: 11 additions & 0 deletions cdp-langchain/examples/farcaster-typescript/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": false,
"trailingComma": "all",
"bracketSpacing": true,
"arrowParens": "avoid",
"printWidth": 100,
"proseWrap": "never"
}
56 changes: 56 additions & 0 deletions cdp-langchain/examples/farcaster-typescript/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# CDP Agentkit Farcaster Langchain Extension Examples - Chatbot Typescript

This example demonstrates an agent setup as a terminal style chatbot with access to Farcaster API actions.

## Ask the chatbot to engage in the onchain ecosystem!
- "Please send a cast for me to Farcaster"

## Requirements

- Node.js 18+
- [CDP API Key](https://portal.cdp.coinbase.com/access/api)
- [OpenAI API Key](https://platform.openai.com/docs/quickstart#create-and-export-an-api-key)
- [Farcaster API Keys via Neynar](https://dev.neynar.com/)

### Farcaster Application Setup
1. Visit the Neynar [Developer Portal](https://dev.neynar.com/)
2. Navigate to your application
3. Copy the API key.
4. Set the copied value in .env as your `NEYNAR_API_KEY`
5. Return to the Neynar [Developer Portal](https://dev.neynar.com/)
6. Navigate to your application
7. Follow the instructions to get a signer UUID by either creating an agent or logging into Farcaster with an existing account.
8. Copy the signer UUID.
9. Set the copied UUID value in .env as your `NEYNAR_MANAGED_SIGNER`.
10. Set the AGENT_FID value to your Farcaster profile FID, this can be found through your profile about modal.

### Checking Node Version

Before using the example, ensure that you have the correct version of Node.js installed. The example requires Node.js 18 or higher. You can check your Node version by running:

```bash
node --version
npm --version
```

## Installation

```bash
npm install
```

## Run the Chatbot

Ensure the following vars are set in .env:
```
AGENT_FID=
CDP_API_KEY_NAME=
CDP_API_KEY_PRIVATE_KEY=
OPENAI_API_KEY=
NEYNAR_API_KEY=
NEYNAR_MANAGER_SIGNER=
```

```bash
npm start
```
226 changes: 226 additions & 0 deletions cdp-langchain/examples/farcaster-typescript/chatbot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
import { getLangChainTools } from "@coinbase/cdp-langchain";
import { MemorySaver } from "@langchain/langgraph";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage } from "@langchain/core/messages";
import * as dotenv from "dotenv";
import * as readline from "readline";

import { AgentKit, farcasterActionProvider } from "@coinbase/cdp-agentkit-core";

dotenv.config();

const modifier = `
You are a helpful agent that can interact with the Farcaster API using the Coinbase Developer Platform Farcaster Agentkit.
You are empowered to interact with Farcaster using your tools.
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 Farcaster API + Agentkit.
Recommend they go to https://docs.neynar.com/ for more information.
Be concise and helpful with your responses.
Refrain from restating your tools' descriptions unless it is explicitly requested.
`;

/**
* Initialize the agent with Farcaster Agentkit
*
* @returns Agent executor and config
*/
async function initialize() {
// Initialize LLM
const llm = new ChatOpenAI({ model: "gpt-4o-mini" });

// Initialize Farcaster action provider
const farcaster = farcasterActionProvider();

if (!process.env.CDP_API_KEY_NAME) {
throw new Error("CDP_API_KEY_NAME is not set");
}

if (!process.env.CDP_API_KEY_PRIVATE_KEY) {
throw new Error("CDP_API_KEY_PRIVATE_KEY is not set");
}

// Initialize AgentKit
const agentKit = await AgentKit.from({
cdpApiKeyName: process.env.CDP_API_KEY_NAME,
cdpApiKeyPrivateKey: process.env.CDP_API_KEY_PRIVATE_KEY.replace(/\\n/g, "\n"),
actionProviders: [farcaster],
});

// Retrieve actions
const actions = agentKit.getActions();
for (const action of actions) {
console.log(action.name);
}

// Retrieve tools
const tools = await getLangChainTools(agentKit);

// Store buffered conversation history in memory
const memory = new MemorySaver();

// React Agent config
const agentConfig = { configurable: { thread_id: "Twitter Agentkit Chatbot Example!" } };

// Create React Agent using the LLM and Twitter (X) tools
const agent = createReactAgent({
llm,
tools,
checkpointSaver: memory,
messageModifier: modifier,
});

return { agent, config: agentConfig };
}

/**
* Run the agent autonomously with specified intervals
*
* @param agent - The agent executor
* @param config - Agent configuration
* @param interval - Time interval between actions in seconds
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async function runAutonomousMode(agent: any, config: any, interval = 10) {
console.log("Starting autonomous mode...");

// eslint-disable-next-line no-constant-condition
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);
}
}
}

/**
* Run the agent interactively based on user input
*
* @param agent - The agent executor
* @param config - Agent configuration
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
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 {
// eslint-disable-next-line no-constant-condition
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();
}
}

/**
* Choose whether to run in autonomous or chat mode based on user input
*
* @returns Selected mode
*/
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));

// eslint-disable-next-line no-constant-condition
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();

if (choice === "1" || choice === "chat") {
rl.close();
return "chat";
} else if (choice === "2" || choice === "auto") {
rl.close();
return "auto";
}
console.log("Invalid choice. Please try again.");
}
}

/**
* Start the chatbot agent
*/
async function main() {
try {
const { agent, config } = await initialize();
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) {
console.log("Starting Agent...");
main().catch(error => {
console.error("Fatal error:", error);
process.exit(1);
});
}
28 changes: 28 additions & 0 deletions cdp-langchain/examples/farcaster-typescript/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "@coinbase/cdp-langchain-farcaster-chatbot-example",
"description": "Farcaster CDP Agentkit Node.js SDK Chatbot Example",
"version": "1.0.0",
"author": "Coinbase Inc.",
"license": "Apache-2.0",
"scripts": {
"start": "NODE_OPTIONS='--no-warnings' ts-node ./chatbot.ts",
"dev": "nodemon ./chatbot.ts",
"lint": "eslint -c .eslintrc.json \"*.ts\"",
"lint:fix": "eslint -c .eslintrc.json \"*.ts\" --fix",
"format": "prettier -c .prettierrc --write \"**/*.{ts,js,cjs,json,md}\"",
"format:check": "prettier -c .prettierrc --check \"**/*.{ts,js,cjs,json,md}\""
},
"dependencies": {
"@coinbase/cdp-agentkit-core": "^0.0.14",
"@coinbase/cdp-langchain": "^0.0.15",
"@langchain/langgraph": "^0.2.21",
"@langchain/openai": "^0.3.14",
"@langchain/core": "^0.3.19",
"dotenv": "^16.4.5",
"zod": "^3.22.4"
},
"devDependencies": {
"nodemon": "^3.1.0",
"ts-node": "^10.9.2"
}
}
9 changes: 9 additions & 0 deletions cdp-langchain/examples/farcaster-typescript/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"preserveSymlinks": true,
"outDir": "./dist",
"rootDir": "."
},
"include": ["*.ts"]
}
Loading

0 comments on commit 3bb28b8

Please sign in to comment.