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

Tool Support for Hedera Integration #7253

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
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
29 changes: 29 additions & 0 deletions docs/core_docs/docs/integrations/tools/hedera.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
hide_table_of_contents: true
---

import CodeBlock from "@theme/CodeBlock";

# Hedera Tool

The Hedera Tool gives your agent the ability to create an account, delete an account, transfer
between accounts, and query the balance of an account.

## Setup

To use the Hedera Tool you need to install the following official peer depencency:

```bash npm2yarn
npm install @hashgraph/sdk
```

## Usage

import ToolExample from "@examples/tools/hedera.ts";

<CodeBlock language="typescript">{ToolExample}</CodeBlock>

## Related

- Tool [conceptual guide](/docs/concepts/tools)
- Tool [how-to guides](/docs/how_to/#tools)
82 changes: 82 additions & 0 deletions examples/src/tools/hedera.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import {
HederaAccountBalance,
HederaTransfer,
HederaCreateAccount,
HederaDeleteAccount,
} from "@langchain/community/tools/hedera";
import { PrivateKey } from "@hashgraph/sdk"
import { StructuredTool } from "@langchain/core/tools";
import { ChatOpenAI } from "@langchain/openai";
import type { ChatPromptTemplate } from "@langchain/core/prompts";
import { createOpenAIFunctionsAgent, AgentExecutor } from "langchain/agents";
import { pull } from "langchain/hub";

export async function run() {
const llm = new ChatOpenAI({
model: "gpt-3.5-turbo",
temperature: 0,
});

// Initalize paramters for hedera tools to use
const hederaParams = {
credentials: {
accountId: process.env.HEDERA_ACCOUNT_ID,
privateKey: process.env.HEDERA_PRIVATE_KEY,
},
network: "testnet",
maxTransactionFee: 100,
maxQueryPayment: 50,
};

// Provide the hedera tools to be used
const tools: StructuredTool[] = [
new HederaAccountBalance(hederaParams),
new HederaTransfer(hederaParams),
new HederaCreateAccount(hederaParams),
new HederaDeleteAccount(hederaParams),
];

// Setup the agent to use the hedera tool
const prompt = await pull<ChatPromptTemplate>(
"hwchase17/openai-functions-agent"
);

const agent = await createOpenAIFunctionsAgent({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use createReactAgent from @langchain/langgraph here instead

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jacoblee93 would it be possible to use createStructuredChatAgent instead?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, let's show the latest recommended practices

llm,
tools,
prompt,
});

const agentExecutor = new AgentExecutor({
agent,
tools,
verbose: false,
});

const newAccountPrivateKey = PrivateKey.generateED25519();
const newAccountPublicKey = newAccountPrivateKey.publicKey;

// Create hedera account
const result1 = await agentExecutor.invoke({
input: `Can you create an account with public key ${newAccountPublicKey}`,
});
console.log(result1.output);

// Get the account balance of client operator
const result2 = await agentExecutor.invoke({
input: `What is my account balance`,
});
console.log(result2.output);

// Get the account balance of a specific account
const result3 = await agentExecutor.invoke({
input: `What is the account balance of 0.0.1111111`,
});
console.log(result3.output);

// Transfer tinybars between accounts
const result4 = await agentExecutor.invoke({
input: `Can you transfer 100000000 tinybars from my account to 0.0.1111111`,
});
console.log(result4.output);
}
4 changes: 4 additions & 0 deletions libs/langchain-community/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ tools/google_routes.cjs
tools/google_routes.js
tools/google_routes.d.ts
tools/google_routes.d.cts
tools/hedera.cjs
tools/hedera.js
tools/hedera.d.ts
tools/hedera.d.cts
tools/ifttt.cjs
tools/ifttt.js
tools/ifttt.d.ts
Expand Down
2 changes: 2 additions & 0 deletions libs/langchain-community/langchain.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export const config = {
"tools/google_custom_search": "tools/google_custom_search",
"tools/google_places": "tools/google_places",
"tools/google_routes": "tools/google_routes",
"tools/hedera": "tools/hedera/index",
"tools/ifttt": "tools/ifttt",
"tools/searchapi": "tools/searchapi",
"tools/searxng_search": "tools/searxng_search",
Expand Down Expand Up @@ -340,6 +341,7 @@ export const config = {
"tools/discord",
"tools/gmail",
"tools/google_calendar",
"tools/hedera",
"agents/toolkits/aws_sfn",
"agents/toolkits/stagehand",
"callbacks/handlers/llmonitor",
Expand Down
18 changes: 18 additions & 0 deletions libs/langchain-community/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"@google-ai/generativelanguage": "^2.5.0",
"@google-cloud/storage": "^7.7.0",
"@gradientai/nodejs-sdk": "^1.2.0",
"@hashgraph/sdk": "^2.53.0",
"@huggingface/inference": "^2.6.4",
"@ibm-cloud/watsonx-ai": "^1.3.0",
"@jest/globals": "^29.5.0",
Expand Down Expand Up @@ -247,6 +248,7 @@
"@google-ai/generativelanguage": "*",
"@google-cloud/storage": "^6.10.1 || ^7.7.0",
"@gradientai/nodejs-sdk": "^1.2.0",
"@hashgraph/sdk": "^2.53.0",
"@huggingface/inference": "^2.6.4",
"@ibm-cloud/watsonx-ai": "*",
"@lancedb/lancedb": "^0.12.0",
Expand Down Expand Up @@ -425,6 +427,9 @@
"@gradientai/nodejs-sdk": {
"optional": true
},
"@hashgraph/sdk": {
"optional": true
},
"@huggingface/inference": {
"optional": true
},
Expand Down Expand Up @@ -883,6 +888,15 @@
"import": "./tools/google_routes.js",
"require": "./tools/google_routes.cjs"
},
"./tools/hedera": {
"types": {
"import": "./tools/hedera.d.ts",
"require": "./tools/hedera.d.cts",
"default": "./tools/hedera.d.ts"
},
"import": "./tools/hedera.js",
"require": "./tools/hedera.cjs"
},
"./tools/ifttt": {
"types": {
"import": "./tools/ifttt.d.ts",
Expand Down Expand Up @@ -3195,6 +3209,10 @@
"tools/google_routes.js",
"tools/google_routes.d.ts",
"tools/google_routes.d.cts",
"tools/hedera.cjs",
"tools/hedera.js",
"tools/hedera.d.ts",
"tools/hedera.d.cts",
"tools/ifttt.cjs",
"tools/ifttt.js",
"tools/ifttt.d.ts",
Expand Down
1 change: 1 addition & 0 deletions libs/langchain-community/src/load/import_constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const optionalImportEntrypoints: string[] = [
"langchain_community/tools/discord",
"langchain_community/tools/gmail",
"langchain_community/tools/google_calendar",
"langchain_community/tools/hedera",
"langchain_community/agents/toolkits/aws_sfn",
"langchain_community/agents/toolkits/stagehand",
"langchain_community/embeddings/bedrock",
Expand Down
85 changes: 85 additions & 0 deletions libs/langchain-community/src/tools/hedera/base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { Client, Hbar } from "@hashgraph/sdk";
import { z } from "zod";
import { StructuredTool } from "@langchain/core/tools";
import { getEnvironmentVariable } from "@langchain/core/utils/env";

export interface HederaBaseToolParams {
credentials?: {
accountId?: string;
privateKey?: string;
};
network?: string;
maxTransactionFee?: number;
maxQueryPayment?: number;
}

export abstract class HederaBaseTool extends StructuredTool {
private CredentialsSchema = z
.object({
accountId: z
.string()
.min(1)
.default(getEnvironmentVariable("HEDERA_ACCOUNT_ID") ?? ""),
privateKey: z
.string()
.min(1)
.default(getEnvironmentVariable("HEDERA_PRIVATE_KEY") ?? ""),
})
.refine(
(credentials: { accountId: string; privateKey: string }) =>
credentials.accountId !== "" || credentials.privateKey !== "",
{
message:
"Missing HEDERA_ACCOUNT_ID or HEDERA_PRIVATE_KEY to interact with Hedera",
}
);

private HederaBaseToolParamsSchema = z.object({
credentials: this.CredentialsSchema.default({}),
network: z.enum(["mainnet", "testnet", "previewnet"]).default("testnet"),
maxTransactionFee: z.number().default(100),
maxQueryPayment: z.number().default(50),
});

name = "Hedera Tool";

description = "A tool for interacting with the Hedera network.";

protected client: Client;

constructor(fields?: Partial<HederaBaseToolParams>) {
super(...arguments);

const { credentials, network, maxTransactionFee, maxQueryPayment } =
this.HederaBaseToolParamsSchema.parse(fields);

this.client = this.getHederaClient(
network,
credentials.accountId,
credentials.privateKey,
maxTransactionFee,
maxQueryPayment
);
}

private getHederaClient(
network: "mainnet" | "testnet" | "previewnet",
accountId: string,
privateKey: string,
maxTransactionFee: number,
maxQueryPayment: number
): Client {
let client: Client;
if (network === "mainnet") {
client = Client.forMainnet();
} else if (network === "testnet") {
client = Client.forTestnet();
} else {
client = Client.forPreviewnet();
}
client.setOperator(accountId, privateKey);
client.setDefaultMaxTransactionFee(new Hbar(maxTransactionFee));
client.setDefaultMaxQueryPayment(new Hbar(maxQueryPayment));
return client;
}
}
59 changes: 59 additions & 0 deletions libs/langchain-community/src/tools/hedera/create_account.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {
AccountCreateTransaction,
AccountBalanceQuery,
Hbar,
PublicKey,
} from "@hashgraph/sdk";
import { z } from "zod";
import { HederaBaseTool, HederaBaseToolParams } from "./base.js";

export class HederaCreateAccount extends HederaBaseTool {
name = "hedera_create_account";

schema = z.object({
newAccountPublicKey: z.string(),
initialAmount: z.number().default(1000),
});

description = `A tool for creating a new Hedera account. Takes the new account's public key and the
intial amount to set the starting balance of the account to. If no inital amount is provided
it will set the starting balance to 1000 tinybar.`;

constructor(fields?: HederaBaseToolParams) {
super(fields);
}

async _call(arg: z.output<typeof this.schema>): Promise<string> {
const { newAccountPublicKey, initialAmount } = arg;

try {
const publicKey = PublicKey.fromString(newAccountPublicKey);
const newAccount = await new AccountCreateTransaction()
.setKey(publicKey)
.setInitialBalance(Hbar.fromTinybars(initialAmount))
.execute(this.client);

const getReceipt = await newAccount.getReceipt(this.client);
const newAccountId = getReceipt.accountId;

if (!newAccountId) {
throw new Error("Account creation failed.");
}

const accountBalance = await new AccountBalanceQuery()
.setAccountId(newAccountId)
.execute(this.client);

return `Account created successfully. Account ID: ${newAccountId},
Initial Balance: ${accountBalance.hbars.toString()} tinybars`;
} catch (error) {
const typedError = error as Error;
throw new Error(`Failed to create account: ${typedError.message}`);
}
}
}

export type AccountCreateSchema = {
newAccountPublicKey: string;
initialAmount?: number;
};
Loading