Skip to content

Commit

Permalink
Address comments
Browse files Browse the repository at this point in the history
  • Loading branch information
EricGustin committed Nov 28, 2024
1 parent 0b0e281 commit 527bae6
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 122 deletions.
6 changes: 1 addition & 5 deletions toolkits/x/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,8 @@ build: clean-build ## Build wheel file using poetry

.PHONY: clean-build
clean-build: ## clean build artifacts
rm -rf dist

.PHONY: clean-dist
clean-dist: ## Clean all built distributions
@echo "🗑️ Cleaning dist directory"
@rm -rf dist
rm -rf dist

.PHONY: test
test: ## Test the code with pytest
Expand Down
42 changes: 6 additions & 36 deletions toolkits/x/arcade_x/tools/tweets.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import httpx
from arcade.sdk import ToolContext, tool
from arcade.sdk.auth import X
from arcade.sdk.errors import RetryableToolError, ToolExecutionError
from arcade.sdk.errors import RetryableToolError

from arcade_x.tools.utils import (
expand_urls_in_tweets,
Expand Down Expand Up @@ -35,13 +35,7 @@ async def post_tweet(

async with httpx.AsyncClient() as client:
response = await client.post(TWEETS_URL, headers=headers, json=payload, timeout=10)

if response.status_code != 201:
error_message = response.text
raise ToolExecutionError( # noqa: TRY003
"Error posting tweet",
developer_message=f"Twitter API Error: {response.status_code} {error_message}",
)
response.raise_for_status()

tweet_id = response.json()["data"]["id"]
return f"Tweet with id {tweet_id} posted successfully. URL: {get_tweet_url(tweet_id)}"
Expand All @@ -59,13 +53,7 @@ async def delete_tweet_by_id(

async with httpx.AsyncClient() as client:
response = await client.delete(url, headers=headers, timeout=10)

if response.status_code != 200:
error_message = response.text
raise ToolExecutionError( # noqa: TRY003
"Error deleting tweet",
developer_message=f"Twitter API Error: {response.status_code} {error_message}",
)
response.raise_for_status()

return f"Tweet with id {tweet_id} deleted successfully."

Expand Down Expand Up @@ -93,13 +81,7 @@ async def search_recent_tweets_by_username(

async with httpx.AsyncClient() as client:
response = await client.get(url, headers=headers, params=params, timeout=10)

if response.status_code != 200:
error_message = response.text
raise ToolExecutionError( # noqa: TRY003
"Error searching recent tweets by username",
developer_message=f"Twitter API Error: {response.status_code} {error_message}",
)
response.raise_for_status()

response_data: dict[str, Any] = response.json()

Expand Down Expand Up @@ -158,13 +140,7 @@ async def search_recent_tweets_by_keywords(

async with httpx.AsyncClient() as client:
response = await client.get(url, headers=headers, params=params, timeout=10)

if response.status_code != 200:
error_message = response.text
raise ToolExecutionError( # noqa: TRY003
"Error searching recent tweets by keywords",
developer_message=f"Twitter API Error: {response.status_code} {error_message}",
)
response.raise_for_status()

response_data: dict[str, Any] = response.json()

Expand Down Expand Up @@ -196,13 +172,7 @@ async def lookup_tweet_by_id(

async with httpx.AsyncClient() as client:
response = await client.get(url, headers=headers, params=params, timeout=10)

if response.status_code != 200:
error_message = response.text
raise ToolExecutionError( # noqa: TRY003
"Error looking up tweet",
developer_message=f"Twitter API Error: {response.status_code} {error_message}",
)
response.raise_for_status()

response_data: dict[str, Any] = response.json()

Expand Down
49 changes: 17 additions & 32 deletions toolkits/x/arcade_x/tools/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import httpx
from arcade.sdk import ToolContext, tool
from arcade.sdk.auth import X
from arcade.sdk.errors import RetryableToolError, ToolExecutionError
from arcade.sdk.errors import RetryableToolError

from arcade_x.tools.utils import (
expand_urls_in_user_description,
Expand Down Expand Up @@ -44,36 +44,21 @@ async def lookup_single_user_by_username(
])
url = f"https://api.x.com/2/users/by/username/{username}?user.fields={user_fields}"

try:
async with httpx.AsyncClient() as client:
response = await client.get(url, headers=headers, timeout=10)
async with httpx.AsyncClient() as client:
response = await client.get(url, headers=headers, timeout=10)
if response.status_code == 404:
# User not found
raise RetryableToolError( # noqa: TRY003
"User not found",
developer_message=f"User with username '{username}' not found.",
additional_prompt_content="Please check the username and try again.",
retry_after_ms=500, # Play nice with X API rate limits
)
response.raise_for_status()
# Parse the response JSON
user_data = response.json()["data"]

if response.status_code != 200:
error_message = response.text
if response.status_code == 404:
# User not found
raise RetryableToolError( # noqa: TRY003
"User not found",
developer_message=f"User with username '{username}' not found.",
additional_prompt_content="Please check the username and try again.",
retry_after_ms=500, # Play nice with X API rate limits
)
else:
raise ToolExecutionError( # noqa: TRY003
"Error looking up user",
developer_message=f"X API Error: {response.status_code} {error_message}",
)
else:
# Parse the response JSON
user_data = response.json()["data"]
user_data = expand_urls_in_user_description(user_data, delete_entities=False)
user_data = expand_urls_in_user_url(user_data, delete_entities=True)

user_data = expand_urls_in_user_description(user_data, delete_entities=False)
user_data = expand_urls_in_user_url(user_data, delete_entities=True)

return {"data": user_data}

except httpx.HTTPError as e:
raise ToolExecutionError( # noqa: TRY003
"Network error during user lookup",
developer_message=str(e),
) from e
return {"data": user_data}
8 changes: 4 additions & 4 deletions toolkits/x/evals/eval_x_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,20 +121,20 @@ def x_eval_suite() -> EvalSuite:
(
search_recent_tweets_by_keywords,
{
"keywords": ["Arcade AI"],
"phrases": [],
"keywords": [],
"phrases": ["Arcade AI"],
"max_results": 10,
},
)
],
critics=[
BinaryCritic(
critic_field="keywords",
weight=0.9,
weight=0.1,
),
BinaryCritic(
critic_field="phrases",
weight=0.1,
weight=0.9,
),
],
)
Expand Down
7 changes: 3 additions & 4 deletions toolkits/x/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
[tool.poetry]
name = "arcade_x"
version = "0.1.0"
description = "LLM tools for interacting with X (twitter)"
authors = ["arcadeai <[email protected]>"]
version = "0.1.3"
description = "LLM tools for interacting with X (Twitter)"
authors = ["Arcade AI <[email protected]>"]

[tool.poetry.dependencies]
python = "^3.10"
Expand All @@ -17,7 +17,6 @@ mypy = "^1.5.1"
pre-commit = "^3.4.0"
tox = "^4.11.1"
ruff = "^0.7.4"
arcade-ai = {version = "0.1.*", extras = ["fastapi", "evals"]}

[build-system]
requires = ["poetry-core>=1.0.0"]
Expand Down
47 changes: 22 additions & 25 deletions toolkits/x/tests/test_tweets.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from unittest.mock import MagicMock

import httpx
import pytest
from arcade.sdk.errors import RetryableToolError, ToolExecutionError

Expand Down Expand Up @@ -34,16 +35,15 @@ async def test_post_tweet_success(tool_context, mock_httpx_client):
async def test_post_tweet_failure(tool_context, mock_httpx_client):
"""Test failure when posting a tweet due to API error."""
# Mock response for a failed tweet post
mock_response = MagicMock()
mock_response.status_code = 400
mock_response.text = "Bad Request"
mock_httpx_client.post.return_value = mock_response
mock_response = httpx.HTTPStatusError(
"Bad Request", request=MagicMock(), response=MagicMock(status_code=400)
)
mock_httpx_client.post.side_effect = mock_response

tweet_text = "Hello, world!"
with pytest.raises(ToolExecutionError) as exc_info:
with pytest.raises(ToolExecutionError):
await post_tweet(tool_context, tweet_text)

assert "Error posting tweet" in str(exc_info.value)
mock_httpx_client.post.assert_called_once()


Expand All @@ -66,16 +66,15 @@ async def test_delete_tweet_by_id_success(tool_context, mock_httpx_client):
async def test_delete_tweet_by_id_failure(tool_context, mock_httpx_client):
"""Test failure when deleting a tweet due to API error."""
# Mock response for a failed tweet deletion
mock_response = MagicMock()
mock_response.status_code = 404
mock_response.text = "Not Found"
mock_httpx_client.delete.return_value = mock_response
mock_response = httpx.HTTPStatusError(
"Internal Server Error", request=MagicMock(), response=MagicMock(status_code=404)
)
mock_httpx_client.delete.side_effect = mock_response

tweet_id = "1234567890"
with pytest.raises(ToolExecutionError) as exc_info:
with pytest.raises(ToolExecutionError):
await delete_tweet_by_id(tool_context, tweet_id)

assert "Error deleting tweet" in str(exc_info.value)
mock_httpx_client.delete.assert_called_once()


Expand Down Expand Up @@ -114,16 +113,15 @@ async def test_search_recent_tweets_by_username_success(tool_context, mock_httpx
async def test_search_recent_tweets_by_username_failure(tool_context, mock_httpx_client):
"""Test failure when searching tweets due to API error."""
# Mock response for a failed tweet search
mock_response = MagicMock()
mock_response.status_code = 500
mock_response.text = "Internal Server Error"
mock_httpx_client.get.return_value = mock_response
mock_response = httpx.HTTPStatusError(
"Internal Server Error", request=MagicMock(), response=MagicMock(status_code=500)
)
mock_httpx_client.get.side_effect = mock_response

username = "testuser"
with pytest.raises(ToolExecutionError) as exc_info:
with pytest.raises(ToolExecutionError):
await search_recent_tweets_by_username(tool_context, username)

assert "Error searching recent tweets by username" in str(exc_info.value)
mock_httpx_client.get.assert_called_once()


Expand Down Expand Up @@ -179,15 +177,14 @@ async def test_lookup_tweet_by_id_success(tool_context, mock_httpx_client):
@pytest.mark.asyncio
async def test_lookup_tweet_by_id_failure(tool_context, mock_httpx_client):
"""Test failure when looking up a tweet due to API error."""
# Use MagicMock for the response
mock_response = MagicMock()
mock_response.status_code = 404
mock_response.text = "Not Found"
mock_httpx_client.get.return_value = mock_response
# Mock response for a failed tweet lookup
mock_response = httpx.HTTPStatusError(
"Not Found", request=MagicMock(), response=MagicMock(status_code=404)
)
mock_httpx_client.get.side_effect = mock_response

tweet_id = "1234567890"
with pytest.raises(ToolExecutionError) as exc_info:
with pytest.raises(ToolExecutionError):
await lookup_tweet_by_id(tool_context, tweet_id)

assert "Error looking up tweet" in str(exc_info.value)
mock_httpx_client.get.assert_called_once()
29 changes: 13 additions & 16 deletions toolkits/x/tests/test_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import httpx
import pytest
from arcade.sdk.errors import RetryableToolError, ToolExecutionError
from arcade.sdk.errors import ToolExecutionError

from arcade_x.tools.users import lookup_single_user_by_username

Expand Down Expand Up @@ -35,35 +35,33 @@ async def test_lookup_single_user_by_username_success(tool_context, mock_httpx_c

@pytest.mark.asyncio
async def test_lookup_single_user_by_username_user_not_found(tool_context, mock_httpx_client):
"""Test behavior when the user is not found (404 error)."""
"""Test behavior when looking up user fails due to API error"""
# Mock response for user not found
mock_response = MagicMock()
mock_response.status_code = 404
mock_response.text = "Not Found"
mock_httpx_client.get.return_value = mock_response
mock_response = httpx.HTTPStatusError(
"Not Found", request=MagicMock(), response=MagicMock(status_code=404)
)
mock_httpx_client.get.side_effect = mock_response

username = "nonexistentuser"
with pytest.raises(RetryableToolError) as exc_info:
with pytest.raises(ToolExecutionError):
await lookup_single_user_by_username(tool_context, username)

assert "User not found" in str(exc_info.value)
mock_httpx_client.get.assert_called_once()


@pytest.mark.asyncio
async def test_lookup_single_user_by_username_api_error(tool_context, mock_httpx_client):
"""Test behavior when API returns an error other than 404."""
# Mock response for API error
mock_response = MagicMock()
mock_response.status_code = 500
mock_response.text = "Internal Server Error"
mock_httpx_client.get.return_value = mock_response
mock_response = httpx.HTTPStatusError(
"Internal Server Error", request=MagicMock(), response=MagicMock(status_code=500)
)
mock_httpx_client.get.side_effect = mock_response

username = "testuser"
with pytest.raises(ToolExecutionError) as exc_info:
with pytest.raises(ToolExecutionError):
await lookup_single_user_by_username(tool_context, username)

assert "Error looking up user" in str(exc_info.value)
mock_httpx_client.get.assert_called_once()


Expand All @@ -74,8 +72,7 @@ async def test_lookup_single_user_by_username_network_error(tool_context, mock_h
mock_httpx_client.get.side_effect = httpx.HTTPError("Network Error")

username = "testuser"
with pytest.raises(ToolExecutionError) as exc_info:
with pytest.raises(ToolExecutionError):
await lookup_single_user_by_username(tool_context, username)

assert "Network error during user lookup" in str(exc_info.value)
mock_httpx_client.get.assert_called_once()

0 comments on commit 527bae6

Please sign in to comment.