intentkit 0.8.17.dev1__py3-none-any.whl → 0.8.17.dev2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of intentkit might be problematic. Click here for more details.
- intentkit/__init__.py +1 -1
- intentkit/abstracts/agent.py +4 -5
- intentkit/abstracts/engine.py +5 -5
- intentkit/abstracts/graph.py +6 -5
- intentkit/abstracts/skill.py +5 -5
- intentkit/abstracts/twitter.py +4 -5
- intentkit/clients/cdp.py +19 -77
- intentkit/clients/twitter.py +26 -34
- intentkit/clients/web3.py +1 -3
- intentkit/config/config.py +4 -0
- intentkit/core/agent.py +15 -15
- intentkit/core/asset.py +1 -2
- intentkit/core/client.py +1 -1
- intentkit/core/credit.py +19 -20
- intentkit/core/engine.py +2 -4
- intentkit/core/node.py +2 -1
- intentkit/core/prompt.py +3 -4
- intentkit/core/scheduler.py +1 -1
- intentkit/core/statistics.py +6 -7
- intentkit/models/agent.py +125 -92
- intentkit/models/agent_data.py +62 -36
- intentkit/models/app_setting.py +6 -6
- intentkit/models/chat.py +27 -24
- intentkit/models/conversation.py +8 -8
- intentkit/models/credit.py +62 -64
- intentkit/models/db.py +8 -7
- intentkit/models/db_mig.py +2 -2
- intentkit/models/llm.py +12 -14
- intentkit/models/redis.py +2 -3
- intentkit/models/skill.py +25 -27
- intentkit/models/user.py +21 -22
- intentkit/skills/acolyt/ask.py +3 -4
- intentkit/skills/acolyt/base.py +1 -3
- intentkit/skills/aixbt/base.py +1 -3
- intentkit/skills/aixbt/projects.py +13 -13
- intentkit/skills/allora/base.py +1 -3
- intentkit/skills/allora/price.py +2 -3
- intentkit/skills/base.py +15 -22
- intentkit/skills/basename/__init__.py +3 -5
- intentkit/skills/carv/__init__.py +7 -8
- intentkit/skills/carv/base.py +6 -6
- intentkit/skills/carv/fetch_news.py +3 -3
- intentkit/skills/carv/onchain_query.py +4 -4
- intentkit/skills/carv/token_info_and_price.py +5 -5
- intentkit/skills/casino/base.py +1 -3
- intentkit/skills/casino/deck_draw.py +1 -2
- intentkit/skills/casino/deck_shuffle.py +1 -2
- intentkit/skills/casino/dice_roll.py +1 -2
- intentkit/skills/cdp/__init__.py +3 -5
- intentkit/skills/cdp/base.py +1 -3
- intentkit/skills/chainlist/base.py +1 -3
- intentkit/skills/chainlist/chain_lookup.py +18 -18
- intentkit/skills/common/base.py +1 -3
- intentkit/skills/common/current_time.py +1 -2
- intentkit/skills/cookiefun/base.py +1 -2
- intentkit/skills/cookiefun/get_account_details.py +7 -7
- intentkit/skills/cookiefun/get_account_feed.py +19 -19
- intentkit/skills/cookiefun/get_account_smart_followers.py +7 -7
- intentkit/skills/cookiefun/get_sectors.py +3 -3
- intentkit/skills/cookiefun/search_accounts.py +9 -9
- intentkit/skills/cryptocompare/api.py +2 -3
- intentkit/skills/cryptocompare/base.py +6 -6
- intentkit/skills/cryptocompare/fetch_news.py +3 -4
- intentkit/skills/cryptocompare/fetch_price.py +5 -6
- intentkit/skills/cryptocompare/fetch_top_exchanges.py +3 -4
- intentkit/skills/cryptocompare/fetch_top_market_cap.py +3 -4
- intentkit/skills/cryptocompare/fetch_top_volume.py +3 -4
- intentkit/skills/cryptocompare/fetch_trading_signals.py +4 -5
- intentkit/skills/cryptopanic/__init__.py +4 -4
- intentkit/skills/cryptopanic/base.py +1 -3
- intentkit/skills/cryptopanic/fetch_crypto_news.py +3 -5
- intentkit/skills/cryptopanic/fetch_crypto_sentiment.py +3 -3
- intentkit/skills/dapplooker/base.py +1 -3
- intentkit/skills/dapplooker/dapplooker_token_data.py +7 -7
- intentkit/skills/defillama/api.py +6 -9
- intentkit/skills/defillama/base.py +5 -6
- intentkit/skills/defillama/coins/fetch_batch_historical_prices.py +6 -8
- intentkit/skills/defillama/coins/fetch_block.py +4 -6
- intentkit/skills/defillama/coins/fetch_current_prices.py +6 -8
- intentkit/skills/defillama/coins/fetch_first_price.py +5 -7
- intentkit/skills/defillama/coins/fetch_historical_prices.py +7 -9
- intentkit/skills/defillama/coins/fetch_price_chart.py +7 -9
- intentkit/skills/defillama/coins/fetch_price_percentage.py +5 -7
- intentkit/skills/defillama/config/chains.py +1 -3
- intentkit/skills/defillama/fees/fetch_fees_overview.py +22 -24
- intentkit/skills/defillama/stablecoins/fetch_stablecoin_chains.py +14 -16
- intentkit/skills/defillama/stablecoins/fetch_stablecoin_charts.py +6 -8
- intentkit/skills/defillama/stablecoins/fetch_stablecoin_prices.py +3 -5
- intentkit/skills/defillama/stablecoins/fetch_stablecoins.py +5 -7
- intentkit/skills/defillama/tests/api_integration.test.py +1 -1
- intentkit/skills/defillama/tvl/fetch_chain_historical_tvl.py +2 -4
- intentkit/skills/defillama/tvl/fetch_chains.py +7 -9
- intentkit/skills/defillama/tvl/fetch_historical_tvl.py +2 -4
- intentkit/skills/defillama/tvl/fetch_protocol.py +30 -36
- intentkit/skills/defillama/tvl/fetch_protocol_current_tvl.py +1 -3
- intentkit/skills/defillama/tvl/fetch_protocols.py +35 -43
- intentkit/skills/defillama/volumes/fetch_dex_overview.py +40 -46
- intentkit/skills/defillama/volumes/fetch_dex_summary.py +33 -35
- intentkit/skills/defillama/volumes/fetch_options_overview.py +22 -26
- intentkit/skills/defillama/yields/fetch_pool_chart.py +8 -10
- intentkit/skills/defillama/yields/fetch_pools.py +24 -28
- intentkit/skills/dexscreener/__init__.py +2 -2
- intentkit/skills/dexscreener/base.py +3 -3
- intentkit/skills/dexscreener/get_pair_info.py +2 -2
- intentkit/skills/dexscreener/get_token_pairs.py +2 -2
- intentkit/skills/dexscreener/get_tokens_info.py +5 -5
- intentkit/skills/dexscreener/model/search_token_response.py +80 -82
- intentkit/skills/dexscreener/search_token.py +182 -182
- intentkit/skills/dexscreener/utils.py +15 -14
- intentkit/skills/dune_analytics/__init__.py +4 -4
- intentkit/skills/dune_analytics/base.py +1 -3
- intentkit/skills/dune_analytics/fetch_kol_buys.py +4 -4
- intentkit/skills/dune_analytics/fetch_nation_metrics.py +5 -5
- intentkit/skills/elfa/base.py +1 -3
- intentkit/skills/elfa/mention.py +19 -21
- intentkit/skills/elfa/stats.py +4 -4
- intentkit/skills/elfa/tokens.py +12 -12
- intentkit/skills/elfa/utils.py +25 -27
- intentkit/skills/enso/__init__.py +2 -2
- intentkit/skills/enso/base.py +5 -8
- intentkit/skills/enso/best_yield.py +4 -6
- intentkit/skills/enso/networks.py +1 -2
- intentkit/skills/enso/prices.py +1 -3
- intentkit/skills/enso/route.py +1 -3
- intentkit/skills/enso/tokens.py +1 -3
- intentkit/skills/enso/wallet.py +5 -5
- intentkit/skills/erc20/__init__.py +4 -6
- intentkit/skills/erc721/__init__.py +4 -6
- intentkit/skills/firecrawl/base.py +1 -3
- intentkit/skills/firecrawl/clear.py +1 -2
- intentkit/skills/firecrawl/crawl.py +9 -10
- intentkit/skills/firecrawl/query.py +1 -2
- intentkit/skills/firecrawl/scrape.py +7 -8
- intentkit/skills/firecrawl/utils.py +13 -13
- intentkit/skills/github/base.py +1 -3
- intentkit/skills/github/github_search.py +1 -2
- intentkit/skills/heurist/base.py +1 -3
- intentkit/skills/heurist/image_generation_animagine_xl.py +7 -8
- intentkit/skills/heurist/image_generation_arthemy_comics.py +7 -8
- intentkit/skills/heurist/image_generation_arthemy_real.py +7 -8
- intentkit/skills/heurist/image_generation_braindance.py +7 -8
- intentkit/skills/heurist/image_generation_cyber_realistic_xl.py +7 -8
- intentkit/skills/heurist/image_generation_flux_1_dev.py +7 -8
- intentkit/skills/heurist/image_generation_sdxl.py +7 -8
- intentkit/skills/http/base.py +1 -3
- intentkit/skills/http/get.py +7 -7
- intentkit/skills/http/post.py +9 -9
- intentkit/skills/http/put.py +9 -9
- intentkit/skills/lifi/__init__.py +4 -4
- intentkit/skills/lifi/base.py +1 -3
- intentkit/skills/lifi/token_execute.py +13 -13
- intentkit/skills/lifi/token_quote.py +6 -6
- intentkit/skills/lifi/utils.py +16 -16
- intentkit/skills/moralis/__init__.py +3 -3
- intentkit/skills/moralis/api.py +6 -7
- intentkit/skills/moralis/base.py +2 -4
- intentkit/skills/moralis/fetch_chain_portfolio.py +10 -11
- intentkit/skills/moralis/fetch_nft_portfolio.py +22 -22
- intentkit/skills/moralis/fetch_solana_portfolio.py +11 -12
- intentkit/skills/moralis/fetch_wallet_portfolio.py +8 -9
- intentkit/skills/morpho/__init__.py +4 -6
- intentkit/skills/nation/__init__.py +2 -2
- intentkit/skills/nation/base.py +1 -3
- intentkit/skills/nation/nft_check.py +3 -4
- intentkit/skills/onchain.py +2 -6
- intentkit/skills/openai/base.py +1 -3
- intentkit/skills/openai/dalle_image_generation.py +1 -3
- intentkit/skills/openai/gpt_image_generation.py +2 -3
- intentkit/skills/openai/gpt_image_to_image.py +2 -3
- intentkit/skills/openai/image_to_text.py +1 -2
- intentkit/skills/portfolio/base.py +6 -6
- intentkit/skills/portfolio/token_balances.py +21 -21
- intentkit/skills/portfolio/wallet_approvals.py +7 -7
- intentkit/skills/portfolio/wallet_defi_positions.py +3 -3
- intentkit/skills/portfolio/wallet_history.py +21 -21
- intentkit/skills/portfolio/wallet_net_worth.py +13 -13
- intentkit/skills/portfolio/wallet_nfts.py +19 -19
- intentkit/skills/portfolio/wallet_profitability.py +7 -7
- intentkit/skills/portfolio/wallet_profitability_summary.py +5 -5
- intentkit/skills/portfolio/wallet_stats.py +3 -3
- intentkit/skills/portfolio/wallet_swaps.py +19 -19
- intentkit/skills/pyth/__init__.py +3 -5
- intentkit/skills/slack/base.py +2 -4
- intentkit/skills/slack/get_channel.py +8 -8
- intentkit/skills/slack/get_message.py +9 -9
- intentkit/skills/slack/schedule_message.py +5 -5
- intentkit/skills/slack/send_message.py +3 -5
- intentkit/skills/supabase/base.py +1 -3
- intentkit/skills/supabase/delete_data.py +4 -4
- intentkit/skills/supabase/fetch_data.py +12 -12
- intentkit/skills/supabase/insert_data.py +4 -4
- intentkit/skills/supabase/invoke_function.py +6 -6
- intentkit/skills/supabase/update_data.py +6 -6
- intentkit/skills/supabase/upsert_data.py +4 -4
- intentkit/skills/superfluid/__init__.py +4 -6
- intentkit/skills/system/add_autonomous_task.py +8 -10
- intentkit/skills/system/edit_autonomous_task.py +12 -14
- intentkit/skills/system/list_autonomous_tasks.py +1 -3
- intentkit/skills/tavily/base.py +1 -3
- intentkit/skills/tavily/tavily_extract.py +1 -2
- intentkit/skills/tavily/tavily_search.py +1 -3
- intentkit/skills/token/base.py +5 -5
- intentkit/skills/token/erc20_transfers.py +19 -19
- intentkit/skills/token/token_analytics.py +3 -3
- intentkit/skills/token/token_price.py +13 -13
- intentkit/skills/token/token_search.py +9 -9
- intentkit/skills/twitter/base.py +3 -4
- intentkit/skills/twitter/follow_user.py +1 -2
- intentkit/skills/twitter/get_mentions.py +3 -4
- intentkit/skills/twitter/get_timeline.py +1 -2
- intentkit/skills/twitter/get_user_by_username.py +1 -2
- intentkit/skills/twitter/get_user_tweets.py +2 -3
- intentkit/skills/twitter/like_tweet.py +1 -2
- intentkit/skills/twitter/post_tweet.py +3 -4
- intentkit/skills/twitter/reply_tweet.py +3 -4
- intentkit/skills/twitter/retweet.py +1 -2
- intentkit/skills/twitter/search_tweets.py +1 -2
- intentkit/skills/unrealspeech/base.py +1 -3
- intentkit/skills/unrealspeech/text_to_speech.py +8 -8
- intentkit/skills/venice_audio/__init__.py +8 -9
- intentkit/skills/venice_audio/base.py +3 -4
- intentkit/skills/venice_audio/input.py +41 -41
- intentkit/skills/venice_audio/venice_audio.py +6 -6
- intentkit/skills/venice_image/__init__.py +5 -5
- intentkit/skills/venice_image/api.py +138 -138
- intentkit/skills/venice_image/base.py +3 -3
- intentkit/skills/venice_image/config.py +33 -35
- intentkit/skills/venice_image/image_enhance/image_enhance.py +2 -3
- intentkit/skills/venice_image/image_enhance/image_enhance_base.py +21 -23
- intentkit/skills/venice_image/image_enhance/image_enhance_input.py +38 -40
- intentkit/skills/venice_image/image_generation/image_generation_base.py +9 -9
- intentkit/skills/venice_image/image_generation/image_generation_fluently_xl.py +26 -26
- intentkit/skills/venice_image/image_generation/image_generation_flux_dev.py +27 -27
- intentkit/skills/venice_image/image_generation/image_generation_flux_dev_uncensored.py +26 -26
- intentkit/skills/venice_image/image_generation/image_generation_input.py +158 -158
- intentkit/skills/venice_image/image_generation/image_generation_lustify_sdxl.py +26 -26
- intentkit/skills/venice_image/image_generation/image_generation_pony_realism.py +26 -26
- intentkit/skills/venice_image/image_generation/image_generation_stable_diffusion_3_5.py +28 -28
- intentkit/skills/venice_image/image_generation/image_generation_venice_sd35.py +28 -28
- intentkit/skills/venice_image/image_upscale/image_upscale.py +3 -3
- intentkit/skills/venice_image/image_upscale/image_upscale_base.py +21 -23
- intentkit/skills/venice_image/image_upscale/image_upscale_input.py +22 -22
- intentkit/skills/venice_image/image_vision/image_vision.py +2 -2
- intentkit/skills/venice_image/image_vision/image_vision_base.py +17 -17
- intentkit/skills/venice_image/image_vision/image_vision_input.py +9 -9
- intentkit/skills/venice_image/utils.py +77 -78
- intentkit/skills/web_scraper/base.py +1 -3
- intentkit/skills/web_scraper/document_indexer.py +1 -2
- intentkit/skills/web_scraper/scrape_and_index.py +4 -5
- intentkit/skills/web_scraper/utils.py +25 -26
- intentkit/skills/web_scraper/website_indexer.py +10 -11
- intentkit/skills/weth/__init__.py +4 -6
- intentkit/skills/wow/__init__.py +4 -6
- intentkit/skills/x402/__init__.py +2 -2
- intentkit/skills/x402/ask_agent.py +7 -7
- intentkit/skills/x402/base.py +2 -1
- intentkit/skills/x402/http_request.py +10 -10
- intentkit/skills/xmtp/base.py +3 -3
- intentkit/skills/xmtp/price.py +2 -2
- intentkit/skills/xmtp/swap.py +2 -4
- intentkit/skills/xmtp/transfer.py +4 -6
- intentkit/utils/error.py +2 -2
- intentkit/utils/logging.py +2 -4
- intentkit/utils/s3.py +8 -9
- intentkit/utils/schema.py +5 -5
- intentkit/utils/slack_alert.py +7 -8
- {intentkit-0.8.17.dev1.dist-info → intentkit-0.8.17.dev2.dist-info}/METADATA +3 -4
- intentkit-0.8.17.dev2.dist-info/RECORD +464 -0
- intentkit/models/generator.py +0 -347
- intentkit-0.8.17.dev1.dist-info/RECORD +0 -465
- {intentkit-0.8.17.dev1.dist-info → intentkit-0.8.17.dev2.dist-info}/WHEEL +0 -0
- {intentkit-0.8.17.dev1.dist-info → intentkit-0.8.17.dev2.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,182 +1,182 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from typing import Any
|
|
3
|
-
|
|
4
|
-
from pydantic import BaseModel, Field, ValidationError
|
|
5
|
-
|
|
6
|
-
from intentkit.skills.dexscreener.base import DexScreenerBaseTool
|
|
7
|
-
from intentkit.skills.dexscreener.model.search_token_response import (
|
|
8
|
-
SearchTokenResponseModel,
|
|
9
|
-
)
|
|
10
|
-
from intentkit.skills.dexscreener.utils import (
|
|
11
|
-
API_ENDPOINTS,
|
|
12
|
-
MAX_SEARCH_RESULTS,
|
|
13
|
-
SEARCH_DISCLAIMER,
|
|
14
|
-
QueryType,
|
|
15
|
-
SortBy,
|
|
16
|
-
VolumeTimeframe,
|
|
17
|
-
create_error_response,
|
|
18
|
-
create_no_results_response,
|
|
19
|
-
determine_query_type,
|
|
20
|
-
filter_address_pairs,
|
|
21
|
-
filter_ticker_pairs,
|
|
22
|
-
format_success_response,
|
|
23
|
-
handle_validation_error,
|
|
24
|
-
sort_pairs_by_criteria,
|
|
25
|
-
truncate_large_fields,
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
logger = logging.getLogger(__name__)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class SearchTokenInput(BaseModel):
|
|
32
|
-
"""Input schema for the DexScreener search_token tool."""
|
|
33
|
-
|
|
34
|
-
query: str = Field(
|
|
35
|
-
description="The search query string (e.g., token symbol 'WIF', pair address, token address '0x...', token name 'Dogwifhat', or ticker '$WIF'). Prefixing with '$' filters results to match the base token symbol exactly (case-insensitive)."
|
|
36
|
-
)
|
|
37
|
-
sort_by:
|
|
38
|
-
default=SortBy.LIQUIDITY,
|
|
39
|
-
description="Sort preference for the results. Options: 'liquidity' (default) or 'volume'",
|
|
40
|
-
)
|
|
41
|
-
volume_timeframe:
|
|
42
|
-
default=VolumeTimeframe.TWENTY_FOUR_HOUR,
|
|
43
|
-
description="Define which timeframe should we use if the 'sort_by' is 'volume'. Available options: '5_minutes', '1_hour', '6_hour', '24_hour'",
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
class SearchToken(DexScreenerBaseTool):
|
|
48
|
-
"""
|
|
49
|
-
Tool to search for token pairs on DexScreener based on a query string.
|
|
50
|
-
"""
|
|
51
|
-
|
|
52
|
-
name: str = "dexscreener_search_token"
|
|
53
|
-
description: str = (
|
|
54
|
-
f"Searches DexScreener for token pairs matching the provided query string "
|
|
55
|
-
f"(e.g., token symbol like 'WIF', pair address, token name like 'Dogwifhat', or ticker like '$WIF'). "
|
|
56
|
-
f"If the query starts with '$', it filters results to only include pairs where the base token symbol exactly matches the ticker (case-insensitive). "
|
|
57
|
-
f"Returns a list of matching pairs with details like price, volume, liquidity, etc., "
|
|
58
|
-
f"sorted by the specified criteria (via 'sort_by': 'liquidity', 'volume'; defaults to 'liquidity'), "
|
|
59
|
-
f"limited to the top {MAX_SEARCH_RESULTS}. "
|
|
60
|
-
f"Use this tool to find token information based on user queries."
|
|
61
|
-
)
|
|
62
|
-
args_schema:
|
|
63
|
-
|
|
64
|
-
async def _arun(
|
|
65
|
-
self,
|
|
66
|
-
query: str,
|
|
67
|
-
sort_by:
|
|
68
|
-
volume_timeframe:
|
|
69
|
-
**kwargs: Any,
|
|
70
|
-
) -> str:
|
|
71
|
-
"""Implementation to search token, with filtering based on query type."""
|
|
72
|
-
|
|
73
|
-
# dexscreener 300 request per minute (across all user) based on dexscreener docs
|
|
74
|
-
# https://docs.dexscreener.com/api/reference#get-latest-dex-search
|
|
75
|
-
await self.global_rate_limit_by_skill(
|
|
76
|
-
limit=300,
|
|
77
|
-
seconds=60,
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
sort_by = sort_by or SortBy.LIQUIDITY
|
|
81
|
-
volume_timeframe = volume_timeframe or VolumeTimeframe.TWENTY_FOUR_HOUR
|
|
82
|
-
|
|
83
|
-
# Determine query type
|
|
84
|
-
query_type = determine_query_type(query)
|
|
85
|
-
|
|
86
|
-
# Process query based on type
|
|
87
|
-
if query_type == QueryType.TICKER:
|
|
88
|
-
search_query = query[1:] # Remove the '$' prefix
|
|
89
|
-
target_ticker = search_query.upper()
|
|
90
|
-
else:
|
|
91
|
-
search_query = query
|
|
92
|
-
target_ticker = None
|
|
93
|
-
|
|
94
|
-
logger.info(
|
|
95
|
-
f"Executing DexScreener search_token tool with query: '{query}' "
|
|
96
|
-
f"(interpreted as {query_type.value} search for '{search_query}'), "
|
|
97
|
-
f"sort_by: {sort_by}"
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
try:
|
|
101
|
-
data, error_details = await self._get(
|
|
102
|
-
path=API_ENDPOINTS["search"], params={"q": search_query}
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
if error_details:
|
|
106
|
-
return await self._handle_error_response(error_details)
|
|
107
|
-
if not data:
|
|
108
|
-
logger.error(f"No data or error details returned for query '{query}'")
|
|
109
|
-
return create_error_response(
|
|
110
|
-
error_type="empty_success",
|
|
111
|
-
message="API call returned empty success response.",
|
|
112
|
-
additional_data={"query": query},
|
|
113
|
-
)
|
|
114
|
-
|
|
115
|
-
try:
|
|
116
|
-
result = SearchTokenResponseModel.model_validate(data)
|
|
117
|
-
except ValidationError as e:
|
|
118
|
-
return handle_validation_error(e, query, len(str(data)))
|
|
119
|
-
|
|
120
|
-
if not result.pairs:
|
|
121
|
-
return create_no_results_response(
|
|
122
|
-
query, reason="returned null or empty for pairs"
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
pairs_list = [p for p in result.pairs if p is not None]
|
|
126
|
-
|
|
127
|
-
# Apply filtering based on query type
|
|
128
|
-
if query_type == QueryType.TICKER and target_ticker:
|
|
129
|
-
pairs_list = filter_ticker_pairs(pairs_list, target_ticker)
|
|
130
|
-
if not pairs_list:
|
|
131
|
-
return create_no_results_response(
|
|
132
|
-
query, reason=f"no match for ticker '${target_ticker}'"
|
|
133
|
-
)
|
|
134
|
-
elif query_type == QueryType.ADDRESS:
|
|
135
|
-
pairs_list = filter_address_pairs(pairs_list, search_query)
|
|
136
|
-
if not pairs_list:
|
|
137
|
-
return create_no_results_response(
|
|
138
|
-
query, reason=f"no match for address '{search_query}'"
|
|
139
|
-
)
|
|
140
|
-
|
|
141
|
-
# Sort pairs by specified criteria
|
|
142
|
-
pairs_list = sort_pairs_by_criteria(pairs_list, sort_by, volume_timeframe)
|
|
143
|
-
|
|
144
|
-
# If sorting failed, pairs_list will be returned unchanged by the utility function
|
|
145
|
-
|
|
146
|
-
final_count = min(len(pairs_list), MAX_SEARCH_RESULTS)
|
|
147
|
-
logger.info(f"Returning {final_count} pairs for query '{query}'")
|
|
148
|
-
return format_success_response(
|
|
149
|
-
{
|
|
150
|
-
**SEARCH_DISCLAIMER,
|
|
151
|
-
"pairs": [p.model_dump() for p in pairs_list[:MAX_SEARCH_RESULTS]],
|
|
152
|
-
}
|
|
153
|
-
)
|
|
154
|
-
except Exception as e:
|
|
155
|
-
return await self._handle_unexpected_runtime_error(e, query)
|
|
156
|
-
|
|
157
|
-
async def _handle_error_response(self, error_details: dict) -> str:
|
|
158
|
-
"""Formats error details (from _get) into a JSON string."""
|
|
159
|
-
if error_details.get("error_type") in [
|
|
160
|
-
"connection_error",
|
|
161
|
-
"parsing_error",
|
|
162
|
-
"unexpected_error",
|
|
163
|
-
]:
|
|
164
|
-
logger.error(f"DexScreener tool encountered an error: {error_details}")
|
|
165
|
-
else: # api_error
|
|
166
|
-
logger.warning(f"DexScreener API returned an error: {error_details}")
|
|
167
|
-
|
|
168
|
-
# Truncate potentially large fields before returning to user/LLM
|
|
169
|
-
truncated_details = truncate_large_fields(error_details)
|
|
170
|
-
return format_success_response(truncated_details)
|
|
171
|
-
|
|
172
|
-
async def _handle_unexpected_runtime_error(self, e: Exception, query: str) -> str:
|
|
173
|
-
"""Formats unexpected runtime exception details into a JSON string."""
|
|
174
|
-
logger.exception(
|
|
175
|
-
f"An unexpected runtime error occurred in search_token tool _arun method for query '{query}': {e}"
|
|
176
|
-
)
|
|
177
|
-
return create_error_response(
|
|
178
|
-
error_type="runtime_error",
|
|
179
|
-
message="An unexpected internal error occurred processing the search request",
|
|
180
|
-
details=str(e),
|
|
181
|
-
additional_data={"query": query},
|
|
182
|
-
)
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, Field, ValidationError
|
|
5
|
+
|
|
6
|
+
from intentkit.skills.dexscreener.base import DexScreenerBaseTool
|
|
7
|
+
from intentkit.skills.dexscreener.model.search_token_response import (
|
|
8
|
+
SearchTokenResponseModel,
|
|
9
|
+
)
|
|
10
|
+
from intentkit.skills.dexscreener.utils import (
|
|
11
|
+
API_ENDPOINTS,
|
|
12
|
+
MAX_SEARCH_RESULTS,
|
|
13
|
+
SEARCH_DISCLAIMER,
|
|
14
|
+
QueryType,
|
|
15
|
+
SortBy,
|
|
16
|
+
VolumeTimeframe,
|
|
17
|
+
create_error_response,
|
|
18
|
+
create_no_results_response,
|
|
19
|
+
determine_query_type,
|
|
20
|
+
filter_address_pairs,
|
|
21
|
+
filter_ticker_pairs,
|
|
22
|
+
format_success_response,
|
|
23
|
+
handle_validation_error,
|
|
24
|
+
sort_pairs_by_criteria,
|
|
25
|
+
truncate_large_fields,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class SearchTokenInput(BaseModel):
|
|
32
|
+
"""Input schema for the DexScreener search_token tool."""
|
|
33
|
+
|
|
34
|
+
query: str = Field(
|
|
35
|
+
description="The search query string (e.g., token symbol 'WIF', pair address, token address '0x...', token name 'Dogwifhat', or ticker '$WIF'). Prefixing with '$' filters results to match the base token symbol exactly (case-insensitive)."
|
|
36
|
+
)
|
|
37
|
+
sort_by: SortBy | None = Field(
|
|
38
|
+
default=SortBy.LIQUIDITY,
|
|
39
|
+
description="Sort preference for the results. Options: 'liquidity' (default) or 'volume'",
|
|
40
|
+
)
|
|
41
|
+
volume_timeframe: VolumeTimeframe | None = Field(
|
|
42
|
+
default=VolumeTimeframe.TWENTY_FOUR_HOUR,
|
|
43
|
+
description="Define which timeframe should we use if the 'sort_by' is 'volume'. Available options: '5_minutes', '1_hour', '6_hour', '24_hour'",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class SearchToken(DexScreenerBaseTool):
|
|
48
|
+
"""
|
|
49
|
+
Tool to search for token pairs on DexScreener based on a query string.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
name: str = "dexscreener_search_token"
|
|
53
|
+
description: str = (
|
|
54
|
+
f"Searches DexScreener for token pairs matching the provided query string "
|
|
55
|
+
f"(e.g., token symbol like 'WIF', pair address, token name like 'Dogwifhat', or ticker like '$WIF'). "
|
|
56
|
+
f"If the query starts with '$', it filters results to only include pairs where the base token symbol exactly matches the ticker (case-insensitive). "
|
|
57
|
+
f"Returns a list of matching pairs with details like price, volume, liquidity, etc., "
|
|
58
|
+
f"sorted by the specified criteria (via 'sort_by': 'liquidity', 'volume'; defaults to 'liquidity'), "
|
|
59
|
+
f"limited to the top {MAX_SEARCH_RESULTS}. "
|
|
60
|
+
f"Use this tool to find token information based on user queries."
|
|
61
|
+
)
|
|
62
|
+
args_schema: type[BaseModel] = SearchTokenInput
|
|
63
|
+
|
|
64
|
+
async def _arun(
|
|
65
|
+
self,
|
|
66
|
+
query: str,
|
|
67
|
+
sort_by: SortBy | None = SortBy.LIQUIDITY,
|
|
68
|
+
volume_timeframe: VolumeTimeframe | None = VolumeTimeframe.TWENTY_FOUR_HOUR,
|
|
69
|
+
**kwargs: Any,
|
|
70
|
+
) -> str:
|
|
71
|
+
"""Implementation to search token, with filtering based on query type."""
|
|
72
|
+
|
|
73
|
+
# dexscreener 300 request per minute (across all user) based on dexscreener docs
|
|
74
|
+
# https://docs.dexscreener.com/api/reference#get-latest-dex-search
|
|
75
|
+
await self.global_rate_limit_by_skill(
|
|
76
|
+
limit=300,
|
|
77
|
+
seconds=60,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
sort_by = sort_by or SortBy.LIQUIDITY
|
|
81
|
+
volume_timeframe = volume_timeframe or VolumeTimeframe.TWENTY_FOUR_HOUR
|
|
82
|
+
|
|
83
|
+
# Determine query type
|
|
84
|
+
query_type = determine_query_type(query)
|
|
85
|
+
|
|
86
|
+
# Process query based on type
|
|
87
|
+
if query_type == QueryType.TICKER:
|
|
88
|
+
search_query = query[1:] # Remove the '$' prefix
|
|
89
|
+
target_ticker = search_query.upper()
|
|
90
|
+
else:
|
|
91
|
+
search_query = query
|
|
92
|
+
target_ticker = None
|
|
93
|
+
|
|
94
|
+
logger.info(
|
|
95
|
+
f"Executing DexScreener search_token tool with query: '{query}' "
|
|
96
|
+
f"(interpreted as {query_type.value} search for '{search_query}'), "
|
|
97
|
+
f"sort_by: {sort_by}"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
data, error_details = await self._get(
|
|
102
|
+
path=API_ENDPOINTS["search"], params={"q": search_query}
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
if error_details:
|
|
106
|
+
return await self._handle_error_response(error_details)
|
|
107
|
+
if not data:
|
|
108
|
+
logger.error(f"No data or error details returned for query '{query}'")
|
|
109
|
+
return create_error_response(
|
|
110
|
+
error_type="empty_success",
|
|
111
|
+
message="API call returned empty success response.",
|
|
112
|
+
additional_data={"query": query},
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
result = SearchTokenResponseModel.model_validate(data)
|
|
117
|
+
except ValidationError as e:
|
|
118
|
+
return handle_validation_error(e, query, len(str(data)))
|
|
119
|
+
|
|
120
|
+
if not result.pairs:
|
|
121
|
+
return create_no_results_response(
|
|
122
|
+
query, reason="returned null or empty for pairs"
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
pairs_list = [p for p in result.pairs if p is not None]
|
|
126
|
+
|
|
127
|
+
# Apply filtering based on query type
|
|
128
|
+
if query_type == QueryType.TICKER and target_ticker:
|
|
129
|
+
pairs_list = filter_ticker_pairs(pairs_list, target_ticker)
|
|
130
|
+
if not pairs_list:
|
|
131
|
+
return create_no_results_response(
|
|
132
|
+
query, reason=f"no match for ticker '${target_ticker}'"
|
|
133
|
+
)
|
|
134
|
+
elif query_type == QueryType.ADDRESS:
|
|
135
|
+
pairs_list = filter_address_pairs(pairs_list, search_query)
|
|
136
|
+
if not pairs_list:
|
|
137
|
+
return create_no_results_response(
|
|
138
|
+
query, reason=f"no match for address '{search_query}'"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# Sort pairs by specified criteria
|
|
142
|
+
pairs_list = sort_pairs_by_criteria(pairs_list, sort_by, volume_timeframe)
|
|
143
|
+
|
|
144
|
+
# If sorting failed, pairs_list will be returned unchanged by the utility function
|
|
145
|
+
|
|
146
|
+
final_count = min(len(pairs_list), MAX_SEARCH_RESULTS)
|
|
147
|
+
logger.info(f"Returning {final_count} pairs for query '{query}'")
|
|
148
|
+
return format_success_response(
|
|
149
|
+
{
|
|
150
|
+
**SEARCH_DISCLAIMER,
|
|
151
|
+
"pairs": [p.model_dump() for p in pairs_list[:MAX_SEARCH_RESULTS]],
|
|
152
|
+
}
|
|
153
|
+
)
|
|
154
|
+
except Exception as e:
|
|
155
|
+
return await self._handle_unexpected_runtime_error(e, query)
|
|
156
|
+
|
|
157
|
+
async def _handle_error_response(self, error_details: dict) -> str:
|
|
158
|
+
"""Formats error details (from _get) into a JSON string."""
|
|
159
|
+
if error_details.get("error_type") in [
|
|
160
|
+
"connection_error",
|
|
161
|
+
"parsing_error",
|
|
162
|
+
"unexpected_error",
|
|
163
|
+
]:
|
|
164
|
+
logger.error(f"DexScreener tool encountered an error: {error_details}")
|
|
165
|
+
else: # api_error
|
|
166
|
+
logger.warning(f"DexScreener API returned an error: {error_details}")
|
|
167
|
+
|
|
168
|
+
# Truncate potentially large fields before returning to user/LLM
|
|
169
|
+
truncated_details = truncate_large_fields(error_details)
|
|
170
|
+
return format_success_response(truncated_details)
|
|
171
|
+
|
|
172
|
+
async def _handle_unexpected_runtime_error(self, e: Exception, query: str) -> str:
|
|
173
|
+
"""Formats unexpected runtime exception details into a JSON string."""
|
|
174
|
+
logger.exception(
|
|
175
|
+
f"An unexpected runtime error occurred in search_token tool _arun method for query '{query}': {e}"
|
|
176
|
+
)
|
|
177
|
+
return create_error_response(
|
|
178
|
+
error_type="runtime_error",
|
|
179
|
+
message="An unexpected internal error occurred processing the search request",
|
|
180
|
+
details=str(e),
|
|
181
|
+
additional_data={"query": query},
|
|
182
|
+
)
|
|
@@ -4,8 +4,9 @@ Utility functions and constants for DexScreener skills.
|
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
6
|
import logging
|
|
7
|
+
from collections.abc import Callable
|
|
7
8
|
from enum import Enum
|
|
8
|
-
from typing import Any
|
|
9
|
+
from typing import Any
|
|
9
10
|
|
|
10
11
|
from pydantic import ValidationError
|
|
11
12
|
|
|
@@ -175,11 +176,11 @@ def get_sort_function(
|
|
|
175
176
|
|
|
176
177
|
|
|
177
178
|
def sort_pairs_by_criteria(
|
|
178
|
-
pairs:
|
|
179
|
+
pairs: list[PairModel],
|
|
179
180
|
sort_by: SortBy = SortBy.LIQUIDITY,
|
|
180
181
|
volume_timeframe: VolumeTimeframe = VolumeTimeframe.TWENTY_FOUR_HOUR,
|
|
181
182
|
reverse: bool = True,
|
|
182
|
-
) ->
|
|
183
|
+
) -> list[PairModel]:
|
|
183
184
|
"""
|
|
184
185
|
Sort pairs by the specified criteria.
|
|
185
186
|
|
|
@@ -200,7 +201,7 @@ def sort_pairs_by_criteria(
|
|
|
200
201
|
return pairs # Return original list if sorting fails
|
|
201
202
|
|
|
202
203
|
|
|
203
|
-
def filter_ticker_pairs(pairs:
|
|
204
|
+
def filter_ticker_pairs(pairs: list[PairModel], target_ticker: str) -> list[PairModel]:
|
|
204
205
|
"""
|
|
205
206
|
Filter pairs to only include those where base token symbol matches target ticker.
|
|
206
207
|
|
|
@@ -222,8 +223,8 @@ def filter_ticker_pairs(pairs: List[PairModel], target_ticker: str) -> List[Pair
|
|
|
222
223
|
|
|
223
224
|
|
|
224
225
|
def filter_address_pairs(
|
|
225
|
-
pairs:
|
|
226
|
-
) ->
|
|
226
|
+
pairs: list[PairModel], target_address: str
|
|
227
|
+
) -> list[PairModel]:
|
|
227
228
|
"""
|
|
228
229
|
Filter pairs to only include those matching the target address.
|
|
229
230
|
Checks pairAddress, baseToken.address, and quoteToken.address.
|
|
@@ -256,8 +257,8 @@ def filter_address_pairs(
|
|
|
256
257
|
def create_error_response(
|
|
257
258
|
error_type: str,
|
|
258
259
|
message: str,
|
|
259
|
-
details:
|
|
260
|
-
additional_data:
|
|
260
|
+
details: str | None = None,
|
|
261
|
+
additional_data: dict[str, Any] | None = None,
|
|
261
262
|
) -> str:
|
|
262
263
|
"""
|
|
263
264
|
Create a standardized error response in JSON format.
|
|
@@ -288,7 +289,7 @@ def create_error_response(
|
|
|
288
289
|
def create_no_results_response(
|
|
289
290
|
query_info: str,
|
|
290
291
|
reason: str = "no results found",
|
|
291
|
-
additional_data:
|
|
292
|
+
additional_data: dict[str, Any] | None = None,
|
|
292
293
|
) -> str:
|
|
293
294
|
"""
|
|
294
295
|
Create a standardized "no results found" response.
|
|
@@ -314,7 +315,7 @@ def create_no_results_response(
|
|
|
314
315
|
|
|
315
316
|
|
|
316
317
|
def handle_validation_error(
|
|
317
|
-
error: ValidationError, query_info: str, data_length:
|
|
318
|
+
error: ValidationError, query_info: str, data_length: int | None = None
|
|
318
319
|
) -> str:
|
|
319
320
|
"""
|
|
320
321
|
Handle validation errors in a standardized way.
|
|
@@ -342,8 +343,8 @@ def handle_validation_error(
|
|
|
342
343
|
|
|
343
344
|
|
|
344
345
|
def truncate_large_fields(
|
|
345
|
-
data:
|
|
346
|
-
) ->
|
|
346
|
+
data: dict[str, Any], max_length: int = 500
|
|
347
|
+
) -> dict[str, Any]:
|
|
347
348
|
"""
|
|
348
349
|
Truncate large string fields in error response data to avoid overwhelming the LLM.
|
|
349
350
|
|
|
@@ -363,7 +364,7 @@ def truncate_large_fields(
|
|
|
363
364
|
return truncated
|
|
364
365
|
|
|
365
366
|
|
|
366
|
-
def group_pairs_by_token(pairs:
|
|
367
|
+
def group_pairs_by_token(pairs: list[PairModel]) -> dict[str, list[PairModel]]:
|
|
367
368
|
"""
|
|
368
369
|
Group pairs by token address for better organization in multi-token responses.
|
|
369
370
|
|
|
@@ -406,7 +407,7 @@ def validate_chain_id(chain_id: str) -> bool:
|
|
|
406
407
|
return chain_id.lower() in SUPPORTED_CHAINS
|
|
407
408
|
|
|
408
409
|
|
|
409
|
-
def format_success_response(data:
|
|
410
|
+
def format_success_response(data: dict[str, Any]) -> str:
|
|
410
411
|
"""
|
|
411
412
|
Format a successful response as JSON string.
|
|
412
413
|
|
|
@@ -4,7 +4,7 @@ Loads and initializes skills for fetching data from Dune Analytics API.
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import logging
|
|
7
|
-
from typing import
|
|
7
|
+
from typing import TypedDict
|
|
8
8
|
|
|
9
9
|
from intentkit.skills.base import SkillConfig, SkillState
|
|
10
10
|
from intentkit.skills.dune_analytics.base import DuneBaseTool
|
|
@@ -12,7 +12,7 @@ from intentkit.skills.dune_analytics.base import DuneBaseTool
|
|
|
12
12
|
logger = logging.getLogger(__name__)
|
|
13
13
|
|
|
14
14
|
# Cache for skill instances
|
|
15
|
-
_skill_cache:
|
|
15
|
+
_skill_cache: dict[str, DuneBaseTool] = {}
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class SkillStates(TypedDict):
|
|
@@ -33,7 +33,7 @@ async def get_skills(
|
|
|
33
33
|
config: Config,
|
|
34
34
|
is_private: bool,
|
|
35
35
|
**kwargs,
|
|
36
|
-
) ->
|
|
36
|
+
) -> list[DuneBaseTool]:
|
|
37
37
|
"""Load Dune Analytics skills based on configuration.
|
|
38
38
|
|
|
39
39
|
Args:
|
|
@@ -67,7 +67,7 @@ async def get_skills(
|
|
|
67
67
|
return loaded_skills
|
|
68
68
|
|
|
69
69
|
|
|
70
|
-
def get_dune_skill(name: str) ->
|
|
70
|
+
def get_dune_skill(name: str) -> DuneBaseTool | None:
|
|
71
71
|
"""Retrieve a Dune Analytics skill instance by name.
|
|
72
72
|
|
|
73
73
|
Args:
|
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
Provides shared functionality for interacting with the Dune Analytics API.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
from typing import Type
|
|
7
|
-
|
|
8
6
|
from langchain_core.tools.base import ToolException
|
|
9
7
|
from pydantic import BaseModel, Field
|
|
10
8
|
|
|
@@ -19,7 +17,7 @@ class DuneBaseTool(IntentKitSkill):
|
|
|
19
17
|
|
|
20
18
|
name: str = Field(description="Tool name")
|
|
21
19
|
description: str = Field(description="Tool description")
|
|
22
|
-
args_schema:
|
|
20
|
+
args_schema: type[BaseModel]
|
|
23
21
|
|
|
24
22
|
def get_api_key(self) -> str:
|
|
25
23
|
"""Retrieve the Dune Analytics API key from context.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Uses query ID 4832844 to retrieve a list of KOL buy transactions.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
from typing import Any
|
|
6
|
+
from typing import Any
|
|
7
7
|
|
|
8
8
|
import httpx
|
|
9
9
|
from pydantic import BaseModel, Field
|
|
@@ -28,7 +28,7 @@ class KOLBuysInput(BaseModel):
|
|
|
28
28
|
class KOLBuyData(BaseModel):
|
|
29
29
|
"""Data model for KOL buy results."""
|
|
30
30
|
|
|
31
|
-
data:
|
|
31
|
+
data: dict[str, Any] = Field(description="KOL buy data from Dune API")
|
|
32
32
|
error: str = Field(default="", description="Error message if fetch failed")
|
|
33
33
|
|
|
34
34
|
|
|
@@ -47,14 +47,14 @@ class FetchKOLBuys(DuneBaseTool):
|
|
|
47
47
|
"Fetches a list of KOL memecoin buy transactions on Solana from Dune Analytics API using query ID 4832844. "
|
|
48
48
|
"Supports a configurable limit for the number of results. Handles rate limits with retries."
|
|
49
49
|
)
|
|
50
|
-
args_schema:
|
|
50
|
+
args_schema: type[BaseModel] = KOLBuysInput
|
|
51
51
|
|
|
52
52
|
@retry(
|
|
53
53
|
stop=stop_after_attempt(3), wait=wait_exponential(multiplier=5, min=5, max=60)
|
|
54
54
|
)
|
|
55
55
|
async def fetch_data(
|
|
56
56
|
self, query_id: int, api_key: str, limit: int = 10
|
|
57
|
-
) ->
|
|
57
|
+
) -> dict[str, Any]:
|
|
58
58
|
"""Fetch data for a specific Dune query.
|
|
59
59
|
|
|
60
60
|
Args:
|
|
@@ -5,7 +5,7 @@ Supports predefined metrics (e.g., total_users, unique_ai_citizens) or direct qu
|
|
|
5
5
|
|
|
6
6
|
import difflib
|
|
7
7
|
import re
|
|
8
|
-
from typing import Any
|
|
8
|
+
from typing import Any
|
|
9
9
|
|
|
10
10
|
import httpx
|
|
11
11
|
from pydantic import BaseModel, Field
|
|
@@ -62,14 +62,14 @@ class MetricData(BaseModel):
|
|
|
62
62
|
"""Data model for a single metric result."""
|
|
63
63
|
|
|
64
64
|
metric: str = Field(description="Metric name or query ID")
|
|
65
|
-
data:
|
|
65
|
+
data: dict[str, Any] = Field(description="Metric data from Dune API")
|
|
66
66
|
error: str = Field(default="", description="Error message if fetch failed")
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
class NationMetricsOutput(BaseModel):
|
|
70
70
|
"""Output schema for Crestal Nation metrics."""
|
|
71
71
|
|
|
72
|
-
metrics:
|
|
72
|
+
metrics: dict[str, MetricData] = Field(
|
|
73
73
|
description="Dictionary of metric names or query IDs to their data"
|
|
74
74
|
)
|
|
75
75
|
summary: str = Field(description="Summary of fetched metrics")
|
|
@@ -84,7 +84,7 @@ class FetchNationMetrics(DuneBaseTool):
|
|
|
84
84
|
"Supports predefined metrics, direct query IDs, or all configured metrics if none specified. "
|
|
85
85
|
"Handles rate limits with retries."
|
|
86
86
|
)
|
|
87
|
-
args_schema:
|
|
87
|
+
args_schema: type[BaseModel] = NationMetricsInput
|
|
88
88
|
|
|
89
89
|
def normalize_metric(self, metric: str) -> str:
|
|
90
90
|
"""Normalize a metric string for matching.
|
|
@@ -122,7 +122,7 @@ class FetchNationMetrics(DuneBaseTool):
|
|
|
122
122
|
)
|
|
123
123
|
async def fetch_data(
|
|
124
124
|
self, query_id: int, api_key: str, limit: int = 1000
|
|
125
|
-
) ->
|
|
125
|
+
) -> dict[str, Any]:
|
|
126
126
|
"""Fetch data for a specific Dune query.
|
|
127
127
|
|
|
128
128
|
Args:
|
intentkit/skills/elfa/base.py
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from typing import Type
|
|
2
|
-
|
|
3
1
|
from langchain_core.tools.base import ToolException
|
|
4
2
|
from pydantic import BaseModel, Field
|
|
5
3
|
|
|
@@ -14,7 +12,7 @@ class ElfaBaseTool(IntentKitSkill):
|
|
|
14
12
|
|
|
15
13
|
name: str = Field(description="The name of the tool")
|
|
16
14
|
description: str = Field(description="A description of what the tool does")
|
|
17
|
-
args_schema:
|
|
15
|
+
args_schema: type[BaseModel]
|
|
18
16
|
|
|
19
17
|
def get_api_key(self) -> str:
|
|
20
18
|
context = self.get_context()
|