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.

Files changed (366) hide show
  1. intentkit/__init__.py +17 -0
  2. intentkit/abstracts/__init__.py +0 -0
  3. intentkit/abstracts/agent.py +60 -0
  4. intentkit/abstracts/api.py +4 -0
  5. intentkit/abstracts/engine.py +38 -0
  6. intentkit/abstracts/exception.py +9 -0
  7. intentkit/abstracts/graph.py +25 -0
  8. intentkit/abstracts/skill.py +129 -0
  9. intentkit/abstracts/twitter.py +54 -0
  10. intentkit/clients/__init__.py +14 -0
  11. intentkit/clients/cdp.py +53 -0
  12. intentkit/clients/twitter.py +445 -0
  13. intentkit/config/__init__.py +0 -0
  14. intentkit/config/config.py +164 -0
  15. intentkit/core/__init__.py +0 -0
  16. intentkit/core/agent.py +191 -0
  17. intentkit/core/api.py +40 -0
  18. intentkit/core/client.py +45 -0
  19. intentkit/core/credit.py +1767 -0
  20. intentkit/core/engine.py +1018 -0
  21. intentkit/core/node.py +223 -0
  22. intentkit/core/prompt.py +58 -0
  23. intentkit/core/skill.py +124 -0
  24. intentkit/models/agent.py +1689 -0
  25. intentkit/models/agent_data.py +810 -0
  26. intentkit/models/agent_schema.json +733 -0
  27. intentkit/models/app_setting.py +156 -0
  28. intentkit/models/base.py +9 -0
  29. intentkit/models/chat.py +581 -0
  30. intentkit/models/conversation.py +286 -0
  31. intentkit/models/credit.py +1406 -0
  32. intentkit/models/db.py +120 -0
  33. intentkit/models/db_mig.py +102 -0
  34. intentkit/models/generator.py +347 -0
  35. intentkit/models/llm.py +746 -0
  36. intentkit/models/redis.py +132 -0
  37. intentkit/models/skill.py +466 -0
  38. intentkit/models/user.py +243 -0
  39. intentkit/skills/__init__.py +12 -0
  40. intentkit/skills/acolyt/__init__.py +83 -0
  41. intentkit/skills/acolyt/acolyt.jpg +0 -0
  42. intentkit/skills/acolyt/ask.py +128 -0
  43. intentkit/skills/acolyt/base.py +28 -0
  44. intentkit/skills/acolyt/schema.json +89 -0
  45. intentkit/skills/aixbt/README.md +71 -0
  46. intentkit/skills/aixbt/__init__.py +73 -0
  47. intentkit/skills/aixbt/aixbt.jpg +0 -0
  48. intentkit/skills/aixbt/base.py +21 -0
  49. intentkit/skills/aixbt/projects.py +153 -0
  50. intentkit/skills/aixbt/schema.json +99 -0
  51. intentkit/skills/allora/__init__.py +83 -0
  52. intentkit/skills/allora/allora.jpeg +0 -0
  53. intentkit/skills/allora/base.py +28 -0
  54. intentkit/skills/allora/price.py +130 -0
  55. intentkit/skills/allora/schema.json +89 -0
  56. intentkit/skills/base.py +174 -0
  57. intentkit/skills/carv/README.md +95 -0
  58. intentkit/skills/carv/__init__.py +121 -0
  59. intentkit/skills/carv/base.py +183 -0
  60. intentkit/skills/carv/carv.webp +0 -0
  61. intentkit/skills/carv/fetch_news.py +92 -0
  62. intentkit/skills/carv/onchain_query.py +164 -0
  63. intentkit/skills/carv/schema.json +137 -0
  64. intentkit/skills/carv/token_info_and_price.py +110 -0
  65. intentkit/skills/cdp/__init__.py +137 -0
  66. intentkit/skills/cdp/base.py +21 -0
  67. intentkit/skills/cdp/cdp.png +0 -0
  68. intentkit/skills/cdp/get_balance.py +81 -0
  69. intentkit/skills/cdp/schema.json +473 -0
  70. intentkit/skills/chainlist/README.md +38 -0
  71. intentkit/skills/chainlist/__init__.py +54 -0
  72. intentkit/skills/chainlist/base.py +21 -0
  73. intentkit/skills/chainlist/chain_lookup.py +208 -0
  74. intentkit/skills/chainlist/chainlist.png +0 -0
  75. intentkit/skills/chainlist/schema.json +47 -0
  76. intentkit/skills/common/__init__.py +82 -0
  77. intentkit/skills/common/base.py +21 -0
  78. intentkit/skills/common/common.jpg +0 -0
  79. intentkit/skills/common/current_time.py +84 -0
  80. intentkit/skills/common/schema.json +57 -0
  81. intentkit/skills/cookiefun/README.md +121 -0
  82. intentkit/skills/cookiefun/__init__.py +78 -0
  83. intentkit/skills/cookiefun/base.py +41 -0
  84. intentkit/skills/cookiefun/constants.py +18 -0
  85. intentkit/skills/cookiefun/cookiefun.png +0 -0
  86. intentkit/skills/cookiefun/get_account_details.py +171 -0
  87. intentkit/skills/cookiefun/get_account_feed.py +282 -0
  88. intentkit/skills/cookiefun/get_account_smart_followers.py +181 -0
  89. intentkit/skills/cookiefun/get_sectors.py +128 -0
  90. intentkit/skills/cookiefun/schema.json +155 -0
  91. intentkit/skills/cookiefun/search_accounts.py +225 -0
  92. intentkit/skills/cryptocompare/__init__.py +130 -0
  93. intentkit/skills/cryptocompare/api.py +159 -0
  94. intentkit/skills/cryptocompare/base.py +303 -0
  95. intentkit/skills/cryptocompare/cryptocompare.png +0 -0
  96. intentkit/skills/cryptocompare/fetch_news.py +96 -0
  97. intentkit/skills/cryptocompare/fetch_price.py +99 -0
  98. intentkit/skills/cryptocompare/fetch_top_exchanges.py +113 -0
  99. intentkit/skills/cryptocompare/fetch_top_market_cap.py +109 -0
  100. intentkit/skills/cryptocompare/fetch_top_volume.py +108 -0
  101. intentkit/skills/cryptocompare/fetch_trading_signals.py +107 -0
  102. intentkit/skills/cryptocompare/schema.json +168 -0
  103. intentkit/skills/cryptopanic/__init__.py +108 -0
  104. intentkit/skills/cryptopanic/base.py +51 -0
  105. intentkit/skills/cryptopanic/cryptopanic.png +0 -0
  106. intentkit/skills/cryptopanic/fetch_crypto_news.py +153 -0
  107. intentkit/skills/cryptopanic/fetch_crypto_sentiment.py +136 -0
  108. intentkit/skills/cryptopanic/schema.json +103 -0
  109. intentkit/skills/dapplooker/README.md +92 -0
  110. intentkit/skills/dapplooker/__init__.py +83 -0
  111. intentkit/skills/dapplooker/base.py +26 -0
  112. intentkit/skills/dapplooker/dapplooker.jpg +0 -0
  113. intentkit/skills/dapplooker/dapplooker_token_data.py +476 -0
  114. intentkit/skills/dapplooker/schema.json +91 -0
  115. intentkit/skills/defillama/__init__.py +323 -0
  116. intentkit/skills/defillama/api.py +315 -0
  117. intentkit/skills/defillama/base.py +135 -0
  118. intentkit/skills/defillama/coins/__init__.py +0 -0
  119. intentkit/skills/defillama/coins/fetch_batch_historical_prices.py +116 -0
  120. intentkit/skills/defillama/coins/fetch_block.py +98 -0
  121. intentkit/skills/defillama/coins/fetch_current_prices.py +105 -0
  122. intentkit/skills/defillama/coins/fetch_first_price.py +100 -0
  123. intentkit/skills/defillama/coins/fetch_historical_prices.py +110 -0
  124. intentkit/skills/defillama/coins/fetch_price_chart.py +109 -0
  125. intentkit/skills/defillama/coins/fetch_price_percentage.py +93 -0
  126. intentkit/skills/defillama/config/__init__.py +0 -0
  127. intentkit/skills/defillama/config/chains.py +433 -0
  128. intentkit/skills/defillama/defillama.jpeg +0 -0
  129. intentkit/skills/defillama/fees/__init__.py +0 -0
  130. intentkit/skills/defillama/fees/fetch_fees_overview.py +130 -0
  131. intentkit/skills/defillama/schema.json +383 -0
  132. intentkit/skills/defillama/stablecoins/__init__.py +0 -0
  133. intentkit/skills/defillama/stablecoins/fetch_stablecoin_chains.py +100 -0
  134. intentkit/skills/defillama/stablecoins/fetch_stablecoin_charts.py +129 -0
  135. intentkit/skills/defillama/stablecoins/fetch_stablecoin_prices.py +83 -0
  136. intentkit/skills/defillama/stablecoins/fetch_stablecoins.py +126 -0
  137. intentkit/skills/defillama/tests/__init__.py +0 -0
  138. intentkit/skills/defillama/tests/api_integration.test.py +192 -0
  139. intentkit/skills/defillama/tests/api_unit.test.py +583 -0
  140. intentkit/skills/defillama/tvl/__init__.py +0 -0
  141. intentkit/skills/defillama/tvl/fetch_chain_historical_tvl.py +106 -0
  142. intentkit/skills/defillama/tvl/fetch_chains.py +107 -0
  143. intentkit/skills/defillama/tvl/fetch_historical_tvl.py +91 -0
  144. intentkit/skills/defillama/tvl/fetch_protocol.py +207 -0
  145. intentkit/skills/defillama/tvl/fetch_protocol_current_tvl.py +93 -0
  146. intentkit/skills/defillama/tvl/fetch_protocols.py +196 -0
  147. intentkit/skills/defillama/volumes/__init__.py +0 -0
  148. intentkit/skills/defillama/volumes/fetch_dex_overview.py +157 -0
  149. intentkit/skills/defillama/volumes/fetch_dex_summary.py +123 -0
  150. intentkit/skills/defillama/volumes/fetch_options_overview.py +131 -0
  151. intentkit/skills/defillama/yields/__init__.py +0 -0
  152. intentkit/skills/defillama/yields/fetch_pool_chart.py +100 -0
  153. intentkit/skills/defillama/yields/fetch_pools.py +126 -0
  154. intentkit/skills/dexscreener/__init__.py +93 -0
  155. intentkit/skills/dexscreener/base.py +133 -0
  156. intentkit/skills/dexscreener/dexscreener.png +0 -0
  157. intentkit/skills/dexscreener/model/__init__.py +0 -0
  158. intentkit/skills/dexscreener/model/search_token_response.py +82 -0
  159. intentkit/skills/dexscreener/schema.json +48 -0
  160. intentkit/skills/dexscreener/search_token.py +321 -0
  161. intentkit/skills/dune_analytics/__init__.py +103 -0
  162. intentkit/skills/dune_analytics/base.py +46 -0
  163. intentkit/skills/dune_analytics/dune.png +0 -0
  164. intentkit/skills/dune_analytics/fetch_kol_buys.py +128 -0
  165. intentkit/skills/dune_analytics/fetch_nation_metrics.py +237 -0
  166. intentkit/skills/dune_analytics/schema.json +99 -0
  167. intentkit/skills/elfa/README.md +100 -0
  168. intentkit/skills/elfa/__init__.py +123 -0
  169. intentkit/skills/elfa/base.py +28 -0
  170. intentkit/skills/elfa/elfa.jpg +0 -0
  171. intentkit/skills/elfa/mention.py +504 -0
  172. intentkit/skills/elfa/schema.json +153 -0
  173. intentkit/skills/elfa/stats.py +118 -0
  174. intentkit/skills/elfa/tokens.py +126 -0
  175. intentkit/skills/enso/README.md +75 -0
  176. intentkit/skills/enso/__init__.py +114 -0
  177. intentkit/skills/enso/abi/__init__.py +0 -0
  178. intentkit/skills/enso/abi/approval.py +279 -0
  179. intentkit/skills/enso/abi/erc20.py +14 -0
  180. intentkit/skills/enso/abi/route.py +129 -0
  181. intentkit/skills/enso/base.py +44 -0
  182. intentkit/skills/enso/best_yield.py +286 -0
  183. intentkit/skills/enso/enso.jpg +0 -0
  184. intentkit/skills/enso/networks.py +105 -0
  185. intentkit/skills/enso/prices.py +93 -0
  186. intentkit/skills/enso/route.py +300 -0
  187. intentkit/skills/enso/schema.json +212 -0
  188. intentkit/skills/enso/tokens.py +223 -0
  189. intentkit/skills/enso/wallet.py +381 -0
  190. intentkit/skills/github/README.md +63 -0
  191. intentkit/skills/github/__init__.py +54 -0
  192. intentkit/skills/github/base.py +21 -0
  193. intentkit/skills/github/github.jpg +0 -0
  194. intentkit/skills/github/github_search.py +183 -0
  195. intentkit/skills/github/schema.json +59 -0
  196. intentkit/skills/heurist/__init__.py +143 -0
  197. intentkit/skills/heurist/base.py +26 -0
  198. intentkit/skills/heurist/heurist.png +0 -0
  199. intentkit/skills/heurist/image_generation_animagine_xl.py +162 -0
  200. intentkit/skills/heurist/image_generation_arthemy_comics.py +162 -0
  201. intentkit/skills/heurist/image_generation_arthemy_real.py +162 -0
  202. intentkit/skills/heurist/image_generation_braindance.py +162 -0
  203. intentkit/skills/heurist/image_generation_cyber_realistic_xl.py +162 -0
  204. intentkit/skills/heurist/image_generation_flux_1_dev.py +162 -0
  205. intentkit/skills/heurist/image_generation_sdxl.py +161 -0
  206. intentkit/skills/heurist/schema.json +196 -0
  207. intentkit/skills/lifi/README.md +294 -0
  208. intentkit/skills/lifi/__init__.py +141 -0
  209. intentkit/skills/lifi/base.py +21 -0
  210. intentkit/skills/lifi/lifi.png +0 -0
  211. intentkit/skills/lifi/schema.json +89 -0
  212. intentkit/skills/lifi/token_execute.py +472 -0
  213. intentkit/skills/lifi/token_quote.py +190 -0
  214. intentkit/skills/lifi/utils.py +656 -0
  215. intentkit/skills/moralis/README.md +490 -0
  216. intentkit/skills/moralis/__init__.py +110 -0
  217. intentkit/skills/moralis/api.py +281 -0
  218. intentkit/skills/moralis/base.py +55 -0
  219. intentkit/skills/moralis/fetch_chain_portfolio.py +191 -0
  220. intentkit/skills/moralis/fetch_nft_portfolio.py +284 -0
  221. intentkit/skills/moralis/fetch_solana_portfolio.py +331 -0
  222. intentkit/skills/moralis/fetch_wallet_portfolio.py +301 -0
  223. intentkit/skills/moralis/moralis.png +0 -0
  224. intentkit/skills/moralis/schema.json +156 -0
  225. intentkit/skills/moralis/tests/__init__.py +0 -0
  226. intentkit/skills/moralis/tests/test_wallet.py +511 -0
  227. intentkit/skills/nation/__init__.py +62 -0
  228. intentkit/skills/nation/base.py +31 -0
  229. intentkit/skills/nation/nation.png +0 -0
  230. intentkit/skills/nation/nft_check.py +106 -0
  231. intentkit/skills/nation/schema.json +58 -0
  232. intentkit/skills/openai/__init__.py +107 -0
  233. intentkit/skills/openai/base.py +32 -0
  234. intentkit/skills/openai/dalle_image_generation.py +128 -0
  235. intentkit/skills/openai/gpt_image_generation.py +152 -0
  236. intentkit/skills/openai/gpt_image_to_image.py +186 -0
  237. intentkit/skills/openai/image_to_text.py +126 -0
  238. intentkit/skills/openai/openai.png +0 -0
  239. intentkit/skills/openai/schema.json +139 -0
  240. intentkit/skills/portfolio/README.md +55 -0
  241. intentkit/skills/portfolio/__init__.py +151 -0
  242. intentkit/skills/portfolio/base.py +107 -0
  243. intentkit/skills/portfolio/constants.py +9 -0
  244. intentkit/skills/portfolio/moralis.png +0 -0
  245. intentkit/skills/portfolio/schema.json +237 -0
  246. intentkit/skills/portfolio/token_balances.py +155 -0
  247. intentkit/skills/portfolio/wallet_approvals.py +102 -0
  248. intentkit/skills/portfolio/wallet_defi_positions.py +80 -0
  249. intentkit/skills/portfolio/wallet_history.py +155 -0
  250. intentkit/skills/portfolio/wallet_net_worth.py +112 -0
  251. intentkit/skills/portfolio/wallet_nfts.py +139 -0
  252. intentkit/skills/portfolio/wallet_profitability.py +101 -0
  253. intentkit/skills/portfolio/wallet_profitability_summary.py +91 -0
  254. intentkit/skills/portfolio/wallet_stats.py +79 -0
  255. intentkit/skills/portfolio/wallet_swaps.py +147 -0
  256. intentkit/skills/skills.toml +103 -0
  257. intentkit/skills/slack/__init__.py +98 -0
  258. intentkit/skills/slack/base.py +55 -0
  259. intentkit/skills/slack/get_channel.py +109 -0
  260. intentkit/skills/slack/get_message.py +136 -0
  261. intentkit/skills/slack/schedule_message.py +92 -0
  262. intentkit/skills/slack/schema.json +135 -0
  263. intentkit/skills/slack/send_message.py +81 -0
  264. intentkit/skills/slack/slack.jpg +0 -0
  265. intentkit/skills/system/__init__.py +90 -0
  266. intentkit/skills/system/base.py +22 -0
  267. intentkit/skills/system/read_agent_api_key.py +87 -0
  268. intentkit/skills/system/regenerate_agent_api_key.py +77 -0
  269. intentkit/skills/system/schema.json +53 -0
  270. intentkit/skills/system/system.svg +76 -0
  271. intentkit/skills/tavily/README.md +86 -0
  272. intentkit/skills/tavily/__init__.py +91 -0
  273. intentkit/skills/tavily/base.py +27 -0
  274. intentkit/skills/tavily/schema.json +119 -0
  275. intentkit/skills/tavily/tavily.jpg +0 -0
  276. intentkit/skills/tavily/tavily_extract.py +147 -0
  277. intentkit/skills/tavily/tavily_search.py +139 -0
  278. intentkit/skills/token/README.md +89 -0
  279. intentkit/skills/token/__init__.py +107 -0
  280. intentkit/skills/token/base.py +154 -0
  281. intentkit/skills/token/constants.py +9 -0
  282. intentkit/skills/token/erc20_transfers.py +145 -0
  283. intentkit/skills/token/moralis.png +0 -0
  284. intentkit/skills/token/schema.json +141 -0
  285. intentkit/skills/token/token_analytics.py +81 -0
  286. intentkit/skills/token/token_price.py +132 -0
  287. intentkit/skills/token/token_search.py +121 -0
  288. intentkit/skills/twitter/__init__.py +146 -0
  289. intentkit/skills/twitter/base.py +68 -0
  290. intentkit/skills/twitter/follow_user.py +69 -0
  291. intentkit/skills/twitter/get_mentions.py +124 -0
  292. intentkit/skills/twitter/get_timeline.py +111 -0
  293. intentkit/skills/twitter/get_user_by_username.py +84 -0
  294. intentkit/skills/twitter/get_user_tweets.py +123 -0
  295. intentkit/skills/twitter/like_tweet.py +65 -0
  296. intentkit/skills/twitter/post_tweet.py +90 -0
  297. intentkit/skills/twitter/reply_tweet.py +98 -0
  298. intentkit/skills/twitter/retweet.py +76 -0
  299. intentkit/skills/twitter/schema.json +258 -0
  300. intentkit/skills/twitter/search_tweets.py +115 -0
  301. intentkit/skills/twitter/twitter.png +0 -0
  302. intentkit/skills/unrealspeech/__init__.py +55 -0
  303. intentkit/skills/unrealspeech/base.py +21 -0
  304. intentkit/skills/unrealspeech/schema.json +100 -0
  305. intentkit/skills/unrealspeech/text_to_speech.py +177 -0
  306. intentkit/skills/unrealspeech/unrealspeech.jpg +0 -0
  307. intentkit/skills/venice_audio/__init__.py +106 -0
  308. intentkit/skills/venice_audio/base.py +119 -0
  309. intentkit/skills/venice_audio/input.py +41 -0
  310. intentkit/skills/venice_audio/schema.json +152 -0
  311. intentkit/skills/venice_audio/venice_audio.py +240 -0
  312. intentkit/skills/venice_audio/venice_logo.jpg +0 -0
  313. intentkit/skills/venice_image/README.md +119 -0
  314. intentkit/skills/venice_image/__init__.py +154 -0
  315. intentkit/skills/venice_image/api.py +138 -0
  316. intentkit/skills/venice_image/base.py +188 -0
  317. intentkit/skills/venice_image/config.py +35 -0
  318. intentkit/skills/venice_image/image_enhance/README.md +119 -0
  319. intentkit/skills/venice_image/image_enhance/__init__.py +0 -0
  320. intentkit/skills/venice_image/image_enhance/image_enhance.py +80 -0
  321. intentkit/skills/venice_image/image_enhance/image_enhance_base.py +23 -0
  322. intentkit/skills/venice_image/image_enhance/image_enhance_input.py +40 -0
  323. intentkit/skills/venice_image/image_generation/README.md +144 -0
  324. intentkit/skills/venice_image/image_generation/__init__.py +0 -0
  325. intentkit/skills/venice_image/image_generation/image_generation_base.py +117 -0
  326. intentkit/skills/venice_image/image_generation/image_generation_fluently_xl.py +26 -0
  327. intentkit/skills/venice_image/image_generation/image_generation_flux_dev.py +27 -0
  328. intentkit/skills/venice_image/image_generation/image_generation_flux_dev_uncensored.py +26 -0
  329. intentkit/skills/venice_image/image_generation/image_generation_input.py +158 -0
  330. intentkit/skills/venice_image/image_generation/image_generation_lustify_sdxl.py +26 -0
  331. intentkit/skills/venice_image/image_generation/image_generation_pony_realism.py +26 -0
  332. intentkit/skills/venice_image/image_generation/image_generation_stable_diffusion_3_5.py +28 -0
  333. intentkit/skills/venice_image/image_generation/image_generation_venice_sd35.py +28 -0
  334. intentkit/skills/venice_image/image_upscale/README.md +111 -0
  335. intentkit/skills/venice_image/image_upscale/__init__.py +0 -0
  336. intentkit/skills/venice_image/image_upscale/image_upscale.py +90 -0
  337. intentkit/skills/venice_image/image_upscale/image_upscale_base.py +23 -0
  338. intentkit/skills/venice_image/image_upscale/image_upscale_input.py +22 -0
  339. intentkit/skills/venice_image/image_vision/README.md +112 -0
  340. intentkit/skills/venice_image/image_vision/__init__.py +0 -0
  341. intentkit/skills/venice_image/image_vision/image_vision.py +100 -0
  342. intentkit/skills/venice_image/image_vision/image_vision_base.py +17 -0
  343. intentkit/skills/venice_image/image_vision/image_vision_input.py +9 -0
  344. intentkit/skills/venice_image/schema.json +267 -0
  345. intentkit/skills/venice_image/utils.py +78 -0
  346. intentkit/skills/venice_image/venice_image.jpg +0 -0
  347. intentkit/skills/web_scraper/README.md +82 -0
  348. intentkit/skills/web_scraper/__init__.py +92 -0
  349. intentkit/skills/web_scraper/base.py +21 -0
  350. intentkit/skills/web_scraper/langchain.png +0 -0
  351. intentkit/skills/web_scraper/schema.json +115 -0
  352. intentkit/skills/web_scraper/scrape_and_index.py +327 -0
  353. intentkit/utils/__init__.py +1 -0
  354. intentkit/utils/chain.py +436 -0
  355. intentkit/utils/error.py +134 -0
  356. intentkit/utils/logging.py +70 -0
  357. intentkit/utils/middleware.py +61 -0
  358. intentkit/utils/random.py +16 -0
  359. intentkit/utils/s3.py +267 -0
  360. intentkit/utils/slack_alert.py +79 -0
  361. intentkit/utils/tx.py +37 -0
  362. {intentkit-0.5.0.dist-info → intentkit-0.5.2.dist-info}/METADATA +1 -1
  363. intentkit-0.5.2.dist-info/RECORD +365 -0
  364. intentkit-0.5.0.dist-info/RECORD +0 -4
  365. {intentkit-0.5.0.dist-info → intentkit-0.5.2.dist-info}/WHEEL +0 -0
  366. {intentkit-0.5.0.dist-info → intentkit-0.5.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,1018 @@
1
+ """AI Agent Management Module.
2
+
3
+ This module provides functionality for initializing and executing AI agents. It handles:
4
+ - Agent initialization with LangChain
5
+ - Tool and skill management
6
+ - Agent execution and response handling
7
+ - Memory management with PostgreSQL
8
+ - Integration with CDP and Twitter
9
+
10
+ The module uses a global cache to store initialized agents for better performance.
11
+ """
12
+
13
+ import importlib
14
+ import logging
15
+ import re
16
+ import textwrap
17
+ import time
18
+ import traceback
19
+ from datetime import datetime
20
+ from typing import Optional
21
+
22
+ import sqlalchemy
23
+ from epyxid import XID
24
+ from fastapi import HTTPException
25
+ from langchain_core.messages import (
26
+ BaseMessage,
27
+ HumanMessage,
28
+ )
29
+ from langchain_core.prompts import ChatPromptTemplate
30
+ from langchain_core.runnables import RunnableConfig
31
+ from langchain_core.tools import BaseTool
32
+ from langgraph.errors import GraphRecursionError
33
+ from langgraph.graph.state import CompiledStateGraph
34
+ from langgraph.prebuilt import create_react_agent
35
+ from sqlalchemy import func, update
36
+ from sqlalchemy.exc import SQLAlchemyError
37
+
38
+ from intentkit.abstracts.graph import AgentError, AgentState
39
+ from intentkit.config.config import config
40
+ from intentkit.core.credit import expense_message, expense_skill
41
+ from intentkit.core.node import PreModelNode, post_model_node
42
+ from intentkit.core.prompt import agent_prompt
43
+ from intentkit.core.skill import skill_store
44
+ from intentkit.models.agent import Agent, AgentTable
45
+ from intentkit.models.agent_data import AgentData, AgentQuota
46
+ from intentkit.models.app_setting import AppSetting
47
+ from intentkit.models.chat import (
48
+ AuthorType,
49
+ ChatMessage,
50
+ ChatMessageCreate,
51
+ ChatMessageSkillCall,
52
+ )
53
+ from intentkit.models.credit import CreditAccount, OwnerType
54
+ from intentkit.models.db import get_langgraph_checkpointer, get_session
55
+ from intentkit.models.llm import LLMModelInfo, LLMProvider
56
+ from intentkit.models.skill import AgentSkillData, Skill, ThreadSkillData
57
+ from intentkit.models.user import User
58
+ from intentkit.utils.error import IntentKitAPIError
59
+
60
+ logger = logging.getLogger(__name__)
61
+
62
+
63
+ async def explain_prompt(message: str) -> str:
64
+ """
65
+ Process message to replace @skill:*:* patterns with (call skill xxxxx) format.
66
+
67
+ Args:
68
+ message (str): The input message to process
69
+
70
+ Returns:
71
+ str: The processed message with @skill patterns replaced
72
+ """
73
+ # Pattern to match @skill:category:config_name with word boundaries
74
+ pattern = r"\b@skill:([^:]+):([^\s]+)\b"
75
+
76
+ async def replace_skill_pattern(match):
77
+ category = match.group(1)
78
+ config_name = match.group(2)
79
+
80
+ # Get skill by category and config_name
81
+ skill = await Skill.get_by_config_name(category, config_name)
82
+
83
+ if skill:
84
+ return f"(call skill {skill.name})"
85
+ else:
86
+ # If skill not found, keep original pattern
87
+ return match.group(0)
88
+
89
+ # Find all matches
90
+ matches = list(re.finditer(pattern, message))
91
+
92
+ # Process matches in reverse order to maintain string positions
93
+ result = message
94
+ for match in reversed(matches):
95
+ replacement = await replace_skill_pattern(match)
96
+ result = result[: match.start()] + replacement + result[match.end() :]
97
+
98
+ return result
99
+
100
+
101
+ # Global variable to cache all agent executors
102
+ _agents: dict[str, CompiledStateGraph] = {}
103
+ _private_agents: dict[str, CompiledStateGraph] = {}
104
+
105
+ # Global dictionaries to cache agent update times
106
+ _agents_updated: dict[str, datetime] = {}
107
+ _private_agents_updated: dict[str, datetime] = {}
108
+
109
+
110
+ async def create_agent(
111
+ agent: Agent, is_private: bool = False, has_search: bool = False
112
+ ) -> CompiledStateGraph:
113
+ """Create an AI agent with specified configuration and tools.
114
+
115
+ This function:
116
+ 1. Initializes LLM with specified model
117
+ 2. Loads and configures requested tools
118
+ 3. Sets up PostgreSQL-based memory
119
+ 4. Creates and returns the agent
120
+
121
+ Args:
122
+ agent (Agent): Agent configuration object
123
+ is_private (bool, optional): Flag indicating whether the agent is private. Defaults to False.
124
+ has_search (bool, optional): Flag indicating whether to include search tools. Defaults to False.
125
+
126
+ Returns:
127
+ CompiledStateGraph: Initialized LangChain agent
128
+ """
129
+ agent_data: Optional[AgentData] = await AgentData.get(agent.id)
130
+
131
+ # ==== Initialize LLM using the LLM abstraction.
132
+ from intentkit.models.llm import create_llm_model
133
+
134
+ # Create the LLM model instance
135
+ llm_model = await create_llm_model(
136
+ model_name=agent.model,
137
+ temperature=agent.temperature,
138
+ frequency_penalty=agent.frequency_penalty,
139
+ presence_penalty=agent.presence_penalty,
140
+ )
141
+
142
+ # Get the LLM instance
143
+ llm = await llm_model.create_instance(config)
144
+
145
+ # Get the token limit from the model info
146
+ input_token_limit = min(config.input_token_limit, llm_model.info.context_length)
147
+
148
+ # ==== Store buffered conversation history in memory.
149
+ memory = get_langgraph_checkpointer()
150
+
151
+ # ==== Load skills
152
+ tools: list[BaseTool | dict] = []
153
+
154
+ if agent.skills:
155
+ for k, v in agent.skills.items():
156
+ if not v.get("enabled", False):
157
+ continue
158
+ try:
159
+ skill_module = importlib.import_module(f"intentkit.skills.{k}")
160
+ if hasattr(skill_module, "get_skills"):
161
+ skill_tools = await skill_module.get_skills(
162
+ v, is_private, skill_store, agent_id=agent.id
163
+ )
164
+ if skill_tools and len(skill_tools) > 0:
165
+ tools.extend(skill_tools)
166
+ else:
167
+ logger.error(f"Skill {k} does not have get_skills function")
168
+ except ImportError as e:
169
+ logger.error(f"Could not import skill module: {k} ({e})")
170
+
171
+ # filter the duplicate tools
172
+ tools = list({tool.name: tool for tool in tools}.values())
173
+
174
+ # Add search tools if requested
175
+ if (
176
+ has_search
177
+ and llm_model.info.provider == LLMProvider.OPENAI
178
+ and llm_model.info.supports_search
179
+ ):
180
+ tools.append({"type": "web_search_preview"})
181
+
182
+ # finally, set up the system prompt
183
+ prompt = agent_prompt(agent, agent_data)
184
+ # Escape curly braces in the prompt
185
+ escaped_prompt = prompt.replace("{", "{{").replace("}", "}}")
186
+ # Process message to handle @skill patterns
187
+ if config.admin_llm_skill_control:
188
+ escaped_prompt = await explain_prompt(escaped_prompt)
189
+ prompt_array = [
190
+ ("system", escaped_prompt),
191
+ ("placeholder", "{entrypoint_prompt}"),
192
+ ("placeholder", "{messages}"),
193
+ ]
194
+ if agent.prompt_append:
195
+ # Escape any curly braces in prompt_append
196
+ escaped_append = agent.prompt_append.replace("{", "{{").replace("}", "}}")
197
+ # Process message to handle @skill patterns
198
+ if config.admin_llm_skill_control:
199
+ escaped_append = await explain_prompt(escaped_append)
200
+ prompt_array.append(("system", escaped_append))
201
+
202
+ prompt_temp = ChatPromptTemplate.from_messages(prompt_array)
203
+
204
+ def formatted_prompt(
205
+ state: AgentState, config: RunnableConfig
206
+ ) -> list[BaseMessage]:
207
+ entrypoint_prompt = []
208
+ if config.get("configurable") and config["configurable"].get(
209
+ "entrypoint_prompt"
210
+ ):
211
+ entrypoint_prompt = [
212
+ ("system", config["configurable"]["entrypoint_prompt"])
213
+ ]
214
+ return prompt_temp.invoke(
215
+ {"messages": state["messages"], "entrypoint_prompt": entrypoint_prompt},
216
+ config,
217
+ )
218
+
219
+ # log final prompt and all skills
220
+ logger.debug(
221
+ f"[{agent.id}{'-private' if is_private else ''}] init prompt: {escaped_prompt}"
222
+ )
223
+ for tool in tools:
224
+ logger.info(
225
+ f"[{agent.id}{'-private' if is_private else ''}] loaded tool: {tool.name if isinstance(tool, BaseTool) else tool}"
226
+ )
227
+
228
+ # Pre model hook
229
+ pre_model_hook = PreModelNode(
230
+ model=llm,
231
+ short_term_memory_strategy=agent.short_term_memory_strategy,
232
+ max_tokens=input_token_limit // 2,
233
+ max_summary_tokens=2048, # later we can let agent to set this
234
+ )
235
+
236
+ # Create ReAct Agent using the LLM and CDP Agentkit tools.
237
+ executor = create_react_agent(
238
+ model=llm,
239
+ tools=tools,
240
+ prompt=formatted_prompt,
241
+ pre_model_hook=pre_model_hook,
242
+ post_model_hook=post_model_node if config.payment_enabled else None,
243
+ state_schema=AgentState,
244
+ checkpointer=memory,
245
+ debug=config.debug_checkpoint,
246
+ name=agent.id,
247
+ )
248
+
249
+ return executor
250
+
251
+
252
+ async def initialize_agent(aid, is_private=False):
253
+ """Initialize an AI agent with specified configuration and tools.
254
+
255
+ This function:
256
+ 1. Loads agent configuration from database
257
+ 2. Uses create_agent to build the agent
258
+ 3. Caches the agent
259
+
260
+ Args:
261
+ aid (str): Agent ID to initialize
262
+ is_private (bool, optional): Flag indicating whether the agent is private. Defaults to False.
263
+
264
+ Returns:
265
+ Agent: Initialized LangChain agent
266
+
267
+ Raises:
268
+ HTTPException: If agent not found (404) or database error (500)
269
+ """
270
+ # get the agent from the database
271
+ agent: Optional[Agent] = await Agent.get(aid)
272
+ if not agent:
273
+ raise HTTPException(status_code=404, detail="Agent not found")
274
+
275
+ # Determine if search should be enabled based on model capabilities
276
+ from intentkit.models.llm import create_llm_model
277
+
278
+ llm_model = await create_llm_model(
279
+ model_name=agent.model,
280
+ temperature=agent.temperature,
281
+ frequency_penalty=agent.frequency_penalty,
282
+ presence_penalty=agent.presence_penalty,
283
+ )
284
+ has_search = (
285
+ llm_model.info.provider == LLMProvider.OPENAI and llm_model.info.supports_search
286
+ )
287
+
288
+ # Create the agent using the new create_agent function
289
+ executor = await create_agent(agent, is_private, has_search)
290
+
291
+ # Cache the agent executor
292
+ if is_private:
293
+ _private_agents[aid] = executor
294
+ _private_agents_updated[aid] = agent.updated_at
295
+ else:
296
+ _agents[aid] = executor
297
+ _agents_updated[aid] = agent.updated_at
298
+
299
+
300
+ async def agent_executor(
301
+ agent_id: str, is_private: bool
302
+ ) -> (CompiledStateGraph, float):
303
+ start = time.perf_counter()
304
+ agent = await Agent.get(agent_id)
305
+ if not agent:
306
+ raise HTTPException(status_code=404, detail="Agent not found")
307
+ agents = _private_agents if is_private else _agents
308
+ agents_updated = _private_agents_updated if is_private else _agents_updated
309
+
310
+ # Check if agent needs reinitialization due to updates
311
+ needs_reinit = False
312
+ if agent_id in agents:
313
+ if (
314
+ agent_id not in agents_updated
315
+ or agent.updated_at != agents_updated[agent_id]
316
+ ):
317
+ needs_reinit = True
318
+ logger.info(
319
+ f"Reinitializing agent {agent_id} due to updates, private mode: {is_private}"
320
+ )
321
+
322
+ # cold start or needs reinitialization
323
+ cold_start_cost = 0.0
324
+ if (agent_id not in agents) or needs_reinit:
325
+ await initialize_agent(agent_id, is_private)
326
+ cold_start_cost = time.perf_counter() - start
327
+ return agents[agent_id], cold_start_cost
328
+
329
+
330
+ async def stream_agent(message: ChatMessageCreate):
331
+ """
332
+ Stream agent execution results as an async generator.
333
+
334
+ This function:
335
+ 1. Configures execution context with thread ID
336
+ 2. Initializes agent if not in cache
337
+ 3. Streams agent execution results
338
+ 4. Formats and times the execution steps
339
+
340
+ Args:
341
+ message (ChatMessageCreate): The chat message containing agent_id, chat_id, and message content
342
+
343
+ Yields:
344
+ ChatMessage: Individual response messages including timing information
345
+ """
346
+ start = time.perf_counter()
347
+ # make sure reply_to is set
348
+ message.reply_to = message.id
349
+
350
+ # save input message first
351
+ input = await message.save()
352
+
353
+ # agent
354
+ agent = await Agent.get(input.agent_id)
355
+
356
+ # model
357
+ model = await LLMModelInfo.get(agent.model)
358
+
359
+ payment_enabled = config.payment_enabled
360
+
361
+ # check user balance
362
+ if payment_enabled:
363
+ if not input.user_id or not agent.owner:
364
+ raise IntentKitAPIError(
365
+ 500,
366
+ "PaymentError",
367
+ "Payment is enabled but user_id or agent owner is not set",
368
+ )
369
+ if agent.fee_percentage and agent.fee_percentage > 100:
370
+ owner = await User.get(agent.owner)
371
+ if owner and agent.fee_percentage > 100 + owner.nft_count * 10:
372
+ error_message_create = ChatMessageCreate(
373
+ id=str(XID()),
374
+ agent_id=input.agent_id,
375
+ chat_id=input.chat_id,
376
+ user_id=input.user_id,
377
+ author_id=input.agent_id,
378
+ author_type=AuthorType.SYSTEM,
379
+ thread_type=input.author_type,
380
+ reply_to=input.id,
381
+ message="If you are the owner of this agent, please Update the Service Fee % to be in compliance with the Nation guidelines (Max 100% + 10% per Nation Pass NFT held)",
382
+ time_cost=time.perf_counter() - start,
383
+ )
384
+ error_message = await error_message_create.save()
385
+ yield error_message
386
+ return
387
+ # payer
388
+ payer = input.user_id
389
+ if input.author_type in [
390
+ AuthorType.TELEGRAM,
391
+ AuthorType.TWITTER,
392
+ AuthorType.API,
393
+ ]:
394
+ payer = agent.owner
395
+ # user account
396
+ user_account = await CreditAccount.get_or_create(OwnerType.USER, payer)
397
+ # quota
398
+ quota = await AgentQuota.get(message.agent_id)
399
+ # payment settings
400
+ payment_settings = await AppSetting.payment()
401
+ # agent abuse check
402
+ abuse_check = True
403
+ if (
404
+ payment_settings.agent_whitelist_enabled
405
+ and agent.id in payment_settings.agent_whitelist
406
+ ):
407
+ abuse_check = False
408
+ if abuse_check and payer != agent.owner and user_account.free_credits > 0:
409
+ if quota and quota.free_income_daily > 24000:
410
+ error_message_create = ChatMessageCreate(
411
+ id=str(XID()),
412
+ agent_id=input.agent_id,
413
+ chat_id=input.chat_id,
414
+ user_id=input.user_id,
415
+ author_id=input.agent_id,
416
+ author_type=AuthorType.SYSTEM,
417
+ thread_type=input.author_type,
418
+ reply_to=input.id,
419
+ message="This Agent has reached its free CAP income limit for today! Start using paid CAPs or wait until this limit expires in less than 24 hours.",
420
+ time_cost=time.perf_counter() - start,
421
+ )
422
+ error_message = await error_message_create.save()
423
+ yield error_message
424
+ return
425
+ # avg cost
426
+ avg_count = 1
427
+ if quota and quota.avg_action_cost > 0:
428
+ avg_count = quota.avg_action_cost
429
+ if not user_account.has_sufficient_credits(avg_count):
430
+ error_message_create = ChatMessageCreate(
431
+ id=str(XID()),
432
+ agent_id=input.agent_id,
433
+ chat_id=input.chat_id,
434
+ user_id=input.user_id,
435
+ author_id=input.agent_id,
436
+ author_type=AuthorType.SYSTEM,
437
+ thread_type=input.author_type,
438
+ reply_to=input.id,
439
+ message="Insufficient balance.",
440
+ time_cost=time.perf_counter() - start,
441
+ )
442
+ error_message = await error_message_create.save()
443
+ yield error_message
444
+ return
445
+
446
+ is_private = False
447
+ if input.user_id == agent.owner:
448
+ is_private = True
449
+
450
+ executor, cold_start_cost = await agent_executor(input.agent_id, is_private)
451
+ last = start + cold_start_cost
452
+
453
+ # Extract images from attachments
454
+ image_urls = []
455
+ if input.attachments:
456
+ image_urls = [
457
+ att["url"]
458
+ for att in input.attachments
459
+ if "type" in att and att["type"] == "image" and "url" in att
460
+ ]
461
+
462
+ # Process input message to handle @skill patterns
463
+ if config.admin_llm_skill_control:
464
+ input_message = await explain_prompt(input.message)
465
+ else:
466
+ input_message = input.message
467
+
468
+ # super mode
469
+ recursion_limit = 30
470
+ if re.search(r"\b@super\b", input_message):
471
+ recursion_limit = 300
472
+ # Remove @super from the message
473
+ input_message = re.sub(r"\b@super\b", "", input_message).strip()
474
+
475
+ # llm native search
476
+ if re.search(r"\b@search\b", input_message) or re.search(
477
+ r"\b@web\b", input_message
478
+ ):
479
+ if model.supports_search:
480
+ input_message = re.sub(
481
+ r"\b@search\b",
482
+ "(You have native search tool, you can use it to get more recent information)",
483
+ input_message,
484
+ ).strip()
485
+ input_message = re.sub(
486
+ r"\b@web\b",
487
+ "(You have native search tool, you can use it to get more recent information)",
488
+ input_message,
489
+ ).strip()
490
+ else:
491
+ input_message = re.sub(r"\b@search\b", "", input_message).strip()
492
+ input_message = re.sub(r"\b@web\b", "", input_message).strip()
493
+
494
+ # content to llm
495
+ content = [
496
+ {"type": "text", "text": input_message},
497
+ ]
498
+ # if the model doesn't natively support image parsing, add the image URLs to the message
499
+ if image_urls:
500
+ if (
501
+ agent.has_image_parser_skill(is_private=is_private)
502
+ and not model.supports_image_input
503
+ ):
504
+ input_message += f"\n\nImages:\n{'\n'.join(image_urls)}"
505
+ content = [
506
+ {"type": "text", "text": input_message},
507
+ ]
508
+ else:
509
+ # anyway, pass it directly to LLM
510
+ content.extend(
511
+ [
512
+ {"type": "image_url", "image_url": {"url": image_url}}
513
+ for image_url in image_urls
514
+ ]
515
+ )
516
+
517
+ messages = [
518
+ HumanMessage(content=content),
519
+ ]
520
+
521
+ entrypoint_prompt = None
522
+ if (
523
+ agent.twitter_entrypoint_enabled
524
+ and agent.twitter_entrypoint_prompt
525
+ and input.author_type == AuthorType.TWITTER
526
+ ):
527
+ entrypoint_prompt = agent.twitter_entrypoint_prompt
528
+ logger.debug("twitter entrypoint prompt added")
529
+ elif (
530
+ agent.telegram_entrypoint_enabled
531
+ and agent.telegram_entrypoint_prompt
532
+ and input.author_type == AuthorType.TELEGRAM
533
+ ):
534
+ entrypoint_prompt = agent.telegram_entrypoint_prompt
535
+ logger.debug("telegram entrypoint prompt added")
536
+ if entrypoint_prompt and config.admin_llm_skill_control:
537
+ entrypoint_prompt = await explain_prompt(entrypoint_prompt)
538
+
539
+ # stream config
540
+ thread_id = f"{input.agent_id}-{input.chat_id}"
541
+ stream_config = {
542
+ "configurable": {
543
+ "agent": agent,
544
+ "thread_id": thread_id,
545
+ "user_id": input.user_id,
546
+ "entrypoint": input.author_type,
547
+ "entrypoint_prompt": entrypoint_prompt,
548
+ "payer": payer if payment_enabled else None,
549
+ },
550
+ "recursion_limit": recursion_limit,
551
+ }
552
+
553
+ # run
554
+ cached_tool_step = None
555
+ try:
556
+ async for chunk in executor.astream({"messages": messages}, stream_config):
557
+ this_time = time.perf_counter()
558
+ logger.debug(f"stream chunk: {chunk}", extra={"thread_id": thread_id})
559
+ if "agent" in chunk and "messages" in chunk["agent"]:
560
+ if len(chunk["agent"]["messages"]) != 1:
561
+ logger.error(
562
+ "unexpected agent message: " + str(chunk["agent"]["messages"]),
563
+ extra={"thread_id": thread_id},
564
+ )
565
+ msg = chunk["agent"]["messages"][0]
566
+ if hasattr(msg, "tool_calls") and msg.tool_calls:
567
+ # tool calls, save for later use, if it is deleted by post_model_hook, will not be used.
568
+ cached_tool_step = msg
569
+ if hasattr(msg, "content") and msg.content:
570
+ content = msg.content
571
+ if isinstance(msg.content, list):
572
+ # in new version, content item maybe a list
573
+ content = msg.content[0]
574
+ if isinstance(content, dict):
575
+ if "text" in content:
576
+ content = content["text"]
577
+ else:
578
+ content = str(content)
579
+ logger.error(f"unexpected content type: {content}")
580
+ # agent message
581
+ chat_message_create = ChatMessageCreate(
582
+ id=str(XID()),
583
+ agent_id=input.agent_id,
584
+ chat_id=input.chat_id,
585
+ user_id=input.user_id,
586
+ author_id=input.agent_id,
587
+ author_type=AuthorType.AGENT,
588
+ model=agent.model,
589
+ thread_type=input.author_type,
590
+ reply_to=input.id,
591
+ message=content,
592
+ input_tokens=(
593
+ msg.usage_metadata.get("input_tokens", 0)
594
+ if hasattr(msg, "usage_metadata") and msg.usage_metadata
595
+ else 0
596
+ ),
597
+ output_tokens=(
598
+ msg.usage_metadata.get("output_tokens", 0)
599
+ if hasattr(msg, "usage_metadata") and msg.usage_metadata
600
+ else 0
601
+ ),
602
+ time_cost=this_time - last,
603
+ )
604
+ last = this_time
605
+ if cold_start_cost > 0:
606
+ chat_message_create.cold_start_cost = cold_start_cost
607
+ cold_start_cost = 0
608
+ # handle message and payment in one transaction
609
+ async with get_session() as session:
610
+ # payment
611
+ if payment_enabled:
612
+ amount = await model.calculate_cost(
613
+ chat_message_create.input_tokens,
614
+ chat_message_create.output_tokens,
615
+ )
616
+
617
+ # Check for web_search_call in additional_kwargs
618
+ if (
619
+ hasattr(msg, "additional_kwargs")
620
+ and msg.additional_kwargs
621
+ ):
622
+ tool_outputs = msg.additional_kwargs.get(
623
+ "tool_outputs", []
624
+ )
625
+ for tool_output in tool_outputs:
626
+ if tool_output.get("type") == "web_search_call":
627
+ logger.info(
628
+ f"[{input.agent_id}] Found web_search_call in additional_kwargs"
629
+ )
630
+ amount += 35
631
+ break
632
+ credit_event = await expense_message(
633
+ session,
634
+ payer,
635
+ chat_message_create.id,
636
+ input.id,
637
+ amount,
638
+ agent,
639
+ )
640
+ logger.info(f"[{input.agent_id}] expense message: {amount}")
641
+ chat_message_create.credit_event_id = credit_event.id
642
+ chat_message_create.credit_cost = credit_event.total_amount
643
+ chat_message = await chat_message_create.save_in_session(
644
+ session
645
+ )
646
+ await session.commit()
647
+ yield chat_message
648
+ elif "tools" in chunk and "messages" in chunk["tools"]:
649
+ if not cached_tool_step:
650
+ logger.error(
651
+ "unexpected tools message: " + str(chunk["tools"]),
652
+ extra={"thread_id": thread_id},
653
+ )
654
+ continue
655
+ skill_calls = []
656
+ have_first_call_in_cache = False # tool node emit every tool call
657
+ for msg in chunk["tools"]["messages"]:
658
+ if not hasattr(msg, "tool_call_id"):
659
+ logger.error(
660
+ "unexpected tools message: " + str(chunk["tools"]),
661
+ extra={"thread_id": thread_id},
662
+ )
663
+ continue
664
+ for call_index, call in enumerate(cached_tool_step.tool_calls):
665
+ if call["id"] == msg.tool_call_id:
666
+ if call_index == 0:
667
+ have_first_call_in_cache = True
668
+ skill_call: ChatMessageSkillCall = {
669
+ "id": msg.tool_call_id,
670
+ "name": call["name"],
671
+ "parameters": call["args"],
672
+ "success": True,
673
+ }
674
+ if msg.status == "error":
675
+ skill_call["success"] = False
676
+ skill_call["error_message"] = str(msg.content)
677
+ else:
678
+ if config.debug:
679
+ skill_call["response"] = str(msg.content)
680
+ else:
681
+ skill_call["response"] = textwrap.shorten(
682
+ str(msg.content), width=1000, placeholder="..."
683
+ )
684
+ skill_calls.append(skill_call)
685
+ break
686
+ skill_message_create = ChatMessageCreate(
687
+ id=str(XID()),
688
+ agent_id=input.agent_id,
689
+ chat_id=input.chat_id,
690
+ user_id=input.user_id,
691
+ author_id=input.agent_id,
692
+ author_type=AuthorType.SKILL,
693
+ model=agent.model,
694
+ thread_type=input.author_type,
695
+ reply_to=input.id,
696
+ message="",
697
+ skill_calls=skill_calls,
698
+ input_tokens=(
699
+ cached_tool_step.usage_metadata.get("input_tokens", 0)
700
+ if hasattr(cached_tool_step, "usage_metadata")
701
+ and cached_tool_step.usage_metadata
702
+ and have_first_call_in_cache
703
+ else 0
704
+ ),
705
+ output_tokens=(
706
+ cached_tool_step.usage_metadata.get("output_tokens", 0)
707
+ if hasattr(cached_tool_step, "usage_metadata")
708
+ and cached_tool_step.usage_metadata
709
+ and have_first_call_in_cache
710
+ else 0
711
+ ),
712
+ time_cost=this_time - last,
713
+ )
714
+ last = this_time
715
+ if cold_start_cost > 0:
716
+ skill_message_create.cold_start_cost = cold_start_cost
717
+ cold_start_cost = 0
718
+ # save message and credit in one transaction
719
+ async with get_session() as session:
720
+ if payment_enabled:
721
+ # message payment, only first call in a group has message bill
722
+ if have_first_call_in_cache:
723
+ message_amount = await model.calculate_cost(
724
+ skill_message_create.input_tokens,
725
+ skill_message_create.output_tokens,
726
+ )
727
+ message_payment_event = await expense_message(
728
+ session,
729
+ payer,
730
+ skill_message_create.id,
731
+ input.id,
732
+ message_amount,
733
+ agent,
734
+ )
735
+ skill_message_create.credit_event_id = (
736
+ message_payment_event.id
737
+ )
738
+ skill_message_create.credit_cost = (
739
+ message_payment_event.total_amount
740
+ )
741
+ # skill payment
742
+ for skill_call in skill_calls:
743
+ if not skill_call["success"]:
744
+ continue
745
+ payment_event = await expense_skill(
746
+ session,
747
+ payer,
748
+ skill_message_create.id,
749
+ input.id,
750
+ skill_call["id"],
751
+ skill_call["name"],
752
+ agent,
753
+ )
754
+ skill_call["credit_event_id"] = payment_event.id
755
+ skill_call["credit_cost"] = payment_event.total_amount
756
+ logger.info(
757
+ f"[{input.agent_id}] skill payment: {skill_call}"
758
+ )
759
+ skill_message_create.skill_calls = skill_calls
760
+ skill_message = await skill_message_create.save_in_session(session)
761
+ await session.commit()
762
+ yield skill_message
763
+ elif "pre_model_hook" in chunk:
764
+ pass
765
+ elif "post_model_hook" in chunk:
766
+ logger.debug(
767
+ f"post_model_hook: {chunk}",
768
+ extra={"thread_id": thread_id},
769
+ )
770
+ if chunk["post_model_hook"] and "error" in chunk["post_model_hook"]:
771
+ if (
772
+ chunk["post_model_hook"]["error"]
773
+ == AgentError.INSUFFICIENT_CREDITS
774
+ ):
775
+ if "messages" in chunk["post_model_hook"]:
776
+ msg = chunk["post_model_hook"]["messages"][-1]
777
+ content = msg.content
778
+ if isinstance(msg.content, list):
779
+ # in new version, content item maybe a list
780
+ content = msg.content[0]
781
+ post_model_message_create = ChatMessageCreate(
782
+ id=str(XID()),
783
+ agent_id=input.agent_id,
784
+ chat_id=input.chat_id,
785
+ user_id=input.user_id,
786
+ author_id=input.agent_id,
787
+ author_type=AuthorType.AGENT,
788
+ model=agent.model,
789
+ thread_type=input.author_type,
790
+ reply_to=input.id,
791
+ message=content,
792
+ input_tokens=0,
793
+ output_tokens=0,
794
+ time_cost=this_time - last,
795
+ )
796
+ last = this_time
797
+ if cold_start_cost > 0:
798
+ post_model_message_create.cold_start_cost = (
799
+ cold_start_cost
800
+ )
801
+ cold_start_cost = 0
802
+ post_model_message = await post_model_message_create.save()
803
+ yield post_model_message
804
+ error_message_create = ChatMessageCreate(
805
+ id=str(XID()),
806
+ agent_id=input.agent_id,
807
+ chat_id=input.chat_id,
808
+ user_id=input.user_id,
809
+ author_id=input.agent_id,
810
+ author_type=AuthorType.SYSTEM,
811
+ thread_type=input.author_type,
812
+ reply_to=input.id,
813
+ message="Insufficient balance.",
814
+ time_cost=0,
815
+ )
816
+ error_message = await error_message_create.save()
817
+ yield error_message
818
+ else:
819
+ error_traceback = traceback.format_exc()
820
+ logger.error(
821
+ f"unexpected message type: {str(chunk)}\n{error_traceback}",
822
+ extra={"thread_id": thread_id},
823
+ )
824
+ except SQLAlchemyError as e:
825
+ error_traceback = traceback.format_exc()
826
+ logger.error(
827
+ f"failed to execute agent: {str(e)}\n{error_traceback}",
828
+ extra={"thread_id": thread_id},
829
+ )
830
+ error_message_create = ChatMessageCreate(
831
+ id=str(XID()),
832
+ agent_id=input.agent_id,
833
+ chat_id=input.chat_id,
834
+ user_id=input.user_id,
835
+ author_id=input.agent_id,
836
+ author_type=AuthorType.SYSTEM,
837
+ thread_type=input.author_type,
838
+ reply_to=input.id,
839
+ message="IntentKit Internal Error",
840
+ time_cost=time.perf_counter() - start,
841
+ )
842
+ error_message = await error_message_create.save()
843
+ yield error_message
844
+ return
845
+ except GraphRecursionError as e:
846
+ error_traceback = traceback.format_exc()
847
+ logger.error(
848
+ f"reached recursion limit: {str(e)}\n{error_traceback}",
849
+ extra={"thread_id": thread_id, "agent_id": input.agent_id},
850
+ )
851
+ error_message_create = ChatMessageCreate(
852
+ id=str(XID()),
853
+ agent_id=input.agent_id,
854
+ chat_id=input.chat_id,
855
+ user_id=input.user_id,
856
+ author_id=input.agent_id,
857
+ author_type=AuthorType.SYSTEM,
858
+ thread_type=input.author_type,
859
+ reply_to=input.id,
860
+ message="Step Limit Error",
861
+ time_cost=time.perf_counter() - start,
862
+ )
863
+ error_message = await error_message_create.save()
864
+ yield error_message
865
+ return
866
+ except Exception as e:
867
+ error_traceback = traceback.format_exc()
868
+ logger.error(
869
+ f"failed to execute agent: {str(e)}\n{error_traceback}",
870
+ extra={"thread_id": thread_id, "agent_id": input.agent_id},
871
+ )
872
+ error_message_create = ChatMessageCreate(
873
+ id=str(XID()),
874
+ agent_id=input.agent_id,
875
+ chat_id=input.chat_id,
876
+ user_id=input.user_id,
877
+ author_id=input.agent_id,
878
+ author_type=AuthorType.SYSTEM,
879
+ thread_type=input.author_type,
880
+ reply_to=input.id,
881
+ message="Internal Agent Error",
882
+ time_cost=time.perf_counter() - start,
883
+ )
884
+ error_message = await error_message_create.save()
885
+ yield error_message
886
+ return
887
+
888
+
889
+ async def execute_agent(message: ChatMessageCreate) -> list[ChatMessage]:
890
+ """
891
+ Execute an agent with the given prompt and return response lines.
892
+
893
+ This function:
894
+ 1. Configures execution context with thread ID
895
+ 2. Initializes agent if not in cache
896
+ 3. Streams agent execution results
897
+ 4. Formats and times the execution steps
898
+
899
+ Args:
900
+ message (ChatMessageCreate): The chat message containing agent_id, chat_id, and message content
901
+ debug (bool): Enable debug mode, will save the skill results
902
+
903
+ Returns:
904
+ list[ChatMessage]: Formatted response lines including timing information
905
+ """
906
+ resp = []
907
+ async for chat_message in stream_agent(message):
908
+ resp.append(chat_message)
909
+ return resp
910
+
911
+
912
+ async def clean_agent_memory(
913
+ agent_id: str,
914
+ chat_id: str = "",
915
+ clean_agent: bool = False,
916
+ clean_skill: bool = False,
917
+ ) -> str:
918
+ """
919
+ Clean an agent's memory with the given prompt and return response.
920
+
921
+ This function:
922
+ 1. Cleans the agents skills data.
923
+ 2. Cleans the thread skills data.
924
+ 3. Cleans the graph checkpoint data.
925
+ 4. Cleans the graph checkpoint_writes data.
926
+ 5. Cleans the graph checkpoint_blobs data.
927
+
928
+ Args:
929
+ agent_id (str): Agent ID
930
+ chat_id (str): Thread ID for the agent memory cleanup
931
+ clean_agent (bool): Whether to clean agent's memory data
932
+ clean_skill (bool): Whether to clean skills memory data
933
+
934
+ Returns:
935
+ str: Successful response message.
936
+ """
937
+ # get the agent from the database
938
+ try:
939
+ if not clean_skill and not clean_agent:
940
+ raise HTTPException(
941
+ status_code=400,
942
+ detail="at least one of skills data or agent memory should be true.",
943
+ )
944
+
945
+ if clean_skill:
946
+ await AgentSkillData.clean_data(agent_id)
947
+ await ThreadSkillData.clean_data(agent_id, chat_id)
948
+
949
+ async with get_session() as db:
950
+ if clean_agent:
951
+ chat_id = chat_id.strip()
952
+ q_suffix = "%"
953
+ if chat_id and chat_id != "":
954
+ q_suffix = chat_id
955
+
956
+ deletion_param = {"value": agent_id + "-" + q_suffix}
957
+ await db.execute(
958
+ sqlalchemy.text(
959
+ "DELETE FROM checkpoints WHERE thread_id like :value",
960
+ ),
961
+ deletion_param,
962
+ )
963
+ await db.execute(
964
+ sqlalchemy.text(
965
+ "DELETE FROM checkpoint_writes WHERE thread_id like :value",
966
+ ),
967
+ deletion_param,
968
+ )
969
+ await db.execute(
970
+ sqlalchemy.text(
971
+ "DELETE FROM checkpoint_blobs WHERE thread_id like :value",
972
+ ),
973
+ deletion_param,
974
+ )
975
+
976
+ # update the updated_at field so that the agent instance will all reload
977
+ await db.execute(
978
+ update(AgentTable)
979
+ .where(AgentTable.id == agent_id)
980
+ .values(updated_at=func.now())
981
+ )
982
+ await db.commit()
983
+
984
+ logger.info(f"Agent [{agent_id}] data cleaned up successfully.")
985
+ return "Agent data cleaned up successfully."
986
+ except SQLAlchemyError as e:
987
+ # Handle other SQLAlchemy-related errors
988
+ logger.error(e)
989
+ raise HTTPException(status_code=500, detail=str(e))
990
+ except Exception as e:
991
+ logger.error("failed to cleanup the agent memory: " + str(e))
992
+ raise e
993
+
994
+
995
+ async def thread_stats(agent_id: str, chat_id: str) -> list[BaseMessage]:
996
+ thread_id = f"{agent_id}-{chat_id}"
997
+ stream_config = {"configurable": {"thread_id": thread_id}}
998
+ is_private = False
999
+ if chat_id.startswith("owner") or chat_id.startswith("autonomous"):
1000
+ is_private = True
1001
+ executor, _ = await agent_executor(agent_id, is_private)
1002
+ snap = await executor.aget_state(stream_config)
1003
+ if snap.values and "messages" in snap.values:
1004
+ return snap.values["messages"]
1005
+ else:
1006
+ return []
1007
+
1008
+
1009
+ async def is_payment_required(input: ChatMessageCreate, agent: Agent) -> bool:
1010
+ if not config.payment_enabled:
1011
+ return False
1012
+ payment_settings = await AppSetting.payment()
1013
+ if payment_settings.agent_whitelist_enabled:
1014
+ if agent.id not in payment_settings.agent_whitelist:
1015
+ return False
1016
+ if input.user_id and agent.owner:
1017
+ return True
1018
+ return False