Skip to content

Commit

Permalink
feat: Scaffolding for Twitter Langchain Toolkit
Browse files Browse the repository at this point in the history
  • Loading branch information
John-peterson-coinbase committed Nov 5, 2024
1 parent ac3fafc commit b110102
Show file tree
Hide file tree
Showing 7 changed files with 2,326 additions and 0 deletions.
1 change: 1 addition & 0 deletions twitter-langchain/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Twitter (X) Langchain Toolkit
2,087 changes: 2,087 additions & 0 deletions twitter-langchain/poetry.lock

Large diffs are not rendered by default.

49 changes: 49 additions & 0 deletions twitter-langchain/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
[tool.poetry]
name = "twitter-langchain"
version = "0.0.1"
description = "Twitter Langchain Toolkit"
authors = ["John Peterson <[email protected]>"]
license = "Apache-2.0"
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.10"
pydantic = "^2.9.2"
langchain = "^0.3.7"
tweepy = "^4.14.0"


[tool.poetry.group.dev.dependencies]
ruff = "^0.7.2"
python-lsp-server = "^1.12.0"
ruff-lsp = "^0.0.58"
pytest = "^8.3.3"
pytest-cov = "^6.0.0"
mypy = "^1.13.0"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

[tool.mypy]
python_version = "3.10"
strict = true
ignore_missing_imports = true

[tool.ruff]
line-length = 100
target-version = "py310"
exclude = ["./build/**", "./dist/**", "./docs/**"]

[tool.ruff.lint]
select = ["E", "F", "I", "N", "W", "D", "UP", "B", "C4", "SIM", "RUF"]
ignore = ["D213", "D203", "D100", "D104", "D107", "E501"]

[tool.ruff.format]
quote-style = "double"
indent-style = "space"
skip-magic-trailing-comma = false
line-ending = "auto"

[tool.ruff.lint.isort]
known-first-party = ["cdp_agentkit_core", "twitter_langchain"]
Empty file.
41 changes: 41 additions & 0 deletions twitter-langchain/twitter_langchain/twitter_action.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""Tool allows agents to interact with the Twitter API.
To use this tool, you must first set as environment variables:
# TODO: List ENV VARs required.
"""

from typing import Any

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

from twitter_langchain.twitter_api_wrapper import TwitterApiWrapper


class TwitterAction(BaseTool): # type: ignore[override]
"""Tool for interacting with the Twitter API."""

twitter_apiwrapper: TwitterApiWrapper
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 Twitter (X) API 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.twitter_api_wrapper.run(self.mode, **parsed_input_args)
19 changes: 19 additions & 0 deletions twitter-langchain/twitter_langchain/twitter_api_wrapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""Util that calls Twitter API."""

from typing import Any

from pydantic import BaseModel, model_validator


class TwitterApiWrapper(BaseModel):
"""Wrapper for Twitter API."""

@model_validator(mode="before")
@classmethod
def validate_environment(cls, values: dict) -> Any:
"""TODO: Implement."""
pass

def run(self, mode: str, **kwargs) -> str:
"""Run the action via the Twitter API."""
raise ValueError("Invalid mode" + mode)
129 changes: 129 additions & 0 deletions twitter-langchain/twitter_langchain/twitter_toolkit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
"""TwitterToolkit."""

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

from twitter_langchain import TwitterAction


class TwitterToolkit(BaseToolkit):
"""Twitter (X) 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 post messages on Twitter (X).
See [Security](https://python.langchain.com/docs/security) for more information.
Setup:
See detailed installation instructions here:
https://python.langchain.com/docs/integrations/tools/twitter/#installation
You will need to set the following environment
variables:
.. code-block:: bash
# TODO: Add relevant ENV VARs
Instantiate:
.. code-block:: python
from twitter_langchain import TwitterToolkit
from twitter_langchain import TwitterAgentkitWrapper
twitter = TwitterAgentkitWrapper()
twitter_toolkit = TwitterToolkit.from_twitter_api_wrapper(twitter)
Tools:
.. code-block:: python
tools = twitter_toolkit.get_tools()
for tool in tools:
print(tool.name)
.. code-block:: none
# TODO: Add list of available tools.
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 == "post_tweet"]
assert len(tools) == 1
llm = ChatOpenAI(model="gpt-4o-mini")
agent_executor = create_react_agent(llm, tools)
example_query = "Post a hello tweet to the world"
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=================================
Post a hello tweet to the world
==================================[1m Ai Message [0m==================================
Tool Calls:
post_tweet (call_iSYJVaM7uchfNHOMJoVPQsOi)
Call ID: call_iSYJVaM7uchfNHOMJoVPQsOi
Args:
no_input: "hello world"
=================================[1m Tool Message [0m=================================
Name: post_tweet
...
==================================[1m Ai Message [0m==================================
I posted the tweet "hello world".
Parameters
----------
tools: List[BaseTool]. The tools in the toolkit. Default is an empty list.
"""

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

@classmethod
def from_twitter_api_wrapper(cls, twitter_api_wrapper: TwitterApiWrapper) -> "TwitterToolkit":
"""Create a TwitterToolkit from a TwitterApiWrapper.
Args:
twitter_api_wrapper: TwitterApiWrapper. The Twitter (X) API wrapper.
Returns:
TwitterToolkit. The Twitter toolkit.
"""
actions: list[dict] = []

tools = [
TwitterAction(
name=action["name"],
description=action["description"],
mode=action["mode"],
twitter_api_wrapper=twitter_api_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

0 comments on commit b110102

Please sign in to comment.