intentkit 0.5.0__py3-none-any.whl → 0.5.2__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 +17 -0
- intentkit/abstracts/__init__.py +0 -0
- intentkit/abstracts/agent.py +60 -0
- intentkit/abstracts/api.py +4 -0
- intentkit/abstracts/engine.py +38 -0
- intentkit/abstracts/exception.py +9 -0
- intentkit/abstracts/graph.py +25 -0
- intentkit/abstracts/skill.py +129 -0
- intentkit/abstracts/twitter.py +54 -0
- intentkit/clients/__init__.py +14 -0
- intentkit/clients/cdp.py +53 -0
- intentkit/clients/twitter.py +445 -0
- intentkit/config/__init__.py +0 -0
- intentkit/config/config.py +164 -0
- intentkit/core/__init__.py +0 -0
- intentkit/core/agent.py +191 -0
- intentkit/core/api.py +40 -0
- intentkit/core/client.py +45 -0
- intentkit/core/credit.py +1767 -0
- intentkit/core/engine.py +1018 -0
- intentkit/core/node.py +223 -0
- intentkit/core/prompt.py +58 -0
- intentkit/core/skill.py +124 -0
- intentkit/models/agent.py +1689 -0
- intentkit/models/agent_data.py +810 -0
- intentkit/models/agent_schema.json +733 -0
- intentkit/models/app_setting.py +156 -0
- intentkit/models/base.py +9 -0
- intentkit/models/chat.py +581 -0
- intentkit/models/conversation.py +286 -0
- intentkit/models/credit.py +1406 -0
- intentkit/models/db.py +120 -0
- intentkit/models/db_mig.py +102 -0
- intentkit/models/generator.py +347 -0
- intentkit/models/llm.py +746 -0
- intentkit/models/redis.py +132 -0
- intentkit/models/skill.py +466 -0
- intentkit/models/user.py +243 -0
- intentkit/skills/__init__.py +12 -0
- intentkit/skills/acolyt/__init__.py +83 -0
- intentkit/skills/acolyt/acolyt.jpg +0 -0
- intentkit/skills/acolyt/ask.py +128 -0
- intentkit/skills/acolyt/base.py +28 -0
- intentkit/skills/acolyt/schema.json +89 -0
- intentkit/skills/aixbt/README.md +71 -0
- intentkit/skills/aixbt/__init__.py +73 -0
- intentkit/skills/aixbt/aixbt.jpg +0 -0
- intentkit/skills/aixbt/base.py +21 -0
- intentkit/skills/aixbt/projects.py +153 -0
- intentkit/skills/aixbt/schema.json +99 -0
- intentkit/skills/allora/__init__.py +83 -0
- intentkit/skills/allora/allora.jpeg +0 -0
- intentkit/skills/allora/base.py +28 -0
- intentkit/skills/allora/price.py +130 -0
- intentkit/skills/allora/schema.json +89 -0
- intentkit/skills/base.py +174 -0
- intentkit/skills/carv/README.md +95 -0
- intentkit/skills/carv/__init__.py +121 -0
- intentkit/skills/carv/base.py +183 -0
- intentkit/skills/carv/carv.webp +0 -0
- intentkit/skills/carv/fetch_news.py +92 -0
- intentkit/skills/carv/onchain_query.py +164 -0
- intentkit/skills/carv/schema.json +137 -0
- intentkit/skills/carv/token_info_and_price.py +110 -0
- intentkit/skills/cdp/__init__.py +137 -0
- intentkit/skills/cdp/base.py +21 -0
- intentkit/skills/cdp/cdp.png +0 -0
- intentkit/skills/cdp/get_balance.py +81 -0
- intentkit/skills/cdp/schema.json +473 -0
- intentkit/skills/chainlist/README.md +38 -0
- intentkit/skills/chainlist/__init__.py +54 -0
- intentkit/skills/chainlist/base.py +21 -0
- intentkit/skills/chainlist/chain_lookup.py +208 -0
- intentkit/skills/chainlist/chainlist.png +0 -0
- intentkit/skills/chainlist/schema.json +47 -0
- intentkit/skills/common/__init__.py +82 -0
- intentkit/skills/common/base.py +21 -0
- intentkit/skills/common/common.jpg +0 -0
- intentkit/skills/common/current_time.py +84 -0
- intentkit/skills/common/schema.json +57 -0
- intentkit/skills/cookiefun/README.md +121 -0
- intentkit/skills/cookiefun/__init__.py +78 -0
- intentkit/skills/cookiefun/base.py +41 -0
- intentkit/skills/cookiefun/constants.py +18 -0
- intentkit/skills/cookiefun/cookiefun.png +0 -0
- intentkit/skills/cookiefun/get_account_details.py +171 -0
- intentkit/skills/cookiefun/get_account_feed.py +282 -0
- intentkit/skills/cookiefun/get_account_smart_followers.py +181 -0
- intentkit/skills/cookiefun/get_sectors.py +128 -0
- intentkit/skills/cookiefun/schema.json +155 -0
- intentkit/skills/cookiefun/search_accounts.py +225 -0
- intentkit/skills/cryptocompare/__init__.py +130 -0
- intentkit/skills/cryptocompare/api.py +159 -0
- intentkit/skills/cryptocompare/base.py +303 -0
- intentkit/skills/cryptocompare/cryptocompare.png +0 -0
- intentkit/skills/cryptocompare/fetch_news.py +96 -0
- intentkit/skills/cryptocompare/fetch_price.py +99 -0
- intentkit/skills/cryptocompare/fetch_top_exchanges.py +113 -0
- intentkit/skills/cryptocompare/fetch_top_market_cap.py +109 -0
- intentkit/skills/cryptocompare/fetch_top_volume.py +108 -0
- intentkit/skills/cryptocompare/fetch_trading_signals.py +107 -0
- intentkit/skills/cryptocompare/schema.json +168 -0
- intentkit/skills/cryptopanic/__init__.py +108 -0
- intentkit/skills/cryptopanic/base.py +51 -0
- intentkit/skills/cryptopanic/cryptopanic.png +0 -0
- intentkit/skills/cryptopanic/fetch_crypto_news.py +153 -0
- intentkit/skills/cryptopanic/fetch_crypto_sentiment.py +136 -0
- intentkit/skills/cryptopanic/schema.json +103 -0
- intentkit/skills/dapplooker/README.md +92 -0
- intentkit/skills/dapplooker/__init__.py +83 -0
- intentkit/skills/dapplooker/base.py +26 -0
- intentkit/skills/dapplooker/dapplooker.jpg +0 -0
- intentkit/skills/dapplooker/dapplooker_token_data.py +476 -0
- intentkit/skills/dapplooker/schema.json +91 -0
- intentkit/skills/defillama/__init__.py +323 -0
- intentkit/skills/defillama/api.py +315 -0
- intentkit/skills/defillama/base.py +135 -0
- intentkit/skills/defillama/coins/__init__.py +0 -0
- intentkit/skills/defillama/coins/fetch_batch_historical_prices.py +116 -0
- intentkit/skills/defillama/coins/fetch_block.py +98 -0
- intentkit/skills/defillama/coins/fetch_current_prices.py +105 -0
- intentkit/skills/defillama/coins/fetch_first_price.py +100 -0
- intentkit/skills/defillama/coins/fetch_historical_prices.py +110 -0
- intentkit/skills/defillama/coins/fetch_price_chart.py +109 -0
- intentkit/skills/defillama/coins/fetch_price_percentage.py +93 -0
- intentkit/skills/defillama/config/__init__.py +0 -0
- intentkit/skills/defillama/config/chains.py +433 -0
- intentkit/skills/defillama/defillama.jpeg +0 -0
- intentkit/skills/defillama/fees/__init__.py +0 -0
- intentkit/skills/defillama/fees/fetch_fees_overview.py +130 -0
- intentkit/skills/defillama/schema.json +383 -0
- intentkit/skills/defillama/stablecoins/__init__.py +0 -0
- intentkit/skills/defillama/stablecoins/fetch_stablecoin_chains.py +100 -0
- intentkit/skills/defillama/stablecoins/fetch_stablecoin_charts.py +129 -0
- intentkit/skills/defillama/stablecoins/fetch_stablecoin_prices.py +83 -0
- intentkit/skills/defillama/stablecoins/fetch_stablecoins.py +126 -0
- intentkit/skills/defillama/tests/__init__.py +0 -0
- intentkit/skills/defillama/tests/api_integration.test.py +192 -0
- intentkit/skills/defillama/tests/api_unit.test.py +583 -0
- intentkit/skills/defillama/tvl/__init__.py +0 -0
- intentkit/skills/defillama/tvl/fetch_chain_historical_tvl.py +106 -0
- intentkit/skills/defillama/tvl/fetch_chains.py +107 -0
- intentkit/skills/defillama/tvl/fetch_historical_tvl.py +91 -0
- intentkit/skills/defillama/tvl/fetch_protocol.py +207 -0
- intentkit/skills/defillama/tvl/fetch_protocol_current_tvl.py +93 -0
- intentkit/skills/defillama/tvl/fetch_protocols.py +196 -0
- intentkit/skills/defillama/volumes/__init__.py +0 -0
- intentkit/skills/defillama/volumes/fetch_dex_overview.py +157 -0
- intentkit/skills/defillama/volumes/fetch_dex_summary.py +123 -0
- intentkit/skills/defillama/volumes/fetch_options_overview.py +131 -0
- intentkit/skills/defillama/yields/__init__.py +0 -0
- intentkit/skills/defillama/yields/fetch_pool_chart.py +100 -0
- intentkit/skills/defillama/yields/fetch_pools.py +126 -0
- intentkit/skills/dexscreener/__init__.py +93 -0
- intentkit/skills/dexscreener/base.py +133 -0
- intentkit/skills/dexscreener/dexscreener.png +0 -0
- intentkit/skills/dexscreener/model/__init__.py +0 -0
- intentkit/skills/dexscreener/model/search_token_response.py +82 -0
- intentkit/skills/dexscreener/schema.json +48 -0
- intentkit/skills/dexscreener/search_token.py +321 -0
- intentkit/skills/dune_analytics/__init__.py +103 -0
- intentkit/skills/dune_analytics/base.py +46 -0
- intentkit/skills/dune_analytics/dune.png +0 -0
- intentkit/skills/dune_analytics/fetch_kol_buys.py +128 -0
- intentkit/skills/dune_analytics/fetch_nation_metrics.py +237 -0
- intentkit/skills/dune_analytics/schema.json +99 -0
- intentkit/skills/elfa/README.md +100 -0
- intentkit/skills/elfa/__init__.py +123 -0
- intentkit/skills/elfa/base.py +28 -0
- intentkit/skills/elfa/elfa.jpg +0 -0
- intentkit/skills/elfa/mention.py +504 -0
- intentkit/skills/elfa/schema.json +153 -0
- intentkit/skills/elfa/stats.py +118 -0
- intentkit/skills/elfa/tokens.py +126 -0
- intentkit/skills/enso/README.md +75 -0
- intentkit/skills/enso/__init__.py +114 -0
- intentkit/skills/enso/abi/__init__.py +0 -0
- intentkit/skills/enso/abi/approval.py +279 -0
- intentkit/skills/enso/abi/erc20.py +14 -0
- intentkit/skills/enso/abi/route.py +129 -0
- intentkit/skills/enso/base.py +44 -0
- intentkit/skills/enso/best_yield.py +286 -0
- intentkit/skills/enso/enso.jpg +0 -0
- intentkit/skills/enso/networks.py +105 -0
- intentkit/skills/enso/prices.py +93 -0
- intentkit/skills/enso/route.py +300 -0
- intentkit/skills/enso/schema.json +212 -0
- intentkit/skills/enso/tokens.py +223 -0
- intentkit/skills/enso/wallet.py +381 -0
- intentkit/skills/github/README.md +63 -0
- intentkit/skills/github/__init__.py +54 -0
- intentkit/skills/github/base.py +21 -0
- intentkit/skills/github/github.jpg +0 -0
- intentkit/skills/github/github_search.py +183 -0
- intentkit/skills/github/schema.json +59 -0
- intentkit/skills/heurist/__init__.py +143 -0
- intentkit/skills/heurist/base.py +26 -0
- intentkit/skills/heurist/heurist.png +0 -0
- intentkit/skills/heurist/image_generation_animagine_xl.py +162 -0
- intentkit/skills/heurist/image_generation_arthemy_comics.py +162 -0
- intentkit/skills/heurist/image_generation_arthemy_real.py +162 -0
- intentkit/skills/heurist/image_generation_braindance.py +162 -0
- intentkit/skills/heurist/image_generation_cyber_realistic_xl.py +162 -0
- intentkit/skills/heurist/image_generation_flux_1_dev.py +162 -0
- intentkit/skills/heurist/image_generation_sdxl.py +161 -0
- intentkit/skills/heurist/schema.json +196 -0
- intentkit/skills/lifi/README.md +294 -0
- intentkit/skills/lifi/__init__.py +141 -0
- intentkit/skills/lifi/base.py +21 -0
- intentkit/skills/lifi/lifi.png +0 -0
- intentkit/skills/lifi/schema.json +89 -0
- intentkit/skills/lifi/token_execute.py +472 -0
- intentkit/skills/lifi/token_quote.py +190 -0
- intentkit/skills/lifi/utils.py +656 -0
- intentkit/skills/moralis/README.md +490 -0
- intentkit/skills/moralis/__init__.py +110 -0
- intentkit/skills/moralis/api.py +281 -0
- intentkit/skills/moralis/base.py +55 -0
- intentkit/skills/moralis/fetch_chain_portfolio.py +191 -0
- intentkit/skills/moralis/fetch_nft_portfolio.py +284 -0
- intentkit/skills/moralis/fetch_solana_portfolio.py +331 -0
- intentkit/skills/moralis/fetch_wallet_portfolio.py +301 -0
- intentkit/skills/moralis/moralis.png +0 -0
- intentkit/skills/moralis/schema.json +156 -0
- intentkit/skills/moralis/tests/__init__.py +0 -0
- intentkit/skills/moralis/tests/test_wallet.py +511 -0
- intentkit/skills/nation/__init__.py +62 -0
- intentkit/skills/nation/base.py +31 -0
- intentkit/skills/nation/nation.png +0 -0
- intentkit/skills/nation/nft_check.py +106 -0
- intentkit/skills/nation/schema.json +58 -0
- intentkit/skills/openai/__init__.py +107 -0
- intentkit/skills/openai/base.py +32 -0
- intentkit/skills/openai/dalle_image_generation.py +128 -0
- intentkit/skills/openai/gpt_image_generation.py +152 -0
- intentkit/skills/openai/gpt_image_to_image.py +186 -0
- intentkit/skills/openai/image_to_text.py +126 -0
- intentkit/skills/openai/openai.png +0 -0
- intentkit/skills/openai/schema.json +139 -0
- intentkit/skills/portfolio/README.md +55 -0
- intentkit/skills/portfolio/__init__.py +151 -0
- intentkit/skills/portfolio/base.py +107 -0
- intentkit/skills/portfolio/constants.py +9 -0
- intentkit/skills/portfolio/moralis.png +0 -0
- intentkit/skills/portfolio/schema.json +237 -0
- intentkit/skills/portfolio/token_balances.py +155 -0
- intentkit/skills/portfolio/wallet_approvals.py +102 -0
- intentkit/skills/portfolio/wallet_defi_positions.py +80 -0
- intentkit/skills/portfolio/wallet_history.py +155 -0
- intentkit/skills/portfolio/wallet_net_worth.py +112 -0
- intentkit/skills/portfolio/wallet_nfts.py +139 -0
- intentkit/skills/portfolio/wallet_profitability.py +101 -0
- intentkit/skills/portfolio/wallet_profitability_summary.py +91 -0
- intentkit/skills/portfolio/wallet_stats.py +79 -0
- intentkit/skills/portfolio/wallet_swaps.py +147 -0
- intentkit/skills/skills.toml +103 -0
- intentkit/skills/slack/__init__.py +98 -0
- intentkit/skills/slack/base.py +55 -0
- intentkit/skills/slack/get_channel.py +109 -0
- intentkit/skills/slack/get_message.py +136 -0
- intentkit/skills/slack/schedule_message.py +92 -0
- intentkit/skills/slack/schema.json +135 -0
- intentkit/skills/slack/send_message.py +81 -0
- intentkit/skills/slack/slack.jpg +0 -0
- intentkit/skills/system/__init__.py +90 -0
- intentkit/skills/system/base.py +22 -0
- intentkit/skills/system/read_agent_api_key.py +87 -0
- intentkit/skills/system/regenerate_agent_api_key.py +77 -0
- intentkit/skills/system/schema.json +53 -0
- intentkit/skills/system/system.svg +76 -0
- intentkit/skills/tavily/README.md +86 -0
- intentkit/skills/tavily/__init__.py +91 -0
- intentkit/skills/tavily/base.py +27 -0
- intentkit/skills/tavily/schema.json +119 -0
- intentkit/skills/tavily/tavily.jpg +0 -0
- intentkit/skills/tavily/tavily_extract.py +147 -0
- intentkit/skills/tavily/tavily_search.py +139 -0
- intentkit/skills/token/README.md +89 -0
- intentkit/skills/token/__init__.py +107 -0
- intentkit/skills/token/base.py +154 -0
- intentkit/skills/token/constants.py +9 -0
- intentkit/skills/token/erc20_transfers.py +145 -0
- intentkit/skills/token/moralis.png +0 -0
- intentkit/skills/token/schema.json +141 -0
- intentkit/skills/token/token_analytics.py +81 -0
- intentkit/skills/token/token_price.py +132 -0
- intentkit/skills/token/token_search.py +121 -0
- intentkit/skills/twitter/__init__.py +146 -0
- intentkit/skills/twitter/base.py +68 -0
- intentkit/skills/twitter/follow_user.py +69 -0
- intentkit/skills/twitter/get_mentions.py +124 -0
- intentkit/skills/twitter/get_timeline.py +111 -0
- intentkit/skills/twitter/get_user_by_username.py +84 -0
- intentkit/skills/twitter/get_user_tweets.py +123 -0
- intentkit/skills/twitter/like_tweet.py +65 -0
- intentkit/skills/twitter/post_tweet.py +90 -0
- intentkit/skills/twitter/reply_tweet.py +98 -0
- intentkit/skills/twitter/retweet.py +76 -0
- intentkit/skills/twitter/schema.json +258 -0
- intentkit/skills/twitter/search_tweets.py +115 -0
- intentkit/skills/twitter/twitter.png +0 -0
- intentkit/skills/unrealspeech/__init__.py +55 -0
- intentkit/skills/unrealspeech/base.py +21 -0
- intentkit/skills/unrealspeech/schema.json +100 -0
- intentkit/skills/unrealspeech/text_to_speech.py +177 -0
- intentkit/skills/unrealspeech/unrealspeech.jpg +0 -0
- intentkit/skills/venice_audio/__init__.py +106 -0
- intentkit/skills/venice_audio/base.py +119 -0
- intentkit/skills/venice_audio/input.py +41 -0
- intentkit/skills/venice_audio/schema.json +152 -0
- intentkit/skills/venice_audio/venice_audio.py +240 -0
- intentkit/skills/venice_audio/venice_logo.jpg +0 -0
- intentkit/skills/venice_image/README.md +119 -0
- intentkit/skills/venice_image/__init__.py +154 -0
- intentkit/skills/venice_image/api.py +138 -0
- intentkit/skills/venice_image/base.py +188 -0
- intentkit/skills/venice_image/config.py +35 -0
- intentkit/skills/venice_image/image_enhance/README.md +119 -0
- intentkit/skills/venice_image/image_enhance/__init__.py +0 -0
- intentkit/skills/venice_image/image_enhance/image_enhance.py +80 -0
- intentkit/skills/venice_image/image_enhance/image_enhance_base.py +23 -0
- intentkit/skills/venice_image/image_enhance/image_enhance_input.py +40 -0
- intentkit/skills/venice_image/image_generation/README.md +144 -0
- intentkit/skills/venice_image/image_generation/__init__.py +0 -0
- intentkit/skills/venice_image/image_generation/image_generation_base.py +117 -0
- intentkit/skills/venice_image/image_generation/image_generation_fluently_xl.py +26 -0
- intentkit/skills/venice_image/image_generation/image_generation_flux_dev.py +27 -0
- intentkit/skills/venice_image/image_generation/image_generation_flux_dev_uncensored.py +26 -0
- intentkit/skills/venice_image/image_generation/image_generation_input.py +158 -0
- intentkit/skills/venice_image/image_generation/image_generation_lustify_sdxl.py +26 -0
- intentkit/skills/venice_image/image_generation/image_generation_pony_realism.py +26 -0
- intentkit/skills/venice_image/image_generation/image_generation_stable_diffusion_3_5.py +28 -0
- intentkit/skills/venice_image/image_generation/image_generation_venice_sd35.py +28 -0
- intentkit/skills/venice_image/image_upscale/README.md +111 -0
- intentkit/skills/venice_image/image_upscale/__init__.py +0 -0
- intentkit/skills/venice_image/image_upscale/image_upscale.py +90 -0
- intentkit/skills/venice_image/image_upscale/image_upscale_base.py +23 -0
- intentkit/skills/venice_image/image_upscale/image_upscale_input.py +22 -0
- intentkit/skills/venice_image/image_vision/README.md +112 -0
- intentkit/skills/venice_image/image_vision/__init__.py +0 -0
- intentkit/skills/venice_image/image_vision/image_vision.py +100 -0
- intentkit/skills/venice_image/image_vision/image_vision_base.py +17 -0
- intentkit/skills/venice_image/image_vision/image_vision_input.py +9 -0
- intentkit/skills/venice_image/schema.json +267 -0
- intentkit/skills/venice_image/utils.py +78 -0
- intentkit/skills/venice_image/venice_image.jpg +0 -0
- intentkit/skills/web_scraper/README.md +82 -0
- intentkit/skills/web_scraper/__init__.py +92 -0
- intentkit/skills/web_scraper/base.py +21 -0
- intentkit/skills/web_scraper/langchain.png +0 -0
- intentkit/skills/web_scraper/schema.json +115 -0
- intentkit/skills/web_scraper/scrape_and_index.py +327 -0
- intentkit/utils/__init__.py +1 -0
- intentkit/utils/chain.py +436 -0
- intentkit/utils/error.py +134 -0
- intentkit/utils/logging.py +70 -0
- intentkit/utils/middleware.py +61 -0
- intentkit/utils/random.py +16 -0
- intentkit/utils/s3.py +267 -0
- intentkit/utils/slack_alert.py +79 -0
- intentkit/utils/tx.py +37 -0
- {intentkit-0.5.0.dist-info → intentkit-0.5.2.dist-info}/METADATA +1 -1
- intentkit-0.5.2.dist-info/RECORD +365 -0
- intentkit-0.5.0.dist-info/RECORD +0 -4
- {intentkit-0.5.0.dist-info → intentkit-0.5.2.dist-info}/WHEEL +0 -0
- {intentkit-0.5.0.dist-info → intentkit-0.5.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""Redis client module for IntentKit."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from redis.asyncio import Redis
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
# Global Redis client instance
|
|
11
|
+
_redis_client: Optional[Redis] = None
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
async def init_redis(
|
|
15
|
+
host: str,
|
|
16
|
+
port: int = 6379,
|
|
17
|
+
db: int = 0,
|
|
18
|
+
password: Optional[str] = None,
|
|
19
|
+
ssl: bool = False,
|
|
20
|
+
encoding: str = "utf-8",
|
|
21
|
+
decode_responses: bool = True,
|
|
22
|
+
) -> Redis:
|
|
23
|
+
"""Initialize the Redis client.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
host: Redis host
|
|
27
|
+
port: Redis port (default: 6379)
|
|
28
|
+
db: Redis database number (default: 0)
|
|
29
|
+
password: Redis password (default: None)
|
|
30
|
+
ssl: Whether to use SSL (default: False)
|
|
31
|
+
encoding: Response encoding (default: utf-8)
|
|
32
|
+
decode_responses: Whether to decode responses (default: True)
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Redis: The initialized Redis client
|
|
36
|
+
"""
|
|
37
|
+
global _redis_client
|
|
38
|
+
|
|
39
|
+
if _redis_client is not None:
|
|
40
|
+
logger.info("Redis client already initialized")
|
|
41
|
+
return _redis_client
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
logger.info(f"Initializing Redis client at {host}:{port}")
|
|
45
|
+
_redis_client = Redis(
|
|
46
|
+
host=host,
|
|
47
|
+
port=port,
|
|
48
|
+
db=db,
|
|
49
|
+
password=password,
|
|
50
|
+
ssl=ssl,
|
|
51
|
+
encoding=encoding,
|
|
52
|
+
decode_responses=decode_responses,
|
|
53
|
+
)
|
|
54
|
+
# Test the connection
|
|
55
|
+
await _redis_client.ping()
|
|
56
|
+
logger.info("Redis client initialized successfully")
|
|
57
|
+
return _redis_client
|
|
58
|
+
except Exception as e:
|
|
59
|
+
logger.error(f"Failed to initialize Redis client: {e}")
|
|
60
|
+
raise
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def get_redis() -> Redis:
|
|
64
|
+
"""Get the Redis client.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Redis: The Redis client
|
|
68
|
+
|
|
69
|
+
Raises:
|
|
70
|
+
RuntimeError: If the Redis client is not initialized
|
|
71
|
+
"""
|
|
72
|
+
if _redis_client is None:
|
|
73
|
+
raise RuntimeError("Redis client not initialized. Call init_redis first.")
|
|
74
|
+
return _redis_client
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
async def send_heartbeat(redis_client: Redis, name: str) -> None:
|
|
78
|
+
"""Set a heartbeat key in Redis that expires after 16 minutes.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
redis_client: Redis client instance
|
|
82
|
+
name: Name identifier for the heartbeat
|
|
83
|
+
"""
|
|
84
|
+
try:
|
|
85
|
+
key = f"intentkit:heartbeat:{name}"
|
|
86
|
+
await redis_client.set(key, 1, ex=190) # 190 seconds = 3 minutes
|
|
87
|
+
except Exception as e:
|
|
88
|
+
logger.error(f"Failed to send heartbeat for {name}: {e}")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
async def check_heartbeat(redis_client: Redis, name: str) -> bool:
|
|
92
|
+
"""Check if a heartbeat key exists in Redis.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
redis_client: Redis client instance
|
|
96
|
+
name: Name identifier for the heartbeat
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
bool: True if heartbeat exists, False otherwise
|
|
100
|
+
"""
|
|
101
|
+
import asyncio
|
|
102
|
+
|
|
103
|
+
key = f"intentkit:heartbeat:{name}"
|
|
104
|
+
retries = 3
|
|
105
|
+
|
|
106
|
+
for attempt in range(retries):
|
|
107
|
+
try:
|
|
108
|
+
exists = await redis_client.exists(key)
|
|
109
|
+
return bool(exists)
|
|
110
|
+
except Exception as e:
|
|
111
|
+
logger.error(
|
|
112
|
+
f"Error checking heartbeat for {name} (attempt {attempt + 1}/{retries}): {e}"
|
|
113
|
+
)
|
|
114
|
+
if attempt < retries - 1: # Don't sleep on the last attempt
|
|
115
|
+
await asyncio.sleep(5) # Wait 5 seconds before retrying
|
|
116
|
+
|
|
117
|
+
return False
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
async def clean_heartbeat(redis_client: Redis, name: str) -> None:
|
|
121
|
+
"""Remove a heartbeat key from Redis.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
redis_client: Redis client instance
|
|
125
|
+
name: Name identifier for the heartbeat to remove
|
|
126
|
+
"""
|
|
127
|
+
try:
|
|
128
|
+
key = f"intentkit:heartbeat:{name}"
|
|
129
|
+
await redis_client.delete(key)
|
|
130
|
+
logger.info(f"Removed heartbeat for {name}")
|
|
131
|
+
except Exception as e:
|
|
132
|
+
logger.error(f"Failed to remove heartbeat for {name}: {e}")
|
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from datetime import datetime, timezone
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
from typing import Annotated, Any, Dict, Optional
|
|
5
|
+
|
|
6
|
+
from intentkit.models.base import Base
|
|
7
|
+
from intentkit.models.db import get_session
|
|
8
|
+
from intentkit.models.redis import get_redis
|
|
9
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
10
|
+
from sqlalchemy import (
|
|
11
|
+
Boolean,
|
|
12
|
+
Column,
|
|
13
|
+
DateTime,
|
|
14
|
+
Integer,
|
|
15
|
+
Numeric,
|
|
16
|
+
String,
|
|
17
|
+
delete,
|
|
18
|
+
func,
|
|
19
|
+
select,
|
|
20
|
+
)
|
|
21
|
+
from sqlalchemy.dialects.postgresql import JSON, JSONB
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AgentSkillDataTable(Base):
|
|
25
|
+
"""Database table model for storing skill-specific data for agents."""
|
|
26
|
+
|
|
27
|
+
__tablename__ = "agent_skill_data"
|
|
28
|
+
|
|
29
|
+
agent_id = Column(String, primary_key=True)
|
|
30
|
+
skill = Column(String, primary_key=True)
|
|
31
|
+
key = Column(String, primary_key=True)
|
|
32
|
+
data = Column(JSON().with_variant(JSONB(), "postgresql"), nullable=True)
|
|
33
|
+
size = Column(Integer, nullable=False, default=0)
|
|
34
|
+
created_at = Column(
|
|
35
|
+
DateTime(timezone=True),
|
|
36
|
+
nullable=False,
|
|
37
|
+
server_default=func.now(),
|
|
38
|
+
)
|
|
39
|
+
updated_at = Column(
|
|
40
|
+
DateTime(timezone=True),
|
|
41
|
+
nullable=False,
|
|
42
|
+
server_default=func.now(),
|
|
43
|
+
onupdate=lambda: datetime.now(timezone.utc),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class AgentSkillDataCreate(BaseModel):
|
|
48
|
+
"""Base model for creating agent skill data records."""
|
|
49
|
+
|
|
50
|
+
model_config = ConfigDict(from_attributes=True)
|
|
51
|
+
|
|
52
|
+
agent_id: Annotated[str, Field(description="ID of the agent this data belongs to")]
|
|
53
|
+
skill: Annotated[str, Field(description="Name of the skill this data is for")]
|
|
54
|
+
key: Annotated[str, Field(description="Key for this specific piece of data")]
|
|
55
|
+
data: Annotated[Dict[str, Any], Field(description="JSON data stored for this key")]
|
|
56
|
+
|
|
57
|
+
async def save(self) -> "AgentSkillData":
|
|
58
|
+
"""Save or update skill data.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
AgentSkillData: The saved agent skill data instance
|
|
62
|
+
|
|
63
|
+
Raises:
|
|
64
|
+
Exception: If the total size would exceed the 10MB limit
|
|
65
|
+
"""
|
|
66
|
+
# Calculate the size of the data
|
|
67
|
+
data_size = len(json.dumps(self.data).encode("utf-8"))
|
|
68
|
+
|
|
69
|
+
async with get_session() as db:
|
|
70
|
+
# Check current total size for this agent
|
|
71
|
+
current_total = await AgentSkillData.total_size(self.agent_id)
|
|
72
|
+
|
|
73
|
+
record = await db.scalar(
|
|
74
|
+
select(AgentSkillDataTable).where(
|
|
75
|
+
AgentSkillDataTable.agent_id == self.agent_id,
|
|
76
|
+
AgentSkillDataTable.skill == self.skill,
|
|
77
|
+
AgentSkillDataTable.key == self.key,
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Calculate new total size
|
|
82
|
+
if record:
|
|
83
|
+
# Update existing record - subtract old size, add new size
|
|
84
|
+
new_total = current_total - record.size + data_size
|
|
85
|
+
else:
|
|
86
|
+
# Create new record - add new size
|
|
87
|
+
new_total = current_total + data_size
|
|
88
|
+
|
|
89
|
+
# Check if new total would exceed limit (10MB = 10 * 1024 * 1024 bytes)
|
|
90
|
+
if new_total > 10 * 1024 * 1024:
|
|
91
|
+
raise Exception(
|
|
92
|
+
f"Total size would exceed 10MB limit. Current: {current_total}, New: {new_total}"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
if record:
|
|
96
|
+
# Update existing record
|
|
97
|
+
record.data = self.data
|
|
98
|
+
record.size = data_size
|
|
99
|
+
else:
|
|
100
|
+
# Create new record
|
|
101
|
+
record = AgentSkillDataTable(
|
|
102
|
+
agent_id=self.agent_id,
|
|
103
|
+
skill=self.skill,
|
|
104
|
+
key=self.key,
|
|
105
|
+
data=self.data,
|
|
106
|
+
size=data_size,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
db.add(record)
|
|
110
|
+
await db.commit()
|
|
111
|
+
await db.refresh(record)
|
|
112
|
+
return AgentSkillData.model_validate(record)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class AgentSkillData(AgentSkillDataCreate):
|
|
116
|
+
"""Model for storing skill-specific data for agents.
|
|
117
|
+
|
|
118
|
+
This model uses a composite primary key of (agent_id, skill, key) to store
|
|
119
|
+
skill-specific data for agents in a flexible way.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
model_config = ConfigDict(
|
|
123
|
+
from_attributes=True,
|
|
124
|
+
json_encoders={datetime: lambda v: v.isoformat(timespec="milliseconds")},
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
size: Annotated[int, Field(description="Size of the data in bytes")]
|
|
128
|
+
created_at: Annotated[
|
|
129
|
+
datetime, Field(description="Timestamp when this data was created")
|
|
130
|
+
]
|
|
131
|
+
updated_at: Annotated[
|
|
132
|
+
datetime, Field(description="Timestamp when this data was updated")
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
@classmethod
|
|
136
|
+
async def total_size(cls, agent_id: str) -> int:
|
|
137
|
+
"""Calculate the total size of all skill data for an agent.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
agent_id: ID of the agent
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
int: Total size in bytes of all skill data for the agent
|
|
144
|
+
"""
|
|
145
|
+
async with get_session() as db:
|
|
146
|
+
result = await db.scalar(
|
|
147
|
+
select(func.coalesce(func.sum(AgentSkillDataTable.size), 0)).where(
|
|
148
|
+
AgentSkillDataTable.agent_id == agent_id
|
|
149
|
+
)
|
|
150
|
+
)
|
|
151
|
+
return result or 0
|
|
152
|
+
|
|
153
|
+
@classmethod
|
|
154
|
+
async def get(cls, agent_id: str, skill: str, key: str) -> Optional[dict]:
|
|
155
|
+
"""Get skill data for an agent.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
agent_id: ID of the agent
|
|
159
|
+
skill: Name of the skill
|
|
160
|
+
key: Data key
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Dictionary containing the skill data if found, None otherwise
|
|
164
|
+
"""
|
|
165
|
+
async with get_session() as db:
|
|
166
|
+
result = await db.scalar(
|
|
167
|
+
select(AgentSkillDataTable).where(
|
|
168
|
+
AgentSkillDataTable.agent_id == agent_id,
|
|
169
|
+
AgentSkillDataTable.skill == skill,
|
|
170
|
+
AgentSkillDataTable.key == key,
|
|
171
|
+
)
|
|
172
|
+
)
|
|
173
|
+
return result.data if result else None
|
|
174
|
+
|
|
175
|
+
@classmethod
|
|
176
|
+
async def clean_data(cls, agent_id: str):
|
|
177
|
+
"""Clean all skill data for an agent.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
agent_id: ID of the agent
|
|
181
|
+
"""
|
|
182
|
+
async with get_session() as db:
|
|
183
|
+
await db.execute(
|
|
184
|
+
delete(AgentSkillDataTable).where(
|
|
185
|
+
AgentSkillDataTable.agent_id == agent_id
|
|
186
|
+
)
|
|
187
|
+
)
|
|
188
|
+
await db.commit()
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class ThreadSkillDataTable(Base):
|
|
192
|
+
"""Database table model for storing skill-specific data for threads."""
|
|
193
|
+
|
|
194
|
+
__tablename__ = "thread_skill_data"
|
|
195
|
+
|
|
196
|
+
thread_id = Column(String, primary_key=True)
|
|
197
|
+
skill = Column(String, primary_key=True)
|
|
198
|
+
key = Column(String, primary_key=True)
|
|
199
|
+
agent_id = Column(String, nullable=False)
|
|
200
|
+
data = Column(JSON().with_variant(JSONB(), "postgresql"), nullable=True)
|
|
201
|
+
created_at = Column(
|
|
202
|
+
DateTime(timezone=True),
|
|
203
|
+
nullable=False,
|
|
204
|
+
server_default=func.now(),
|
|
205
|
+
)
|
|
206
|
+
updated_at = Column(
|
|
207
|
+
DateTime(timezone=True),
|
|
208
|
+
nullable=False,
|
|
209
|
+
server_default=func.now(),
|
|
210
|
+
onupdate=lambda: datetime.now(timezone.utc),
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class ThreadSkillDataCreate(BaseModel):
|
|
215
|
+
"""Base model for creating thread skill data records."""
|
|
216
|
+
|
|
217
|
+
model_config = ConfigDict(from_attributes=True)
|
|
218
|
+
|
|
219
|
+
thread_id: Annotated[
|
|
220
|
+
str, Field(description="ID of the thread this data belongs to")
|
|
221
|
+
]
|
|
222
|
+
skill: Annotated[str, Field(description="Name of the skill this data is for")]
|
|
223
|
+
key: Annotated[str, Field(description="Key for this specific piece of data")]
|
|
224
|
+
agent_id: Annotated[str, Field(description="ID of the agent that owns this thread")]
|
|
225
|
+
data: Annotated[Dict[str, Any], Field(description="JSON data stored for this key")]
|
|
226
|
+
|
|
227
|
+
async def save(self) -> "ThreadSkillData":
|
|
228
|
+
"""Save or update skill data.
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
ThreadSkillData: The saved thread skill data instance
|
|
232
|
+
"""
|
|
233
|
+
async with get_session() as db:
|
|
234
|
+
record = await db.scalar(
|
|
235
|
+
select(ThreadSkillDataTable).where(
|
|
236
|
+
ThreadSkillDataTable.thread_id == self.thread_id,
|
|
237
|
+
ThreadSkillDataTable.skill == self.skill,
|
|
238
|
+
ThreadSkillDataTable.key == self.key,
|
|
239
|
+
)
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
if record:
|
|
243
|
+
# Update existing record
|
|
244
|
+
record.data = self.data
|
|
245
|
+
record.agent_id = self.agent_id
|
|
246
|
+
else:
|
|
247
|
+
# Create new record
|
|
248
|
+
record = ThreadSkillDataTable(**self.model_dump())
|
|
249
|
+
db.add(record)
|
|
250
|
+
await db.commit()
|
|
251
|
+
await db.refresh(record)
|
|
252
|
+
return ThreadSkillData.model_validate(record)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class ThreadSkillData(ThreadSkillDataCreate):
|
|
256
|
+
"""Model for storing skill-specific data for threads.
|
|
257
|
+
|
|
258
|
+
This model uses a composite primary key of (thread_id, skill, key) to store
|
|
259
|
+
skill-specific data for threads in a flexible way. It also includes agent_id
|
|
260
|
+
as a required field for tracking ownership.
|
|
261
|
+
"""
|
|
262
|
+
|
|
263
|
+
model_config = ConfigDict(
|
|
264
|
+
from_attributes=True,
|
|
265
|
+
json_encoders={datetime: lambda v: v.isoformat(timespec="milliseconds")},
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
created_at: Annotated[
|
|
269
|
+
datetime, Field(description="Timestamp when this data was created")
|
|
270
|
+
]
|
|
271
|
+
updated_at: Annotated[
|
|
272
|
+
datetime, Field(description="Timestamp when this data was updated")
|
|
273
|
+
]
|
|
274
|
+
|
|
275
|
+
@classmethod
|
|
276
|
+
async def get(cls, thread_id: str, skill: str, key: str) -> Optional[dict]:
|
|
277
|
+
"""Get skill data for a thread.
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
thread_id: ID of the thread
|
|
281
|
+
skill: Name of the skill
|
|
282
|
+
key: Data key
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
Dictionary containing the skill data if found, None otherwise
|
|
286
|
+
"""
|
|
287
|
+
async with get_session() as db:
|
|
288
|
+
record = await db.scalar(
|
|
289
|
+
select(ThreadSkillDataTable).where(
|
|
290
|
+
ThreadSkillDataTable.thread_id == thread_id,
|
|
291
|
+
ThreadSkillDataTable.skill == skill,
|
|
292
|
+
ThreadSkillDataTable.key == key,
|
|
293
|
+
)
|
|
294
|
+
)
|
|
295
|
+
return record.data if record else None
|
|
296
|
+
|
|
297
|
+
@classmethod
|
|
298
|
+
async def clean_data(
|
|
299
|
+
cls,
|
|
300
|
+
agent_id: str,
|
|
301
|
+
thread_id: Annotated[
|
|
302
|
+
str,
|
|
303
|
+
Field(
|
|
304
|
+
default="",
|
|
305
|
+
description="Optional ID of the thread. If provided, only cleans data for that thread.",
|
|
306
|
+
),
|
|
307
|
+
],
|
|
308
|
+
):
|
|
309
|
+
"""Clean all skill data for a thread or agent.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
agent_id: ID of the agent
|
|
313
|
+
thread_id: Optional ID of the thread. If provided, only cleans data for that thread.
|
|
314
|
+
If empty, cleans all data for the agent.
|
|
315
|
+
"""
|
|
316
|
+
async with get_session() as db:
|
|
317
|
+
if thread_id and thread_id != "":
|
|
318
|
+
await db.execute(
|
|
319
|
+
delete(ThreadSkillDataTable).where(
|
|
320
|
+
ThreadSkillDataTable.agent_id == agent_id,
|
|
321
|
+
ThreadSkillDataTable.thread_id == thread_id,
|
|
322
|
+
)
|
|
323
|
+
)
|
|
324
|
+
else:
|
|
325
|
+
await db.execute(
|
|
326
|
+
delete(ThreadSkillDataTable).where(
|
|
327
|
+
ThreadSkillDataTable.agent_id == agent_id
|
|
328
|
+
)
|
|
329
|
+
)
|
|
330
|
+
await db.commit()
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
class SkillTable(Base):
|
|
334
|
+
"""Database table model for Skill."""
|
|
335
|
+
|
|
336
|
+
__tablename__ = "skills"
|
|
337
|
+
|
|
338
|
+
name = Column(String, primary_key=True)
|
|
339
|
+
enabled = Column(Boolean, nullable=False, default=True)
|
|
340
|
+
category = Column(String, nullable=False)
|
|
341
|
+
config_name = Column(String, nullable=True)
|
|
342
|
+
price_level = Column(Integer, nullable=True)
|
|
343
|
+
price = Column(Numeric(22, 4), nullable=False, default=1)
|
|
344
|
+
price_self_key = Column(Numeric(22, 4), nullable=False, default=1)
|
|
345
|
+
rate_limit_count = Column(Integer, nullable=True)
|
|
346
|
+
rate_limit_minutes = Column(Integer, nullable=True)
|
|
347
|
+
author = Column(String, nullable=True)
|
|
348
|
+
created_at = Column(
|
|
349
|
+
DateTime(timezone=True),
|
|
350
|
+
nullable=False,
|
|
351
|
+
server_default=func.now(),
|
|
352
|
+
)
|
|
353
|
+
updated_at = Column(
|
|
354
|
+
DateTime(timezone=True),
|
|
355
|
+
nullable=False,
|
|
356
|
+
server_default=func.now(),
|
|
357
|
+
onupdate=lambda: datetime.now(timezone.utc),
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
class Skill(BaseModel):
|
|
362
|
+
"""Pydantic model for Skill."""
|
|
363
|
+
|
|
364
|
+
model_config = ConfigDict(
|
|
365
|
+
from_attributes=True,
|
|
366
|
+
json_encoders={
|
|
367
|
+
datetime: lambda v: v.isoformat(timespec="milliseconds"),
|
|
368
|
+
},
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
name: Annotated[str, Field(description="Name of the skill")]
|
|
372
|
+
enabled: Annotated[bool, Field(description="Is this skill enabled?")]
|
|
373
|
+
category: Annotated[str, Field(description="Category of the skill")]
|
|
374
|
+
config_name: Annotated[Optional[str], Field(description="Config name of the skill")]
|
|
375
|
+
price_level: Annotated[
|
|
376
|
+
Optional[int], Field(description="Price level for this skill")
|
|
377
|
+
]
|
|
378
|
+
price: Annotated[
|
|
379
|
+
Decimal, Field(description="Price for this skill", default=Decimal("1"))
|
|
380
|
+
]
|
|
381
|
+
price_self_key: Annotated[
|
|
382
|
+
Decimal,
|
|
383
|
+
Field(description="Price for this skill with self key", default=Decimal("1")),
|
|
384
|
+
]
|
|
385
|
+
rate_limit_count: Annotated[Optional[int], Field(description="Rate limit count")]
|
|
386
|
+
rate_limit_minutes: Annotated[
|
|
387
|
+
Optional[int], Field(description="Rate limit minutes")
|
|
388
|
+
]
|
|
389
|
+
author: Annotated[Optional[str], Field(description="Author of the skill")]
|
|
390
|
+
created_at: Annotated[
|
|
391
|
+
datetime, Field(description="Timestamp when this record was created")
|
|
392
|
+
]
|
|
393
|
+
updated_at: Annotated[
|
|
394
|
+
datetime, Field(description="Timestamp when this record was last updated")
|
|
395
|
+
]
|
|
396
|
+
|
|
397
|
+
@staticmethod
|
|
398
|
+
async def get(name: str) -> Optional["Skill"]:
|
|
399
|
+
"""Get a skill by name with Redis caching.
|
|
400
|
+
|
|
401
|
+
The skill is cached in Redis for 3 minutes.
|
|
402
|
+
|
|
403
|
+
Args:
|
|
404
|
+
name: Name of the skill to retrieve
|
|
405
|
+
|
|
406
|
+
Returns:
|
|
407
|
+
Skill: The skill if found, None otherwise
|
|
408
|
+
"""
|
|
409
|
+
# Redis cache key for skill
|
|
410
|
+
cache_key = f"intentkit:skill:{name}"
|
|
411
|
+
cache_ttl = 180 # 3 minutes in seconds
|
|
412
|
+
|
|
413
|
+
# Try to get from Redis cache first
|
|
414
|
+
redis = get_redis()
|
|
415
|
+
cached_data = await redis.get(cache_key)
|
|
416
|
+
|
|
417
|
+
if cached_data:
|
|
418
|
+
# If found in cache, deserialize and return
|
|
419
|
+
try:
|
|
420
|
+
return Skill.model_validate_json(cached_data)
|
|
421
|
+
except (json.JSONDecodeError, TypeError):
|
|
422
|
+
# If cache is corrupted, invalidate it
|
|
423
|
+
await redis.delete(cache_key)
|
|
424
|
+
|
|
425
|
+
# If not in cache or cache is invalid, get from database
|
|
426
|
+
async with get_session() as session:
|
|
427
|
+
# Query the database for the skill
|
|
428
|
+
stmt = select(SkillTable).where(SkillTable.name == name)
|
|
429
|
+
skill = await session.scalar(stmt)
|
|
430
|
+
|
|
431
|
+
# If skill doesn't exist, return None
|
|
432
|
+
if not skill:
|
|
433
|
+
return None
|
|
434
|
+
|
|
435
|
+
# Convert to Skill model
|
|
436
|
+
skill_model = Skill.model_validate(skill)
|
|
437
|
+
|
|
438
|
+
# Cache the skill in Redis
|
|
439
|
+
await redis.set(cache_key, skill_model.model_dump_json(), ex=cache_ttl)
|
|
440
|
+
|
|
441
|
+
return skill_model
|
|
442
|
+
|
|
443
|
+
@staticmethod
|
|
444
|
+
async def get_by_config_name(category: str, config_name: str) -> Optional["Skill"]:
|
|
445
|
+
"""Get a skill by category and config_name.
|
|
446
|
+
|
|
447
|
+
Args:
|
|
448
|
+
category: Category of the skill
|
|
449
|
+
config_name: Config name of the skill
|
|
450
|
+
|
|
451
|
+
Returns:
|
|
452
|
+
Skill: The skill if found, None otherwise
|
|
453
|
+
"""
|
|
454
|
+
async with get_session() as session:
|
|
455
|
+
# Query the database for the skill
|
|
456
|
+
stmt = select(SkillTable).where(
|
|
457
|
+
SkillTable.category == category, SkillTable.config_name == config_name
|
|
458
|
+
)
|
|
459
|
+
skill = await session.scalar(stmt)
|
|
460
|
+
|
|
461
|
+
# If skill doesn't exist, return None
|
|
462
|
+
if not skill:
|
|
463
|
+
return None
|
|
464
|
+
|
|
465
|
+
# Convert to Skill model
|
|
466
|
+
return Skill.model_validate(skill)
|