ag2 0.9.1__py3-none-any.whl → 0.9.1.post0__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 ag2 might be problematic. Click here for more details.
- {ag2-0.9.1.dist-info → ag2-0.9.1.post0.dist-info}/METADATA +264 -73
- ag2-0.9.1.post0.dist-info/RECORD +392 -0
- {ag2-0.9.1.dist-info → ag2-0.9.1.post0.dist-info}/WHEEL +1 -2
- autogen/__init__.py +89 -0
- autogen/_website/__init__.py +3 -0
- autogen/_website/generate_api_references.py +427 -0
- autogen/_website/generate_mkdocs.py +1174 -0
- autogen/_website/notebook_processor.py +476 -0
- autogen/_website/process_notebooks.py +656 -0
- autogen/_website/utils.py +412 -0
- autogen/agentchat/__init__.py +44 -0
- autogen/agentchat/agent.py +182 -0
- autogen/agentchat/assistant_agent.py +85 -0
- autogen/agentchat/chat.py +309 -0
- autogen/agentchat/contrib/__init__.py +5 -0
- autogen/agentchat/contrib/agent_eval/README.md +7 -0
- autogen/agentchat/contrib/agent_eval/agent_eval.py +108 -0
- autogen/agentchat/contrib/agent_eval/criterion.py +43 -0
- autogen/agentchat/contrib/agent_eval/critic_agent.py +44 -0
- autogen/agentchat/contrib/agent_eval/quantifier_agent.py +39 -0
- autogen/agentchat/contrib/agent_eval/subcritic_agent.py +45 -0
- autogen/agentchat/contrib/agent_eval/task.py +42 -0
- autogen/agentchat/contrib/agent_optimizer.py +429 -0
- autogen/agentchat/contrib/capabilities/__init__.py +5 -0
- autogen/agentchat/contrib/capabilities/agent_capability.py +20 -0
- autogen/agentchat/contrib/capabilities/generate_images.py +301 -0
- autogen/agentchat/contrib/capabilities/teachability.py +393 -0
- autogen/agentchat/contrib/capabilities/text_compressors.py +66 -0
- autogen/agentchat/contrib/capabilities/tools_capability.py +22 -0
- autogen/agentchat/contrib/capabilities/transform_messages.py +93 -0
- autogen/agentchat/contrib/capabilities/transforms.py +566 -0
- autogen/agentchat/contrib/capabilities/transforms_util.py +122 -0
- autogen/agentchat/contrib/capabilities/vision_capability.py +214 -0
- autogen/agentchat/contrib/captainagent/__init__.py +9 -0
- autogen/agentchat/contrib/captainagent/agent_builder.py +790 -0
- autogen/agentchat/contrib/captainagent/captainagent.py +512 -0
- autogen/agentchat/contrib/captainagent/tool_retriever.py +335 -0
- autogen/agentchat/contrib/captainagent/tools/README.md +44 -0
- autogen/agentchat/contrib/captainagent/tools/__init__.py +5 -0
- autogen/agentchat/contrib/captainagent/tools/data_analysis/calculate_correlation.py +40 -0
- autogen/agentchat/contrib/captainagent/tools/data_analysis/calculate_skewness_and_kurtosis.py +28 -0
- autogen/agentchat/contrib/captainagent/tools/data_analysis/detect_outlier_iqr.py +28 -0
- autogen/agentchat/contrib/captainagent/tools/data_analysis/detect_outlier_zscore.py +28 -0
- autogen/agentchat/contrib/captainagent/tools/data_analysis/explore_csv.py +21 -0
- autogen/agentchat/contrib/captainagent/tools/data_analysis/shapiro_wilk_test.py +30 -0
- autogen/agentchat/contrib/captainagent/tools/information_retrieval/arxiv_download.py +27 -0
- autogen/agentchat/contrib/captainagent/tools/information_retrieval/arxiv_search.py +53 -0
- autogen/agentchat/contrib/captainagent/tools/information_retrieval/extract_pdf_image.py +53 -0
- autogen/agentchat/contrib/captainagent/tools/information_retrieval/extract_pdf_text.py +38 -0
- autogen/agentchat/contrib/captainagent/tools/information_retrieval/get_wikipedia_text.py +21 -0
- autogen/agentchat/contrib/captainagent/tools/information_retrieval/get_youtube_caption.py +34 -0
- autogen/agentchat/contrib/captainagent/tools/information_retrieval/image_qa.py +60 -0
- autogen/agentchat/contrib/captainagent/tools/information_retrieval/optical_character_recognition.py +61 -0
- autogen/agentchat/contrib/captainagent/tools/information_retrieval/perform_web_search.py +47 -0
- autogen/agentchat/contrib/captainagent/tools/information_retrieval/scrape_wikipedia_tables.py +33 -0
- autogen/agentchat/contrib/captainagent/tools/information_retrieval/transcribe_audio_file.py +21 -0
- autogen/agentchat/contrib/captainagent/tools/information_retrieval/youtube_download.py +35 -0
- autogen/agentchat/contrib/captainagent/tools/math/calculate_circle_area_from_diameter.py +21 -0
- autogen/agentchat/contrib/captainagent/tools/math/calculate_day_of_the_week.py +18 -0
- autogen/agentchat/contrib/captainagent/tools/math/calculate_fraction_sum.py +28 -0
- autogen/agentchat/contrib/captainagent/tools/math/calculate_matrix_power.py +31 -0
- autogen/agentchat/contrib/captainagent/tools/math/calculate_reflected_point.py +16 -0
- autogen/agentchat/contrib/captainagent/tools/math/complex_numbers_product.py +25 -0
- autogen/agentchat/contrib/captainagent/tools/math/compute_currency_conversion.py +23 -0
- autogen/agentchat/contrib/captainagent/tools/math/count_distinct_permutations.py +27 -0
- autogen/agentchat/contrib/captainagent/tools/math/evaluate_expression.py +28 -0
- autogen/agentchat/contrib/captainagent/tools/math/find_continuity_point.py +34 -0
- autogen/agentchat/contrib/captainagent/tools/math/fraction_to_mixed_numbers.py +39 -0
- autogen/agentchat/contrib/captainagent/tools/math/modular_inverse_sum.py +23 -0
- autogen/agentchat/contrib/captainagent/tools/math/simplify_mixed_numbers.py +36 -0
- autogen/agentchat/contrib/captainagent/tools/math/sum_of_digit_factorials.py +15 -0
- autogen/agentchat/contrib/captainagent/tools/math/sum_of_primes_below.py +15 -0
- autogen/agentchat/contrib/captainagent/tools/requirements.txt +10 -0
- autogen/agentchat/contrib/captainagent/tools/tool_description.tsv +34 -0
- autogen/agentchat/contrib/gpt_assistant_agent.py +526 -0
- autogen/agentchat/contrib/graph_rag/__init__.py +9 -0
- autogen/agentchat/contrib/graph_rag/document.py +29 -0
- autogen/agentchat/contrib/graph_rag/falkor_graph_query_engine.py +170 -0
- autogen/agentchat/contrib/graph_rag/falkor_graph_rag_capability.py +103 -0
- autogen/agentchat/contrib/graph_rag/graph_query_engine.py +53 -0
- autogen/agentchat/contrib/graph_rag/graph_rag_capability.py +63 -0
- autogen/agentchat/contrib/graph_rag/neo4j_graph_query_engine.py +268 -0
- autogen/agentchat/contrib/graph_rag/neo4j_graph_rag_capability.py +83 -0
- autogen/agentchat/contrib/graph_rag/neo4j_native_graph_query_engine.py +210 -0
- autogen/agentchat/contrib/graph_rag/neo4j_native_graph_rag_capability.py +93 -0
- autogen/agentchat/contrib/img_utils.py +397 -0
- autogen/agentchat/contrib/llamaindex_conversable_agent.py +117 -0
- autogen/agentchat/contrib/llava_agent.py +187 -0
- autogen/agentchat/contrib/math_user_proxy_agent.py +464 -0
- autogen/agentchat/contrib/multimodal_conversable_agent.py +125 -0
- autogen/agentchat/contrib/qdrant_retrieve_user_proxy_agent.py +324 -0
- autogen/agentchat/contrib/rag/__init__.py +10 -0
- autogen/agentchat/contrib/rag/chromadb_query_engine.py +272 -0
- autogen/agentchat/contrib/rag/llamaindex_query_engine.py +198 -0
- autogen/agentchat/contrib/rag/mongodb_query_engine.py +329 -0
- autogen/agentchat/contrib/rag/query_engine.py +74 -0
- autogen/agentchat/contrib/retrieve_assistant_agent.py +56 -0
- autogen/agentchat/contrib/retrieve_user_proxy_agent.py +703 -0
- autogen/agentchat/contrib/society_of_mind_agent.py +199 -0
- autogen/agentchat/contrib/swarm_agent.py +1425 -0
- autogen/agentchat/contrib/text_analyzer_agent.py +79 -0
- autogen/agentchat/contrib/vectordb/__init__.py +5 -0
- autogen/agentchat/contrib/vectordb/base.py +232 -0
- autogen/agentchat/contrib/vectordb/chromadb.py +315 -0
- autogen/agentchat/contrib/vectordb/couchbase.py +407 -0
- autogen/agentchat/contrib/vectordb/mongodb.py +550 -0
- autogen/agentchat/contrib/vectordb/pgvectordb.py +928 -0
- autogen/agentchat/contrib/vectordb/qdrant.py +320 -0
- autogen/agentchat/contrib/vectordb/utils.py +126 -0
- autogen/agentchat/contrib/web_surfer.py +303 -0
- autogen/agentchat/conversable_agent.py +4020 -0
- autogen/agentchat/group/__init__.py +64 -0
- autogen/agentchat/group/available_condition.py +91 -0
- autogen/agentchat/group/context_condition.py +77 -0
- autogen/agentchat/group/context_expression.py +238 -0
- autogen/agentchat/group/context_str.py +41 -0
- autogen/agentchat/group/context_variables.py +192 -0
- autogen/agentchat/group/group_tool_executor.py +202 -0
- autogen/agentchat/group/group_utils.py +591 -0
- autogen/agentchat/group/handoffs.py +244 -0
- autogen/agentchat/group/llm_condition.py +93 -0
- autogen/agentchat/group/multi_agent_chat.py +237 -0
- autogen/agentchat/group/on_condition.py +58 -0
- autogen/agentchat/group/on_context_condition.py +54 -0
- autogen/agentchat/group/patterns/__init__.py +18 -0
- autogen/agentchat/group/patterns/auto.py +159 -0
- autogen/agentchat/group/patterns/manual.py +176 -0
- autogen/agentchat/group/patterns/pattern.py +288 -0
- autogen/agentchat/group/patterns/random.py +106 -0
- autogen/agentchat/group/patterns/round_robin.py +117 -0
- autogen/agentchat/group/reply_result.py +26 -0
- autogen/agentchat/group/speaker_selection_result.py +41 -0
- autogen/agentchat/group/targets/__init__.py +4 -0
- autogen/agentchat/group/targets/group_chat_target.py +132 -0
- autogen/agentchat/group/targets/group_manager_target.py +151 -0
- autogen/agentchat/group/targets/transition_target.py +413 -0
- autogen/agentchat/group/targets/transition_utils.py +6 -0
- autogen/agentchat/groupchat.py +1694 -0
- autogen/agentchat/realtime/__init__.py +3 -0
- autogen/agentchat/realtime/experimental/__init__.py +20 -0
- autogen/agentchat/realtime/experimental/audio_adapters/__init__.py +8 -0
- autogen/agentchat/realtime/experimental/audio_adapters/twilio_audio_adapter.py +148 -0
- autogen/agentchat/realtime/experimental/audio_adapters/websocket_audio_adapter.py +139 -0
- autogen/agentchat/realtime/experimental/audio_observer.py +42 -0
- autogen/agentchat/realtime/experimental/clients/__init__.py +15 -0
- autogen/agentchat/realtime/experimental/clients/gemini/__init__.py +7 -0
- autogen/agentchat/realtime/experimental/clients/gemini/client.py +274 -0
- autogen/agentchat/realtime/experimental/clients/oai/__init__.py +8 -0
- autogen/agentchat/realtime/experimental/clients/oai/base_client.py +220 -0
- autogen/agentchat/realtime/experimental/clients/oai/rtc_client.py +243 -0
- autogen/agentchat/realtime/experimental/clients/oai/utils.py +48 -0
- autogen/agentchat/realtime/experimental/clients/realtime_client.py +190 -0
- autogen/agentchat/realtime/experimental/function_observer.py +85 -0
- autogen/agentchat/realtime/experimental/realtime_agent.py +158 -0
- autogen/agentchat/realtime/experimental/realtime_events.py +42 -0
- autogen/agentchat/realtime/experimental/realtime_observer.py +100 -0
- autogen/agentchat/realtime/experimental/realtime_swarm.py +475 -0
- autogen/agentchat/realtime/experimental/websockets.py +21 -0
- autogen/agentchat/realtime_agent/__init__.py +21 -0
- autogen/agentchat/user_proxy_agent.py +111 -0
- autogen/agentchat/utils.py +206 -0
- autogen/agents/__init__.py +3 -0
- autogen/agents/contrib/__init__.py +10 -0
- autogen/agents/contrib/time/__init__.py +8 -0
- autogen/agents/contrib/time/time_reply_agent.py +73 -0
- autogen/agents/contrib/time/time_tool_agent.py +51 -0
- autogen/agents/experimental/__init__.py +27 -0
- autogen/agents/experimental/deep_research/__init__.py +7 -0
- autogen/agents/experimental/deep_research/deep_research.py +52 -0
- autogen/agents/experimental/discord/__init__.py +7 -0
- autogen/agents/experimental/discord/discord.py +66 -0
- autogen/agents/experimental/document_agent/__init__.py +19 -0
- autogen/agents/experimental/document_agent/chroma_query_engine.py +316 -0
- autogen/agents/experimental/document_agent/docling_doc_ingest_agent.py +118 -0
- autogen/agents/experimental/document_agent/document_agent.py +461 -0
- autogen/agents/experimental/document_agent/document_conditions.py +50 -0
- autogen/agents/experimental/document_agent/document_utils.py +380 -0
- autogen/agents/experimental/document_agent/inmemory_query_engine.py +220 -0
- autogen/agents/experimental/document_agent/parser_utils.py +130 -0
- autogen/agents/experimental/document_agent/url_utils.py +426 -0
- autogen/agents/experimental/reasoning/__init__.py +7 -0
- autogen/agents/experimental/reasoning/reasoning_agent.py +1178 -0
- autogen/agents/experimental/slack/__init__.py +7 -0
- autogen/agents/experimental/slack/slack.py +73 -0
- autogen/agents/experimental/telegram/__init__.py +7 -0
- autogen/agents/experimental/telegram/telegram.py +77 -0
- autogen/agents/experimental/websurfer/__init__.py +7 -0
- autogen/agents/experimental/websurfer/websurfer.py +62 -0
- autogen/agents/experimental/wikipedia/__init__.py +7 -0
- autogen/agents/experimental/wikipedia/wikipedia.py +90 -0
- autogen/browser_utils.py +309 -0
- autogen/cache/__init__.py +10 -0
- autogen/cache/abstract_cache_base.py +75 -0
- autogen/cache/cache.py +203 -0
- autogen/cache/cache_factory.py +88 -0
- autogen/cache/cosmos_db_cache.py +144 -0
- autogen/cache/disk_cache.py +102 -0
- autogen/cache/in_memory_cache.py +58 -0
- autogen/cache/redis_cache.py +123 -0
- autogen/code_utils.py +596 -0
- autogen/coding/__init__.py +22 -0
- autogen/coding/base.py +119 -0
- autogen/coding/docker_commandline_code_executor.py +268 -0
- autogen/coding/factory.py +47 -0
- autogen/coding/func_with_reqs.py +202 -0
- autogen/coding/jupyter/__init__.py +23 -0
- autogen/coding/jupyter/base.py +36 -0
- autogen/coding/jupyter/docker_jupyter_server.py +167 -0
- autogen/coding/jupyter/embedded_ipython_code_executor.py +182 -0
- autogen/coding/jupyter/import_utils.py +82 -0
- autogen/coding/jupyter/jupyter_client.py +231 -0
- autogen/coding/jupyter/jupyter_code_executor.py +160 -0
- autogen/coding/jupyter/local_jupyter_server.py +172 -0
- autogen/coding/local_commandline_code_executor.py +405 -0
- autogen/coding/markdown_code_extractor.py +45 -0
- autogen/coding/utils.py +56 -0
- autogen/doc_utils.py +34 -0
- autogen/events/__init__.py +7 -0
- autogen/events/agent_events.py +1010 -0
- autogen/events/base_event.py +99 -0
- autogen/events/client_events.py +167 -0
- autogen/events/helpers.py +36 -0
- autogen/events/print_event.py +46 -0
- autogen/exception_utils.py +73 -0
- autogen/extensions/__init__.py +5 -0
- autogen/fast_depends/__init__.py +16 -0
- autogen/fast_depends/_compat.py +80 -0
- autogen/fast_depends/core/__init__.py +14 -0
- autogen/fast_depends/core/build.py +225 -0
- autogen/fast_depends/core/model.py +576 -0
- autogen/fast_depends/dependencies/__init__.py +15 -0
- autogen/fast_depends/dependencies/model.py +29 -0
- autogen/fast_depends/dependencies/provider.py +39 -0
- autogen/fast_depends/library/__init__.py +10 -0
- autogen/fast_depends/library/model.py +46 -0
- autogen/fast_depends/py.typed +6 -0
- autogen/fast_depends/schema.py +66 -0
- autogen/fast_depends/use.py +280 -0
- autogen/fast_depends/utils.py +187 -0
- autogen/formatting_utils.py +83 -0
- autogen/function_utils.py +13 -0
- autogen/graph_utils.py +178 -0
- autogen/import_utils.py +526 -0
- autogen/interop/__init__.py +22 -0
- autogen/interop/crewai/__init__.py +7 -0
- autogen/interop/crewai/crewai.py +88 -0
- autogen/interop/interoperability.py +71 -0
- autogen/interop/interoperable.py +46 -0
- autogen/interop/langchain/__init__.py +8 -0
- autogen/interop/langchain/langchain_chat_model_factory.py +155 -0
- autogen/interop/langchain/langchain_tool.py +82 -0
- autogen/interop/litellm/__init__.py +7 -0
- autogen/interop/litellm/litellm_config_factory.py +113 -0
- autogen/interop/pydantic_ai/__init__.py +7 -0
- autogen/interop/pydantic_ai/pydantic_ai.py +168 -0
- autogen/interop/registry.py +69 -0
- autogen/io/__init__.py +15 -0
- autogen/io/base.py +151 -0
- autogen/io/console.py +56 -0
- autogen/io/processors/__init__.py +12 -0
- autogen/io/processors/base.py +21 -0
- autogen/io/processors/console_event_processor.py +56 -0
- autogen/io/run_response.py +293 -0
- autogen/io/thread_io_stream.py +63 -0
- autogen/io/websockets.py +213 -0
- autogen/json_utils.py +43 -0
- autogen/llm_config.py +379 -0
- autogen/logger/__init__.py +11 -0
- autogen/logger/base_logger.py +128 -0
- autogen/logger/file_logger.py +261 -0
- autogen/logger/logger_factory.py +42 -0
- autogen/logger/logger_utils.py +57 -0
- autogen/logger/sqlite_logger.py +523 -0
- autogen/math_utils.py +339 -0
- autogen/mcp/__init__.py +7 -0
- autogen/mcp/mcp_client.py +208 -0
- autogen/messages/__init__.py +7 -0
- autogen/messages/agent_messages.py +948 -0
- autogen/messages/base_message.py +107 -0
- autogen/messages/client_messages.py +171 -0
- autogen/messages/print_message.py +49 -0
- autogen/oai/__init__.py +53 -0
- autogen/oai/anthropic.py +714 -0
- autogen/oai/bedrock.py +628 -0
- autogen/oai/cerebras.py +299 -0
- autogen/oai/client.py +1435 -0
- autogen/oai/client_utils.py +169 -0
- autogen/oai/cohere.py +479 -0
- autogen/oai/gemini.py +990 -0
- autogen/oai/gemini_types.py +129 -0
- autogen/oai/groq.py +305 -0
- autogen/oai/mistral.py +303 -0
- autogen/oai/oai_models/__init__.py +11 -0
- autogen/oai/oai_models/_models.py +16 -0
- autogen/oai/oai_models/chat_completion.py +87 -0
- autogen/oai/oai_models/chat_completion_audio.py +32 -0
- autogen/oai/oai_models/chat_completion_message.py +86 -0
- autogen/oai/oai_models/chat_completion_message_tool_call.py +37 -0
- autogen/oai/oai_models/chat_completion_token_logprob.py +63 -0
- autogen/oai/oai_models/completion_usage.py +60 -0
- autogen/oai/ollama.py +643 -0
- autogen/oai/openai_utils.py +881 -0
- autogen/oai/together.py +370 -0
- autogen/retrieve_utils.py +491 -0
- autogen/runtime_logging.py +160 -0
- autogen/token_count_utils.py +267 -0
- autogen/tools/__init__.py +20 -0
- autogen/tools/contrib/__init__.py +9 -0
- autogen/tools/contrib/time/__init__.py +7 -0
- autogen/tools/contrib/time/time.py +41 -0
- autogen/tools/dependency_injection.py +254 -0
- autogen/tools/experimental/__init__.py +43 -0
- autogen/tools/experimental/browser_use/__init__.py +7 -0
- autogen/tools/experimental/browser_use/browser_use.py +161 -0
- autogen/tools/experimental/crawl4ai/__init__.py +7 -0
- autogen/tools/experimental/crawl4ai/crawl4ai.py +153 -0
- autogen/tools/experimental/deep_research/__init__.py +7 -0
- autogen/tools/experimental/deep_research/deep_research.py +328 -0
- autogen/tools/experimental/duckduckgo/__init__.py +7 -0
- autogen/tools/experimental/duckduckgo/duckduckgo_search.py +109 -0
- autogen/tools/experimental/google/__init__.py +14 -0
- autogen/tools/experimental/google/authentication/__init__.py +11 -0
- autogen/tools/experimental/google/authentication/credentials_hosted_provider.py +43 -0
- autogen/tools/experimental/google/authentication/credentials_local_provider.py +91 -0
- autogen/tools/experimental/google/authentication/credentials_provider.py +35 -0
- autogen/tools/experimental/google/drive/__init__.py +9 -0
- autogen/tools/experimental/google/drive/drive_functions.py +124 -0
- autogen/tools/experimental/google/drive/toolkit.py +88 -0
- autogen/tools/experimental/google/model.py +17 -0
- autogen/tools/experimental/google/toolkit_protocol.py +19 -0
- autogen/tools/experimental/google_search/__init__.py +8 -0
- autogen/tools/experimental/google_search/google_search.py +93 -0
- autogen/tools/experimental/google_search/youtube_search.py +181 -0
- autogen/tools/experimental/messageplatform/__init__.py +17 -0
- autogen/tools/experimental/messageplatform/discord/__init__.py +7 -0
- autogen/tools/experimental/messageplatform/discord/discord.py +288 -0
- autogen/tools/experimental/messageplatform/slack/__init__.py +7 -0
- autogen/tools/experimental/messageplatform/slack/slack.py +391 -0
- autogen/tools/experimental/messageplatform/telegram/__init__.py +7 -0
- autogen/tools/experimental/messageplatform/telegram/telegram.py +275 -0
- autogen/tools/experimental/perplexity/__init__.py +7 -0
- autogen/tools/experimental/perplexity/perplexity_search.py +260 -0
- autogen/tools/experimental/tavily/__init__.py +7 -0
- autogen/tools/experimental/tavily/tavily_search.py +183 -0
- autogen/tools/experimental/web_search_preview/__init__.py +7 -0
- autogen/tools/experimental/web_search_preview/web_search_preview.py +114 -0
- autogen/tools/experimental/wikipedia/__init__.py +7 -0
- autogen/tools/experimental/wikipedia/wikipedia.py +287 -0
- autogen/tools/function_utils.py +411 -0
- autogen/tools/tool.py +187 -0
- autogen/tools/toolkit.py +86 -0
- autogen/types.py +29 -0
- autogen/version.py +7 -0
- ag2-0.9.1.dist-info/RECORD +0 -6
- ag2-0.9.1.dist-info/top_level.txt +0 -1
- {ag2-0.9.1.dist-info → ag2-0.9.1.post0.dist-info/licenses}/LICENSE +0 -0
- {ag2-0.9.1.dist-info → ag2-0.9.1.post0.dist-info/licenses}/NOTICE.md +0 -0
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
# Copyright (c) 2023 - 2025, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Optional, Union
|
|
9
|
+
from urllib.parse import urlparse
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel, Field
|
|
12
|
+
|
|
13
|
+
from ....doc_utils import export_module
|
|
14
|
+
from ....import_utils import optional_import_block, require_optional_import
|
|
15
|
+
from .url_utils import ExtensionToFormat, InputFormat, URLAnalyzer
|
|
16
|
+
|
|
17
|
+
with optional_import_block():
|
|
18
|
+
import requests
|
|
19
|
+
from selenium import webdriver
|
|
20
|
+
from selenium.webdriver.chrome.service import Service as ChromeService
|
|
21
|
+
from webdriver_manager.chrome import ChromeDriverManager
|
|
22
|
+
|
|
23
|
+
__all__ = ["handle_input", "preprocess_path"]
|
|
24
|
+
|
|
25
|
+
_logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class QueryType(Enum):
|
|
29
|
+
RAG_QUERY = "RAG_QUERY"
|
|
30
|
+
# COMMON_QUESTION = "COMMON_QUESTION"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Ingest(BaseModel):
|
|
34
|
+
path_or_url: str = Field(description="The path or URL of the documents to ingest.")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Query(BaseModel):
|
|
38
|
+
query_type: QueryType = Field(description="The type of query to perform for the Document Agent.")
|
|
39
|
+
query: str = Field(description="The query to perform for the Document Agent.")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def is_url(url: str) -> bool:
|
|
43
|
+
"""Check if the string is a valid URL.
|
|
44
|
+
|
|
45
|
+
It checks whether the URL has a valid scheme and network location.
|
|
46
|
+
"""
|
|
47
|
+
try:
|
|
48
|
+
url = url.strip()
|
|
49
|
+
result = urlparse(url)
|
|
50
|
+
# urlparse will not raise an exception for invalid URLs, so we need to check the components
|
|
51
|
+
return_bool = bool(result.scheme and result.netloc)
|
|
52
|
+
return return_bool
|
|
53
|
+
except Exception:
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@require_optional_import(["selenium", "webdriver_manager", "requests"], "rag")
|
|
58
|
+
def _download_rendered_html(url: str) -> str:
|
|
59
|
+
"""Downloads a rendered HTML page of a given URL using headless ChromeDriver.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
url (str): URL of the page to download.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
str: The rendered HTML content of the page.
|
|
66
|
+
"""
|
|
67
|
+
# Set up Chrome options
|
|
68
|
+
options = webdriver.ChromeOptions()
|
|
69
|
+
options.add_argument("--headless") # Enable headless mode
|
|
70
|
+
options.add_argument("--disable-gpu") # Disabling GPU hardware acceleration
|
|
71
|
+
options.add_argument("--no-sandbox") # Bypass OS security model
|
|
72
|
+
options.add_argument("--disable-dev-shm-usage") # Overcome limited resource problems
|
|
73
|
+
|
|
74
|
+
# Set the location of the ChromeDriver
|
|
75
|
+
service = ChromeService(ChromeDriverManager().install())
|
|
76
|
+
|
|
77
|
+
# Create a new instance of the Chrome driver with specified options
|
|
78
|
+
driver = webdriver.Chrome(service=service, options=options)
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
# Open a page
|
|
82
|
+
driver.get(url)
|
|
83
|
+
|
|
84
|
+
# Get the rendered HTML
|
|
85
|
+
html_content = driver.page_source
|
|
86
|
+
return str(html_content)
|
|
87
|
+
|
|
88
|
+
finally:
|
|
89
|
+
# Close the browser
|
|
90
|
+
driver.quit()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@require_optional_import(["requests", "selenium", "webdriver_manager"], "rag")
|
|
94
|
+
def _download_binary_file(url: str, output_dir: Path) -> Path:
|
|
95
|
+
"""Downloads a file directly from the given URL.
|
|
96
|
+
|
|
97
|
+
Uses appropriate mode (binary/text) based on file extension or content type.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
url (str): URL of the file to download.
|
|
101
|
+
output_dir (Path): Directory to save the file.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
Path: Path to the saved file.
|
|
105
|
+
"""
|
|
106
|
+
# Ensure output directory exists
|
|
107
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
108
|
+
|
|
109
|
+
# Use URLAnalyzer to get information about the URL
|
|
110
|
+
analyzer = URLAnalyzer(url)
|
|
111
|
+
analysis = analyzer.analyze(test_url=True, follow_redirects=True)
|
|
112
|
+
|
|
113
|
+
# Get file info
|
|
114
|
+
final_url = analysis.get("final_url", url)
|
|
115
|
+
file_type = analysis.get("file_type")
|
|
116
|
+
content_type = analysis.get("mime_type", "")
|
|
117
|
+
|
|
118
|
+
_logger.info(f"Original URL: {url}")
|
|
119
|
+
_logger.info(f"Final URL after redirects: {final_url}")
|
|
120
|
+
_logger.info(f"Detected content type: {content_type}")
|
|
121
|
+
_logger.info(f"Detected file type: {file_type}")
|
|
122
|
+
|
|
123
|
+
# Check if the file type is supported
|
|
124
|
+
if file_type == InputFormat.INVALID:
|
|
125
|
+
raise ValueError(f"File type is not supported: {analysis}")
|
|
126
|
+
|
|
127
|
+
# Parse URL components from the final URL
|
|
128
|
+
parsed_url = urlparse(final_url)
|
|
129
|
+
path = Path(parsed_url.path)
|
|
130
|
+
|
|
131
|
+
# Extract filename and extension from URL
|
|
132
|
+
filename = path.name
|
|
133
|
+
suffix = path.suffix.lower()
|
|
134
|
+
|
|
135
|
+
# For URLs without proper filename/extension, or with generic content types
|
|
136
|
+
if not filename or not suffix:
|
|
137
|
+
# Create a unique filename
|
|
138
|
+
unique_id = abs(hash(url)) % 10000
|
|
139
|
+
|
|
140
|
+
# Determine extension from file type
|
|
141
|
+
if file_type is not None and isinstance(file_type, InputFormat):
|
|
142
|
+
ext = _get_extension_from_file_type(file_type, content_type)
|
|
143
|
+
else:
|
|
144
|
+
ext = None
|
|
145
|
+
|
|
146
|
+
# Create filename
|
|
147
|
+
prefix = "image" if file_type == InputFormat.IMAGE else "download"
|
|
148
|
+
filename = f"{prefix}_{unique_id}{ext}"
|
|
149
|
+
|
|
150
|
+
# Ensure the filename has the correct extension
|
|
151
|
+
if suffix:
|
|
152
|
+
# Check if the extension is valid for the file type
|
|
153
|
+
current_ext = suffix[1:] if suffix.startswith(".") else suffix
|
|
154
|
+
if file_type is not None and isinstance(file_type, InputFormat):
|
|
155
|
+
if not _is_valid_extension_for_file_type(current_ext, file_type):
|
|
156
|
+
# If not, add the correct extension
|
|
157
|
+
ext = _get_extension_from_file_type(file_type, content_type)
|
|
158
|
+
filename = f"{Path(filename).stem}{ext}"
|
|
159
|
+
else:
|
|
160
|
+
ext = _get_extension_from_file_type(InputFormat.INVALID, content_type)
|
|
161
|
+
filename = f"{Path(filename).stem}{ext}"
|
|
162
|
+
else:
|
|
163
|
+
# No extension, add one based on file type
|
|
164
|
+
if file_type is not None and isinstance(file_type, InputFormat):
|
|
165
|
+
ext = _get_extension_from_file_type(file_type, content_type)
|
|
166
|
+
else:
|
|
167
|
+
ext = _get_extension_from_file_type(InputFormat.INVALID, content_type)
|
|
168
|
+
filename = f"{filename}{ext}"
|
|
169
|
+
|
|
170
|
+
_logger.info(f"Using filename: {filename} for URL: {url}")
|
|
171
|
+
|
|
172
|
+
# Create final filepath
|
|
173
|
+
filepath = output_dir / filename
|
|
174
|
+
|
|
175
|
+
# Determine if this is binary or text based on extension
|
|
176
|
+
suffix = Path(filename).suffix.lower()
|
|
177
|
+
text_extensions = [".md", ".txt", ".csv", ".html", ".htm", ".xml", ".json", ".adoc"]
|
|
178
|
+
is_binary = suffix not in text_extensions
|
|
179
|
+
|
|
180
|
+
# Download with appropriate mode
|
|
181
|
+
try:
|
|
182
|
+
if not is_binary:
|
|
183
|
+
_logger.info(f"Downloading as text file: {final_url}")
|
|
184
|
+
response = requests.get(final_url, timeout=30)
|
|
185
|
+
response.raise_for_status()
|
|
186
|
+
|
|
187
|
+
with open(filepath, "w", encoding="utf-8") as f:
|
|
188
|
+
f.write(response.text)
|
|
189
|
+
else:
|
|
190
|
+
_logger.info(f"Downloading as binary file: {final_url}")
|
|
191
|
+
response = requests.get(final_url, stream=True, timeout=30)
|
|
192
|
+
response.raise_for_status()
|
|
193
|
+
|
|
194
|
+
with open(filepath, "wb") as f:
|
|
195
|
+
for chunk in response.iter_content(chunk_size=8192):
|
|
196
|
+
if chunk: # Filter out keep-alive chunks
|
|
197
|
+
f.write(chunk)
|
|
198
|
+
except Exception as e:
|
|
199
|
+
_logger.error(f"Download failed: {e}")
|
|
200
|
+
raise
|
|
201
|
+
|
|
202
|
+
return filepath
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _get_extension_from_file_type(file_type: InputFormat, content_type: str = "") -> str:
|
|
206
|
+
"""Get a file extension based on the file type and content type."""
|
|
207
|
+
# Create a reverse mapping from InputFormat to a default extension
|
|
208
|
+
# We choose the first extension found for each format
|
|
209
|
+
format_to_extension = {}
|
|
210
|
+
for ext, fmt in ExtensionToFormat.items():
|
|
211
|
+
if fmt not in format_to_extension:
|
|
212
|
+
format_to_extension[fmt] = ext
|
|
213
|
+
|
|
214
|
+
# Special case for images: use content type to determine exact image format
|
|
215
|
+
if file_type == InputFormat.IMAGE:
|
|
216
|
+
if "jpeg" in content_type or "jpg" in content_type:
|
|
217
|
+
return ".jpeg"
|
|
218
|
+
elif "png" in content_type:
|
|
219
|
+
return ".png"
|
|
220
|
+
elif "tiff" in content_type:
|
|
221
|
+
return ".tiff"
|
|
222
|
+
elif "bmp" in content_type:
|
|
223
|
+
return ".bmp"
|
|
224
|
+
# Fallback to default image extension
|
|
225
|
+
ext = format_to_extension.get(InputFormat.IMAGE, "png")
|
|
226
|
+
return f".{ext}"
|
|
227
|
+
|
|
228
|
+
# For all other formats, use the default extension
|
|
229
|
+
if file_type in format_to_extension:
|
|
230
|
+
return f".{format_to_extension[file_type]}"
|
|
231
|
+
|
|
232
|
+
return ".bin" # Default for unknown types
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _is_valid_extension_for_file_type(extension: str, file_type: InputFormat) -> bool:
|
|
236
|
+
"""Check if the extension is valid for the given file type."""
|
|
237
|
+
# Remove leading dot if present
|
|
238
|
+
if extension.startswith("."):
|
|
239
|
+
extension = extension[1:]
|
|
240
|
+
|
|
241
|
+
# Check if the extension is in URLAnalyzer.ExtensionToFormat
|
|
242
|
+
# and if it maps to the given file type
|
|
243
|
+
return extension in ExtensionToFormat and ExtensionToFormat[extension] == file_type
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
@require_optional_import(["selenium", "webdriver_manager", "requests"], "rag")
|
|
247
|
+
def download_url(url: Any, output_dir: Optional[Union[str, Path]] = None) -> Path:
|
|
248
|
+
"""Download the content of a URL and save it as a file.
|
|
249
|
+
|
|
250
|
+
For direct file URLs (.md, .pdf, .docx, etc.), downloads the raw file.
|
|
251
|
+
For web pages without file extensions or .html/.htm extensions, uses Selenium to render the content.
|
|
252
|
+
"""
|
|
253
|
+
url = str(url)
|
|
254
|
+
output_dir = Path(output_dir) if output_dir else Path()
|
|
255
|
+
|
|
256
|
+
# Use URLAnalyzer to determine what type of file the URL is
|
|
257
|
+
analyzer = URLAnalyzer(url)
|
|
258
|
+
analysis = analyzer.analyze(test_url=True, follow_redirects=True)
|
|
259
|
+
|
|
260
|
+
# Log the analysis result
|
|
261
|
+
_logger.info(f"URL analysis result: {analysis}")
|
|
262
|
+
|
|
263
|
+
# Get the final URL after redirects
|
|
264
|
+
final_url = analysis.get("final_url", url)
|
|
265
|
+
|
|
266
|
+
# Determine the file type
|
|
267
|
+
is_file = analysis.get("is_file", False)
|
|
268
|
+
file_type = analysis.get("file_type")
|
|
269
|
+
|
|
270
|
+
# If it's a direct file URL (not HTML), download it directly
|
|
271
|
+
if is_file and file_type != InputFormat.HTML and file_type != InputFormat.INVALID:
|
|
272
|
+
_logger.info("Detected direct file URL. Downloading...")
|
|
273
|
+
return _download_binary_file(url=final_url, output_dir=output_dir)
|
|
274
|
+
|
|
275
|
+
# If it's a web page, use Selenium to render it
|
|
276
|
+
if file_type == InputFormat.HTML or not is_file:
|
|
277
|
+
_logger.info("Detected web page. Rendering...")
|
|
278
|
+
rendered_html = _download_rendered_html(final_url)
|
|
279
|
+
|
|
280
|
+
# Determine filename
|
|
281
|
+
parsed_url = urlparse(final_url)
|
|
282
|
+
path = Path(parsed_url.path)
|
|
283
|
+
filename = path.name or "downloaded_content.html"
|
|
284
|
+
if not filename.endswith(".html"):
|
|
285
|
+
filename += ".html"
|
|
286
|
+
|
|
287
|
+
# Save the rendered HTML
|
|
288
|
+
filepath = output_dir / filename
|
|
289
|
+
with open(file=filepath, mode="w", encoding="utf-8") as f:
|
|
290
|
+
f.write(rendered_html)
|
|
291
|
+
|
|
292
|
+
return filepath
|
|
293
|
+
|
|
294
|
+
# Otherwise, try to download as a binary file
|
|
295
|
+
_logger.info("Unknown URL type. Trying to download as binary file...")
|
|
296
|
+
return _download_binary_file(url=final_url, output_dir=output_dir)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def list_files(directory: Union[Path, str]) -> list[Path]:
|
|
300
|
+
"""Recursively list all files in a directory.
|
|
301
|
+
|
|
302
|
+
This function will raise an exception if the directory does not exist.
|
|
303
|
+
"""
|
|
304
|
+
path = Path(directory)
|
|
305
|
+
|
|
306
|
+
if not path.is_dir():
|
|
307
|
+
raise ValueError(f"The directory {directory} does not exist.")
|
|
308
|
+
|
|
309
|
+
return [f for f in path.rglob("*") if f.is_file()]
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
@export_module("autogen.agents.experimental.document_agent")
|
|
313
|
+
def handle_input(input_path: Union[Path, str], output_dir: Union[Path, str] = "./output") -> list[Path]:
|
|
314
|
+
"""Process the input string and return the appropriate file paths"""
|
|
315
|
+
|
|
316
|
+
output_dir = preprocess_path(str_or_path=output_dir, is_dir=True, mk_path=True)
|
|
317
|
+
if isinstance(input_path, str) and is_url(input_path):
|
|
318
|
+
_logger.info("Detected URL. Downloading content...")
|
|
319
|
+
try:
|
|
320
|
+
return [download_url(url=input_path, output_dir=output_dir)]
|
|
321
|
+
except Exception as e:
|
|
322
|
+
raise e
|
|
323
|
+
|
|
324
|
+
if isinstance(input_path, str):
|
|
325
|
+
input_path = Path(input_path)
|
|
326
|
+
if not input_path.exists():
|
|
327
|
+
raise ValueError("The input provided does not exist.")
|
|
328
|
+
elif input_path.is_dir():
|
|
329
|
+
_logger.info("Detected directory. Listing files...")
|
|
330
|
+
return list_files(directory=input_path)
|
|
331
|
+
elif input_path.is_file():
|
|
332
|
+
_logger.info("Detected file. Returning file path...")
|
|
333
|
+
return [input_path]
|
|
334
|
+
else:
|
|
335
|
+
raise ValueError("The input provided is neither a URL, directory, nor a file path.")
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
@export_module("autogen.agents.experimental.document_agent")
|
|
339
|
+
def preprocess_path(
|
|
340
|
+
str_or_path: Union[Path, str], mk_path: bool = False, is_file: bool = False, is_dir: bool = True
|
|
341
|
+
) -> Path:
|
|
342
|
+
"""Preprocess the path for file operations.
|
|
343
|
+
|
|
344
|
+
Args:
|
|
345
|
+
str_or_path (Union[Path, str]): The path to be processed.
|
|
346
|
+
mk_path (bool, optional): Whether to create the path if it doesn't exist. Default is True.
|
|
347
|
+
is_file (bool, optional): Whether the path is a file. Default is False.
|
|
348
|
+
is_dir (bool, optional): Whether the path is a directory. Default is True.
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
Path: The preprocessed path.
|
|
352
|
+
"""
|
|
353
|
+
|
|
354
|
+
# Convert the input to a Path object if it's a string
|
|
355
|
+
temp_path = Path(str_or_path)
|
|
356
|
+
|
|
357
|
+
# Ensure the path is absolute
|
|
358
|
+
absolute_path = temp_path.absolute()
|
|
359
|
+
absolute_path = absolute_path.resolve()
|
|
360
|
+
if absolute_path.exists():
|
|
361
|
+
return absolute_path
|
|
362
|
+
|
|
363
|
+
# Check if the path should be a file or directory
|
|
364
|
+
if is_file and is_dir:
|
|
365
|
+
raise ValueError("Path cannot be both a file and a directory.")
|
|
366
|
+
|
|
367
|
+
# If mk_path is True, create the directory or parent directory
|
|
368
|
+
if mk_path:
|
|
369
|
+
if is_file and not absolute_path.parent.exists():
|
|
370
|
+
absolute_path.parent.mkdir(parents=True, exist_ok=True)
|
|
371
|
+
elif is_dir and not absolute_path.exists():
|
|
372
|
+
absolute_path.mkdir(parents=True, exist_ok=True)
|
|
373
|
+
|
|
374
|
+
# Perform checks based on is_file and is_dir flags
|
|
375
|
+
if is_file and not absolute_path.is_file():
|
|
376
|
+
raise FileNotFoundError(f"File not found: {absolute_path}")
|
|
377
|
+
elif is_dir and not absolute_path.is_dir():
|
|
378
|
+
raise NotADirectoryError(f"Directory not found: {absolute_path}")
|
|
379
|
+
|
|
380
|
+
return absolute_path
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# Copyright (c) 2023 - 2025, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
import copy
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import TYPE_CHECKING, Any, Optional, Sequence, Union
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel
|
|
12
|
+
|
|
13
|
+
from .... import ConversableAgent
|
|
14
|
+
from ....agentchat.contrib.rag import RAGQueryEngine
|
|
15
|
+
from ....doc_utils import export_module
|
|
16
|
+
from ....llm_config import LLMConfig
|
|
17
|
+
|
|
18
|
+
__all__ = ["InMemoryQueryEngine"]
|
|
19
|
+
|
|
20
|
+
# REPLIES
|
|
21
|
+
QUERY_NO_INGESTIONS_REPLY = "Sorry, please ingest some documents/URLs before querying." # Default response for queries without ingested documents
|
|
22
|
+
EMPTY_RESPONSE_REPLY = "Sorry, I couldn't find any information on that. If you haven't ingested any documents, please try that." # Default response for queries without results
|
|
23
|
+
ERROR_RESPONSE_REPLY = "Sorry, there was an error processing your query: " # Default response for queries with errors
|
|
24
|
+
COULD_NOT_ANSWER_REPLY = "Sorry, I couldn't answer that question from the ingested documents/URLs" # Default response for queries that could not be answered
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# Documents and Content structure
|
|
28
|
+
class DocumentStore(BaseModel):
|
|
29
|
+
ingestation_name: str
|
|
30
|
+
content: str
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# Answer question structure
|
|
34
|
+
class QueryAnswer(BaseModel):
|
|
35
|
+
could_answer: bool
|
|
36
|
+
answer: str
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@export_module("autogen.agents.experimental")
|
|
40
|
+
class InMemoryQueryEngine:
|
|
41
|
+
"""
|
|
42
|
+
This engine stores ingested documents in memory and then injects them into an internal agent's system message for answering queries.
|
|
43
|
+
|
|
44
|
+
This implements the autogen.agentchat.contrib.rag.RAGQueryEngine protocol.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
llm_config: Union[LLMConfig, dict[str, Any]],
|
|
50
|
+
) -> None:
|
|
51
|
+
# Deep copy the llm config to avoid changing the original
|
|
52
|
+
structured_config = copy.deepcopy(llm_config)
|
|
53
|
+
|
|
54
|
+
# The query agent will answer with a structured output
|
|
55
|
+
structured_config["response_format"] = QueryAnswer
|
|
56
|
+
|
|
57
|
+
# Our agents for querying
|
|
58
|
+
self._query_agent = ConversableAgent(
|
|
59
|
+
name="inmemory_query_agent",
|
|
60
|
+
llm_config=structured_config,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# In-memory storage for ingested documents
|
|
64
|
+
self._ingested_documents: list[DocumentStore] = []
|
|
65
|
+
|
|
66
|
+
def query(self, question: str, *args: Any, **kwargs: Any) -> str:
|
|
67
|
+
"""Run a query against the ingested documents and return the answer."""
|
|
68
|
+
|
|
69
|
+
# If no documents have been ingested, return an empty response
|
|
70
|
+
if not self._ingested_documents:
|
|
71
|
+
return QUERY_NO_INGESTIONS_REPLY
|
|
72
|
+
|
|
73
|
+
# Put the context into the system message
|
|
74
|
+
context_parts = []
|
|
75
|
+
for i, doc in enumerate(self._ingested_documents, 1):
|
|
76
|
+
context_parts.append(f"Ingested File/URL {i} - '{doc.ingestation_name}':\n{doc.content}\n")
|
|
77
|
+
|
|
78
|
+
context = "\n".join(context_parts)
|
|
79
|
+
|
|
80
|
+
system_message = (
|
|
81
|
+
"You are a query agent tasked with answering questions based on ingested documents.\n\n"
|
|
82
|
+
"AVAILABLE DOCUMENTS:\n"
|
|
83
|
+
+ "\n".join([f"- {doc.ingestation_name}" for doc in self._ingested_documents])
|
|
84
|
+
+ "\n\n"
|
|
85
|
+
"When answering questions about these documents, use ONLY the information in the following context:\n\n"
|
|
86
|
+
f"{context}\n\n"
|
|
87
|
+
"IMPORTANT: The user will ask about these documents by name. When they do, provide helpful, detailed answers based on the document content above."
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
self._query_agent.update_system_message(system_message)
|
|
91
|
+
|
|
92
|
+
message = f"Using ONLY the document content in your system message, answer this question: {question}"
|
|
93
|
+
|
|
94
|
+
response = self._query_agent.run(
|
|
95
|
+
message=message,
|
|
96
|
+
max_turns=1,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
response.process()
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
# Get the structured output and return the answer
|
|
103
|
+
answer_object = QueryAnswer.model_validate(json.loads(response.summary)) # type: ignore[arg-type]
|
|
104
|
+
|
|
105
|
+
if answer_object.could_answer:
|
|
106
|
+
return answer_object.answer
|
|
107
|
+
else:
|
|
108
|
+
if answer_object.answer:
|
|
109
|
+
return COULD_NOT_ANSWER_REPLY + ": " + answer_object.answer
|
|
110
|
+
else:
|
|
111
|
+
return COULD_NOT_ANSWER_REPLY
|
|
112
|
+
|
|
113
|
+
except Exception as e:
|
|
114
|
+
# Error converting the response to the structured output
|
|
115
|
+
return ERROR_RESPONSE_REPLY + str(e)
|
|
116
|
+
|
|
117
|
+
def add_docs(
|
|
118
|
+
self,
|
|
119
|
+
new_doc_dir: Optional[Union[Path, str]] = None,
|
|
120
|
+
new_doc_paths_or_urls: Optional[Sequence[Union[Path, str]]] = None,
|
|
121
|
+
) -> None:
|
|
122
|
+
"""
|
|
123
|
+
Add additional documents to the in-memory store
|
|
124
|
+
|
|
125
|
+
Loads new Docling-parsed Markdown files from a specified directory or a list of file paths
|
|
126
|
+
and inserts them into the in-memory store.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
new_doc_dir: The directory path from which to load additional documents.
|
|
130
|
+
If provided, all eligible files in this directory are loaded.
|
|
131
|
+
new_doc_paths_or_urls: A list of file paths specifying additional documents to load.
|
|
132
|
+
Each file should be a Docling-parsed Markdown file.
|
|
133
|
+
"""
|
|
134
|
+
new_doc_dir = new_doc_dir or ""
|
|
135
|
+
new_doc_paths = new_doc_paths_or_urls or []
|
|
136
|
+
self._load_doc(input_dir=new_doc_dir, input_docs=new_doc_paths)
|
|
137
|
+
|
|
138
|
+
def _load_doc(
|
|
139
|
+
self, input_dir: Optional[Union[Path, str]], input_docs: Optional[Sequence[Union[Path, str]]]
|
|
140
|
+
) -> None:
|
|
141
|
+
"""
|
|
142
|
+
Load documents from a directory and/or a list of file paths into the in-memory store.
|
|
143
|
+
|
|
144
|
+
This helper method reads files using native Python file operations and stores them
|
|
145
|
+
in the in-memory document store. It supports reading text-based files, with the primary
|
|
146
|
+
intended use being for documents processed by Docling.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
input_dir (Optional[Union[Path, str]]): The directory containing documents to be loaded.
|
|
150
|
+
If provided, all files in the directory will be considered.
|
|
151
|
+
input_docs (Optional[list[Union[Path, str]]]): A list of individual file paths to load.
|
|
152
|
+
Each path must point to an existing file.
|
|
153
|
+
|
|
154
|
+
Raises:
|
|
155
|
+
ValueError: If the specified directory does not exist.
|
|
156
|
+
ValueError: If any provided file path does not exist.
|
|
157
|
+
ValueError: If neither input_dir nor input_docs is provided.
|
|
158
|
+
"""
|
|
159
|
+
if not input_dir and not input_docs:
|
|
160
|
+
raise ValueError("No input directory or docs provided!")
|
|
161
|
+
|
|
162
|
+
# Process directory if provided
|
|
163
|
+
if input_dir:
|
|
164
|
+
# logger.info(f"Loading docs from directory: {input_dir}")
|
|
165
|
+
if not os.path.exists(input_dir):
|
|
166
|
+
raise ValueError(f"Input directory not found: {input_dir}")
|
|
167
|
+
|
|
168
|
+
# Get all files from the directory
|
|
169
|
+
dir_path = Path(input_dir)
|
|
170
|
+
for file_path in dir_path.iterdir():
|
|
171
|
+
if file_path.is_file():
|
|
172
|
+
self._read_and_store_file(file_path)
|
|
173
|
+
|
|
174
|
+
# Process individual files if provided
|
|
175
|
+
if input_docs:
|
|
176
|
+
for doc_path in input_docs:
|
|
177
|
+
# logger.info(f"Loading input doc: {doc_path}")
|
|
178
|
+
if not os.path.exists(doc_path):
|
|
179
|
+
raise ValueError(f"Document file not found: {doc_path}")
|
|
180
|
+
self._read_and_store_file(doc_path)
|
|
181
|
+
|
|
182
|
+
def _read_and_store_file(self, file_path: Union[Path, str]) -> None:
|
|
183
|
+
"""
|
|
184
|
+
Read a file and store its content in the in-memory document store.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
file_path (Union[Path, str]): Path to the file to be read
|
|
188
|
+
"""
|
|
189
|
+
file_path = Path(file_path)
|
|
190
|
+
try:
|
|
191
|
+
with open(file_path, "r", encoding="utf-8") as file:
|
|
192
|
+
content = file.read()
|
|
193
|
+
|
|
194
|
+
# Store the document in the in-memory store
|
|
195
|
+
document = DocumentStore(ingestation_name=file_path.name, content=content)
|
|
196
|
+
self._ingested_documents.append(document)
|
|
197
|
+
except Exception as e:
|
|
198
|
+
raise ValueError(f"Error reading file {file_path}: {str(e)}")
|
|
199
|
+
|
|
200
|
+
def init_db(
|
|
201
|
+
self,
|
|
202
|
+
new_doc_dir: Optional[Union[Path, str]] = None,
|
|
203
|
+
new_doc_paths_or_urls: Optional[Sequence[Union[Path, str]]] = None,
|
|
204
|
+
*args: Any,
|
|
205
|
+
**kwargs: Any,
|
|
206
|
+
) -> bool:
|
|
207
|
+
"""Not required nor implemented for InMemoryQueryEngine"""
|
|
208
|
+
raise NotImplementedError("Method, init_db, not required nor implemented for InMemoryQueryEngine")
|
|
209
|
+
|
|
210
|
+
def connect_db(self, *args: Any, **kwargs: Any) -> bool:
|
|
211
|
+
"""Not required nor implemented for InMemoryQueryEngine"""
|
|
212
|
+
raise NotImplementedError("Method, connect_db, not required nor implemented for InMemoryQueryEngine")
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
# mypy will fail if ChromaDBQueryEngine does not implement RAGQueryEngine protocol
|
|
216
|
+
if TYPE_CHECKING:
|
|
217
|
+
from ....agentchat.contrib.rag.query_engine import RAGQueryEngine
|
|
218
|
+
|
|
219
|
+
def _check_implement_protocol(o: InMemoryQueryEngine) -> RAGQueryEngine:
|
|
220
|
+
return o
|