diff --git a/cdp-agentkit-core/CHANGELOG.md b/cdp-agentkit-core/CHANGELOG.md index 3a5487c34..e2070421f 100644 --- a/cdp-agentkit-core/CHANGELOG.md +++ b/cdp-agentkit-core/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +- Added `account_mentions` action to `twitter-langchain`. +- Added `post_tweet_reply` action to `twitter-langchain`. + ## [0.0.4] - 2024-11-15 ### Added diff --git a/cdp-agentkit-core/cdp_agentkit_core/actions/social/twitter/__init__.py b/cdp-agentkit-core/cdp_agentkit_core/actions/social/twitter/__init__.py index c8735ce9a..410a3d7e2 100644 --- a/cdp-agentkit-core/cdp_agentkit_core/actions/social/twitter/__init__.py +++ b/cdp-agentkit-core/cdp_agentkit_core/actions/social/twitter/__init__.py @@ -1,6 +1,8 @@ from cdp_agentkit_core.actions.social.twitter.account_details import AccountDetailsAction +from cdp_agentkit_core.actions.social.twitter.account_mentions import AccountMentionsAction from cdp_agentkit_core.actions.social.twitter.action import TwitterAction from cdp_agentkit_core.actions.social.twitter.post_tweet import PostTweetAction +from cdp_agentkit_core.actions.social.twitter.post_tweet_reply import PostTweetReplyAction def get_all_twitter_actions() -> list[type[TwitterAction]]: @@ -16,6 +18,8 @@ def get_all_twitter_actions() -> list[type[TwitterAction]]: __all__ = [ "TwitterAction", "AccountDetailsAction", + "AccountMentionsAction", "PostTweetAction", + "PostTweetReplyAction", "TWITTER_ACTIONS", ] diff --git a/cdp-agentkit-core/cdp_agentkit_core/actions/social/twitter/account_details.py b/cdp-agentkit-core/cdp_agentkit_core/actions/social/twitter/account_details.py index 49d48fdba..5af2281f2 100644 --- a/cdp-agentkit-core/cdp_agentkit_core/actions/social/twitter/account_details.py +++ b/cdp-agentkit-core/cdp_agentkit_core/actions/social/twitter/account_details.py @@ -1,4 +1,5 @@ from collections.abc import Callable +from json import dumps import tweepy from pydantic import BaseModel @@ -6,7 +7,16 @@ from cdp_agentkit_core.actions.social.twitter.action import TwitterAction ACCOUNT_DETAILS_PROMPT = """ -This tool will return account details for the currently authenticated Twitter (X) user context.""" +This tool will return account details for the currently authenticated Twitter (X) user context. + +A successful response will return a message with the api response as a json payload: + {"data": {"id": "1853889445319331840", "name": "CDP AgentKit", "username": "CDPAgentKit"}} + +A failure response will return a message with the tweepy client api request error: + Error retrieving authenticated user account: 429 Too Many Requests + + +""" class AccountDetailsInput(BaseModel): @@ -27,13 +37,10 @@ def account_details(client: tweepy.Client) -> str: try: response = client.get_me() - user = response.data + data = response['data'] + data['url'] = f"https://x.com/{data['username']}" - message = f"""Successfully retrieved authenticated user account details. Please present the following as json and not markdown: - id: {user.id} - name: {user.name} - username: {user.username} - link: https://x.com/{user.username}""" + message = f"""Successfully retrieved authenticated user account details:\n{dumps(response)}""" except tweepy.errors.TweepyException as e: message = f"Error retrieving authenticated user account details: {e}" diff --git a/cdp-agentkit-core/cdp_agentkit_core/actions/social/twitter/account_mentions.py b/cdp-agentkit-core/cdp_agentkit_core/actions/social/twitter/account_mentions.py new file mode 100644 index 000000000..6cc173a12 --- /dev/null +++ b/cdp-agentkit-core/cdp_agentkit_core/actions/social/twitter/account_mentions.py @@ -0,0 +1,62 @@ +from collections.abc import Callable +from json import dumps + +import tweepy +from pydantic import BaseModel, Field + +from cdp_agentkit_core.actions.social.twitter.action import TwitterAction + +ACCOUNT_MENTIONS_PROMPT = """ +This tool will return account mentions for the currently authenticated Twitter (X) user context. +Please note that this may only be called once every 15 minutes under the free api tier. + +A successful response will return a message with the api response as a json payload: + {"data": [{"id": "1857479287504584856", "edit_history_tweet_ids": ["1857479287504584856"], "text": "@CDPAgentKit reply"}], "meta": {"result_count": 1, "newest_id": "1857479287504584856", "oldest_id": "1857479287504584856"}} + + +A failure response will return a message with the tweepy client api request error: + Error retrieving authenticated user account mentions: 429 Too Many Requests + +""" + + +class AccountMentionsInput(BaseModel): + """Input argument schema for Twitter account mentions action.""" + + account_id: str = Field( + ..., + description="The account id for the Twitter (X) user to get mentions for", + ) + + +def account_mentions(client: tweepy.Client, account_id: str) -> str: + """Get the authenticated Twitter (X) user account mentions. + + Args: + client (tweepy.Client): The Twitter (X) client used to authenticate with. + account_id (str): The Twitter (X) account id to get mentions for. + + Returns: + str: A message containing account mentions for the authenticated user context. + + """ + message = "" + + print(f"attempting to get mentions for account_id: {account_id}") + + try: + response = client.get_users_mentions(account_id) + message = f"Successfully retrieved authenticated user account mentions:\n{dumps(response)}" + except tweepy.errors.TweepyException as e: + message = f"Error retrieving authenticated user account mentions:\n{e}" + + return message + + +class AccountMentionsAction(TwitterAction): + """Twitter (X) account mentions action.""" + + name: str = "account_mentions" + description: str = ACCOUNT_MENTIONS_PROMPT + args_schema: type[BaseModel] | None = AccountMentionsInput + func: Callable[..., str] = account_mentions diff --git a/cdp-agentkit-core/cdp_agentkit_core/actions/social/twitter/post_tweet.py b/cdp-agentkit-core/cdp_agentkit_core/actions/social/twitter/post_tweet.py index 3a05740f1..e984f2673 100644 --- a/cdp-agentkit-core/cdp_agentkit_core/actions/social/twitter/post_tweet.py +++ b/cdp-agentkit-core/cdp_agentkit_core/actions/social/twitter/post_tweet.py @@ -1,4 +1,5 @@ from collections.abc import Callable +from json import dumps import tweepy from pydantic import BaseModel, Field @@ -6,7 +7,15 @@ from cdp_agentkit_core.actions.social.twitter import TwitterAction POST_TWEET_PROMPT = """ -This tool will post a tweet on Twitter. The tool takes the text of the tweet as input. Tweets can be maximum 280 characters.""" +This tool will post a tweet on Twitter. The tool takes the text of the tweet as input. Tweets can be maximum 280 characters. + +A successful response will return a message with the api response as a json payload: + {"data": {"text": "hello, world!", "id": "0123456789012345678", "edit_history_tweet_ids": ["0123456789012345678"]}} + +A failure response will return a message with the tweepy client api request error: + You are not allowed to create a Tweet with duplicate content. + +""" class PostTweetInput(BaseModel): @@ -32,10 +41,10 @@ def post_tweet(client: tweepy.Client, tweet: str) -> str: message = "" try: - client.create_tweet(text=tweet) - message = f"Successfully posted to Twitter:\n{tweet}" + response = client.create_tweet(text=tweet) + message = f"Successfully posted to Twitter:\n{dumps(response)}" except tweepy.errors.TweepyException as e: - message = f"Error posting to Twitter:\n{tweet}\n{e}" + message = f"Error posting to Twitter:\n{e}" return message diff --git a/cdp-agentkit-core/cdp_agentkit_core/actions/social/twitter/post_tweet_reply.py b/cdp-agentkit-core/cdp_agentkit_core/actions/social/twitter/post_tweet_reply.py new file mode 100644 index 000000000..52e5c9f88 --- /dev/null +++ b/cdp-agentkit-core/cdp_agentkit_core/actions/social/twitter/post_tweet_reply.py @@ -0,0 +1,64 @@ +from collections.abc import Callable +from json import dumps + +import tweepy +from pydantic import BaseModel, Field + +from cdp_agentkit_core.actions.social.twitter import TwitterAction + +POST_TWEET_REPLY_PROMPT = """ +This tool will post a reply to a tweet on Twitter. The tool takes the text of the reply and the tweet id to reply to as input. Tweets can be maximum 280 characters. + +A successful response will return a message with the api response as a json payload: + {"data": {"id": "0123456789012345678", "text": "hellllloooo!", "edit_history_tweet_ids": ["1234567890123456789"]}} + +A failure response will return a message with the tweepy client api request error: + You are not allowed to create a Tweet with duplicate content. + +""" + + +class PostTweetReplyInput(BaseModel): + """Input argument schema for twitter post tweet reply action.""" + + tweet_id: str = Field( + ..., + description="The tweet id to post a reply to twitter", + ) + + tweet_reply: str = Field( + ..., + description="The text of the tweet to post in reply to another tweet on twitter. Tweets can be maximum 280 characters.", + ) + + +def post_tweet_reply(client: tweepy.Client, tweet_id: str, tweet_reply: str) -> str: + """Post tweet reply to Twitter. + + Args: + client (tweepy.Client): The tweepy client to use. + tweet_id (str): The id of the tweet to reply to in twitter. + tweet_reply (str): The text of the reply to post in reponse to a tweet on twitter. + + Returns: + str: A message containing the result of the reply action and any associated data. + + """ + message = "" + + try: + response = client.create_tweet(text=tweet_reply, in_reply_to_tweet_id=tweet_id) + message = f"Successfully posted reply to Twitter:\n{dumps(response)}" + except tweepy.errors.TweepyException as e: + message = f"Error posting reply to Twitter:\n{e}" + + return message + + +class PostTweetReplyAction(TwitterAction): + """Twitter (X) post tweet reply action.""" + + name: str = "post_tweet_reply" + description: str = POST_TWEET_REPLY_PROMPT + args_schema: type[BaseModel] | None = PostTweetReplyInput + func: Callable[..., str] = post_tweet_reply diff --git a/twitter-langchain/examples/account_details/Makefile b/twitter-langchain/examples/account_details/Makefile deleted file mode 100644 index b2bf7df51..000000000 --- a/twitter-langchain/examples/account_details/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -ifneq (,$(wildcard ./.env)) - include .env -endif - -export - -.PHONY: run -run: - poetry install --with dev - poetry run python account_details.py diff --git a/twitter-langchain/examples/account_details/account_details.py b/twitter-langchain/examples/account_details/account_details.py deleted file mode 100644 index 2bd6e5b6f..000000000 --- a/twitter-langchain/examples/account_details/account_details.py +++ /dev/null @@ -1,58 +0,0 @@ -from langchain_core.messages import HumanMessage -from langchain_openai import ChatOpenAI -from langgraph.prebuilt import create_react_agent - -from twitter_langchain import TwitterApiWrapper, TwitterToolkit - -# Initialize TwitterApiwrapper -twitter_api_wrapper = TwitterApiWrapper() - -# Create TwitterToolkit from the api wrapper -twitter_toolkit = TwitterToolkit.from_twitter_api_wrapper(twitter_api_wrapper) - -# View available tools -tools = twitter_toolkit.get_tools() -for tool in tools: - print(tool.name) - -# Initialize LLM -llm = ChatOpenAI(model="gpt-4o-mini") - -# Create agent -agent_executor = create_react_agent(llm, tools) - -# Example - get account details -events = agent_executor.stream( - { - "messages": [ - HumanMessage(content="Please obtain my twitter account information"), - ], - }, - stream_mode="values", -) - -for event in events: - event["messages"][-1].pretty_print() - -# ================================ Human Message ================================= -# Please obtain my twitter account information -# ================================== Ai Message ================================== -# Tool Calls: -# account_details (call_pYME8H1tHfdMakFZ1FTS0VBX) -# Call ID: call_pYME8H1tHfdMakFZ1FTS0VBX -# Args: -# ================================= Tool Message ================================= -# Name: account_details - -# Successfully retrieved authenticated user account details. Please present the following as json and not markdown: -# id: 1234567890123456789 -# name: My Twitter Name -# username: MyTwitterUserName -# link: https://x.com/MyTwitterUserName -# ================================== Ai Message ================================== -# { -# "id": "1234567890123456789", -# "name": "My Twitter Name", -# "username": "MyTwitterUserName", -# "link": "https://x.com/MyTwitterUserName" -# } diff --git a/twitter-langchain/examples/chatbot/.env-local b/twitter-langchain/examples/chatbot/.env-local new file mode 100644 index 000000000..a810998e2 --- /dev/null +++ b/twitter-langchain/examples/chatbot/.env-local @@ -0,0 +1,6 @@ +OPENAI_API_KEY= +TWITTER_ACCESS_TOKEN= +TWITTER_ACCESS_TOKEN_SECRET= +TWITTER_API_KEY= +TWITTER_API_SECRET= +TWITTER_BEARER_TOKEN= diff --git a/twitter-langchain/examples/chatbot/.gitignore b/twitter-langchain/examples/chatbot/.gitignore new file mode 100644 index 000000000..ff3ea062f --- /dev/null +++ b/twitter-langchain/examples/chatbot/.gitignore @@ -0,0 +1,2 @@ +.env +poetry.lock diff --git a/twitter-langchain/examples/post_tweet/Makefile b/twitter-langchain/examples/chatbot/Makefile similarity index 54% rename from twitter-langchain/examples/post_tweet/Makefile rename to twitter-langchain/examples/chatbot/Makefile index 243dee120..44d813633 100644 --- a/twitter-langchain/examples/post_tweet/Makefile +++ b/twitter-langchain/examples/chatbot/Makefile @@ -6,5 +6,5 @@ export .PHONY: run run: - poetry install --with dev - poetry run python post_tweet.py + poetry install --with dev && \ + poetry run python chatbot.py diff --git a/twitter-langchain/examples/chatbot/README.md b/twitter-langchain/examples/chatbot/README.md new file mode 100644 index 000000000..8c102a105 --- /dev/null +++ b/twitter-langchain/examples/chatbot/README.md @@ -0,0 +1,50 @@ +# CDP Agentkit Twitter Langchain Extension Examples - Chatbot + +This example demonstrates an agent setup as a terminal style chatbot with access to Twitter (X) API actions. + +## Ask the chatbot to engage in the Twitter (X) ecosystem! +- "What are my account details?" +- "Please post a message for me to Twitter" +- "Please get my mentions" +- "Please post responses to my mentions" + +## Requirements +- Python 3.10+ +- [OpenAI API Key](https://platform.openai.com/docs/quickstart#create-and-export-an-api-key) +- [Twitter (X) API Key's](https://developer.x.com/en/portal/dashboard) + +### Twitter Application Setup +1. Visit the Twitter (X) [Developer Portal](https://developer.x.com/en/portal/dashboard) +2. Navigate to your project +3. Navigate to your application +4. Edit "User authentication settings" +5. Set "App permissions" to "Read and write and Direct message" +6. Set "Type of App" to "Web App, Automated app or Bot" +7. Set "App info" urls +8. Save +9. Navigate to "Keys and tokens" +10. Regenerate all keys and tokens + +### Checking Python Version + +```bash +python --version +pip --version +``` + +## Run the Chatbot + +### Env +Ensure the following vars are set in .env-local: +- "OPENAI_API_KEY" +- "TWITTER_ACCESS_TOKEN" +- "TWITTER_ACCESS_TOKEN_SECRET" +- "TWITTER_API_KEY" +- "TWITTER_API_SECRET" +- "TWITTER_BEARER_TOKEN" + +Rename .env-local to .env + +```bash +make run +``` diff --git a/twitter-langchain/examples/chatbot/__init__.py b/twitter-langchain/examples/chatbot/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/twitter-langchain/examples/chatbot/chatbot.py b/twitter-langchain/examples/chatbot/chatbot.py new file mode 100644 index 000000000..3220fa351 --- /dev/null +++ b/twitter-langchain/examples/chatbot/chatbot.py @@ -0,0 +1,130 @@ +import os +import sys +import time + +from langchain_core.messages import HumanMessage +from langchain_openai import ChatOpenAI +from langgraph.checkpoint.memory import MemorySaver +from langgraph.prebuilt import create_react_agent + +# Import CDP Agentkit Twitter Langchain Extension. +from twitter_langchain import ( + TwitterApiWrapper, + TwitterToolkit, +) + +# Configure a file to persist the agent's CDP MPC Wallet Data. +wallet_data_file = "wallet_data.txt" + + +def initialize_agent(): + """Initialize the agent with CDP Agentkit Twitter Langchain.""" + # Initialize LLM. + llm = ChatOpenAI(model="gpt-4o-mini") + + # Configure CDP Agentkit Twitter Langchain Extension. + values = {} + + # Initialize CDP Agentkit Twitter Langchain + wrapper = TwitterApiWrapper(**values) + toolkit = TwitterToolkit.from_twitter_api_wrapper(wrapper) + tools = toolkit.get_tools() + + # Store buffered conversation history in memory. + memory = MemorySaver() + config = {"configurable": {"thread_id": "CDP Agentkit Chatbot Example!"}} + + # Create ReAct Agent using the LLM and CDP Agentkit tools. + return create_react_agent( + llm, + tools=tools, + checkpointer=memory, + state_modifier="You are a helpful agent that can interact onchain using the Coinbase Developer Platform Agentkit. You are empowered to interact onchain using your tools. If you ever need funds, you can request them from the faucet if you are on network ID `base-sepolia`. If not, you can provide your wallet details and request funds from the user. 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 CDP SDK + Agentkit, recommend they go to docs.cdp.coinbase.com for more informaton. Be concise and helpful with your responses. Refrain from restating your tools' descriptions unless it is explicitly requested.", + ), config + + +# Autonomous Mode +def run_autonomous_mode(agent_executor, config, interval=10): + """Run the agent autonomously with specified intervals.""" + print("Starting autonomous mode...") + while True: + try: + # Provide instructions autonomously + thought = ( + "Be creative and do something interesting on the blockchain. " + "Choose an action or set of actions and execute it that highlights your abilities." + ) + + # Run agent in autonomous mode + for chunk in agent_executor.stream( + {"messages": [HumanMessage(content=thought)]}, config + ): + if "agent" in chunk: + print(chunk["agent"]["messages"][0].content) + elif "tools" in chunk: + print(chunk["tools"]["messages"][0].content) + print("-------------------") + + # Wait before the next action + time.sleep(interval) + + except KeyboardInterrupt: + print("Goodbye Agent!") + sys.exit(0) + + +# Chat Mode +def run_chat_mode(agent_executor, config): + """Run the agent interactively based on user input.""" + print("Starting chat mode... Type 'exit' to end.") + while True: + try: + user_input = input("\nUser: ") + if user_input.lower() == "exit": + break + + # Run agent with the user's input in chat mode + for chunk in agent_executor.stream( + {"messages": [HumanMessage(content=user_input)]}, config + ): + if "agent" in chunk: + print(chunk["agent"]["messages"][0].content) + elif "tools" in chunk: + print(chunk["tools"]["messages"][0].content) + print("-------------------") + + except KeyboardInterrupt: + print("Goodbye Agent!") + sys.exit(0) + + +# Mode Selection +def choose_mode(): + """Choose whether to run in autonomous or chat mode based on user input.""" + while True: + print("\nAvailable modes:") + print("1. chat - Interactive chat mode") + print("2. auto - Autonomous action mode") + + choice = input("\nChoose a mode (enter number or name): ").lower().strip() + if choice in ["1", "chat"]: + return "chat" + elif choice in ["2", "auto"]: + return "auto" + print("Invalid choice. Please try again.") + + +def main(): + """Start the chatbot agent.""" + agent_executor, config = initialize_agent() + + mode = choose_mode() + if mode == "chat": + run_chat_mode(agent_executor=agent_executor, config=config) + elif mode == "auto": + run_autonomous_mode(agent_executor=agent_executor, config=config) + + +if __name__ == "__main__": + print("Starting Agent...") + main() diff --git a/twitter-langchain/examples/chatbot/pyproject.toml b/twitter-langchain/examples/chatbot/pyproject.toml new file mode 100644 index 000000000..f38800ecb --- /dev/null +++ b/twitter-langchain/examples/chatbot/pyproject.toml @@ -0,0 +1,26 @@ +[tool.poetry] +name = "chat" +version = "0.0.1" +description = "CDP Agentkit Langchain Chat Example" +authors = ["John Peterson "] +readme = "README.md" +license = "Apache-2.0" +keywords = ["coinbase", "sdk", "crypto", "cdp", "agentkit", "ai", "agent", "langchain", "toolkit"] +packages = [] + +[tool.poetry.dependencies] +cdp-sdk = "^0.10.3" +cdp-agentkit-core = {path = "../../../cdp-agentkit-core", develop = true} +cdp-langchain = {path = "../../../cdp-langchain", develop = true} +twitter-langchain = {path = "../../../twitter-langchain", develop = true} +langchain = "^0.3.4" +langchain-openai = "^0.2.4" +langgraph = "^0.2.39" +python = "^3.10" +tweepy = "^4.14.0" + +[tool.poetry.group.dev.dependencies] + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/twitter-langchain/examples/post_tweet/post_tweet.py b/twitter-langchain/examples/post_tweet/post_tweet.py deleted file mode 100644 index d5faba9cd..000000000 --- a/twitter-langchain/examples/post_tweet/post_tweet.py +++ /dev/null @@ -1,52 +0,0 @@ -import uuid - -from langchain_core.messages import HumanMessage -from langchain_openai import ChatOpenAI -from langgraph.prebuilt import create_react_agent - -from twitter_langchain import TwitterApiWrapper, TwitterToolkit - -# Initialize TwitterApiwrapper -twitter_api_wrapper = TwitterApiWrapper() - -# Create TwitterToolkit from the api wrapper -twitter_toolkit = TwitterToolkit.from_twitter_api_wrapper(twitter_api_wrapper) - -# View available tools -tools = twitter_toolkit.get_tools() -for tool in tools: - print(tool.name) - -# Initialize LLM -llm = ChatOpenAI(model="gpt-4o-mini") - -# Create agent -agent_executor = create_react_agent(llm, tools) - -# Example - post tweet -events = agent_executor.stream( - { - "messages": [ - HumanMessage(content=f"Please post 'hello, world! {uuid.uuid4().hex}' to twitter"), - ], - }, - stream_mode="values", -) - -for event in events: - event["messages"][-1].pretty_print() - -# Successful Output -# ================================ Human Message ================================= -# Please post 'hello, world! c4b8e3744c2e4345be9e0622b4c0a8aa' to twitter -# ================================== Ai Message ================================== -# Tool Calls: -# post_tweet (call_xVx4BMCSlCmCcbEQG1yyebbq) -# Call ID: call_xVx4BMCSlCmCcbEQG1yyebbq -# Args: -# text: hello, world! c4b8e3744c2e4345be9e0622b4c0a8aa -# ================================= Tool Message ================================= -# Name: post_tweet -# Successfully posted! -# ================================== Ai Message ================================== -# The message "hello, world! c4b8e3744c2e4345be9e0622b4c0a8aa" has been successfully posted to Twitter! diff --git a/twitter-langchain/twitter_langchain/twitter_api_wrapper.py b/twitter-langchain/twitter_langchain/twitter_api_wrapper.py index 7e57a6ac3..2e823c01b 100644 --- a/twitter-langchain/twitter_langchain/twitter_api_wrapper.py +++ b/twitter-langchain/twitter_langchain/twitter_api_wrapper.py @@ -21,9 +21,8 @@ def validate_environment(cls, values: dict) -> Any: api_key = get_from_dict_or_env(values, "twitter_api_key", "TWITTER_API_KEY") api_secret = get_from_dict_or_env(values, "twitter_api_secret", "TWITTER_API_SECRET") access_token = get_from_dict_or_env(values, "twitter_access_token", "TWITTER_ACCESS_TOKEN") - access_token_secret = get_from_dict_or_env( - values, "twitter_access_token_secret", "TWITTER_ACCESS_TOKEN_SECRET" - ) + access_token_secret = get_from_dict_or_env(values, "twitter_access_token_secret", "TWITTER_ACCESS_TOKEN_SECRET") + bearer_token = get_from_dict_or_env(values, "twitter_bearer_token", "TWITTER_BEARER_TOKEN") try: import tweepy @@ -31,7 +30,7 @@ def validate_environment(cls, values: dict) -> Any: raise ImportError( "Tweepy Twitter SDK is not installed. " -"Please install it with `pip install tweepy`" + "Please install it with `pip install tweepy`" ) from None client = tweepy.Client( @@ -39,6 +38,8 @@ def validate_environment(cls, values: dict) -> Any: consumer_secret=api_secret, access_token=access_token, access_token_secret=access_token_secret, + bearer_token=bearer_token, + return_type=dict, ) values["client"] = client @@ -46,6 +47,7 @@ def validate_environment(cls, values: dict) -> Any: values["api_secret"] = api_secret values["access_token"] = access_token values["access_token_secret"] = access_token_secret + values["bearer_token"] = bearer_token return values