Skip to content

Commit

Permalink
add cdp-langchain
Browse files Browse the repository at this point in the history
  • Loading branch information
0xAlec committed Nov 4, 2024
1 parent bed6a98 commit 09a920d
Show file tree
Hide file tree
Showing 21 changed files with 4,913 additions and 0 deletions.
File renamed without changes.
11 changes: 11 additions & 0 deletions cdp-langchain/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.PHONY: format
format:
ruff format .

.PHONY: lint
lint:
ruff check .

.PHONY: lint-fix
lint-fix:
ruff check . --fix
60 changes: 60 additions & 0 deletions cdp-langchain/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# CDP Agentkit Extension - Langchain Toolkit

## Developing
- Agentkit uses `poetry` for package management and tooling
- [Poetry Installation Instructions](https://python-poetry.org/docs/#installation)
- Run `poetry install` to install `cdp-langchain` dependencies
- Run `poetry shell` to activate the virtual environment

### Formatting
`make format`

### Linting
- Check linter
`make lint`

- Fix linter errors
`make lint-fix`

## Adding an Agentic Action to the Langchain Toolkit
1. Ensure the action is implemented in `cdp-agentkit-core`.
2. Add a wrapper method to `CdpAgentkitWrapper` in `./cdp_langchain/utils/cdp_agentkit_wrapper.py`
- E.g.
```python
def mint_nft_wrapper(self, contract_address: str, destination: str) -> str:
"""Mint an NFT (ERC-721) to a specified destination address onchain via a contract invocation.
Args:
contract_address (str): "The contract address of the NFT (ERC-721) to mint, e.g. `0x036CbD53842c5426634e7929541eC2318f3dCF7e`".
destination (str): "The destination address that will receieve the NFT onchain, e.g. `0x036CbD53842c5426634e7929541eC2318f3dCF7e`".
Returns:
str: A message containing the NFT mint details.
"""
return mint_nft(
wallet=self.wallet,
contract_address=contract_address,
destination=destination,
)
```
3. Add call to the wrapper in `CdpAgentkitWrapper.run` in `./cdp_langchain/utils/cdp_agentkit_wrapper.py`
- E.g.
```python
if mode == "mint_nft":
return self.mint_nft_wrapper(**kwargs)

```
4. Add the action to the list of available tools in the `CdpToolkit` in `./cdp_langchain/agent_toolkits/cdp_toolkit.py`
- E.g.
```python
actions: List[Dict] = [
{
"mode": "mint_nft",
"name": "mint_nft",
"description": MINT_NFT_PROMPT,
"args_schema": MintNftInput,
},
]
```
5. Add the action to the list of tools in the `CdpToolkit` class documentation.
1 change: 1 addition & 0 deletions cdp-langchain/cdp_langchain/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

3 changes: 3 additions & 0 deletions cdp-langchain/cdp_langchain/agent_toolkits/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from cdp_langchain.agent_toolkits.cdp_toolkit import CdpToolkit

__all__ = ["CdpToolkit"]
215 changes: 215 additions & 0 deletions cdp-langchain/cdp_langchain/agent_toolkits/cdp_toolkit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
"""CDP Toolkit."""

from langchain_core.tools import BaseTool
from langchain_core.tools.base import BaseToolkit

from cdp_agentkit_core.actions import (
DEPLOY_NFT_PROMPT,
DEPLOY_TOKEN_PROMPT,
GET_BALANCE_PROMPT,
GET_WALLET_DETAILS_PROMPT,
MINT_NFT_PROMPT,
REGISTER_BASENAME_PROMPT,
REQUEST_FAUCET_FUNDS_PROMPT,
TRADE_PROMPT,
TRANSFER_PROMPT,
DeployNftInput,
DeployTokenInput,
GetBalanceInput,
GetWalletDetailsInput,
MintNftInput,
RegisterBasenameInput,
RequestFaucetFundsInput,
TradeInput,
TransferInput,
)
from cdp_langchain.tools import CdpAction
from cdp_langchain.utils import CdpAgentkitWrapper


class CdpToolkit(BaseToolkit):
"""Coinbase Developer Platform (CDP) Toolkit.
*Security Note*: This toolkit contains tools that can read and modify
the state of a service; e.g., by creating, deleting, or updating,
reading underlying data.
For example, this toolkit can be used to create wallets, transactions,
and smart contract invocations on CDP supported blockchains.
See [Security](https://python.langchain.com/docs/security) for more information.
Setup:
See detailed installation instructions here:
https://python.langchain.com/docs/integrations/tools/cdp/#installation
You will need to set the following environment
variables:
.. code-block:: bash
export CDP_API_KEY_NAME="cdp-api-key-name"
export CDP_API_KEY_PRIVATE_KEY="cdp-api-key-private-key"
export NETWORK_ID="network-id"
Instantiate:
.. code-block:: python
from cdp_langchain.agent_toolkits import CdpToolkit
from cdp_langchain.utils import CdpAgentkitWrapper
cdp = CdpAgentkitWrapper()
cdp_toolkit = CdpToolkit.from_cdp_agentkit_wrapper(cdp)
Tools:
.. code-block:: python
tools = cdp_toolkit.get_tools()
for tool in tools:
print(tool.name)
.. code-block:: none
get_wallet_details
get_balance
request_faucet_funds
transfer
trade
deploy_token
mint_nft
deploy_nft
register_basename
Use within an agent:
.. code-block:: python
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
# Select example tool
tools = [tool for tool in toolkit.get_tools() if tool.name == "get_wallet_details"]
assert len(tools) == 1
llm = ChatOpenAI(model="gpt-4o-mini")
agent_executor = create_react_agent(llm, tools)
example_query = "Tell me about your wallet"
events = agent_executor.stream(
{"messages": [("user", example_query)]},
stream_mode="values",
)
for event in events:
event["messages"][-1].pretty_print()
.. code-block:: none
================================[1m Human Message [0m=================================
Tell me about your wallet
==================================[1m Ai Message [0m==================================
Tool Calls:
get_wallet_details (call_iSYJVaM7uchfNHOMJoVPQsOi)
Call ID: call_iSYJVaM7uchfNHOMJoVPQsOi
Args:
no_input: ""
=================================[1m Tool Message [0m=================================
Name: get_wallet_details
...
==================================[1m Ai Message [0m==================================
My wallet is wallet-id-123 on Base Sepolia with default address 0x0123
Parameters
----------
tools: List[BaseTool]. The tools in the toolkit. Default is an empty list.
"""

tools: list[BaseTool] = [] # noqa: RUF012

@classmethod
def from_cdp_agentkit_wrapper(cls, cdp_agentkit_wrapper: CdpAgentkitWrapper) -> "CdpToolkit":
"""Create a CdpToolkit from a CdpAgentkitWrapper.
Args:
cdp_agentkit_wrapper: CdpAgentkitWrapper. The CDP Agentkit wrapper.
Returns:
CdpToolkit. The CDP toolkit.
"""
actions: list[dict] = [
{
"mode": "get_wallet_details",
"name": "get_wallet_details",
"description": GET_WALLET_DETAILS_PROMPT,
"args_schema": GetWalletDetailsInput,
},
{
"mode": "get_balance",
"name": "get_balance",
"description": GET_BALANCE_PROMPT,
"args_schema": GetBalanceInput,
},
{
"mode": "request_faucet_funds",
"name": "request_faucet_funds",
"description": REQUEST_FAUCET_FUNDS_PROMPT,
"args_schema": RequestFaucetFundsInput,
},
{
"mode": "transfer",
"name": "transfer",
"description": TRANSFER_PROMPT,
"args_schema": TransferInput,
},
{
"mode": "trade",
"name": "trade",
"description": TRADE_PROMPT,
"args_schema": TradeInput,
},
{
"mode": "deploy_token",
"name": "deploy_token",
"description": DEPLOY_TOKEN_PROMPT,
"args_schema": DeployTokenInput,
},
{
"mode": "mint_nft",
"name": "mint_nft",
"description": MINT_NFT_PROMPT,
"args_schema": MintNftInput,
},
{
"mode": "deploy_nft",
"name": "deploy_nft",
"description": DEPLOY_NFT_PROMPT,
"args_schema": DeployNftInput,
},
{
"mode": "register_basename",
"name": "register_basename",
"description": REGISTER_BASENAME_PROMPT,
"args_schema": RegisterBasenameInput,
},
]

tools = [
CdpAction(
name=action["name"],
description=action["description"],
mode=action["mode"],
cdp_agentkit_wrapper=cdp_agentkit_wrapper,
args_schema=action.get("args_schema", None),
)
for action in actions
]

return cls(tools=tools) # type: ignore[arg-type]

def get_tools(self) -> list[BaseTool]:
"""Get the tools in the toolkit."""
return self.tools
5 changes: 5 additions & 0 deletions cdp-langchain/cdp_langchain/tools/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""CDP Tool."""

from cdp_langchain.tools.cdp_action import CdpAction

__all__ = ["CdpAction"]
43 changes: 43 additions & 0 deletions cdp-langchain/cdp_langchain/tools/cdp_action.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""Tool allows agents to interact with the cdp-sdk library and control an MPC Wallet onchain.
To use this tool, you must first set as environment variables:
CDP_API_KEY_NAME
CDP_API_KEY_PRIVATE_KEY
NETWORK_ID
"""

from typing import Any

from langchain_core.callbacks import CallbackManagerForToolRun
from langchain_core.tools import BaseTool
from pydantic import BaseModel

from cdp_langchain.utils.cdp_agentkit_wrapper import CdpAgentkitWrapper


class CdpAction(BaseTool): # type: ignore[override]
"""Tool for interacting with the CDP SDK."""

cdp_agentkit_wrapper: CdpAgentkitWrapper
mode: str
name: str = ""
description: str = ""
args_schema: type[BaseModel] | None = None

def _run(
self,
instructions: str | None = "",
run_manager: CallbackManagerForToolRun | None = None,
**kwargs: Any,
) -> str:
"""Use the CDP SDK to run an operation."""
if not instructions or instructions == "{}":
# Catch other forms of empty input that GPT-4 likes to send.
instructions = ""
if self.args_schema is not None:
validated_input_data = self.args_schema(**kwargs)
parsed_input_args = validated_input_data.model_dump()
else:
parsed_input_args = {"instructions": instructions}
return self.cdp_agentkit_wrapper.run(self.mode, **parsed_input_args)
5 changes: 5 additions & 0 deletions cdp-langchain/cdp_langchain/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""**Utilities** are the integration wrappers that LangChain uses to interact with third-party systems and packages."""

from cdp_langchain.utils.cdp_agentkit_wrapper import CdpAgentkitWrapper

__all__ = ["CdpAgentkitWrapper"]
Loading

0 comments on commit 09a920d

Please sign in to comment.