camel-ai 0.2.65__py3-none-any.whl → 0.2.83a6__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 camel-ai might be problematic. Click here for more details.

Files changed (509) hide show
  1. camel/__init__.py +3 -3
  2. camel/agents/__init__.py +2 -2
  3. camel/agents/_types.py +9 -4
  4. camel/agents/_utils.py +40 -2
  5. camel/agents/base.py +2 -2
  6. camel/agents/chat_agent.py +5107 -995
  7. camel/agents/critic_agent.py +2 -2
  8. camel/agents/deductive_reasoner_agent.py +56 -56
  9. camel/agents/embodied_agent.py +2 -2
  10. camel/agents/knowledge_graph_agent.py +20 -20
  11. camel/agents/mcp_agent.py +35 -36
  12. camel/agents/multi_hop_generator_agent.py +3 -3
  13. camel/agents/programmed_agent_instruction.py +2 -2
  14. camel/agents/repo_agent.py +4 -3
  15. camel/agents/role_assignment_agent.py +2 -2
  16. camel/agents/search_agent.py +2 -2
  17. camel/agents/task_agent.py +2 -2
  18. camel/agents/tool_agents/__init__.py +2 -2
  19. camel/agents/tool_agents/base.py +2 -2
  20. camel/agents/tool_agents/hugging_face_tool_agent.py +3 -3
  21. camel/benchmarks/__init__.py +2 -2
  22. camel/benchmarks/apibank.py +5 -5
  23. camel/benchmarks/apibench.py +2 -2
  24. camel/benchmarks/base.py +2 -2
  25. camel/benchmarks/browsecomp.py +44 -33
  26. camel/benchmarks/gaia.py +17 -13
  27. camel/benchmarks/mock_website/README.md +1 -3
  28. camel/benchmarks/mock_website/mock_web.py +2 -2
  29. camel/benchmarks/mock_website/requirements.txt +1 -1
  30. camel/benchmarks/mock_website/shopping_mall/app.py +2 -2
  31. camel/benchmarks/mock_website/task.json +1 -1
  32. camel/benchmarks/nexus.py +3 -3
  33. camel/benchmarks/ragbench.py +2 -2
  34. camel/bots/__init__.py +2 -2
  35. camel/bots/discord/__init__.py +2 -2
  36. camel/bots/discord/discord_app.py +2 -2
  37. camel/bots/discord/discord_installation.py +2 -2
  38. camel/bots/discord/discord_store.py +3 -3
  39. camel/bots/slack/__init__.py +2 -2
  40. camel/bots/slack/models.py +4 -4
  41. camel/bots/slack/slack_app.py +2 -2
  42. camel/bots/telegram_bot.py +2 -2
  43. camel/configs/__init__.py +29 -2
  44. camel/configs/aihubmix_config.py +90 -0
  45. camel/configs/aiml_config.py +2 -2
  46. camel/configs/amd_config.py +70 -0
  47. camel/configs/anthropic_config.py +2 -2
  48. camel/configs/base_config.py +2 -2
  49. camel/configs/bedrock_config.py +5 -3
  50. camel/configs/cerebras_config.py +98 -0
  51. camel/configs/cohere_config.py +2 -2
  52. camel/configs/cometapi_config.py +106 -0
  53. camel/configs/crynux_config.py +2 -2
  54. camel/configs/deepseek_config.py +9 -8
  55. camel/configs/function_gemma_config.py +59 -0
  56. camel/configs/gemini_config.py +6 -4
  57. camel/configs/groq_config.py +6 -4
  58. camel/configs/internlm_config.py +6 -4
  59. camel/configs/litellm_config.py +2 -2
  60. camel/configs/lmstudio_config.py +6 -4
  61. camel/configs/minimax_config.py +95 -0
  62. camel/configs/mistral_config.py +2 -2
  63. camel/configs/modelscope_config.py +5 -3
  64. camel/configs/moonshot_config.py +2 -2
  65. camel/configs/nebius_config.py +105 -0
  66. camel/configs/netmind_config.py +2 -2
  67. camel/configs/novita_config.py +2 -2
  68. camel/configs/nvidia_config.py +2 -2
  69. camel/configs/ollama_config.py +2 -2
  70. camel/configs/openai_config.py +5 -3
  71. camel/configs/openrouter_config.py +6 -4
  72. camel/configs/ppio_config.py +2 -2
  73. camel/configs/qianfan_config.py +85 -0
  74. camel/configs/qwen_config.py +2 -2
  75. camel/configs/reka_config.py +2 -2
  76. camel/configs/samba_config.py +6 -4
  77. camel/configs/sglang_config.py +2 -2
  78. camel/configs/siliconflow_config.py +2 -2
  79. camel/configs/togetherai_config.py +2 -2
  80. camel/configs/vllm_config.py +4 -2
  81. camel/configs/watsonx_config.py +2 -2
  82. camel/configs/yi_config.py +6 -4
  83. camel/configs/zhipuai_config.py +6 -4
  84. camel/data_collectors/__init__.py +2 -2
  85. camel/data_collectors/alpaca_collector.py +18 -9
  86. camel/data_collectors/base.py +2 -2
  87. camel/data_collectors/sharegpt_collector.py +2 -2
  88. camel/datagen/__init__.py +2 -2
  89. camel/datagen/cot_datagen.py +3 -3
  90. camel/datagen/evol_instruct/__init__.py +2 -2
  91. camel/datagen/evol_instruct/evol_instruct.py +2 -2
  92. camel/datagen/evol_instruct/scorer.py +12 -12
  93. camel/datagen/evol_instruct/templates.py +16 -16
  94. camel/datagen/self_improving_cot.py +5 -5
  95. camel/datagen/self_instruct/__init__.py +2 -2
  96. camel/datagen/self_instruct/filter/__init__.py +2 -2
  97. camel/datagen/self_instruct/filter/filter_function.py +2 -2
  98. camel/datagen/self_instruct/filter/filter_registry.py +2 -2
  99. camel/datagen/self_instruct/filter/instruction_filter.py +2 -2
  100. camel/datagen/self_instruct/self_instruct.py +2 -2
  101. camel/datagen/self_instruct/templates.py +47 -47
  102. camel/datagen/source2synth/__init__.py +2 -2
  103. camel/datagen/source2synth/data_processor.py +2 -2
  104. camel/datagen/source2synth/models.py +2 -2
  105. camel/datagen/source2synth/user_data_processor_config.py +2 -2
  106. camel/datahubs/__init__.py +2 -2
  107. camel/datahubs/base.py +2 -2
  108. camel/datahubs/huggingface.py +2 -2
  109. camel/datahubs/models.py +2 -2
  110. camel/datasets/__init__.py +2 -2
  111. camel/datasets/base_generator.py +41 -12
  112. camel/datasets/few_shot_generator.py +18 -18
  113. camel/datasets/models.py +2 -2
  114. camel/datasets/self_instruct_generator.py +2 -2
  115. camel/datasets/static_dataset.py +2 -2
  116. camel/embeddings/__init__.py +2 -2
  117. camel/embeddings/azure_embedding.py +2 -2
  118. camel/embeddings/base.py +2 -2
  119. camel/embeddings/gemini_embedding.py +2 -2
  120. camel/embeddings/jina_embedding.py +2 -2
  121. camel/embeddings/mistral_embedding.py +2 -2
  122. camel/embeddings/openai_compatible_embedding.py +2 -2
  123. camel/embeddings/openai_embedding.py +2 -2
  124. camel/embeddings/sentence_transformers_embeddings.py +2 -2
  125. camel/embeddings/together_embedding.py +2 -2
  126. camel/embeddings/vlm_embedding.py +2 -2
  127. camel/environments/__init__.py +14 -2
  128. camel/environments/models.py +2 -2
  129. camel/environments/multi_step.py +2 -2
  130. camel/environments/rlcards_env.py +860 -0
  131. camel/environments/single_step.py +30 -5
  132. camel/environments/tic_tac_toe.py +3 -3
  133. camel/extractors/__init__.py +2 -2
  134. camel/extractors/base.py +2 -2
  135. camel/extractors/python_strategies.py +2 -2
  136. camel/generators.py +2 -2
  137. camel/human.py +2 -2
  138. camel/interpreters/__init__.py +4 -2
  139. camel/interpreters/base.py +2 -2
  140. camel/interpreters/docker/Dockerfile +14 -24
  141. camel/interpreters/docker_interpreter.py +5 -4
  142. camel/interpreters/e2b_interpreter.py +36 -3
  143. camel/interpreters/internal_python_interpreter.py +53 -4
  144. camel/interpreters/interpreter_error.py +2 -2
  145. camel/interpreters/ipython_interpreter.py +2 -2
  146. camel/interpreters/microsandbox_interpreter.py +395 -0
  147. camel/interpreters/subprocess_interpreter.py +2 -2
  148. camel/loaders/__init__.py +13 -4
  149. camel/loaders/apify_reader.py +2 -2
  150. camel/loaders/base_io.py +2 -2
  151. camel/loaders/base_loader.py +85 -0
  152. camel/loaders/chunkr_reader.py +11 -2
  153. camel/loaders/crawl4ai_reader.py +2 -2
  154. camel/loaders/firecrawl_reader.py +6 -6
  155. camel/loaders/jina_url_reader.py +2 -2
  156. camel/loaders/markitdown.py +2 -2
  157. camel/loaders/mineru_extractor.py +2 -2
  158. camel/loaders/mistral_reader.py +2 -2
  159. camel/loaders/scrapegraph_reader.py +2 -2
  160. camel/loaders/unstructured_io.py +2 -2
  161. camel/logger.py +5 -5
  162. camel/memories/__init__.py +2 -2
  163. camel/memories/agent_memories.py +86 -3
  164. camel/memories/base.py +36 -2
  165. camel/memories/blocks/__init__.py +2 -2
  166. camel/memories/blocks/chat_history_block.py +125 -7
  167. camel/memories/blocks/vectordb_block.py +10 -3
  168. camel/memories/context_creators/__init__.py +2 -2
  169. camel/memories/context_creators/score_based.py +109 -230
  170. camel/memories/records.py +90 -10
  171. camel/messages/__init__.py +2 -2
  172. camel/messages/base.py +178 -43
  173. camel/messages/conversion/__init__.py +2 -2
  174. camel/messages/conversion/alpaca.py +2 -2
  175. camel/messages/conversion/conversation_models.py +2 -2
  176. camel/messages/conversion/sharegpt/__init__.py +2 -2
  177. camel/messages/conversion/sharegpt/function_call_formatter.py +2 -2
  178. camel/messages/conversion/sharegpt/hermes/__init__.py +2 -2
  179. camel/messages/conversion/sharegpt/hermes/hermes_function_formatter.py +2 -2
  180. camel/messages/func_message.py +54 -17
  181. camel/models/__init__.py +18 -2
  182. camel/models/_utils.py +3 -3
  183. camel/models/aihubmix_model.py +83 -0
  184. camel/models/aiml_model.py +11 -18
  185. camel/models/amd_model.py +101 -0
  186. camel/models/anthropic_model.py +127 -20
  187. camel/models/aws_bedrock_model.py +12 -35
  188. camel/models/azure_openai_model.py +214 -115
  189. camel/models/base_audio_model.py +5 -3
  190. camel/models/base_model.py +378 -31
  191. camel/models/cerebras_model.py +83 -0
  192. camel/models/cohere_model.py +18 -49
  193. camel/models/cometapi_model.py +83 -0
  194. camel/models/crynux_model.py +11 -18
  195. camel/models/deepseek_model.py +20 -84
  196. camel/models/fish_audio_model.py +8 -2
  197. camel/models/function_gemma_model.py +889 -0
  198. camel/models/gemini_model.py +391 -52
  199. camel/models/groq_model.py +11 -19
  200. camel/models/internlm_model.py +11 -18
  201. camel/models/litellm_model.py +57 -49
  202. camel/models/lmstudio_model.py +17 -20
  203. camel/models/minimax_model.py +83 -0
  204. camel/models/mistral_model.py +20 -47
  205. camel/models/model_factory.py +39 -3
  206. camel/models/model_manager.py +26 -8
  207. camel/models/modelscope_model.py +13 -193
  208. camel/models/moonshot_model.py +183 -21
  209. camel/models/nebius_model.py +83 -0
  210. camel/models/nemotron_model.py +19 -9
  211. camel/models/netmind_model.py +11 -18
  212. camel/models/novita_model.py +11 -18
  213. camel/models/nvidia_model.py +11 -18
  214. camel/models/ollama_model.py +14 -21
  215. camel/models/openai_audio_models.py +2 -2
  216. camel/models/openai_compatible_model.py +190 -71
  217. camel/models/openai_model.py +192 -86
  218. camel/models/openrouter_model.py +11 -19
  219. camel/models/ppio_model.py +11 -18
  220. camel/models/qianfan_model.py +89 -0
  221. camel/models/qwen_model.py +13 -193
  222. camel/models/reka_model.py +23 -49
  223. camel/models/reward/__init__.py +2 -2
  224. camel/models/reward/base_reward_model.py +2 -2
  225. camel/models/reward/evaluator.py +2 -2
  226. camel/models/reward/nemotron_model.py +2 -2
  227. camel/models/reward/skywork_model.py +2 -2
  228. camel/models/samba_model.py +50 -75
  229. camel/models/sglang_model.py +90 -68
  230. camel/models/siliconflow_model.py +12 -35
  231. camel/models/stub_model.py +10 -7
  232. camel/models/togetherai_model.py +11 -18
  233. camel/models/vllm_model.py +10 -18
  234. camel/models/volcano_model.py +158 -19
  235. camel/models/watsonx_model.py +9 -47
  236. camel/models/yi_model.py +11 -18
  237. camel/models/zhipuai_model.py +70 -18
  238. camel/parsers/__init__.py +18 -0
  239. camel/parsers/mcp_tool_call_parser.py +176 -0
  240. camel/personas/__init__.py +2 -2
  241. camel/personas/persona.py +2 -2
  242. camel/personas/persona_hub.py +2 -2
  243. camel/prompts/__init__.py +2 -2
  244. camel/prompts/ai_society.py +2 -2
  245. camel/prompts/base.py +2 -2
  246. camel/prompts/code.py +2 -2
  247. camel/prompts/evaluation.py +2 -2
  248. camel/prompts/generate_text_embedding_data.py +2 -2
  249. camel/prompts/image_craft.py +2 -2
  250. camel/prompts/misalignment.py +2 -2
  251. camel/prompts/multi_condition_image_craft.py +2 -2
  252. camel/prompts/object_recognition.py +2 -2
  253. camel/prompts/persona_hub.py +3 -3
  254. camel/prompts/prompt_templates.py +2 -2
  255. camel/prompts/role_description_prompt_template.py +2 -2
  256. camel/prompts/solution_extraction.py +8 -8
  257. camel/prompts/task_prompt_template.py +2 -2
  258. camel/prompts/translation.py +2 -2
  259. camel/prompts/video_description_prompt.py +3 -3
  260. camel/responses/__init__.py +2 -2
  261. camel/responses/agent_responses.py +2 -2
  262. camel/retrievers/__init__.py +2 -2
  263. camel/retrievers/auto_retriever.py +3 -2
  264. camel/retrievers/base.py +2 -2
  265. camel/retrievers/bm25_retriever.py +2 -2
  266. camel/retrievers/cohere_rerank_retriever.py +2 -2
  267. camel/retrievers/hybrid_retrival.py +2 -2
  268. camel/retrievers/vector_retriever.py +2 -2
  269. camel/runtimes/Dockerfile.multi-toolkit +90 -0
  270. camel/runtimes/__init__.py +2 -2
  271. camel/runtimes/api.py +79 -23
  272. camel/runtimes/base.py +2 -2
  273. camel/runtimes/configs.py +13 -13
  274. camel/runtimes/daytona_runtime.py +17 -18
  275. camel/runtimes/docker_runtime.py +12 -12
  276. camel/runtimes/llm_guard_runtime.py +26 -26
  277. camel/runtimes/remote_http_runtime.py +11 -11
  278. camel/runtimes/ubuntu_docker_runtime.py +2 -2
  279. camel/runtimes/utils/__init__.py +2 -2
  280. camel/runtimes/utils/function_risk_toolkit.py +2 -2
  281. camel/runtimes/utils/ignore_risk_toolkit.py +2 -2
  282. camel/schemas/__init__.py +2 -2
  283. camel/schemas/base.py +2 -2
  284. camel/schemas/openai_converter.py +3 -3
  285. camel/schemas/outlines_converter.py +2 -2
  286. camel/services/agent_openapi_server.py +380 -0
  287. camel/societies/__init__.py +4 -2
  288. camel/societies/babyagi_playing.py +2 -2
  289. camel/societies/role_playing.py +201 -80
  290. camel/societies/workforce/__init__.py +10 -3
  291. camel/societies/workforce/base.py +2 -2
  292. camel/societies/workforce/events.py +145 -0
  293. camel/societies/workforce/prompts.py +259 -33
  294. camel/societies/workforce/role_playing_worker.py +88 -31
  295. camel/societies/workforce/single_agent_worker.py +638 -40
  296. camel/societies/workforce/structured_output_handler.py +512 -0
  297. camel/societies/workforce/task_channel.py +182 -38
  298. camel/societies/workforce/utils.py +780 -65
  299. camel/societies/workforce/worker.py +92 -26
  300. camel/societies/workforce/workflow_memory_manager.py +1746 -0
  301. camel/societies/workforce/workforce.py +5354 -372
  302. camel/societies/workforce/workforce_callback.py +103 -0
  303. camel/societies/workforce/workforce_logger.py +647 -0
  304. camel/societies/workforce/workforce_metrics.py +33 -0
  305. camel/storages/__init__.py +6 -2
  306. camel/storages/graph_storages/__init__.py +2 -2
  307. camel/storages/graph_storages/base.py +2 -2
  308. camel/storages/graph_storages/graph_element.py +2 -2
  309. camel/storages/graph_storages/nebula_graph.py +4 -4
  310. camel/storages/graph_storages/neo4j_graph.py +7 -7
  311. camel/storages/key_value_storages/__init__.py +2 -2
  312. camel/storages/key_value_storages/base.py +2 -2
  313. camel/storages/key_value_storages/in_memory.py +2 -2
  314. camel/storages/key_value_storages/json.py +17 -4
  315. camel/storages/key_value_storages/mem0_cloud.py +50 -49
  316. camel/storages/key_value_storages/redis.py +2 -2
  317. camel/storages/object_storages/__init__.py +2 -2
  318. camel/storages/object_storages/amazon_s3.py +2 -2
  319. camel/storages/object_storages/azure_blob.py +2 -2
  320. camel/storages/object_storages/base.py +2 -2
  321. camel/storages/object_storages/google_cloud.py +3 -3
  322. camel/storages/vectordb_storages/__init__.py +8 -2
  323. camel/storages/vectordb_storages/base.py +2 -2
  324. camel/storages/vectordb_storages/chroma.py +731 -0
  325. camel/storages/vectordb_storages/faiss.py +2 -2
  326. camel/storages/vectordb_storages/milvus.py +2 -2
  327. camel/storages/vectordb_storages/oceanbase.py +15 -15
  328. camel/storages/vectordb_storages/pgvector.py +349 -0
  329. camel/storages/vectordb_storages/qdrant.py +6 -6
  330. camel/storages/vectordb_storages/surreal.py +372 -0
  331. camel/storages/vectordb_storages/tidb.py +11 -8
  332. camel/storages/vectordb_storages/weaviate.py +2 -2
  333. camel/tasks/__init__.py +2 -2
  334. camel/tasks/task.py +348 -26
  335. camel/tasks/task_prompt.py +3 -3
  336. camel/terminators/__init__.py +2 -2
  337. camel/terminators/base.py +2 -2
  338. camel/terminators/response_terminator.py +2 -2
  339. camel/terminators/token_limit_terminator.py +2 -2
  340. camel/toolkits/__init__.py +57 -10
  341. camel/toolkits/aci_toolkit.py +66 -21
  342. camel/toolkits/arxiv_toolkit.py +8 -8
  343. camel/toolkits/ask_news_toolkit.py +2 -2
  344. camel/toolkits/async_browser_toolkit.py +4 -4
  345. camel/toolkits/audio_analysis_toolkit.py +3 -3
  346. camel/toolkits/base.py +106 -6
  347. camel/toolkits/bohrium_toolkit.py +2 -2
  348. camel/toolkits/browser_toolkit.py +34 -21
  349. camel/toolkits/browser_toolkit_commons.py +4 -4
  350. camel/toolkits/code_execution.py +31 -4
  351. camel/toolkits/context_summarizer_toolkit.py +684 -0
  352. camel/toolkits/craw4ai_toolkit.py +93 -0
  353. camel/toolkits/dappier_toolkit.py +12 -8
  354. camel/toolkits/data_commons_toolkit.py +2 -2
  355. camel/toolkits/dingtalk.py +1135 -0
  356. camel/toolkits/earth_science_toolkit.py +5367 -0
  357. camel/toolkits/edgeone_pages_mcp_toolkit.py +49 -0
  358. camel/toolkits/excel_toolkit.py +905 -71
  359. camel/toolkits/file_toolkit.py +1402 -0
  360. camel/toolkits/function_tool.py +205 -27
  361. camel/toolkits/github_toolkit.py +109 -22
  362. camel/toolkits/gmail_toolkit.py +1839 -0
  363. camel/toolkits/google_calendar_toolkit.py +40 -6
  364. camel/toolkits/google_drive_mcp_toolkit.py +54 -0
  365. camel/toolkits/google_maps_toolkit.py +2 -2
  366. camel/toolkits/google_scholar_toolkit.py +2 -2
  367. camel/toolkits/human_toolkit.py +36 -12
  368. camel/toolkits/hybrid_browser_toolkit/__init__.py +18 -0
  369. camel/toolkits/hybrid_browser_toolkit/config_loader.py +185 -0
  370. camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit.py +246 -0
  371. camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit_ts.py +1958 -0
  372. camel/toolkits/hybrid_browser_toolkit/installer.py +203 -0
  373. camel/toolkits/hybrid_browser_toolkit/ts/package-lock.json +4589 -0
  374. camel/toolkits/hybrid_browser_toolkit/ts/package.json +33 -0
  375. camel/toolkits/hybrid_browser_toolkit/ts/src/browser-scripts.js +125 -0
  376. camel/toolkits/hybrid_browser_toolkit/ts/src/browser-session.ts +1940 -0
  377. camel/toolkits/hybrid_browser_toolkit/ts/src/config-loader.ts +233 -0
  378. camel/toolkits/hybrid_browser_toolkit/ts/src/hybrid-browser-toolkit.ts +589 -0
  379. camel/toolkits/hybrid_browser_toolkit/ts/src/index.ts +7 -0
  380. camel/toolkits/hybrid_browser_toolkit/ts/src/parent-child-filter.ts +226 -0
  381. camel/toolkits/hybrid_browser_toolkit/ts/src/snapshot-parser.ts +219 -0
  382. camel/toolkits/hybrid_browser_toolkit/ts/src/som-screenshot-injected.ts +543 -0
  383. camel/toolkits/hybrid_browser_toolkit/ts/src/types.ts +129 -0
  384. camel/toolkits/hybrid_browser_toolkit/ts/tsconfig.json +27 -0
  385. camel/toolkits/hybrid_browser_toolkit/ts/websocket-server.js +325 -0
  386. camel/toolkits/hybrid_browser_toolkit/ws_wrapper.py +1037 -0
  387. camel/toolkits/hybrid_browser_toolkit_py/__init__.py +17 -0
  388. camel/toolkits/hybrid_browser_toolkit_py/actions.py +575 -0
  389. camel/toolkits/hybrid_browser_toolkit_py/agent.py +311 -0
  390. camel/toolkits/hybrid_browser_toolkit_py/browser_session.py +787 -0
  391. camel/toolkits/hybrid_browser_toolkit_py/config_loader.py +490 -0
  392. camel/toolkits/hybrid_browser_toolkit_py/hybrid_browser_toolkit.py +2390 -0
  393. camel/toolkits/hybrid_browser_toolkit_py/snapshot.py +233 -0
  394. camel/toolkits/hybrid_browser_toolkit_py/stealth_script.js +0 -0
  395. camel/toolkits/hybrid_browser_toolkit_py/unified_analyzer.js +1043 -0
  396. camel/toolkits/image_analysis_toolkit.py +3 -6
  397. camel/toolkits/image_generation_toolkit.py +390 -0
  398. camel/toolkits/jina_reranker_toolkit.py +5 -6
  399. camel/toolkits/klavis_toolkit.py +7 -3
  400. camel/toolkits/linkedin_toolkit.py +2 -2
  401. camel/toolkits/markitdown_toolkit.py +104 -0
  402. camel/toolkits/math_toolkit.py +66 -12
  403. camel/toolkits/mcp_toolkit.py +412 -36
  404. camel/toolkits/memory_toolkit.py +7 -3
  405. camel/toolkits/meshy_toolkit.py +2 -2
  406. camel/toolkits/message_agent_toolkit.py +608 -0
  407. camel/toolkits/message_integration.py +728 -0
  408. camel/toolkits/microsoft_outlook_mail_toolkit.py +1885 -0
  409. camel/toolkits/mineru_toolkit.py +2 -2
  410. camel/toolkits/minimax_mcp_toolkit.py +195 -0
  411. camel/toolkits/networkx_toolkit.py +2 -2
  412. camel/toolkits/note_taking_toolkit.py +277 -0
  413. camel/toolkits/notion_mcp_toolkit.py +224 -0
  414. camel/toolkits/notion_toolkit.py +2 -2
  415. camel/toolkits/open_api_specs/biztoc/__init__.py +2 -2
  416. camel/toolkits/open_api_specs/biztoc/ai-plugin.json +1 -1
  417. camel/toolkits/open_api_specs/coursera/__init__.py +2 -2
  418. camel/toolkits/open_api_specs/create_qr_code/__init__.py +2 -2
  419. camel/toolkits/open_api_specs/klarna/__init__.py +2 -2
  420. camel/toolkits/open_api_specs/nasa_apod/__init__.py +2 -2
  421. camel/toolkits/open_api_specs/outschool/__init__.py +2 -2
  422. camel/toolkits/open_api_specs/outschool/ai-plugin.json +1 -1
  423. camel/toolkits/open_api_specs/outschool/openapi.yaml +1 -1
  424. camel/toolkits/open_api_specs/outschool/paths/__init__.py +2 -2
  425. camel/toolkits/open_api_specs/outschool/paths/get_classes.py +2 -2
  426. camel/toolkits/open_api_specs/outschool/paths/search_teachers.py +2 -2
  427. camel/toolkits/open_api_specs/security_config.py +2 -2
  428. camel/toolkits/open_api_specs/speak/__init__.py +2 -2
  429. camel/toolkits/open_api_specs/web_scraper/__init__.py +2 -2
  430. camel/toolkits/open_api_specs/web_scraper/ai-plugin.json +1 -1
  431. camel/toolkits/open_api_specs/web_scraper/paths/__init__.py +2 -2
  432. camel/toolkits/open_api_specs/web_scraper/paths/scraper.py +2 -2
  433. camel/toolkits/open_api_toolkit.py +2 -2
  434. camel/toolkits/openbb_toolkit.py +7 -3
  435. camel/toolkits/origene_mcp_toolkit.py +56 -0
  436. camel/toolkits/page_script.js +53 -53
  437. camel/toolkits/playwright_mcp_toolkit.py +13 -31
  438. camel/toolkits/pptx_toolkit.py +36 -23
  439. camel/toolkits/pubmed_toolkit.py +2 -2
  440. camel/toolkits/pulse_mcp_search_toolkit.py +2 -2
  441. camel/toolkits/pyautogui_toolkit.py +2 -2
  442. camel/toolkits/reddit_toolkit.py +2 -2
  443. camel/toolkits/resend_toolkit.py +168 -0
  444. camel/toolkits/retrieval_toolkit.py +2 -2
  445. camel/toolkits/screenshot_toolkit.py +213 -0
  446. camel/toolkits/search_toolkit.py +606 -156
  447. camel/toolkits/searxng_toolkit.py +2 -2
  448. camel/toolkits/semantic_scholar_toolkit.py +2 -2
  449. camel/toolkits/slack_toolkit.py +108 -58
  450. camel/toolkits/sql_toolkit.py +712 -0
  451. camel/toolkits/stripe_toolkit.py +2 -2
  452. camel/toolkits/sympy_toolkit.py +3 -3
  453. camel/toolkits/task_planning_toolkit.py +5 -5
  454. camel/toolkits/terminal_toolkit/__init__.py +18 -0
  455. camel/toolkits/terminal_toolkit/terminal_toolkit.py +1281 -0
  456. camel/toolkits/terminal_toolkit/utils.py +659 -0
  457. camel/toolkits/thinking_toolkit.py +3 -3
  458. camel/toolkits/twitter_toolkit.py +2 -2
  459. camel/toolkits/vertex_ai_veo_toolkit.py +590 -0
  460. camel/toolkits/video_analysis_toolkit.py +109 -29
  461. camel/toolkits/video_download_toolkit.py +19 -16
  462. camel/toolkits/weather_toolkit.py +2 -2
  463. camel/toolkits/web_deploy_toolkit.py +1219 -0
  464. camel/toolkits/wechat_official_toolkit.py +483 -0
  465. camel/toolkits/whatsapp_toolkit.py +2 -2
  466. camel/toolkits/wolfram_alpha_toolkit.py +2 -2
  467. camel/toolkits/zapier_toolkit.py +7 -3
  468. camel/types/__init__.py +4 -4
  469. camel/types/agents/__init__.py +2 -2
  470. camel/types/agents/tool_calling_record.py +6 -3
  471. camel/types/enums.py +381 -41
  472. camel/types/mcp_registries.py +2 -2
  473. camel/types/openai_types.py +4 -4
  474. camel/types/unified_model_type.py +46 -10
  475. camel/utils/__init__.py +5 -2
  476. camel/utils/agent_context.py +41 -0
  477. camel/utils/async_func.py +2 -2
  478. camel/utils/chunker/__init__.py +2 -2
  479. camel/utils/chunker/base.py +2 -2
  480. camel/utils/chunker/code_chunker.py +2 -2
  481. camel/utils/chunker/uio_chunker.py +2 -2
  482. camel/utils/commons.py +38 -7
  483. camel/utils/constants.py +5 -2
  484. camel/utils/context_utils.py +1134 -0
  485. camel/utils/deduplication.py +2 -2
  486. camel/utils/filename.py +2 -2
  487. camel/utils/langfuse.py +18 -10
  488. camel/utils/mcp.py +140 -6
  489. camel/utils/mcp_client.py +48 -38
  490. camel/utils/message_summarizer.py +148 -0
  491. camel/utils/response_format.py +2 -2
  492. camel/utils/token_counting.py +45 -22
  493. camel/utils/tool_result.py +44 -0
  494. camel/verifiers/__init__.py +2 -2
  495. camel/verifiers/base.py +2 -2
  496. camel/verifiers/math_verifier.py +2 -2
  497. camel/verifiers/models.py +2 -2
  498. camel/verifiers/physics_verifier.py +2 -2
  499. camel/verifiers/python_verifier.py +2 -2
  500. {camel_ai-0.2.65.dist-info → camel_ai-0.2.83a6.dist-info}/METADATA +355 -117
  501. camel_ai-0.2.83a6.dist-info/RECORD +511 -0
  502. {camel_ai-0.2.65.dist-info → camel_ai-0.2.83a6.dist-info}/WHEEL +1 -1
  503. {camel_ai-0.2.65.dist-info → camel_ai-0.2.83a6.dist-info}/licenses/LICENSE +1 -1
  504. camel/loaders/pandas_reader.py +0 -368
  505. camel/toolkits/dalle_toolkit.py +0 -175
  506. camel/toolkits/file_write_toolkit.py +0 -444
  507. camel/toolkits/openai_agent_toolkit.py +0 -135
  508. camel/toolkits/terminal_toolkit.py +0 -1037
  509. camel_ai-0.2.65.dist-info/RECORD +0 -426
@@ -0,0 +1,712 @@
1
+ # ========= Copyright 2023-2026 @ CAMEL-AI.org. All Rights Reserved. =========
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ # ========= Copyright 2023-2026 @ CAMEL-AI.org. All Rights Reserved. =========
14
+
15
+ import re
16
+ from typing import (
17
+ TYPE_CHECKING,
18
+ Any,
19
+ ClassVar,
20
+ Dict,
21
+ List,
22
+ Literal,
23
+ Optional,
24
+ Union,
25
+ )
26
+
27
+ from camel.logger import get_logger
28
+ from camel.toolkits.base import BaseToolkit
29
+ from camel.toolkits.function_tool import FunctionTool
30
+ from camel.utils import MCPServer
31
+
32
+ if TYPE_CHECKING:
33
+ import sqlite3
34
+
35
+ import duckdb
36
+
37
+ logger = get_logger(__name__)
38
+
39
+
40
+ @MCPServer()
41
+ class SQLToolkit(BaseToolkit):
42
+ r"""A toolkit for executing SQL queries against various SQL databases.
43
+
44
+ This toolkit provides functionality to execute SQL queries with support
45
+ for read-only and read-write modes. It currently supports DuckDB and
46
+ SQLite, with extensibility for MySQL and other SQL databases.
47
+
48
+ Args:
49
+ database_path (Optional[str]): Path to the database file. If None,
50
+ uses an in-memory database. For DuckDB and SQLite, use ":memory:"
51
+ for in-memory or a file path for persistent storage.
52
+ (default: :obj:`None`)
53
+ database_type (Literal["duckdb", "sqlite"]): Type of database to use.
54
+ Currently supports "duckdb" and "sqlite".
55
+ (default: :obj:`"duckdb"`)
56
+ read_only (bool, optional): If True, only SELECT queries are allowed.
57
+ Write operations (INSERT, UPDATE, DELETE, etc.) will be rejected.
58
+ (default: :obj:`False`)
59
+ timeout (Optional[float], optional): The timeout for database
60
+ operations in seconds. Defaults to 180 seconds if not specified.
61
+ (default: :obj:`180.0`)
62
+
63
+ Raises:
64
+ ValueError: If database_type is not supported.
65
+ ImportError: If required database driver is not installed.
66
+ """
67
+
68
+ # SQL keywords that indicate write operations
69
+ _WRITE_KEYWORDS: ClassVar[List[str]] = [
70
+ "INSERT",
71
+ "UPDATE",
72
+ "DELETE",
73
+ "DROP",
74
+ "CREATE",
75
+ "ALTER",
76
+ "TRUNCATE",
77
+ "REPLACE",
78
+ "MERGE",
79
+ "GRANT",
80
+ "REVOKE",
81
+ "COPY",
82
+ "ATTACH",
83
+ "DETACH",
84
+ "LOAD",
85
+ "IMPORT",
86
+ "EXPORT",
87
+ ]
88
+
89
+ # Supported database types
90
+ _SUPPORTED_DATABASES: ClassVar[List[str]] = ["duckdb", "sqlite"]
91
+
92
+ def __init__(
93
+ self,
94
+ database_path: Optional[str] = None,
95
+ database_type: Literal["duckdb", "sqlite"] = "duckdb",
96
+ read_only: bool = False,
97
+ timeout: Optional[float] = 180.0,
98
+ ) -> None:
99
+ super().__init__(timeout=timeout)
100
+ self._validate_database_type(database_type)
101
+
102
+ self.database_path = database_path
103
+ self.database_type = database_type.lower()
104
+ self.read_only = read_only
105
+
106
+ # Initialize database connection
107
+ self._connection = self._create_connection()
108
+
109
+ logger.info(
110
+ f"Initialized SQL toolkit with database_type: {database_type}, "
111
+ f"database_path: {database_path or ':memory:'}, "
112
+ f"read_only: {read_only}, timeout: {self.timeout}s"
113
+ )
114
+
115
+ def _validate_database_type(self, database_type: str) -> None:
116
+ r"""Validate if the database type is supported.
117
+
118
+ Args:
119
+ database_type (str): The database type to validate.
120
+
121
+ Raises:
122
+ ValueError: If the database type is not supported.
123
+ """
124
+ if database_type.lower() not in self._SUPPORTED_DATABASES:
125
+ raise ValueError(
126
+ f"Unsupported database_type: {database_type}. "
127
+ f"Supported types: {self._SUPPORTED_DATABASES}"
128
+ )
129
+
130
+ def _create_connection(
131
+ self,
132
+ ) -> "Union[duckdb.DuckDBPyConnection, sqlite3.Connection]":
133
+ r"""Create a database connection based on the database type.
134
+
135
+ Returns:
136
+ Union[duckdb.DuckDBPyConnection, sqlite3.Connection]: A database
137
+ connection object.
138
+
139
+ Raises:
140
+ ImportError: If the required database driver is not installed.
141
+ """
142
+ if self.database_type == "duckdb":
143
+ try:
144
+ import duckdb
145
+ except ImportError:
146
+ raise ImportError(
147
+ "duckdb package is required for DuckDB support. "
148
+ "Install it with: pip install duckdb"
149
+ )
150
+
151
+ if self.database_path is None or self.database_path == ":memory:":
152
+ return duckdb.connect(":memory:")
153
+ else:
154
+ # Pass read_only parameter for file-based connections
155
+ # This provides database-level protection in addition to
156
+ # application-level checks
157
+ return duckdb.connect(
158
+ self.database_path, read_only=self.read_only
159
+ )
160
+ elif self.database_type == "sqlite":
161
+ try:
162
+ import sqlite3
163
+ except ImportError:
164
+ raise ImportError(
165
+ "sqlite3 module is required for SQLite support. "
166
+ "It should be included with Python, but if missing, "
167
+ "ensure you're using a standard Python installation."
168
+ )
169
+
170
+ if self.database_path is None or self.database_path == ":memory:":
171
+ conn = sqlite3.connect(":memory:", check_same_thread=False)
172
+ else:
173
+ # SQLite read-only mode using URI
174
+ if self.read_only:
175
+ uri = f"file:{self.database_path}?mode=ro"
176
+ conn = sqlite3.connect(
177
+ uri, uri=True, check_same_thread=False
178
+ )
179
+ else:
180
+ conn = sqlite3.connect(
181
+ self.database_path, check_same_thread=False
182
+ )
183
+
184
+ # Enable row factory to return dict-like rows
185
+ conn.row_factory = sqlite3.Row
186
+ return conn
187
+ else:
188
+ raise ValueError(
189
+ f"Unsupported database type: {self.database_type}"
190
+ )
191
+
192
+ def _is_write_query(self, query: str) -> bool:
193
+ r"""Check if a SQL query is a write operation.
194
+
195
+ This method analyzes the query string to determine if it contains
196
+ any write operations. It handles comments and case-insensitive
197
+ matching.
198
+
199
+ Args:
200
+ query (str): The SQL query to check.
201
+
202
+ Returns:
203
+ bool: True if the query is a write operation, False otherwise.
204
+ """
205
+ # Remove SQL comments (-- and /* */ style)
206
+ # Remove single-line comments
207
+ query_no_comments = re.sub(r"--.*$", "", query, flags=re.MULTILINE)
208
+ # Remove multi-line comments
209
+ query_no_comments = re.sub(
210
+ r"/\*.*?\*/", "", query_no_comments, flags=re.DOTALL
211
+ )
212
+
213
+ # Normalize whitespace and convert to uppercase for keyword matching
214
+ query_normalized = " ".join(query_no_comments.split()).upper()
215
+
216
+ # Check for write keywords at the start of the query (after whitespace)
217
+ for keyword in self._WRITE_KEYWORDS:
218
+ # Match keyword at start of query or after whitespace/semicolon
219
+ pattern = r"(^|\s|;)" + re.escape(keyword) + r"(\s|$)"
220
+ if re.search(pattern, query_normalized):
221
+ return True
222
+
223
+ return False
224
+
225
+ def _quote_identifier(self, identifier: str) -> str:
226
+ r"""Safely quote a SQL identifier (table name, column name, etc.).
227
+
228
+ This method validates and quotes SQL identifiers to prevent SQL
229
+ injection. For DuckDB, identifiers are quoted with double quotes. Any
230
+ double quotes within the identifier are escaped by doubling them.
231
+
232
+ Args:
233
+ identifier (str): The identifier to quote (e.g., table name,
234
+ column name).
235
+
236
+ Returns:
237
+ str: The safely quoted identifier.
238
+
239
+ Raises:
240
+ ValueError: If the identifier is empty or contains invalid
241
+ characters.
242
+ """
243
+ if not identifier or not identifier.strip():
244
+ raise ValueError("Identifier cannot be empty")
245
+
246
+ identifier = identifier.strip()
247
+
248
+ # Validate identifier doesn't contain null bytes or other
249
+ # dangerous characters
250
+ if "\x00" in identifier:
251
+ raise ValueError("Identifier cannot contain null bytes")
252
+
253
+ # Escape double quotes by doubling them, then wrap in double quotes
254
+ escaped = identifier.replace('"', '""')
255
+ return f'"{escaped}"'
256
+
257
+ def execute_query(
258
+ self,
259
+ query: str,
260
+ params: Optional[
261
+ Union[
262
+ List[Union[str, int, float, bool, None]],
263
+ Dict[str, Union[str, int, float, bool, None]],
264
+ ]
265
+ ] = None,
266
+ ) -> Union[List[Dict[str, Any]], Dict[str, Any], str]:
267
+ r"""Execute a SQL query and return results.
268
+
269
+ This method executes a SQL query against the configured database and
270
+ returns the results. For SELECT queries, returns a list of dictionaries
271
+ where each dictionary represents a row. For write operations (INSERT,
272
+ UPDATE, DELETE, etc.), returns a status dictionary with execution info.
273
+
274
+ Args:
275
+ query (str): The SQL query to execute.
276
+ params (Optional[Union[List[Union[str, int, float, bool, None]],
277
+ Dict[str, Union[str, int, float, bool, None]]]], optional):
278
+ Parameters for parameterized queries. Can be a list for
279
+ positional parameters (with ? placeholders) or a dict for
280
+ named parameters. Values can be strings, numbers, booleans,
281
+ or None. Note: tuples are also accepted at runtime but should
282
+ be passed as lists for type compatibility.
283
+ (default: :obj:`None`)
284
+
285
+ Returns:
286
+ Union[List[Dict[str, Any]], Dict[str, Any], str]:
287
+ - For SELECT queries: List of dictionaries with column names as
288
+ keys and row values as values.
289
+ - For write operations (INSERT, UPDATE, DELETE, CREATE, etc.):
290
+ A dictionary with 'status', 'message', and optionally
291
+ 'rows_affected' keys.
292
+ - For errors: An error message string starting with "Error:".
293
+ """
294
+ if not query or not query.strip():
295
+ return "Error: Query cannot be empty"
296
+
297
+ # Validate query mode (check for write operations in read-only mode)
298
+ if self.read_only and self._is_write_query(query):
299
+ return (
300
+ "Error: Write operations are not allowed in read-only mode. "
301
+ "The query contains write operations (INSERT, UPDATE, DELETE, "
302
+ "DROP, CREATE, ALTER, TRUNCATE, etc.). Only SELECT queries "
303
+ "are permitted."
304
+ )
305
+
306
+ try:
307
+ logger.debug(f"Executing query: {query}...")
308
+
309
+ cursor = self._connection.cursor()
310
+
311
+ # Execute query with or without parameters
312
+ if params is not None:
313
+ cursor.execute(query, params)
314
+ else:
315
+ cursor.execute(query)
316
+
317
+ # Check if this is a write operation first
318
+ # DuckDB returns results for INSERT/UPDATE/DELETE (with count),
319
+ # but we want to return empty list for write operations
320
+ is_write = self._is_write_query(query)
321
+
322
+ # Check if the query returned results by checking cursor.
323
+ # description
324
+ # This handles SELECT queries, CTEs (WITH ... SELECT ...),
325
+ # EXPLAIN queries, SHOW queries, etc.
326
+ if cursor.description is not None and not is_write:
327
+ # Query returned results and it's not a write operation,
328
+ # fetch them
329
+ rows = cursor.fetchall()
330
+
331
+ # Convert rows to dictionaries
332
+ # SQLite with row_factory returns Row objects that can be
333
+ # converted to dict
334
+ # DuckDB returns tuples that need column names
335
+ if (
336
+ self.database_type == "sqlite"
337
+ and rows
338
+ and hasattr(rows[0], "keys")
339
+ ):
340
+ # SQLite Row objects can be converted directly to dict
341
+ results = [dict(row) for row in rows]
342
+ else:
343
+ # DuckDB or other databases: use column names from
344
+ # description
345
+ columns = [desc[0] for desc in cursor.description]
346
+ results = [dict(zip(columns, row)) for row in rows]
347
+
348
+ logger.debug(f"Query returned {len(results)} rows")
349
+ return results
350
+ else:
351
+ # Query did not return results or is a write operation
352
+ # (INSERT, UPDATE, DELETE, etc.)
353
+ # Commit the transaction
354
+ self._connection.commit()
355
+
356
+ # Get affected rows count if available
357
+ # Note: DuckDB doesn't support rowcount, SQLite does for DML
358
+ rows_affected = getattr(cursor, "rowcount", -1)
359
+ result_dict: Dict[str, Any] = {
360
+ "status": "success",
361
+ }
362
+ if rows_affected >= 0:
363
+ result_dict["rows_affected"] = rows_affected
364
+ result_dict["message"] = (
365
+ f"Query executed successfully. "
366
+ f"{rows_affected} row(s) affected."
367
+ )
368
+ logger.debug(
369
+ f"Write query executed successfully, "
370
+ f"{rows_affected} rows affected"
371
+ )
372
+ else:
373
+ result_dict["message"] = "Query executed successfully."
374
+ logger.debug("Query executed successfully")
375
+ return result_dict
376
+
377
+ except Exception as e:
378
+ error_msg = str(e)
379
+ logger.error(f"Query execution failed: {error_msg}")
380
+ # Rollback on error
381
+ try:
382
+ self._connection.rollback()
383
+ except Exception as rollback_error:
384
+ logger.debug(f"Rollback failed: {rollback_error!s}")
385
+ return f"Error: Query execution failed: {error_msg}"
386
+
387
+ def list_tables(self) -> Union[List[str], str]:
388
+ r"""List all tables in the database.
389
+
390
+ This method queries the database to discover all available tables.
391
+ It uses database-specific queries to retrieve table names.
392
+
393
+ Returns:
394
+ Union[List[str], str]: A list of table names in the database,
395
+ or an error message string if the operation fails.
396
+ """
397
+ try:
398
+ if self.database_type == "duckdb":
399
+ # DuckDB uses SHOW TABLES
400
+ result = self.execute_query("SHOW TABLES")
401
+ # Check if result is an error message or unexpected dict
402
+ if isinstance(result, str):
403
+ return result
404
+ if isinstance(result, dict):
405
+ return (
406
+ f"Error: Unexpected result from SHOW TABLES: {result}"
407
+ )
408
+ # Result format: [{'name': 'table1'}, {'name': 'table2'}]
409
+ return [row["name"] for row in result]
410
+ elif self.database_type == "sqlite":
411
+ # SQLite uses sqlite_master system table
412
+ result = self.execute_query(
413
+ "SELECT name FROM sqlite_master "
414
+ "WHERE type='table' AND name NOT LIKE 'sqlite_%'"
415
+ )
416
+ # Check if result is an error message or unexpected dict
417
+ if isinstance(result, str):
418
+ return result
419
+ if isinstance(result, dict):
420
+ return f"Error: Unexpected result from query: {result}"
421
+ # Result format: [{'name': 'table1'}, {'name': 'table2'}]
422
+ return [row["name"] for row in result]
423
+ else:
424
+ # For other databases, could use information_schema or similar
425
+ return (
426
+ f"Error: list_tables not yet implemented for "
427
+ f"{self.database_type}"
428
+ )
429
+ except Exception as e:
430
+ logger.error(f"Failed to list tables: {e!s}")
431
+ return f"Error: Failed to list tables: {e!s}"
432
+
433
+ def _get_table_schema(self, table_name: str) -> Union[Dict[str, Any], str]:
434
+ r"""Internal helper method to get table schema information.
435
+
436
+ Args:
437
+ table_name (str): The name of the table to describe.
438
+
439
+ Returns:
440
+ Union[Dict[str, Any], str]: A dictionary containing 'columns',
441
+ 'primary_keys', and 'foreign_keys', or an error message string
442
+ if the operation fails.
443
+ """
444
+ if not table_name or not table_name.strip():
445
+ return "Error: Table name cannot be empty"
446
+
447
+ try:
448
+ if self.database_type == "duckdb":
449
+ # Get column information using DESCRIBE
450
+ # Safely quote the table name to prevent SQL injection
451
+ quoted_table_name = self._quote_identifier(table_name)
452
+ columns = self.execute_query(f"DESCRIBE {quoted_table_name}")
453
+
454
+ # Check if result is an error message
455
+ if isinstance(columns, str):
456
+ return columns
457
+ # Also check if result is a status dict (not a list of columns)
458
+ if isinstance(columns, dict):
459
+ return f"Error: Unexpected result from DESCRIBE: {columns}"
460
+
461
+ # Extract primary keys
462
+ primary_keys = [
463
+ col["column_name"]
464
+ for col in columns
465
+ if col.get("key") == "PRI"
466
+ ]
467
+
468
+ # Get foreign keys from information_schema
469
+ # This query retrieves FK relationships by joining system
470
+ # tables:
471
+ # - table_constraints: finds FOREIGN KEY constraints
472
+ # - referential_constraints: links FK to referenced constraint
473
+ # - key_column_usage: gets column names (used twice for source/
474
+ # target)
475
+ foreign_keys = []
476
+ try:
477
+ fk_query = """
478
+ SELECT
479
+ kcu.column_name,
480
+ kcu2.table_name AS references_table,
481
+ kcu2.column_name AS references_column
482
+ FROM information_schema.table_constraints AS tc
483
+ JOIN information_schema.referential_constraints AS rc
484
+ ON tc.constraint_name = rc.constraint_name
485
+ JOIN information_schema.key_column_usage AS kcu
486
+ ON tc.constraint_name = kcu.constraint_name
487
+ JOIN information_schema.table_constraints AS tc2
488
+ ON rc.unique_constraint_name = tc2.constraint_name
489
+ JOIN information_schema.key_column_usage AS kcu2
490
+ ON tc2.constraint_name = kcu2.constraint_name
491
+ WHERE tc.constraint_type = 'FOREIGN KEY'
492
+ AND tc.table_name = ?
493
+ """
494
+ fk_results = self.execute_query(
495
+ fk_query, params=[table_name]
496
+ )
497
+ # Only process if result is a list of rows
498
+ if isinstance(fk_results, list):
499
+ foreign_keys = [
500
+ {
501
+ "column": fk["column_name"],
502
+ "references_table": fk["references_table"],
503
+ "references_column": fk["references_column"],
504
+ }
505
+ for fk in fk_results
506
+ ]
507
+ except Exception as e:
508
+ # If foreign key query fails, log but don't fail
509
+ logger.debug(
510
+ f"Could not retrieve foreign keys "
511
+ f"for {table_name}: {e!s}"
512
+ )
513
+
514
+ return {
515
+ "columns": columns,
516
+ "primary_keys": primary_keys,
517
+ "foreign_keys": foreign_keys,
518
+ }
519
+ elif self.database_type == "sqlite":
520
+ # SQLite uses PRAGMA table_info for column information
521
+ quoted_table_name = self._quote_identifier(table_name)
522
+ pragma_result = self.execute_query(
523
+ f"PRAGMA table_info({quoted_table_name})"
524
+ )
525
+
526
+ # Check if result is an error message
527
+ if isinstance(pragma_result, str):
528
+ return pragma_result
529
+ # Also check if result is a status dict (not a list of rows)
530
+ if isinstance(pragma_result, dict):
531
+ return (
532
+ f"Error: Unexpected result from "
533
+ f"PRAGMA: {pragma_result}"
534
+ )
535
+
536
+ # Convert PRAGMA format to match DuckDB format
537
+ sqlite_columns: List[Dict[str, Any]] = []
538
+ sqlite_pks: List[str] = []
539
+ for row in pragma_result:
540
+ is_primary_key = row["pk"] > 0
541
+ col_info: Dict[str, Any] = {
542
+ "column_name": row["name"],
543
+ "column_type": row["type"],
544
+ "null": "YES" if row["notnull"] == 0 else "NO",
545
+ "key": "PRI" if is_primary_key else None,
546
+ "default": row["dflt_value"],
547
+ "extra": None,
548
+ }
549
+ sqlite_columns.append(col_info)
550
+ if is_primary_key:
551
+ sqlite_pks.append(row["name"])
552
+
553
+ # Get foreign keys using PRAGMA foreign_key_list (much
554
+ # simpler!)
555
+ sqlite_fks: List[Dict[str, Any]] = []
556
+ try:
557
+ fk_result = self.execute_query(
558
+ f"PRAGMA foreign_key_list({quoted_table_name})"
559
+ )
560
+ # Only process if result is a list of rows
561
+ if isinstance(fk_result, list):
562
+ sqlite_fks = [
563
+ {
564
+ "column": fk["from"],
565
+ "references_table": fk["table"],
566
+ "references_column": fk["to"],
567
+ }
568
+ for fk in fk_result
569
+ ]
570
+ except Exception as e:
571
+ # If foreign key query fails, log but don't fail
572
+ logger.debug(
573
+ f"Could not retrieve foreign keys for "
574
+ f"{table_name}: {e!s}"
575
+ )
576
+
577
+ return {
578
+ "columns": sqlite_columns,
579
+ "primary_keys": sqlite_pks,
580
+ "foreign_keys": sqlite_fks,
581
+ }
582
+ else:
583
+ # For other databases, could use information_schema or similar
584
+ return (
585
+ f"Error: get_table_info not yet implemented for "
586
+ f"{self.database_type}"
587
+ )
588
+ except Exception as e:
589
+ logger.error(f"Failed to get table schema for {table_name}: {e!s}")
590
+ return f"Error: Failed to get table schema for {table_name}: {e!s}"
591
+
592
+ def get_table_info(
593
+ self, table_name: Optional[str] = None
594
+ ) -> Union[Dict[str, Any], str]:
595
+ r"""Get comprehensive information about table(s) in the database.
596
+
597
+ This method provides a summary of table information including schema,
598
+ primary keys, foreign keys, and row counts. If table_name is provided,
599
+ returns info for that specific table. Otherwise, returns info for all
600
+ tables.
601
+
602
+ Args:
603
+ table_name (Optional[str], optional): Name of a specific table to
604
+ get info for. If None, returns info for all tables.
605
+ (default: :obj:`None`)
606
+
607
+ Returns:
608
+ Union[Dict[str, Any], str]: A dictionary containing table
609
+ information, or an error message string if the operation fails.
610
+ If table_name is provided, returns info for that table with
611
+ keys: 'table_name', 'columns', 'primary_keys', 'foreign_keys',
612
+ 'row_count'. Otherwise, returns a dictionary mapping table
613
+ names to their info dictionaries.
614
+ """
615
+ # Validate table_name if provided
616
+ if table_name is not None and (
617
+ not table_name or not table_name.strip()
618
+ ):
619
+ return "Error: Table name cannot be empty"
620
+
621
+ try:
622
+ if table_name:
623
+ # Get info for specific table
624
+ schema_info = self._get_table_schema(table_name)
625
+ # Check if result is an error message
626
+ if isinstance(schema_info, str):
627
+ return schema_info
628
+
629
+ # Get row count - safely quote table name to prevent SQL
630
+ # injection
631
+ quoted_table_name = self._quote_identifier(table_name)
632
+ count_result = self.execute_query(
633
+ f"SELECT COUNT(*) as row_count FROM {quoted_table_name}"
634
+ )
635
+ # Check if result is an error message or not a list
636
+ if isinstance(count_result, str):
637
+ return count_result
638
+ if not isinstance(count_result, list):
639
+ return (
640
+ f"Error: Unexpected result from COUNT: {count_result}"
641
+ )
642
+ row_count = count_result[0]["row_count"] if count_result else 0
643
+
644
+ return {
645
+ "table_name": table_name,
646
+ "columns": schema_info["columns"],
647
+ "primary_keys": schema_info["primary_keys"],
648
+ "foreign_keys": schema_info["foreign_keys"],
649
+ "row_count": row_count,
650
+ }
651
+ else:
652
+ # Get info for all tables
653
+ tables = self.list_tables()
654
+ # Check if result is an error message
655
+ if isinstance(tables, str):
656
+ return tables
657
+
658
+ result = {}
659
+ for table in tables:
660
+ schema_info = self._get_table_schema(table)
661
+ # Check if result is an error message
662
+ if isinstance(schema_info, str):
663
+ return schema_info
664
+
665
+ # Safely quote table name to prevent SQL injection
666
+ quoted_table = self._quote_identifier(table)
667
+ count_result = self.execute_query(
668
+ f"SELECT COUNT(*) as row_count FROM {quoted_table}"
669
+ )
670
+ # Check if result is an error message or not a list
671
+ if isinstance(count_result, str):
672
+ return count_result
673
+ if not isinstance(count_result, list):
674
+ return (
675
+ f"Error: Unexpected result from COUNT: "
676
+ f"{count_result}"
677
+ )
678
+ row_count = (
679
+ count_result[0]["row_count"] if count_result else 0
680
+ )
681
+ result[table] = {
682
+ "table_name": table,
683
+ "columns": schema_info["columns"],
684
+ "primary_keys": schema_info["primary_keys"],
685
+ "foreign_keys": schema_info["foreign_keys"],
686
+ "row_count": row_count,
687
+ }
688
+ return result
689
+ except Exception as e:
690
+ logger.error(f"Failed to get table info: {e!s}")
691
+ return f"Error: Failed to get table info: {e!s}"
692
+
693
+ def get_tools(self) -> List[FunctionTool]:
694
+ r"""Get the list of available tools in the toolkit.
695
+
696
+ Returns:
697
+ List[FunctionTool]: A list of FunctionTool objects representing the
698
+ available functions in the toolkit.
699
+ """
700
+ return [
701
+ FunctionTool(self.execute_query),
702
+ FunctionTool(self.list_tables),
703
+ FunctionTool(self.get_table_info),
704
+ ]
705
+
706
+ def __del__(self) -> None:
707
+ r"""Clean up database connection on deletion."""
708
+ if hasattr(self, "_connection") and self._connection:
709
+ try:
710
+ self._connection.close()
711
+ except Exception as e:
712
+ logger.debug(f"Error closing connection in __del__: {e}")