mirror of
https://github.com/hwchase17/langchain
synced 2024-11-06 03:20:49 +00:00
Add AINetwork blockchain toolkit integration (#9527)
# Description This PR introduces a new toolkit for interacting with the AINetwork blockchain. The toolkit provides a set of tools for performing various operations on the AINetwork blockchain, such as transferring AIN, reading and writing values to the blockchain database, managing apps, setting rules and owners. # Dependencies [ain-py](https://github.com/ainblockchain/ain-py) >= 1.0.2 # Misc The example notebook (langchain/docs/extras/integrations/toolkits/ainetwork.ipynb) is in the PR --------- Co-authored-by: kriii <kriii@users.noreply.github.com> Co-authored-by: Bagatur <baskaryan@gmail.com>
This commit is contained in:
parent
e99ef12cb1
commit
b868ef23bc
461
docs/extras/integrations/toolkits/ainetwork.ipynb
Normal file
461
docs/extras/integrations/toolkits/ainetwork.ipynb
Normal file
@ -0,0 +1,461 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# AINetwork Toolkit\n",
|
||||
"\n",
|
||||
"The AINetwork Toolkit is a set of tools for interacting with the AINetwork Blockchain. These tools allow you to transfer AIN, read and write values, create apps, and set permissions for specific paths within the blockchain database."
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Installing dependencies\n",
|
||||
"\n",
|
||||
"Before using the AINetwork Toolkit, you need to install the ain-py package. You can install it with pip:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!pip install ain-py"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Set environmental variables\n",
|
||||
"\n",
|
||||
"You need to set the `AIN_BLOCKCHAIN_ACCOUNT_PRIVATE_KEY` environmental variable to your AIN Blockchain Account Private Key."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.environ[\"AIN_BLOCKCHAIN_ACCOUNT_PRIVATE_KEY\"] = \"\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Get AIN Blockchain private key"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n",
|
||||
"address: 0x5BEB4Defa2ccc274498416Fd7Cb34235DbC122Ac\n",
|
||||
"private_key: f5e2f359bb6b7836a2ac70815473d1a290c517f847d096f5effe818de8c2cf14\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"\n",
|
||||
"from ain.account import Account\n",
|
||||
"\n",
|
||||
"if os.environ.get(\"AIN_BLOCKCHAIN_ACCOUNT_PRIVATE_KEY\", None):\n",
|
||||
" account = Account(os.environ[\"AIN_BLOCKCHAIN_ACCOUNT_PRIVATE_KEY\"])\n",
|
||||
"else:\n",
|
||||
" account = Account.create()\n",
|
||||
" os.environ[\"AIN_BLOCKCHAIN_ACCOUNT_PRIVATE_KEY\"] = account.private_key\n",
|
||||
" print(\n",
|
||||
" f\"\"\"\n",
|
||||
"address: {account.address}\n",
|
||||
"private_key: {account.private_key}\n",
|
||||
"\"\"\"\n",
|
||||
" )\n",
|
||||
"# IMPORTANT: If you plan to use this account in the future, make sure to save the\n",
|
||||
"# private key in a secure place. Losing access to your private key means losing\n",
|
||||
"# access to your account."
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Initialize the AINetwork Toolkit\n",
|
||||
"\n",
|
||||
"You can initialize the AINetwork Toolkit like this:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain.agents.agent_toolkits.ainetwork.toolkit import AINetworkToolkit\n",
|
||||
"\n",
|
||||
"toolkit = AINetworkToolkit()\n",
|
||||
"tools = toolkit.get_tools()\n",
|
||||
"address = tools[0].interface.wallet.defaultAccount.address"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Initialize the Agent with the AINetwork Toolkit\n",
|
||||
"\n",
|
||||
"You can initialize the agent with the AINetwork Toolkit like this:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain.chat_models import ChatOpenAI\n",
|
||||
"from langchain.agents import initialize_agent, AgentType\n",
|
||||
"\n",
|
||||
"llm = ChatOpenAI(temperature=0)\n",
|
||||
"agent = initialize_agent(\n",
|
||||
" tools=tools,\n",
|
||||
" llm=llm,\n",
|
||||
" verbose=True,\n",
|
||||
" agent=AgentType.OPENAI_FUNCTIONS,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Example Usage\n",
|
||||
"\n",
|
||||
"Here are some examples of how you can use the agent with the AINetwork Toolkit:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Define App name to test"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"appName = f\"langchain_demo_{address.lower()}\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Create an app in the AINetwork Blockchain database"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n",
|
||||
"\n",
|
||||
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
|
||||
"\u001b[32;1m\u001b[1;3m\n",
|
||||
"Invoking: `AINappOps` with `{'type': 'SET_ADMIN', 'appName': 'langchain_demo_0x5beb4defa2ccc274498416fd7cb34235dbc122ac'}`\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\u001b[0m\u001b[36;1m\u001b[1;3m{\"tx_hash\": \"0x018846d6a9fc111edb1a2246ae2484ef05573bd2c584f3d0da155fa4b4936a9e\", \"result\": {\"gas_amount_total\": {\"bandwidth\": {\"service\": 4002, \"app\": {\"langchain_demo_0x5beb4defa2ccc274498416fd7cb34235dbc122ac\": 2}}, \"state\": {\"service\": 1640}}, \"gas_cost_total\": 0, \"func_results\": {\"_createApp\": {\"op_results\": {\"0\": {\"path\": \"/apps/langchain_demo_0x5beb4defa2ccc274498416fd7cb34235dbc122ac\", \"result\": {\"code\": 0, \"bandwidth_gas_amount\": 1}}, \"1\": {\"path\": \"/apps/langchain_demo_0x5beb4defa2ccc274498416fd7cb34235dbc122ac\", \"result\": {\"code\": 0, \"bandwidth_gas_amount\": 1}}, \"2\": {\"path\": \"/manage_app/langchain_demo_0x5beb4defa2ccc274498416fd7cb34235dbc122ac/config/admin\", \"result\": {\"code\": 0, \"bandwidth_gas_amount\": 1}}}, \"code\": 0, \"bandwidth_gas_amount\": 2000}}, \"code\": 0, \"bandwidth_gas_amount\": 2001, \"gas_amount_charged\": 5642}}\u001b[0m\u001b[32;1m\u001b[1;3mThe app with the name \"langchain_demo_0x5beb4defa2ccc274498416fd7cb34235dbc122ac\" has been created in the AINetwork Blockchain database.\u001b[0m\n",
|
||||
"\n",
|
||||
"\u001b[1m> Finished chain.\u001b[0m\n",
|
||||
"The app with the name \"langchain_demo_0x5beb4defa2ccc274498416fd7cb34235dbc122ac\" has been created in the AINetwork Blockchain database.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(\n",
|
||||
" agent.run(\n",
|
||||
" f\"Create an app in the AINetwork Blockchain database with the name {appName}\"\n",
|
||||
" )\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Set a value at a given path in the AINetwork Blockchain database"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n",
|
||||
"\n",
|
||||
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
|
||||
"\u001b[32;1m\u001b[1;3m\n",
|
||||
"Invoking: `AINvalueOps` with `{'type': 'SET', 'path': '/apps/langchain_demo_0x5beb4defa2ccc274498416fd7cb34235dbc122ac/object', 'value': {'1': 2, '34': 56}}`\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\u001b[0m\u001b[33;1m\u001b[1;3m{\"tx_hash\": \"0x3d1a16d9808830088cdf4d37f90f4b1fa1242e2d5f6f983829064f45107b5279\", \"result\": {\"gas_amount_total\": {\"bandwidth\": {\"service\": 0, \"app\": {\"langchain_demo_0x5beb4defa2ccc274498416fd7cb34235dbc122ac\": 1}}, \"state\": {\"service\": 0, \"app\": {\"langchain_demo_0x5beb4defa2ccc274498416fd7cb34235dbc122ac\": 674}}}, \"gas_cost_total\": 0, \"code\": 0, \"bandwidth_gas_amount\": 1, \"gas_amount_charged\": 0}}\u001b[0m\u001b[32;1m\u001b[1;3mThe value {1: 2, '34': 56} has been set at the path /apps/langchain_demo_0x5beb4defa2ccc274498416fd7cb34235dbc122ac/object.\u001b[0m\n",
|
||||
"\n",
|
||||
"\u001b[1m> Finished chain.\u001b[0m\n",
|
||||
"The value {1: 2, '34': 56} has been set at the path /apps/langchain_demo_0x5beb4defa2ccc274498416fd7cb34235dbc122ac/object.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(\n",
|
||||
" agent.run(f\"Set the value {{1: 2, '34': 56}} at the path /apps/{appName}/object .\")\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Set permissions for a path in the AINetwork Blockchain database"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n",
|
||||
"\n",
|
||||
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
|
||||
"\u001b[32;1m\u001b[1;3m\n",
|
||||
"Invoking: `AINruleOps` with `{'type': 'SET', 'path': '/apps/langchain_demo_0x5beb4defa2ccc274498416fd7cb34235dbc122ac/user/$from', 'eval': 'auth.addr===$from'}`\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\u001b[0m\u001b[38;5;200m\u001b[1;3m{\"tx_hash\": \"0x37d5264e580f6a217a347059a735bfa9eb5aad85ff28a95531c6dc09252664d2\", \"result\": {\"gas_amount_total\": {\"bandwidth\": {\"service\": 0, \"app\": {\"langchain_demo_0x5beb4defa2ccc274498416fd7cb34235dbc122ac\": 1}}, \"state\": {\"service\": 0, \"app\": {\"langchain_demo_0x5beb4defa2ccc274498416fd7cb34235dbc122ac\": 712}}}, \"gas_cost_total\": 0, \"code\": 0, \"bandwidth_gas_amount\": 1, \"gas_amount_charged\": 0}}\u001b[0m\u001b[32;1m\u001b[1;3mThe write permissions for the path `/apps/langchain_demo_0x5beb4defa2ccc274498416fd7cb34235dbc122ac/user/$from` have been set with the eval string `auth.addr===$from`.\u001b[0m\n",
|
||||
"\n",
|
||||
"\u001b[1m> Finished chain.\u001b[0m\n",
|
||||
"The write permissions for the path `/apps/langchain_demo_0x5beb4defa2ccc274498416fd7cb34235dbc122ac/user/$from` have been set with the eval string `auth.addr===$from`.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(\n",
|
||||
" agent.run(\n",
|
||||
" f\"Set the write permissions for the path /apps/{appName}/user/$from with the\"\n",
|
||||
" \" eval string auth.addr===$from .\"\n",
|
||||
" )\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Retrieve the permissions for a path in the AINetwork Blockchain database"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n",
|
||||
"\n",
|
||||
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
|
||||
"\u001b[32;1m\u001b[1;3m\n",
|
||||
"Invoking: `AINownerOps` with `{'type': 'GET', 'path': '/apps/langchain_demo_0x5beb4defa2ccc274498416fd7cb34235dbc122ac'}`\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\u001b[0m\u001b[33;1m\u001b[1;3m{\".owner\": {\"owners\": {\"0x5BEB4Defa2ccc274498416Fd7Cb34235DbC122Ac\": {\"branch_owner\": true, \"write_function\": true, \"write_owner\": true, \"write_rule\": true}}}}\u001b[0m\u001b[32;1m\u001b[1;3mThe permissions for the path /apps/langchain_demo_0x5beb4defa2ccc274498416fd7cb34235dbc122ac are as follows:\n",
|
||||
"\n",
|
||||
"- Address: 0x5BEB4Defa2ccc274498416Fd7Cb34235DbC122Ac\n",
|
||||
" - branch_owner: true\n",
|
||||
" - write_function: true\n",
|
||||
" - write_owner: true\n",
|
||||
" - write_rule: true\u001b[0m\n",
|
||||
"\n",
|
||||
"\u001b[1m> Finished chain.\u001b[0m\n",
|
||||
"The permissions for the path /apps/langchain_demo_0x5beb4defa2ccc274498416fd7cb34235dbc122ac are as follows:\n",
|
||||
"\n",
|
||||
"- Address: 0x5BEB4Defa2ccc274498416Fd7Cb34235DbC122Ac\n",
|
||||
" - branch_owner: true\n",
|
||||
" - write_function: true\n",
|
||||
" - write_owner: true\n",
|
||||
" - write_rule: true\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(agent.run(f\"Retrieve the permissions for the path /apps/{appName}.\"))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Get AIN from faucet"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{\"result\":\"0x0eb07b67b7d0a702cb60e865d3deafff3070d8508077ef793d69d6819fd92ea3\",\"time\":1692348112376}"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"!curl http://faucet.ainetwork.ai/api/test/{address}/"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Get AIN Balance"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n",
|
||||
"\n",
|
||||
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
|
||||
"\u001b[32;1m\u001b[1;3m\n",
|
||||
"Invoking: `AINvalueOps` with `{'type': 'GET', 'path': '/accounts/0x5BEB4Defa2ccc274498416Fd7Cb34235DbC122Ac/balance'}`\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\u001b[0m\u001b[33;1m\u001b[1;3m100\u001b[0m\u001b[32;1m\u001b[1;3mThe AIN balance of address 0x5BEB4Defa2ccc274498416Fd7Cb34235DbC122Ac is 100 AIN.\u001b[0m\n",
|
||||
"\n",
|
||||
"\u001b[1m> Finished chain.\u001b[0m\n",
|
||||
"The AIN balance of address 0x5BEB4Defa2ccc274498416Fd7Cb34235DbC122Ac is 100 AIN.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(agent.run(f\"Check AIN balance of {address}\"))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Transfer AIN"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n",
|
||||
"\n",
|
||||
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
|
||||
"\u001b[32;1m\u001b[1;3m\n",
|
||||
"Invoking: `AINtransfer` with `{'address': '0x19937b227b1b13f29e7ab18676a89ea3bdea9c5b', 'amount': 100}`\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\u001b[0m\u001b[36;1m\u001b[1;3m{\"tx_hash\": \"0xa59d15d23373bcc00e413ac8ba18cb016bb3bdd54058d62606aec688c6ad3d2e\", \"result\": {\"gas_amount_total\": {\"bandwidth\": {\"service\": 3}, \"state\": {\"service\": 866}}, \"gas_cost_total\": 0, \"func_results\": {\"_transfer\": {\"op_results\": {\"0\": {\"path\": \"/accounts/0x5BEB4Defa2ccc274498416Fd7Cb34235DbC122Ac/balance\", \"result\": {\"code\": 0, \"bandwidth_gas_amount\": 1}}, \"1\": {\"path\": \"/accounts/0x19937B227b1b13f29e7AB18676a89EA3BDEA9C5b/balance\", \"result\": {\"code\": 0, \"bandwidth_gas_amount\": 1}}}, \"code\": 0, \"bandwidth_gas_amount\": 0}}, \"code\": 0, \"bandwidth_gas_amount\": 1, \"gas_amount_charged\": 869}}\u001b[0m\u001b[32;1m\u001b[1;3mThe transfer of 100 AIN to the address 0x19937b227b1b13f29e7ab18676a89ea3bdea9c5b was successful. The transaction hash is 0xa59d15d23373bcc00e413ac8ba18cb016bb3bdd54058d62606aec688c6ad3d2e.\u001b[0m\n",
|
||||
"\n",
|
||||
"\u001b[1m> Finished chain.\u001b[0m\n",
|
||||
"The transfer of 100 AIN to the address 0x19937b227b1b13f29e7ab18676a89ea3bdea9c5b was successful. The transaction hash is 0xa59d15d23373bcc00e413ac8ba18cb016bb3bdd54058d62606aec688c6ad3d2e.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(\n",
|
||||
" agent.run(\n",
|
||||
" \"Transfer 100 AIN to the address 0x19937b227b1b13f29e7ab18676a89ea3bdea9c5b\"\n",
|
||||
" )\n",
|
||||
")"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.12"
|
||||
},
|
||||
"orig_nbformat": 4
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
"""Agent toolkits."""
|
||||
from langchain.agents.agent_toolkits.ainetwork.toolkit import AINetworkToolkit
|
||||
from langchain.agents.agent_toolkits.amadeus.toolkit import AmadeusToolkit
|
||||
from langchain.agents.agent_toolkits.azure_cognitive_services import (
|
||||
AzureCognitiveServicesToolkit,
|
||||
@ -46,6 +47,7 @@ from langchain.agents.agent_toolkits.xorbits.base import create_xorbits_agent
|
||||
from langchain.agents.agent_toolkits.zapier.toolkit import ZapierToolkit
|
||||
|
||||
__all__ = [
|
||||
"AINetworkToolkit",
|
||||
"AmadeusToolkit",
|
||||
"AzureCognitiveServicesToolkit",
|
||||
"FileManagementToolkit",
|
||||
|
@ -0,0 +1 @@
|
||||
"""AINetwork toolkit."""
|
@ -0,0 +1,45 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, List, Literal, Optional
|
||||
|
||||
from langchain.agents.agent_toolkits.base import BaseToolkit
|
||||
from langchain.pydantic_v1 import root_validator
|
||||
from langchain.tools import BaseTool
|
||||
from langchain.tools.ainetwork.app import AINAppOps
|
||||
from langchain.tools.ainetwork.owner import AINOwnerOps
|
||||
from langchain.tools.ainetwork.rule import AINRuleOps
|
||||
from langchain.tools.ainetwork.transfer import AINTransfer
|
||||
from langchain.tools.ainetwork.utils import authenticate
|
||||
from langchain.tools.ainetwork.value import AINValueOps
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ain.ain import Ain
|
||||
|
||||
|
||||
class AINetworkToolkit(BaseToolkit):
|
||||
"""Toolkit for interacting with AINetwork Blockchain."""
|
||||
|
||||
network: Optional[Literal["mainnet", "testnet"]] = "testnet"
|
||||
interface: Optional[Ain] = None
|
||||
|
||||
@root_validator(pre=True)
|
||||
def set_interface(cls, values: dict) -> dict:
|
||||
if not values.get("interface"):
|
||||
values["interface"] = authenticate(network=values.get("network", "testnet"))
|
||||
return values
|
||||
|
||||
class Config:
|
||||
"""Pydantic config."""
|
||||
|
||||
validate_all = True
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
def get_tools(self) -> List[BaseTool]:
|
||||
"""Get the tools in the toolkit."""
|
||||
return [
|
||||
AINAppOps(),
|
||||
AINOwnerOps(),
|
||||
AINRuleOps(),
|
||||
AINTransfer(),
|
||||
AINValueOps(),
|
||||
]
|
@ -17,6 +17,11 @@ tool for the job.
|
||||
CallbackManagerForToolRun, AsyncCallbackManagerForToolRun
|
||||
"""
|
||||
|
||||
from langchain.tools.ainetwork.app import AINAppOps
|
||||
from langchain.tools.ainetwork.owner import AINOwnerOps
|
||||
from langchain.tools.ainetwork.rule import AINRuleOps
|
||||
from langchain.tools.ainetwork.transfer import AINTransfer
|
||||
from langchain.tools.ainetwork.value import AINValueOps
|
||||
from langchain.tools.arxiv.tool import ArxivQueryRun
|
||||
from langchain.tools.azure_cognitive_services import (
|
||||
AzureCogsFormRecognizerTool,
|
||||
@ -118,6 +123,11 @@ from langchain.tools.youtube.search import YouTubeSearchTool
|
||||
from langchain.tools.zapier.tool import ZapierNLAListActions, ZapierNLARunAction
|
||||
|
||||
__all__ = [
|
||||
"AINAppOps",
|
||||
"AINOwnerOps",
|
||||
"AINRuleOps",
|
||||
"AINTransfer",
|
||||
"AINValueOps",
|
||||
"AIPluginTool",
|
||||
"APIOperation",
|
||||
"ArxivQueryRun",
|
||||
|
95
libs/langchain/langchain/tools/ainetwork/app.py
Normal file
95
libs/langchain/langchain/tools/ainetwork/app.py
Normal file
@ -0,0 +1,95 @@
|
||||
import builtins
|
||||
import json
|
||||
from enum import Enum
|
||||
from typing import List, Optional, Type, Union
|
||||
|
||||
from langchain.callbacks.manager import AsyncCallbackManagerForToolRun
|
||||
from langchain.pydantic_v1 import BaseModel, Field
|
||||
from langchain.tools.ainetwork.base import AINBaseTool
|
||||
|
||||
|
||||
class AppOperationType(str, Enum):
|
||||
SET_ADMIN = "SET_ADMIN"
|
||||
GET_CONFIG = "GET_CONFIG"
|
||||
|
||||
|
||||
class AppSchema(BaseModel):
|
||||
type: AppOperationType = Field(...)
|
||||
appName: str = Field(..., description="Name of the application on the blockchain")
|
||||
address: Optional[Union[str, List[str]]] = Field(
|
||||
None,
|
||||
description=(
|
||||
"A single address or a list of addresses. Default: current session's "
|
||||
"address"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class AINAppOps(AINBaseTool):
|
||||
name: str = "AINappOps"
|
||||
description: str = """
|
||||
Create an app in the AINetwork Blockchain database by creating the /apps/<appName> path.
|
||||
An address set as `admin` can grant `owner` rights to other addresses (refer to `AINownerOps` for more details).
|
||||
Also, `admin` is initialized to have all `owner` permissions and `rule` allowed for that path.
|
||||
|
||||
## appName Rule
|
||||
- [a-z_0-9]+
|
||||
|
||||
## address Rules
|
||||
- 0x[0-9a-fA-F]{40}
|
||||
- Defaults to the current session's address
|
||||
- Multiple addresses can be specified if needed
|
||||
|
||||
## SET_ADMIN Example 1
|
||||
- type: SET_ADMIN
|
||||
- appName: ain_project
|
||||
|
||||
### Result:
|
||||
1. Path /apps/ain_project created.
|
||||
2. Current session's address registered as admin.
|
||||
|
||||
## SET_ADMIN Example 2
|
||||
- type: SET_ADMIN
|
||||
- appName: test_project
|
||||
- address: [<address1>, <address2>]
|
||||
|
||||
### Result:
|
||||
1. Path /apps/test_project created.
|
||||
2. <address1> and <address2> registered as admin.
|
||||
|
||||
""" # noqa: E501
|
||||
args_schema: Type[BaseModel] = AppSchema
|
||||
|
||||
async def _arun(
|
||||
self,
|
||||
type: AppOperationType,
|
||||
appName: str,
|
||||
address: Optional[Union[str, List[str]]] = None,
|
||||
run_manager: Optional[AsyncCallbackManagerForToolRun] = None,
|
||||
) -> str:
|
||||
from ain.types import ValueOnlyTransactionInput
|
||||
from ain.utils import getTimestamp
|
||||
|
||||
try:
|
||||
if type is AppOperationType.SET_ADMIN:
|
||||
if address is None:
|
||||
address = self.interface.wallet.defaultAccount.address
|
||||
if isinstance(address, str):
|
||||
address = [address]
|
||||
|
||||
res = await self.interface.db.ref(
|
||||
f"/manage_app/{appName}/create/{getTimestamp()}"
|
||||
).setValue(
|
||||
transactionInput=ValueOnlyTransactionInput(
|
||||
value={"admin": {address: True for address in address}}
|
||||
)
|
||||
)
|
||||
elif type is AppOperationType.GET_CONFIG:
|
||||
res = await self.interface.db.ref(
|
||||
f"/manage_app/{appName}/config"
|
||||
).getValue()
|
||||
else:
|
||||
raise ValueError(f"Unsupported 'type': {type}.")
|
||||
return json.dumps(res, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
return f"{builtins.type(e).__name__}: {str(e)}"
|
71
libs/langchain/langchain/tools/ainetwork/base.py
Normal file
71
libs/langchain/langchain/tools/ainetwork/base.py
Normal file
@ -0,0 +1,71 @@
|
||||
"""Base class for AINetwork tools."""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import threading
|
||||
from enum import Enum
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from langchain.callbacks.manager import CallbackManagerForToolRun
|
||||
from langchain.pydantic_v1 import Field
|
||||
from langchain.tools.ainetwork.utils import authenticate
|
||||
from langchain.tools.base import BaseTool
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ain.ain import Ain
|
||||
|
||||
|
||||
class OperationType(str, Enum):
|
||||
SET = "SET"
|
||||
GET = "GET"
|
||||
|
||||
|
||||
class AINBaseTool(BaseTool):
|
||||
"""Base class for the AINetwork tools."""
|
||||
|
||||
interface: Ain = Field(default_factory=authenticate)
|
||||
"""The interface object for the AINetwork Blockchain."""
|
||||
|
||||
def _run(
|
||||
self,
|
||||
*args: Any,
|
||||
run_manager: Optional[CallbackManagerForToolRun] = None,
|
||||
**kwargs: Any,
|
||||
) -> str:
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
except RuntimeError:
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
if loop.is_closed():
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
if loop.is_running():
|
||||
result_container = []
|
||||
|
||||
def thread_target() -> None:
|
||||
nonlocal result_container
|
||||
new_loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(new_loop)
|
||||
try:
|
||||
result_container.append(
|
||||
new_loop.run_until_complete(self._arun(*args, **kwargs))
|
||||
)
|
||||
except Exception as e:
|
||||
result_container.append(e)
|
||||
finally:
|
||||
new_loop.close()
|
||||
|
||||
thread = threading.Thread(target=thread_target)
|
||||
thread.start()
|
||||
thread.join()
|
||||
result = result_container[0]
|
||||
if isinstance(result, Exception):
|
||||
raise result
|
||||
return result
|
||||
|
||||
else:
|
||||
result = loop.run_until_complete(self._arun(*args, **kwargs))
|
||||
loop.close()
|
||||
return result
|
110
libs/langchain/langchain/tools/ainetwork/owner.py
Normal file
110
libs/langchain/langchain/tools/ainetwork/owner.py
Normal file
@ -0,0 +1,110 @@
|
||||
import builtins
|
||||
import json
|
||||
from typing import List, Optional, Type, Union
|
||||
|
||||
from langchain.callbacks.manager import AsyncCallbackManagerForToolRun
|
||||
from langchain.pydantic_v1 import BaseModel, Field
|
||||
from langchain.tools.ainetwork.base import AINBaseTool, OperationType
|
||||
|
||||
|
||||
class RuleSchema(BaseModel):
|
||||
type: OperationType = Field(...)
|
||||
path: str = Field(..., description="Blockchain reference path")
|
||||
address: Optional[Union[str, List[str]]] = Field(
|
||||
None, description="A single address or a list of addresses"
|
||||
)
|
||||
write_owner: Optional[bool] = Field(
|
||||
False, description="Authority to edit the `owner` property of the path"
|
||||
)
|
||||
write_rule: Optional[bool] = Field(
|
||||
False, description="Authority to edit `write rule` for the path"
|
||||
)
|
||||
write_function: Optional[bool] = Field(
|
||||
False, description="Authority to `set function` for the path"
|
||||
)
|
||||
branch_owner: Optional[bool] = Field(
|
||||
False, description="Authority to initialize `owner` of sub-paths"
|
||||
)
|
||||
|
||||
|
||||
class AINOwnerOps(AINBaseTool):
|
||||
name: str = "AINownerOps"
|
||||
description: str = """
|
||||
Rules for `owner` in AINetwork Blockchain database.
|
||||
An address set as `owner` can modify permissions according to its granted authorities
|
||||
|
||||
## Path Rule
|
||||
- (/[a-zA-Z_0-9]+)+
|
||||
- Permission checks ascend from the most specific (child) path to broader (parent) paths until an `owner` is located.
|
||||
|
||||
## Address Rules
|
||||
- 0x[0-9a-fA-F]{40}: 40-digit hexadecimal address
|
||||
- *: All addresses permitted
|
||||
- Defaults to the current session's address
|
||||
|
||||
## SET
|
||||
- `SET` alters permissions for specific addresses, while other addresses remain unaffected.
|
||||
- When removing an address of `owner`, set all authorities for that address to false.
|
||||
- message `write_owner permission evaluated false` if fail
|
||||
|
||||
### Example
|
||||
- type: SET
|
||||
- path: /apps/langchain
|
||||
- address: [<address 1>, <address 2>]
|
||||
- write_owner: True
|
||||
- write_rule: True
|
||||
- write_function: True
|
||||
- branch_owner: True
|
||||
|
||||
## GET
|
||||
- Provides all addresses with `owner` permissions and their authorities in the path.
|
||||
|
||||
### Example
|
||||
- type: GET
|
||||
- path: /apps/langchain
|
||||
""" # noqa: E501
|
||||
args_schema: Type[BaseModel] = RuleSchema
|
||||
|
||||
async def _arun(
|
||||
self,
|
||||
type: OperationType,
|
||||
path: str,
|
||||
address: Optional[Union[str, List[str]]] = None,
|
||||
write_owner: Optional[bool] = None,
|
||||
write_rule: Optional[bool] = None,
|
||||
write_function: Optional[bool] = None,
|
||||
branch_owner: Optional[bool] = None,
|
||||
run_manager: Optional[AsyncCallbackManagerForToolRun] = None,
|
||||
) -> str:
|
||||
from ain.types import ValueOnlyTransactionInput
|
||||
|
||||
try:
|
||||
if type is OperationType.SET:
|
||||
if address is None:
|
||||
address = self.interface.wallet.defaultAccount.address
|
||||
if isinstance(address, str):
|
||||
address = [address]
|
||||
res = await self.interface.db.ref(path).setOwner(
|
||||
transactionInput=ValueOnlyTransactionInput(
|
||||
value={
|
||||
".owner": {
|
||||
"owners": {
|
||||
address: {
|
||||
"write_owner": write_owner or False,
|
||||
"write_rule": write_rule or False,
|
||||
"write_function": write_function or False,
|
||||
"branch_owner": branch_owner or False,
|
||||
}
|
||||
for address in address
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
elif type is OperationType.GET:
|
||||
res = await self.interface.db.ref(path).getOwner()
|
||||
else:
|
||||
raise ValueError(f"Unsupported 'type': {type}.")
|
||||
return json.dumps(res, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
return f"{builtins.type(e).__name__}: {str(e)}"
|
77
libs/langchain/langchain/tools/ainetwork/rule.py
Normal file
77
libs/langchain/langchain/tools/ainetwork/rule.py
Normal file
@ -0,0 +1,77 @@
|
||||
import builtins
|
||||
import json
|
||||
from typing import Optional, Type
|
||||
|
||||
from langchain.callbacks.manager import AsyncCallbackManagerForToolRun
|
||||
from langchain.pydantic_v1 import BaseModel, Field
|
||||
from langchain.tools.ainetwork.base import AINBaseTool, OperationType
|
||||
|
||||
|
||||
class RuleSchema(BaseModel):
|
||||
type: OperationType = Field(...)
|
||||
path: str = Field(..., description="Path on the blockchain where the rule applies")
|
||||
eval: Optional[str] = Field(None, description="eval string to determine permission")
|
||||
|
||||
|
||||
class AINRuleOps(AINBaseTool):
|
||||
name: str = "AINruleOps"
|
||||
description: str = """
|
||||
Covers the write `rule` for the AINetwork Blockchain database. The SET type specifies write permissions using the `eval` variable as a JavaScript eval string.
|
||||
In order to AINvalueOps with SET at the path, the execution result of the `eval` string must be true.
|
||||
|
||||
## Path Rules
|
||||
1. Allowed characters for directory: `[a-zA-Z_0-9]`
|
||||
2. Use `$<key>` for template variables as directory.
|
||||
|
||||
## Eval String Special Variables
|
||||
- auth.addr: Address of the writer for the path
|
||||
- newData: New data for the path
|
||||
- data: Current data for the path
|
||||
- currentTime: Time in seconds
|
||||
- lastBlockNumber: Latest processed block number
|
||||
|
||||
## Eval String Functions
|
||||
- getValue(<path>)
|
||||
- getRule(<path>)
|
||||
- getOwner(<path>)
|
||||
- getFunction(<path>)
|
||||
- evalRule(<path>, <value to set>, auth, currentTime)
|
||||
- evalOwner(<path>, 'write_owner', auth)
|
||||
|
||||
## SET Example
|
||||
- type: SET
|
||||
- path: /apps/langchain_project_1/$from/$to/$img
|
||||
- eval: auth.addr===$from&&!getValue('/apps/image_db/'+$img)
|
||||
|
||||
## GET Example
|
||||
- type: GET
|
||||
- path: /apps/langchain_project_1
|
||||
""" # noqa: E501
|
||||
args_schema: Type[BaseModel] = RuleSchema
|
||||
|
||||
async def _arun(
|
||||
self,
|
||||
type: OperationType,
|
||||
path: str,
|
||||
eval: Optional[str] = None,
|
||||
run_manager: Optional[AsyncCallbackManagerForToolRun] = None,
|
||||
) -> str:
|
||||
from ain.types import ValueOnlyTransactionInput
|
||||
|
||||
try:
|
||||
if type is OperationType.SET:
|
||||
if eval is None:
|
||||
raise ValueError("'eval' is required for SET operation.")
|
||||
|
||||
res = await self.interface.db.ref(path).setRule(
|
||||
transactionInput=ValueOnlyTransactionInput(
|
||||
value={".rule": {"write": eval}}
|
||||
)
|
||||
)
|
||||
elif type is OperationType.GET:
|
||||
res = await self.interface.db.ref(path).getRule()
|
||||
else:
|
||||
raise ValueError(f"Unsupported 'type': {type}.")
|
||||
return json.dumps(res, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
return f"{builtins.type(e).__name__}: {str(e)}"
|
29
libs/langchain/langchain/tools/ainetwork/transfer.py
Normal file
29
libs/langchain/langchain/tools/ainetwork/transfer.py
Normal file
@ -0,0 +1,29 @@
|
||||
import json
|
||||
from typing import Optional, Type
|
||||
|
||||
from langchain.callbacks.manager import AsyncCallbackManagerForToolRun
|
||||
from langchain.pydantic_v1 import BaseModel, Field
|
||||
from langchain.tools.ainetwork.base import AINBaseTool
|
||||
|
||||
|
||||
class TransferSchema(BaseModel):
|
||||
address: str = Field(..., description="Address to transfer AIN to")
|
||||
amount: int = Field(..., description="Amount of AIN to transfer")
|
||||
|
||||
|
||||
class AINTransfer(AINBaseTool):
|
||||
name: str = "AINtransfer"
|
||||
description: str = "Transfers AIN to a specified address"
|
||||
args_schema: Type[TransferSchema] = TransferSchema
|
||||
|
||||
async def _arun(
|
||||
self,
|
||||
address: str,
|
||||
amount: int,
|
||||
run_manager: Optional[AsyncCallbackManagerForToolRun] = None,
|
||||
) -> str:
|
||||
try:
|
||||
res = await self.interface.wallet.transfer(address, amount, nonce=-1)
|
||||
return json.dumps(res, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
return f"{type(e).__name__}: {str(e)}"
|
62
libs/langchain/langchain/tools/ainetwork/utils.py
Normal file
62
libs/langchain/langchain/tools/ainetwork/utils.py
Normal file
@ -0,0 +1,62 @@
|
||||
"""AINetwork Blockchain tool utils."""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import TYPE_CHECKING, Literal, Optional
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ain.ain import Ain
|
||||
|
||||
|
||||
def authenticate(network: Optional[Literal["mainnet", "testnet"]] = "testnet") -> Ain:
|
||||
"""Authenticate using the AIN Blockchain"""
|
||||
|
||||
try:
|
||||
from ain.ain import Ain
|
||||
except ImportError as e:
|
||||
raise ImportError(
|
||||
"Cannot import ain-py related modules. Please install the package with "
|
||||
"`pip install ain-py`."
|
||||
) from e
|
||||
|
||||
if network == "mainnet":
|
||||
provider_url = "https://mainnet-api.ainetwork.ai/"
|
||||
chain_id = 1
|
||||
if "AIN_BLOCKCHAIN_ACCOUNT_PRIVATE_KEY" in os.environ:
|
||||
private_key = os.environ["AIN_BLOCKCHAIN_ACCOUNT_PRIVATE_KEY"]
|
||||
else:
|
||||
raise EnvironmentError(
|
||||
"Error: The AIN_BLOCKCHAIN_ACCOUNT_PRIVATE_KEY environmental variable "
|
||||
"has not been set."
|
||||
)
|
||||
elif network == "testnet":
|
||||
provider_url = "https://testnet-api.ainetwork.ai/"
|
||||
chain_id = 0
|
||||
if "AIN_BLOCKCHAIN_ACCOUNT_PRIVATE_KEY" in os.environ:
|
||||
private_key = os.environ["AIN_BLOCKCHAIN_ACCOUNT_PRIVATE_KEY"]
|
||||
else:
|
||||
raise EnvironmentError(
|
||||
"Error: The AIN_BLOCKCHAIN_ACCOUNT_PRIVATE_KEY environmental variable "
|
||||
"has not been set."
|
||||
)
|
||||
elif network is None:
|
||||
if (
|
||||
"AIN_BLOCKCHAIN_PROVIDER_URL" in os.environ
|
||||
and "AIN_BLOCKCHAIN_CHAIN_ID" in os.environ
|
||||
and "AIN_BLOCKCHAIN_ACCOUNT_PRIVATE_KEY" in os.environ
|
||||
):
|
||||
provider_url = os.environ["AIN_BLOCKCHAIN_PROVIDER_URL"]
|
||||
chain_id = int(os.environ["AIN_BLOCKCHAIN_CHAIN_ID"])
|
||||
private_key = os.environ["AIN_BLOCKCHAIN_ACCOUNT_PRIVATE_KEY"]
|
||||
else:
|
||||
raise EnvironmentError(
|
||||
"Error: The AIN_BLOCKCHAIN_PROVIDER_URL and "
|
||||
"AIN_BLOCKCHAIN_ACCOUNT_PRIVATE_KEY and AIN_BLOCKCHAIN_CHAIN_ID "
|
||||
"environmental variable has not been set."
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Unsupported 'network': {network}")
|
||||
|
||||
ain = Ain(provider_url, chain_id)
|
||||
ain.wallet.addAndSetDefaultAccount(private_key)
|
||||
return ain
|
80
libs/langchain/langchain/tools/ainetwork/value.py
Normal file
80
libs/langchain/langchain/tools/ainetwork/value.py
Normal file
@ -0,0 +1,80 @@
|
||||
import builtins
|
||||
import json
|
||||
from typing import Optional, Type, Union
|
||||
|
||||
from langchain.callbacks.manager import AsyncCallbackManagerForToolRun
|
||||
from langchain.pydantic_v1 import BaseModel, Field
|
||||
from langchain.tools.ainetwork.base import AINBaseTool, OperationType
|
||||
|
||||
|
||||
class ValueSchema(BaseModel):
|
||||
type: OperationType = Field(...)
|
||||
path: str = Field(..., description="Blockchain reference path")
|
||||
value: Optional[Union[int, str, float, dict]] = Field(
|
||||
None, description="Value to be set at the path"
|
||||
)
|
||||
|
||||
|
||||
class AINValueOps(AINBaseTool):
|
||||
name: str = "AINvalueOps"
|
||||
description: str = """
|
||||
Covers the read and write value for the AINetwork Blockchain database.
|
||||
|
||||
## SET
|
||||
- Set a value at a given path
|
||||
|
||||
### Example
|
||||
- type: SET
|
||||
- path: /apps/langchain_test_1/object
|
||||
- value: {1: 2, "34": 56}
|
||||
|
||||
## GET
|
||||
- Retrieve a value at a given path
|
||||
|
||||
### Example
|
||||
- type: GET
|
||||
- path: /apps/langchain_test_1/DB
|
||||
|
||||
## Special paths
|
||||
- `/accounts/<address>/balance`: Account balance
|
||||
- `/accounts/<address>/nonce`: Account nonce
|
||||
- `/apps`: Applications
|
||||
- `/consensus`: Consensus
|
||||
- `/checkin`: Check-in
|
||||
- `/deposit/<service id>/<address>/<deposit id>`: Deposit
|
||||
- `/deposit_accounts/<service id>/<address>/<account id>`: Deposit accounts
|
||||
- `/escrow`: Escrow
|
||||
- `/payments`: Payment
|
||||
- `/sharding`: Sharding
|
||||
- `/token/name`: Token name
|
||||
- `/token/symbol`: Token symbol
|
||||
- `/token/total_supply`: Token total supply
|
||||
- `/transfer/<address from>/<address to>/<key>/value`: Transfer
|
||||
- `/withdraw/<service id>/<address>/<withdraw id>`: Withdraw
|
||||
"""
|
||||
args_schema: Type[BaseModel] = ValueSchema
|
||||
|
||||
async def _arun(
|
||||
self,
|
||||
type: OperationType,
|
||||
path: str,
|
||||
value: Optional[Union[int, str, float, dict]] = None,
|
||||
run_manager: Optional[AsyncCallbackManagerForToolRun] = None,
|
||||
) -> str:
|
||||
from ain.types import ValueOnlyTransactionInput
|
||||
|
||||
try:
|
||||
if type is OperationType.SET:
|
||||
if value is None:
|
||||
raise ValueError("'value' is required for SET operation.")
|
||||
|
||||
res = await self.interface.db.ref(path).setValue(
|
||||
transactionInput=ValueOnlyTransactionInput(value=value)
|
||||
)
|
||||
elif type is OperationType.GET:
|
||||
res = await self.interface.db.ref(path).getValue()
|
||||
else:
|
||||
raise ValueError(f"Unsupported 'type': {type}.")
|
||||
return json.dumps(res, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
return f"{builtins.type(e).__name__}: {str(e)}"
|
@ -0,0 +1,175 @@
|
||||
import asyncio
|
||||
import os
|
||||
import time
|
||||
import urllib.request
|
||||
import uuid
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
from urllib.error import HTTPError
|
||||
|
||||
import pytest
|
||||
|
||||
from langchain.agents import AgentType, initialize_agent
|
||||
from langchain.agents.agent_toolkits.ainetwork.toolkit import AINetworkToolkit
|
||||
from langchain.chat_models import ChatOpenAI
|
||||
from langchain.tools.ainetwork.utils import authenticate
|
||||
|
||||
|
||||
class Match(Enum):
|
||||
__test__ = False
|
||||
ListWildcard = 1
|
||||
StrWildcard = 2
|
||||
DictWildcard = 3
|
||||
IntWildcard = 4
|
||||
FloatWildcard = 5
|
||||
ObjectWildcard = 6
|
||||
|
||||
@classmethod
|
||||
def match(cls, value: Any, template: Any) -> bool:
|
||||
if template is cls.ListWildcard:
|
||||
return isinstance(value, list)
|
||||
elif template is cls.StrWildcard:
|
||||
return isinstance(value, str)
|
||||
elif template is cls.DictWildcard:
|
||||
return isinstance(value, dict)
|
||||
elif template is cls.IntWildcard:
|
||||
return isinstance(value, int)
|
||||
elif template is cls.FloatWildcard:
|
||||
return isinstance(value, float)
|
||||
elif template is cls.ObjectWildcard:
|
||||
return True
|
||||
elif type(value) != type(template):
|
||||
return False
|
||||
elif isinstance(value, dict):
|
||||
if len(value) != len(template):
|
||||
return False
|
||||
for k, v in value.items():
|
||||
if k not in template or not cls.match(v, template[k]):
|
||||
return False
|
||||
return True
|
||||
elif isinstance(value, list):
|
||||
if len(value) != len(template):
|
||||
return False
|
||||
for i in range(len(value)):
|
||||
if not cls.match(value[i], template[i]):
|
||||
return False
|
||||
return True
|
||||
else:
|
||||
return value == template
|
||||
|
||||
|
||||
@pytest.mark.requires("ain")
|
||||
def test_ainetwork_toolkit() -> None:
|
||||
def get(path: str, type: str = "value", default: Any = None) -> Any:
|
||||
ref = ain.db.ref(path)
|
||||
value = asyncio.run(
|
||||
{
|
||||
"value": ref.getValue,
|
||||
"rule": ref.getRule,
|
||||
"owner": ref.getOwner,
|
||||
}[type]()
|
||||
)
|
||||
return default if value is None else value
|
||||
|
||||
def validate(path: str, template: Any, type: str = "value") -> bool:
|
||||
value = get(path, type)
|
||||
return Match.match(value, template)
|
||||
|
||||
if not os.environ.get("AIN_BLOCKCHAIN_ACCOUNT_PRIVATE_KEY", None):
|
||||
from ain.account import Account
|
||||
|
||||
account = Account.create()
|
||||
os.environ["AIN_BLOCKCHAIN_ACCOUNT_PRIVATE_KEY"] = account.private_key
|
||||
|
||||
interface = authenticate(network="testnet")
|
||||
toolkit = AINetworkToolkit(network="testnet", interface=interface)
|
||||
llm = ChatOpenAI(model="gpt-4", temperature=0)
|
||||
agent = initialize_agent(
|
||||
tools=toolkit.get_tools(),
|
||||
llm=llm,
|
||||
verbose=True,
|
||||
agent=AgentType.OPENAI_FUNCTIONS,
|
||||
)
|
||||
ain = interface
|
||||
self_address = ain.wallet.defaultAccount.address
|
||||
co_address = "0x6813Eb9362372EEF6200f3b1dbC3f819671cBA69"
|
||||
|
||||
# Test creating an app
|
||||
UUID = uuid.UUID(
|
||||
int=(int(time.time() * 1000) << 64) | (uuid.uuid4().int & ((1 << 64) - 1))
|
||||
)
|
||||
app_name = f"_langchain_test__{str(UUID).replace('-', '_')}"
|
||||
agent.run(f"""Create app {app_name}""")
|
||||
validate(f"/manage_app/{app_name}/config", {"admin": {self_address: True}})
|
||||
validate(f"/apps/{app_name}/DB", None, "owner")
|
||||
|
||||
# Test reading owner config
|
||||
agent.run(f"""Read owner config of /apps/{app_name}/DB .""")
|
||||
assert ...
|
||||
|
||||
# Test granting owner config
|
||||
agent.run(
|
||||
f"""Grant owner authority to {co_address} for edit write rule permission of /apps/{app_name}/DB_co .""" # noqa: E501
|
||||
)
|
||||
validate(
|
||||
f"/apps/{app_name}/DB_co",
|
||||
{
|
||||
".owner": {
|
||||
"owners": {
|
||||
co_address: {
|
||||
"branch_owner": False,
|
||||
"write_function": False,
|
||||
"write_owner": False,
|
||||
"write_rule": True,
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"owner",
|
||||
)
|
||||
|
||||
# Test reading owner config
|
||||
agent.run(f"""Read owner config of /apps/{app_name}/DB_co .""")
|
||||
assert ...
|
||||
|
||||
# Test reading owner config
|
||||
agent.run(f"""Read owner config of /apps/{app_name}/DB .""")
|
||||
assert ... # Check if owner {self_address} exists
|
||||
|
||||
# Test reading a value
|
||||
agent.run(f"""Read value in /apps/{app_name}/DB""")
|
||||
assert ... # empty
|
||||
|
||||
# Test writing a value
|
||||
agent.run(f"""Write value {{1: 1904, 2: 43}} in /apps/{app_name}/DB""")
|
||||
validate(f"/apps/{app_name}/DB", {1: 1904, 2: 43})
|
||||
|
||||
# Test reading a value
|
||||
agent.run(f"""Read value in /apps/{app_name}/DB""")
|
||||
assert ... # check value
|
||||
|
||||
# Test reading a rule
|
||||
agent.run(f"""Read write rule of app {app_name} .""")
|
||||
assert ... # check rule that self_address exists
|
||||
|
||||
# Test sending AIN
|
||||
self_balance = get(f"/accounts/{self_address}/balance", default=0)
|
||||
transaction_history = get(f"/transfer/{self_address}/{co_address}", default={})
|
||||
if self_balance < 1:
|
||||
try:
|
||||
with urllib.request.urlopen(
|
||||
f"http://faucet.ainetwork.ai/api/test/{self_address}/"
|
||||
) as response:
|
||||
try_test = response.getcode()
|
||||
except HTTPError as e:
|
||||
try_test = e.getcode()
|
||||
else:
|
||||
try_test = 200
|
||||
|
||||
if try_test == 200:
|
||||
agent.run(f"""Send 1 AIN to {co_address}""")
|
||||
transaction_update = get(f"/transfer/{self_address}/{co_address}", default={})
|
||||
assert any(
|
||||
transaction_update[key]["value"] == 1
|
||||
for key in transaction_update.keys() - transaction_history.keys()
|
||||
)
|
@ -2,6 +2,11 @@
|
||||
from langchain.tools import __all__ as public_api
|
||||
|
||||
_EXPECTED = [
|
||||
"AINAppOps",
|
||||
"AINOwnerOps",
|
||||
"AINRuleOps",
|
||||
"AINTransfer",
|
||||
"AINValueOps",
|
||||
"AIPluginTool",
|
||||
"APIOperation",
|
||||
"ArxivQueryRun",
|
||||
|
Loading…
Reference in New Issue
Block a user