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.
- camel/__init__.py +3 -3
- camel/agents/__init__.py +2 -2
- camel/agents/_types.py +9 -4
- camel/agents/_utils.py +40 -2
- camel/agents/base.py +2 -2
- camel/agents/chat_agent.py +5107 -995
- camel/agents/critic_agent.py +2 -2
- camel/agents/deductive_reasoner_agent.py +56 -56
- camel/agents/embodied_agent.py +2 -2
- camel/agents/knowledge_graph_agent.py +20 -20
- camel/agents/mcp_agent.py +35 -36
- camel/agents/multi_hop_generator_agent.py +3 -3
- camel/agents/programmed_agent_instruction.py +2 -2
- camel/agents/repo_agent.py +4 -3
- camel/agents/role_assignment_agent.py +2 -2
- camel/agents/search_agent.py +2 -2
- camel/agents/task_agent.py +2 -2
- camel/agents/tool_agents/__init__.py +2 -2
- camel/agents/tool_agents/base.py +2 -2
- camel/agents/tool_agents/hugging_face_tool_agent.py +3 -3
- camel/benchmarks/__init__.py +2 -2
- camel/benchmarks/apibank.py +5 -5
- camel/benchmarks/apibench.py +2 -2
- camel/benchmarks/base.py +2 -2
- camel/benchmarks/browsecomp.py +44 -33
- camel/benchmarks/gaia.py +17 -13
- camel/benchmarks/mock_website/README.md +1 -3
- camel/benchmarks/mock_website/mock_web.py +2 -2
- camel/benchmarks/mock_website/requirements.txt +1 -1
- camel/benchmarks/mock_website/shopping_mall/app.py +2 -2
- camel/benchmarks/mock_website/task.json +1 -1
- camel/benchmarks/nexus.py +3 -3
- camel/benchmarks/ragbench.py +2 -2
- camel/bots/__init__.py +2 -2
- camel/bots/discord/__init__.py +2 -2
- camel/bots/discord/discord_app.py +2 -2
- camel/bots/discord/discord_installation.py +2 -2
- camel/bots/discord/discord_store.py +3 -3
- camel/bots/slack/__init__.py +2 -2
- camel/bots/slack/models.py +4 -4
- camel/bots/slack/slack_app.py +2 -2
- camel/bots/telegram_bot.py +2 -2
- camel/configs/__init__.py +29 -2
- camel/configs/aihubmix_config.py +90 -0
- camel/configs/aiml_config.py +2 -2
- camel/configs/amd_config.py +70 -0
- camel/configs/anthropic_config.py +2 -2
- camel/configs/base_config.py +2 -2
- camel/configs/bedrock_config.py +5 -3
- camel/configs/cerebras_config.py +98 -0
- camel/configs/cohere_config.py +2 -2
- camel/configs/cometapi_config.py +106 -0
- camel/configs/crynux_config.py +2 -2
- camel/configs/deepseek_config.py +9 -8
- camel/configs/function_gemma_config.py +59 -0
- camel/configs/gemini_config.py +6 -4
- camel/configs/groq_config.py +6 -4
- camel/configs/internlm_config.py +6 -4
- camel/configs/litellm_config.py +2 -2
- camel/configs/lmstudio_config.py +6 -4
- camel/configs/minimax_config.py +95 -0
- camel/configs/mistral_config.py +2 -2
- camel/configs/modelscope_config.py +5 -3
- camel/configs/moonshot_config.py +2 -2
- camel/configs/nebius_config.py +105 -0
- camel/configs/netmind_config.py +2 -2
- camel/configs/novita_config.py +2 -2
- camel/configs/nvidia_config.py +2 -2
- camel/configs/ollama_config.py +2 -2
- camel/configs/openai_config.py +5 -3
- camel/configs/openrouter_config.py +6 -4
- camel/configs/ppio_config.py +2 -2
- camel/configs/qianfan_config.py +85 -0
- camel/configs/qwen_config.py +2 -2
- camel/configs/reka_config.py +2 -2
- camel/configs/samba_config.py +6 -4
- camel/configs/sglang_config.py +2 -2
- camel/configs/siliconflow_config.py +2 -2
- camel/configs/togetherai_config.py +2 -2
- camel/configs/vllm_config.py +4 -2
- camel/configs/watsonx_config.py +2 -2
- camel/configs/yi_config.py +6 -4
- camel/configs/zhipuai_config.py +6 -4
- camel/data_collectors/__init__.py +2 -2
- camel/data_collectors/alpaca_collector.py +18 -9
- camel/data_collectors/base.py +2 -2
- camel/data_collectors/sharegpt_collector.py +2 -2
- camel/datagen/__init__.py +2 -2
- camel/datagen/cot_datagen.py +3 -3
- camel/datagen/evol_instruct/__init__.py +2 -2
- camel/datagen/evol_instruct/evol_instruct.py +2 -2
- camel/datagen/evol_instruct/scorer.py +12 -12
- camel/datagen/evol_instruct/templates.py +16 -16
- camel/datagen/self_improving_cot.py +5 -5
- camel/datagen/self_instruct/__init__.py +2 -2
- camel/datagen/self_instruct/filter/__init__.py +2 -2
- camel/datagen/self_instruct/filter/filter_function.py +2 -2
- camel/datagen/self_instruct/filter/filter_registry.py +2 -2
- camel/datagen/self_instruct/filter/instruction_filter.py +2 -2
- camel/datagen/self_instruct/self_instruct.py +2 -2
- camel/datagen/self_instruct/templates.py +47 -47
- camel/datagen/source2synth/__init__.py +2 -2
- camel/datagen/source2synth/data_processor.py +2 -2
- camel/datagen/source2synth/models.py +2 -2
- camel/datagen/source2synth/user_data_processor_config.py +2 -2
- camel/datahubs/__init__.py +2 -2
- camel/datahubs/base.py +2 -2
- camel/datahubs/huggingface.py +2 -2
- camel/datahubs/models.py +2 -2
- camel/datasets/__init__.py +2 -2
- camel/datasets/base_generator.py +41 -12
- camel/datasets/few_shot_generator.py +18 -18
- camel/datasets/models.py +2 -2
- camel/datasets/self_instruct_generator.py +2 -2
- camel/datasets/static_dataset.py +2 -2
- camel/embeddings/__init__.py +2 -2
- camel/embeddings/azure_embedding.py +2 -2
- camel/embeddings/base.py +2 -2
- camel/embeddings/gemini_embedding.py +2 -2
- camel/embeddings/jina_embedding.py +2 -2
- camel/embeddings/mistral_embedding.py +2 -2
- camel/embeddings/openai_compatible_embedding.py +2 -2
- camel/embeddings/openai_embedding.py +2 -2
- camel/embeddings/sentence_transformers_embeddings.py +2 -2
- camel/embeddings/together_embedding.py +2 -2
- camel/embeddings/vlm_embedding.py +2 -2
- camel/environments/__init__.py +14 -2
- camel/environments/models.py +2 -2
- camel/environments/multi_step.py +2 -2
- camel/environments/rlcards_env.py +860 -0
- camel/environments/single_step.py +30 -5
- camel/environments/tic_tac_toe.py +3 -3
- camel/extractors/__init__.py +2 -2
- camel/extractors/base.py +2 -2
- camel/extractors/python_strategies.py +2 -2
- camel/generators.py +2 -2
- camel/human.py +2 -2
- camel/interpreters/__init__.py +4 -2
- camel/interpreters/base.py +2 -2
- camel/interpreters/docker/Dockerfile +14 -24
- camel/interpreters/docker_interpreter.py +5 -4
- camel/interpreters/e2b_interpreter.py +36 -3
- camel/interpreters/internal_python_interpreter.py +53 -4
- camel/interpreters/interpreter_error.py +2 -2
- camel/interpreters/ipython_interpreter.py +2 -2
- camel/interpreters/microsandbox_interpreter.py +395 -0
- camel/interpreters/subprocess_interpreter.py +2 -2
- camel/loaders/__init__.py +13 -4
- camel/loaders/apify_reader.py +2 -2
- camel/loaders/base_io.py +2 -2
- camel/loaders/base_loader.py +85 -0
- camel/loaders/chunkr_reader.py +11 -2
- camel/loaders/crawl4ai_reader.py +2 -2
- camel/loaders/firecrawl_reader.py +6 -6
- camel/loaders/jina_url_reader.py +2 -2
- camel/loaders/markitdown.py +2 -2
- camel/loaders/mineru_extractor.py +2 -2
- camel/loaders/mistral_reader.py +2 -2
- camel/loaders/scrapegraph_reader.py +2 -2
- camel/loaders/unstructured_io.py +2 -2
- camel/logger.py +5 -5
- camel/memories/__init__.py +2 -2
- camel/memories/agent_memories.py +86 -3
- camel/memories/base.py +36 -2
- camel/memories/blocks/__init__.py +2 -2
- camel/memories/blocks/chat_history_block.py +125 -7
- camel/memories/blocks/vectordb_block.py +10 -3
- camel/memories/context_creators/__init__.py +2 -2
- camel/memories/context_creators/score_based.py +109 -230
- camel/memories/records.py +90 -10
- camel/messages/__init__.py +2 -2
- camel/messages/base.py +178 -43
- camel/messages/conversion/__init__.py +2 -2
- camel/messages/conversion/alpaca.py +2 -2
- camel/messages/conversion/conversation_models.py +2 -2
- camel/messages/conversion/sharegpt/__init__.py +2 -2
- camel/messages/conversion/sharegpt/function_call_formatter.py +2 -2
- camel/messages/conversion/sharegpt/hermes/__init__.py +2 -2
- camel/messages/conversion/sharegpt/hermes/hermes_function_formatter.py +2 -2
- camel/messages/func_message.py +54 -17
- camel/models/__init__.py +18 -2
- camel/models/_utils.py +3 -3
- camel/models/aihubmix_model.py +83 -0
- camel/models/aiml_model.py +11 -18
- camel/models/amd_model.py +101 -0
- camel/models/anthropic_model.py +127 -20
- camel/models/aws_bedrock_model.py +12 -35
- camel/models/azure_openai_model.py +214 -115
- camel/models/base_audio_model.py +5 -3
- camel/models/base_model.py +378 -31
- camel/models/cerebras_model.py +83 -0
- camel/models/cohere_model.py +18 -49
- camel/models/cometapi_model.py +83 -0
- camel/models/crynux_model.py +11 -18
- camel/models/deepseek_model.py +20 -84
- camel/models/fish_audio_model.py +8 -2
- camel/models/function_gemma_model.py +889 -0
- camel/models/gemini_model.py +391 -52
- camel/models/groq_model.py +11 -19
- camel/models/internlm_model.py +11 -18
- camel/models/litellm_model.py +57 -49
- camel/models/lmstudio_model.py +17 -20
- camel/models/minimax_model.py +83 -0
- camel/models/mistral_model.py +20 -47
- camel/models/model_factory.py +39 -3
- camel/models/model_manager.py +26 -8
- camel/models/modelscope_model.py +13 -193
- camel/models/moonshot_model.py +183 -21
- camel/models/nebius_model.py +83 -0
- camel/models/nemotron_model.py +19 -9
- camel/models/netmind_model.py +11 -18
- camel/models/novita_model.py +11 -18
- camel/models/nvidia_model.py +11 -18
- camel/models/ollama_model.py +14 -21
- camel/models/openai_audio_models.py +2 -2
- camel/models/openai_compatible_model.py +190 -71
- camel/models/openai_model.py +192 -86
- camel/models/openrouter_model.py +11 -19
- camel/models/ppio_model.py +11 -18
- camel/models/qianfan_model.py +89 -0
- camel/models/qwen_model.py +13 -193
- camel/models/reka_model.py +23 -49
- camel/models/reward/__init__.py +2 -2
- camel/models/reward/base_reward_model.py +2 -2
- camel/models/reward/evaluator.py +2 -2
- camel/models/reward/nemotron_model.py +2 -2
- camel/models/reward/skywork_model.py +2 -2
- camel/models/samba_model.py +50 -75
- camel/models/sglang_model.py +90 -68
- camel/models/siliconflow_model.py +12 -35
- camel/models/stub_model.py +10 -7
- camel/models/togetherai_model.py +11 -18
- camel/models/vllm_model.py +10 -18
- camel/models/volcano_model.py +158 -19
- camel/models/watsonx_model.py +9 -47
- camel/models/yi_model.py +11 -18
- camel/models/zhipuai_model.py +70 -18
- camel/parsers/__init__.py +18 -0
- camel/parsers/mcp_tool_call_parser.py +176 -0
- camel/personas/__init__.py +2 -2
- camel/personas/persona.py +2 -2
- camel/personas/persona_hub.py +2 -2
- camel/prompts/__init__.py +2 -2
- camel/prompts/ai_society.py +2 -2
- camel/prompts/base.py +2 -2
- camel/prompts/code.py +2 -2
- camel/prompts/evaluation.py +2 -2
- camel/prompts/generate_text_embedding_data.py +2 -2
- camel/prompts/image_craft.py +2 -2
- camel/prompts/misalignment.py +2 -2
- camel/prompts/multi_condition_image_craft.py +2 -2
- camel/prompts/object_recognition.py +2 -2
- camel/prompts/persona_hub.py +3 -3
- camel/prompts/prompt_templates.py +2 -2
- camel/prompts/role_description_prompt_template.py +2 -2
- camel/prompts/solution_extraction.py +8 -8
- camel/prompts/task_prompt_template.py +2 -2
- camel/prompts/translation.py +2 -2
- camel/prompts/video_description_prompt.py +3 -3
- camel/responses/__init__.py +2 -2
- camel/responses/agent_responses.py +2 -2
- camel/retrievers/__init__.py +2 -2
- camel/retrievers/auto_retriever.py +3 -2
- camel/retrievers/base.py +2 -2
- camel/retrievers/bm25_retriever.py +2 -2
- camel/retrievers/cohere_rerank_retriever.py +2 -2
- camel/retrievers/hybrid_retrival.py +2 -2
- camel/retrievers/vector_retriever.py +2 -2
- camel/runtimes/Dockerfile.multi-toolkit +90 -0
- camel/runtimes/__init__.py +2 -2
- camel/runtimes/api.py +79 -23
- camel/runtimes/base.py +2 -2
- camel/runtimes/configs.py +13 -13
- camel/runtimes/daytona_runtime.py +17 -18
- camel/runtimes/docker_runtime.py +12 -12
- camel/runtimes/llm_guard_runtime.py +26 -26
- camel/runtimes/remote_http_runtime.py +11 -11
- camel/runtimes/ubuntu_docker_runtime.py +2 -2
- camel/runtimes/utils/__init__.py +2 -2
- camel/runtimes/utils/function_risk_toolkit.py +2 -2
- camel/runtimes/utils/ignore_risk_toolkit.py +2 -2
- camel/schemas/__init__.py +2 -2
- camel/schemas/base.py +2 -2
- camel/schemas/openai_converter.py +3 -3
- camel/schemas/outlines_converter.py +2 -2
- camel/services/agent_openapi_server.py +380 -0
- camel/societies/__init__.py +4 -2
- camel/societies/babyagi_playing.py +2 -2
- camel/societies/role_playing.py +201 -80
- camel/societies/workforce/__init__.py +10 -3
- camel/societies/workforce/base.py +2 -2
- camel/societies/workforce/events.py +145 -0
- camel/societies/workforce/prompts.py +259 -33
- camel/societies/workforce/role_playing_worker.py +88 -31
- camel/societies/workforce/single_agent_worker.py +638 -40
- camel/societies/workforce/structured_output_handler.py +512 -0
- camel/societies/workforce/task_channel.py +182 -38
- camel/societies/workforce/utils.py +780 -65
- camel/societies/workforce/worker.py +92 -26
- camel/societies/workforce/workflow_memory_manager.py +1746 -0
- camel/societies/workforce/workforce.py +5354 -372
- camel/societies/workforce/workforce_callback.py +103 -0
- camel/societies/workforce/workforce_logger.py +647 -0
- camel/societies/workforce/workforce_metrics.py +33 -0
- camel/storages/__init__.py +6 -2
- camel/storages/graph_storages/__init__.py +2 -2
- camel/storages/graph_storages/base.py +2 -2
- camel/storages/graph_storages/graph_element.py +2 -2
- camel/storages/graph_storages/nebula_graph.py +4 -4
- camel/storages/graph_storages/neo4j_graph.py +7 -7
- camel/storages/key_value_storages/__init__.py +2 -2
- camel/storages/key_value_storages/base.py +2 -2
- camel/storages/key_value_storages/in_memory.py +2 -2
- camel/storages/key_value_storages/json.py +17 -4
- camel/storages/key_value_storages/mem0_cloud.py +50 -49
- camel/storages/key_value_storages/redis.py +2 -2
- camel/storages/object_storages/__init__.py +2 -2
- camel/storages/object_storages/amazon_s3.py +2 -2
- camel/storages/object_storages/azure_blob.py +2 -2
- camel/storages/object_storages/base.py +2 -2
- camel/storages/object_storages/google_cloud.py +3 -3
- camel/storages/vectordb_storages/__init__.py +8 -2
- camel/storages/vectordb_storages/base.py +2 -2
- camel/storages/vectordb_storages/chroma.py +731 -0
- camel/storages/vectordb_storages/faiss.py +2 -2
- camel/storages/vectordb_storages/milvus.py +2 -2
- camel/storages/vectordb_storages/oceanbase.py +15 -15
- camel/storages/vectordb_storages/pgvector.py +349 -0
- camel/storages/vectordb_storages/qdrant.py +6 -6
- camel/storages/vectordb_storages/surreal.py +372 -0
- camel/storages/vectordb_storages/tidb.py +11 -8
- camel/storages/vectordb_storages/weaviate.py +2 -2
- camel/tasks/__init__.py +2 -2
- camel/tasks/task.py +348 -26
- camel/tasks/task_prompt.py +3 -3
- camel/terminators/__init__.py +2 -2
- camel/terminators/base.py +2 -2
- camel/terminators/response_terminator.py +2 -2
- camel/terminators/token_limit_terminator.py +2 -2
- camel/toolkits/__init__.py +57 -10
- camel/toolkits/aci_toolkit.py +66 -21
- camel/toolkits/arxiv_toolkit.py +8 -8
- camel/toolkits/ask_news_toolkit.py +2 -2
- camel/toolkits/async_browser_toolkit.py +4 -4
- camel/toolkits/audio_analysis_toolkit.py +3 -3
- camel/toolkits/base.py +106 -6
- camel/toolkits/bohrium_toolkit.py +2 -2
- camel/toolkits/browser_toolkit.py +34 -21
- camel/toolkits/browser_toolkit_commons.py +4 -4
- camel/toolkits/code_execution.py +31 -4
- camel/toolkits/context_summarizer_toolkit.py +684 -0
- camel/toolkits/craw4ai_toolkit.py +93 -0
- camel/toolkits/dappier_toolkit.py +12 -8
- camel/toolkits/data_commons_toolkit.py +2 -2
- camel/toolkits/dingtalk.py +1135 -0
- camel/toolkits/earth_science_toolkit.py +5367 -0
- camel/toolkits/edgeone_pages_mcp_toolkit.py +49 -0
- camel/toolkits/excel_toolkit.py +905 -71
- camel/toolkits/file_toolkit.py +1402 -0
- camel/toolkits/function_tool.py +205 -27
- camel/toolkits/github_toolkit.py +109 -22
- camel/toolkits/gmail_toolkit.py +1839 -0
- camel/toolkits/google_calendar_toolkit.py +40 -6
- camel/toolkits/google_drive_mcp_toolkit.py +54 -0
- camel/toolkits/google_maps_toolkit.py +2 -2
- camel/toolkits/google_scholar_toolkit.py +2 -2
- camel/toolkits/human_toolkit.py +36 -12
- camel/toolkits/hybrid_browser_toolkit/__init__.py +18 -0
- camel/toolkits/hybrid_browser_toolkit/config_loader.py +185 -0
- camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit.py +246 -0
- camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit_ts.py +1958 -0
- camel/toolkits/hybrid_browser_toolkit/installer.py +203 -0
- camel/toolkits/hybrid_browser_toolkit/ts/package-lock.json +4589 -0
- camel/toolkits/hybrid_browser_toolkit/ts/package.json +33 -0
- camel/toolkits/hybrid_browser_toolkit/ts/src/browser-scripts.js +125 -0
- camel/toolkits/hybrid_browser_toolkit/ts/src/browser-session.ts +1940 -0
- camel/toolkits/hybrid_browser_toolkit/ts/src/config-loader.ts +233 -0
- camel/toolkits/hybrid_browser_toolkit/ts/src/hybrid-browser-toolkit.ts +589 -0
- camel/toolkits/hybrid_browser_toolkit/ts/src/index.ts +7 -0
- camel/toolkits/hybrid_browser_toolkit/ts/src/parent-child-filter.ts +226 -0
- camel/toolkits/hybrid_browser_toolkit/ts/src/snapshot-parser.ts +219 -0
- camel/toolkits/hybrid_browser_toolkit/ts/src/som-screenshot-injected.ts +543 -0
- camel/toolkits/hybrid_browser_toolkit/ts/src/types.ts +129 -0
- camel/toolkits/hybrid_browser_toolkit/ts/tsconfig.json +27 -0
- camel/toolkits/hybrid_browser_toolkit/ts/websocket-server.js +325 -0
- camel/toolkits/hybrid_browser_toolkit/ws_wrapper.py +1037 -0
- camel/toolkits/hybrid_browser_toolkit_py/__init__.py +17 -0
- camel/toolkits/hybrid_browser_toolkit_py/actions.py +575 -0
- camel/toolkits/hybrid_browser_toolkit_py/agent.py +311 -0
- camel/toolkits/hybrid_browser_toolkit_py/browser_session.py +787 -0
- camel/toolkits/hybrid_browser_toolkit_py/config_loader.py +490 -0
- camel/toolkits/hybrid_browser_toolkit_py/hybrid_browser_toolkit.py +2390 -0
- camel/toolkits/hybrid_browser_toolkit_py/snapshot.py +233 -0
- camel/toolkits/hybrid_browser_toolkit_py/stealth_script.js +0 -0
- camel/toolkits/hybrid_browser_toolkit_py/unified_analyzer.js +1043 -0
- camel/toolkits/image_analysis_toolkit.py +3 -6
- camel/toolkits/image_generation_toolkit.py +390 -0
- camel/toolkits/jina_reranker_toolkit.py +5 -6
- camel/toolkits/klavis_toolkit.py +7 -3
- camel/toolkits/linkedin_toolkit.py +2 -2
- camel/toolkits/markitdown_toolkit.py +104 -0
- camel/toolkits/math_toolkit.py +66 -12
- camel/toolkits/mcp_toolkit.py +412 -36
- camel/toolkits/memory_toolkit.py +7 -3
- camel/toolkits/meshy_toolkit.py +2 -2
- camel/toolkits/message_agent_toolkit.py +608 -0
- camel/toolkits/message_integration.py +728 -0
- camel/toolkits/microsoft_outlook_mail_toolkit.py +1885 -0
- camel/toolkits/mineru_toolkit.py +2 -2
- camel/toolkits/minimax_mcp_toolkit.py +195 -0
- camel/toolkits/networkx_toolkit.py +2 -2
- camel/toolkits/note_taking_toolkit.py +277 -0
- camel/toolkits/notion_mcp_toolkit.py +224 -0
- camel/toolkits/notion_toolkit.py +2 -2
- camel/toolkits/open_api_specs/biztoc/__init__.py +2 -2
- camel/toolkits/open_api_specs/biztoc/ai-plugin.json +1 -1
- camel/toolkits/open_api_specs/coursera/__init__.py +2 -2
- camel/toolkits/open_api_specs/create_qr_code/__init__.py +2 -2
- camel/toolkits/open_api_specs/klarna/__init__.py +2 -2
- camel/toolkits/open_api_specs/nasa_apod/__init__.py +2 -2
- camel/toolkits/open_api_specs/outschool/__init__.py +2 -2
- camel/toolkits/open_api_specs/outschool/ai-plugin.json +1 -1
- camel/toolkits/open_api_specs/outschool/openapi.yaml +1 -1
- camel/toolkits/open_api_specs/outschool/paths/__init__.py +2 -2
- camel/toolkits/open_api_specs/outschool/paths/get_classes.py +2 -2
- camel/toolkits/open_api_specs/outschool/paths/search_teachers.py +2 -2
- camel/toolkits/open_api_specs/security_config.py +2 -2
- camel/toolkits/open_api_specs/speak/__init__.py +2 -2
- camel/toolkits/open_api_specs/web_scraper/__init__.py +2 -2
- camel/toolkits/open_api_specs/web_scraper/ai-plugin.json +1 -1
- camel/toolkits/open_api_specs/web_scraper/paths/__init__.py +2 -2
- camel/toolkits/open_api_specs/web_scraper/paths/scraper.py +2 -2
- camel/toolkits/open_api_toolkit.py +2 -2
- camel/toolkits/openbb_toolkit.py +7 -3
- camel/toolkits/origene_mcp_toolkit.py +56 -0
- camel/toolkits/page_script.js +53 -53
- camel/toolkits/playwright_mcp_toolkit.py +13 -31
- camel/toolkits/pptx_toolkit.py +36 -23
- camel/toolkits/pubmed_toolkit.py +2 -2
- camel/toolkits/pulse_mcp_search_toolkit.py +2 -2
- camel/toolkits/pyautogui_toolkit.py +2 -2
- camel/toolkits/reddit_toolkit.py +2 -2
- camel/toolkits/resend_toolkit.py +168 -0
- camel/toolkits/retrieval_toolkit.py +2 -2
- camel/toolkits/screenshot_toolkit.py +213 -0
- camel/toolkits/search_toolkit.py +606 -156
- camel/toolkits/searxng_toolkit.py +2 -2
- camel/toolkits/semantic_scholar_toolkit.py +2 -2
- camel/toolkits/slack_toolkit.py +108 -58
- camel/toolkits/sql_toolkit.py +712 -0
- camel/toolkits/stripe_toolkit.py +2 -2
- camel/toolkits/sympy_toolkit.py +3 -3
- camel/toolkits/task_planning_toolkit.py +5 -5
- camel/toolkits/terminal_toolkit/__init__.py +18 -0
- camel/toolkits/terminal_toolkit/terminal_toolkit.py +1281 -0
- camel/toolkits/terminal_toolkit/utils.py +659 -0
- camel/toolkits/thinking_toolkit.py +3 -3
- camel/toolkits/twitter_toolkit.py +2 -2
- camel/toolkits/vertex_ai_veo_toolkit.py +590 -0
- camel/toolkits/video_analysis_toolkit.py +109 -29
- camel/toolkits/video_download_toolkit.py +19 -16
- camel/toolkits/weather_toolkit.py +2 -2
- camel/toolkits/web_deploy_toolkit.py +1219 -0
- camel/toolkits/wechat_official_toolkit.py +483 -0
- camel/toolkits/whatsapp_toolkit.py +2 -2
- camel/toolkits/wolfram_alpha_toolkit.py +2 -2
- camel/toolkits/zapier_toolkit.py +7 -3
- camel/types/__init__.py +4 -4
- camel/types/agents/__init__.py +2 -2
- camel/types/agents/tool_calling_record.py +6 -3
- camel/types/enums.py +381 -41
- camel/types/mcp_registries.py +2 -2
- camel/types/openai_types.py +4 -4
- camel/types/unified_model_type.py +46 -10
- camel/utils/__init__.py +5 -2
- camel/utils/agent_context.py +41 -0
- camel/utils/async_func.py +2 -2
- camel/utils/chunker/__init__.py +2 -2
- camel/utils/chunker/base.py +2 -2
- camel/utils/chunker/code_chunker.py +2 -2
- camel/utils/chunker/uio_chunker.py +2 -2
- camel/utils/commons.py +38 -7
- camel/utils/constants.py +5 -2
- camel/utils/context_utils.py +1134 -0
- camel/utils/deduplication.py +2 -2
- camel/utils/filename.py +2 -2
- camel/utils/langfuse.py +18 -10
- camel/utils/mcp.py +140 -6
- camel/utils/mcp_client.py +48 -38
- camel/utils/message_summarizer.py +148 -0
- camel/utils/response_format.py +2 -2
- camel/utils/token_counting.py +45 -22
- camel/utils/tool_result.py +44 -0
- camel/verifiers/__init__.py +2 -2
- camel/verifiers/base.py +2 -2
- camel/verifiers/math_verifier.py +2 -2
- camel/verifiers/models.py +2 -2
- camel/verifiers/physics_verifier.py +2 -2
- camel/verifiers/python_verifier.py +2 -2
- {camel_ai-0.2.65.dist-info → camel_ai-0.2.83a6.dist-info}/METADATA +355 -117
- camel_ai-0.2.83a6.dist-info/RECORD +511 -0
- {camel_ai-0.2.65.dist-info → camel_ai-0.2.83a6.dist-info}/WHEEL +1 -1
- {camel_ai-0.2.65.dist-info → camel_ai-0.2.83a6.dist-info}/licenses/LICENSE +1 -1
- camel/loaders/pandas_reader.py +0 -368
- camel/toolkits/dalle_toolkit.py +0 -175
- camel/toolkits/file_write_toolkit.py +0 -444
- camel/toolkits/openai_agent_toolkit.py +0 -135
- camel/toolkits/terminal_toolkit.py +0 -1037
- 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}")
|