lfx-nightly 0.2.0.dev25__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 lfx-nightly might be problematic. Click here for more details.
- lfx/__init__.py +0 -0
- lfx/__main__.py +25 -0
- lfx/_assets/component_index.json +1 -0
- lfx/base/__init__.py +0 -0
- lfx/base/agents/__init__.py +0 -0
- lfx/base/agents/agent.py +375 -0
- lfx/base/agents/altk_base_agent.py +380 -0
- lfx/base/agents/altk_tool_wrappers.py +565 -0
- lfx/base/agents/callback.py +130 -0
- lfx/base/agents/context.py +109 -0
- lfx/base/agents/crewai/__init__.py +0 -0
- lfx/base/agents/crewai/crew.py +231 -0
- lfx/base/agents/crewai/tasks.py +12 -0
- lfx/base/agents/default_prompts.py +23 -0
- lfx/base/agents/errors.py +15 -0
- lfx/base/agents/events.py +430 -0
- lfx/base/agents/utils.py +237 -0
- lfx/base/astra_assistants/__init__.py +0 -0
- lfx/base/astra_assistants/util.py +171 -0
- lfx/base/chains/__init__.py +0 -0
- lfx/base/chains/model.py +19 -0
- lfx/base/composio/__init__.py +0 -0
- lfx/base/composio/composio_base.py +2584 -0
- lfx/base/compressors/__init__.py +0 -0
- lfx/base/compressors/model.py +60 -0
- lfx/base/constants.py +46 -0
- lfx/base/curl/__init__.py +0 -0
- lfx/base/curl/parse.py +188 -0
- lfx/base/data/__init__.py +5 -0
- lfx/base/data/base_file.py +810 -0
- lfx/base/data/docling_utils.py +338 -0
- lfx/base/data/storage_utils.py +192 -0
- lfx/base/data/utils.py +362 -0
- lfx/base/datastax/__init__.py +5 -0
- lfx/base/datastax/astradb_base.py +896 -0
- lfx/base/document_transformers/__init__.py +0 -0
- lfx/base/document_transformers/model.py +43 -0
- lfx/base/embeddings/__init__.py +0 -0
- lfx/base/embeddings/aiml_embeddings.py +62 -0
- lfx/base/embeddings/embeddings_class.py +113 -0
- lfx/base/embeddings/model.py +26 -0
- lfx/base/flow_processing/__init__.py +0 -0
- lfx/base/flow_processing/utils.py +86 -0
- lfx/base/huggingface/__init__.py +0 -0
- lfx/base/huggingface/model_bridge.py +133 -0
- lfx/base/io/__init__.py +0 -0
- lfx/base/io/chat.py +21 -0
- lfx/base/io/text.py +22 -0
- lfx/base/knowledge_bases/__init__.py +3 -0
- lfx/base/knowledge_bases/knowledge_base_utils.py +137 -0
- lfx/base/langchain_utilities/__init__.py +0 -0
- lfx/base/langchain_utilities/model.py +35 -0
- lfx/base/langchain_utilities/spider_constants.py +1 -0
- lfx/base/langwatch/__init__.py +0 -0
- lfx/base/langwatch/utils.py +18 -0
- lfx/base/mcp/__init__.py +0 -0
- lfx/base/mcp/constants.py +2 -0
- lfx/base/mcp/util.py +1659 -0
- lfx/base/memory/__init__.py +0 -0
- lfx/base/memory/memory.py +49 -0
- lfx/base/memory/model.py +38 -0
- lfx/base/models/__init__.py +3 -0
- lfx/base/models/aiml_constants.py +51 -0
- lfx/base/models/anthropic_constants.py +51 -0
- lfx/base/models/aws_constants.py +151 -0
- lfx/base/models/chat_result.py +76 -0
- lfx/base/models/cometapi_constants.py +54 -0
- lfx/base/models/google_generative_ai_constants.py +70 -0
- lfx/base/models/google_generative_ai_model.py +38 -0
- lfx/base/models/groq_constants.py +150 -0
- lfx/base/models/groq_model_discovery.py +265 -0
- lfx/base/models/model.py +375 -0
- lfx/base/models/model_input_constants.py +378 -0
- lfx/base/models/model_metadata.py +41 -0
- lfx/base/models/model_utils.py +108 -0
- lfx/base/models/novita_constants.py +35 -0
- lfx/base/models/ollama_constants.py +52 -0
- lfx/base/models/openai_constants.py +129 -0
- lfx/base/models/sambanova_constants.py +18 -0
- lfx/base/models/watsonx_constants.py +36 -0
- lfx/base/processing/__init__.py +0 -0
- lfx/base/prompts/__init__.py +0 -0
- lfx/base/prompts/api_utils.py +224 -0
- lfx/base/prompts/utils.py +61 -0
- lfx/base/textsplitters/__init__.py +0 -0
- lfx/base/textsplitters/model.py +28 -0
- lfx/base/tools/__init__.py +0 -0
- lfx/base/tools/base.py +26 -0
- lfx/base/tools/component_tool.py +325 -0
- lfx/base/tools/constants.py +49 -0
- lfx/base/tools/flow_tool.py +132 -0
- lfx/base/tools/run_flow.py +698 -0
- lfx/base/vectorstores/__init__.py +0 -0
- lfx/base/vectorstores/model.py +193 -0
- lfx/base/vectorstores/utils.py +22 -0
- lfx/base/vectorstores/vector_store_connection_decorator.py +52 -0
- lfx/cli/__init__.py +5 -0
- lfx/cli/commands.py +327 -0
- lfx/cli/common.py +650 -0
- lfx/cli/run.py +506 -0
- lfx/cli/script_loader.py +289 -0
- lfx/cli/serve_app.py +546 -0
- lfx/cli/validation.py +69 -0
- lfx/components/FAISS/__init__.py +34 -0
- lfx/components/FAISS/faiss.py +111 -0
- lfx/components/Notion/__init__.py +19 -0
- lfx/components/Notion/add_content_to_page.py +269 -0
- lfx/components/Notion/create_page.py +94 -0
- lfx/components/Notion/list_database_properties.py +68 -0
- lfx/components/Notion/list_pages.py +122 -0
- lfx/components/Notion/list_users.py +77 -0
- lfx/components/Notion/page_content_viewer.py +93 -0
- lfx/components/Notion/search.py +111 -0
- lfx/components/Notion/update_page_property.py +114 -0
- lfx/components/__init__.py +428 -0
- lfx/components/_importing.py +42 -0
- lfx/components/agentql/__init__.py +3 -0
- lfx/components/agentql/agentql_api.py +151 -0
- lfx/components/aiml/__init__.py +37 -0
- lfx/components/aiml/aiml.py +115 -0
- lfx/components/aiml/aiml_embeddings.py +37 -0
- lfx/components/altk/__init__.py +34 -0
- lfx/components/altk/altk_agent.py +193 -0
- lfx/components/amazon/__init__.py +36 -0
- lfx/components/amazon/amazon_bedrock_converse.py +195 -0
- lfx/components/amazon/amazon_bedrock_embedding.py +109 -0
- lfx/components/amazon/amazon_bedrock_model.py +130 -0
- lfx/components/amazon/s3_bucket_uploader.py +211 -0
- lfx/components/anthropic/__init__.py +34 -0
- lfx/components/anthropic/anthropic.py +187 -0
- lfx/components/apify/__init__.py +5 -0
- lfx/components/apify/apify_actor.py +325 -0
- lfx/components/arxiv/__init__.py +3 -0
- lfx/components/arxiv/arxiv.py +169 -0
- lfx/components/assemblyai/__init__.py +46 -0
- lfx/components/assemblyai/assemblyai_get_subtitles.py +83 -0
- lfx/components/assemblyai/assemblyai_lemur.py +183 -0
- lfx/components/assemblyai/assemblyai_list_transcripts.py +95 -0
- lfx/components/assemblyai/assemblyai_poll_transcript.py +72 -0
- lfx/components/assemblyai/assemblyai_start_transcript.py +188 -0
- lfx/components/azure/__init__.py +37 -0
- lfx/components/azure/azure_openai.py +95 -0
- lfx/components/azure/azure_openai_embeddings.py +83 -0
- lfx/components/baidu/__init__.py +32 -0
- lfx/components/baidu/baidu_qianfan_chat.py +113 -0
- lfx/components/bing/__init__.py +3 -0
- lfx/components/bing/bing_search_api.py +61 -0
- lfx/components/cassandra/__init__.py +40 -0
- lfx/components/cassandra/cassandra.py +264 -0
- lfx/components/cassandra/cassandra_chat.py +92 -0
- lfx/components/cassandra/cassandra_graph.py +238 -0
- lfx/components/chains/__init__.py +3 -0
- lfx/components/chroma/__init__.py +34 -0
- lfx/components/chroma/chroma.py +169 -0
- lfx/components/cleanlab/__init__.py +40 -0
- lfx/components/cleanlab/cleanlab_evaluator.py +155 -0
- lfx/components/cleanlab/cleanlab_rag_evaluator.py +254 -0
- lfx/components/cleanlab/cleanlab_remediator.py +131 -0
- lfx/components/clickhouse/__init__.py +34 -0
- lfx/components/clickhouse/clickhouse.py +135 -0
- lfx/components/cloudflare/__init__.py +32 -0
- lfx/components/cloudflare/cloudflare.py +81 -0
- lfx/components/cohere/__init__.py +40 -0
- lfx/components/cohere/cohere_embeddings.py +81 -0
- lfx/components/cohere/cohere_models.py +46 -0
- lfx/components/cohere/cohere_rerank.py +51 -0
- lfx/components/cometapi/__init__.py +32 -0
- lfx/components/cometapi/cometapi.py +166 -0
- lfx/components/composio/__init__.py +222 -0
- lfx/components/composio/agentql_composio.py +11 -0
- lfx/components/composio/agiled_composio.py +11 -0
- lfx/components/composio/airtable_composio.py +11 -0
- lfx/components/composio/apollo_composio.py +11 -0
- lfx/components/composio/asana_composio.py +11 -0
- lfx/components/composio/attio_composio.py +11 -0
- lfx/components/composio/bitbucket_composio.py +11 -0
- lfx/components/composio/bolna_composio.py +11 -0
- lfx/components/composio/brightdata_composio.py +11 -0
- lfx/components/composio/calendly_composio.py +11 -0
- lfx/components/composio/canva_composio.py +11 -0
- lfx/components/composio/canvas_composio.py +11 -0
- lfx/components/composio/coda_composio.py +11 -0
- lfx/components/composio/composio_api.py +278 -0
- lfx/components/composio/contentful_composio.py +11 -0
- lfx/components/composio/digicert_composio.py +11 -0
- lfx/components/composio/discord_composio.py +11 -0
- lfx/components/composio/dropbox_compnent.py +11 -0
- lfx/components/composio/elevenlabs_composio.py +11 -0
- lfx/components/composio/exa_composio.py +11 -0
- lfx/components/composio/figma_composio.py +11 -0
- lfx/components/composio/finage_composio.py +11 -0
- lfx/components/composio/firecrawl_composio.py +11 -0
- lfx/components/composio/fireflies_composio.py +11 -0
- lfx/components/composio/fixer_composio.py +11 -0
- lfx/components/composio/flexisign_composio.py +11 -0
- lfx/components/composio/freshdesk_composio.py +11 -0
- lfx/components/composio/github_composio.py +11 -0
- lfx/components/composio/gmail_composio.py +38 -0
- lfx/components/composio/googlebigquery_composio.py +11 -0
- lfx/components/composio/googlecalendar_composio.py +11 -0
- lfx/components/composio/googleclassroom_composio.py +11 -0
- lfx/components/composio/googledocs_composio.py +11 -0
- lfx/components/composio/googlemeet_composio.py +11 -0
- lfx/components/composio/googlesheets_composio.py +11 -0
- lfx/components/composio/googletasks_composio.py +8 -0
- lfx/components/composio/heygen_composio.py +11 -0
- lfx/components/composio/instagram_composio.py +11 -0
- lfx/components/composio/jira_composio.py +11 -0
- lfx/components/composio/jotform_composio.py +11 -0
- lfx/components/composio/klaviyo_composio.py +11 -0
- lfx/components/composio/linear_composio.py +11 -0
- lfx/components/composio/listennotes_composio.py +11 -0
- lfx/components/composio/mem0_composio.py +11 -0
- lfx/components/composio/miro_composio.py +11 -0
- lfx/components/composio/missive_composio.py +11 -0
- lfx/components/composio/notion_composio.py +11 -0
- lfx/components/composio/onedrive_composio.py +11 -0
- lfx/components/composio/outlook_composio.py +11 -0
- lfx/components/composio/pandadoc_composio.py +11 -0
- lfx/components/composio/peopledatalabs_composio.py +11 -0
- lfx/components/composio/perplexityai_composio.py +11 -0
- lfx/components/composio/reddit_composio.py +11 -0
- lfx/components/composio/serpapi_composio.py +11 -0
- lfx/components/composio/slack_composio.py +11 -0
- lfx/components/composio/slackbot_composio.py +11 -0
- lfx/components/composio/snowflake_composio.py +11 -0
- lfx/components/composio/supabase_composio.py +11 -0
- lfx/components/composio/tavily_composio.py +11 -0
- lfx/components/composio/timelinesai_composio.py +11 -0
- lfx/components/composio/todoist_composio.py +11 -0
- lfx/components/composio/wrike_composio.py +11 -0
- lfx/components/composio/youtube_composio.py +11 -0
- lfx/components/confluence/__init__.py +3 -0
- lfx/components/confluence/confluence.py +84 -0
- lfx/components/couchbase/__init__.py +34 -0
- lfx/components/couchbase/couchbase.py +102 -0
- lfx/components/crewai/__init__.py +49 -0
- lfx/components/crewai/crewai.py +108 -0
- lfx/components/crewai/hierarchical_crew.py +47 -0
- lfx/components/crewai/hierarchical_task.py +45 -0
- lfx/components/crewai/sequential_crew.py +53 -0
- lfx/components/crewai/sequential_task.py +74 -0
- lfx/components/crewai/sequential_task_agent.py +144 -0
- lfx/components/cuga/__init__.py +34 -0
- lfx/components/cuga/cuga_agent.py +730 -0
- lfx/components/custom_component/__init__.py +34 -0
- lfx/components/custom_component/custom_component.py +31 -0
- lfx/components/data/__init__.py +114 -0
- lfx/components/data_source/__init__.py +58 -0
- lfx/components/data_source/api_request.py +577 -0
- lfx/components/data_source/csv_to_data.py +101 -0
- lfx/components/data_source/json_to_data.py +106 -0
- lfx/components/data_source/mock_data.py +398 -0
- lfx/components/data_source/news_search.py +166 -0
- lfx/components/data_source/rss.py +71 -0
- lfx/components/data_source/sql_executor.py +101 -0
- lfx/components/data_source/url.py +311 -0
- lfx/components/data_source/web_search.py +326 -0
- lfx/components/datastax/__init__.py +76 -0
- lfx/components/datastax/astradb_assistant_manager.py +307 -0
- lfx/components/datastax/astradb_chatmemory.py +40 -0
- lfx/components/datastax/astradb_cql.py +288 -0
- lfx/components/datastax/astradb_graph.py +217 -0
- lfx/components/datastax/astradb_tool.py +378 -0
- lfx/components/datastax/astradb_vectorize.py +122 -0
- lfx/components/datastax/astradb_vectorstore.py +449 -0
- lfx/components/datastax/create_assistant.py +59 -0
- lfx/components/datastax/create_thread.py +33 -0
- lfx/components/datastax/dotenv.py +36 -0
- lfx/components/datastax/get_assistant.py +38 -0
- lfx/components/datastax/getenvvar.py +31 -0
- lfx/components/datastax/graph_rag.py +141 -0
- lfx/components/datastax/hcd.py +315 -0
- lfx/components/datastax/list_assistants.py +26 -0
- lfx/components/datastax/run.py +90 -0
- lfx/components/deactivated/__init__.py +15 -0
- lfx/components/deactivated/amazon_kendra.py +66 -0
- lfx/components/deactivated/chat_litellm_model.py +158 -0
- lfx/components/deactivated/code_block_extractor.py +26 -0
- lfx/components/deactivated/documents_to_data.py +22 -0
- lfx/components/deactivated/embed.py +16 -0
- lfx/components/deactivated/extract_key_from_data.py +46 -0
- lfx/components/deactivated/json_document_builder.py +57 -0
- lfx/components/deactivated/list_flows.py +20 -0
- lfx/components/deactivated/mcp_sse.py +61 -0
- lfx/components/deactivated/mcp_stdio.py +62 -0
- lfx/components/deactivated/merge_data.py +93 -0
- lfx/components/deactivated/message.py +37 -0
- lfx/components/deactivated/metal.py +54 -0
- lfx/components/deactivated/multi_query.py +59 -0
- lfx/components/deactivated/retriever.py +43 -0
- lfx/components/deactivated/selective_passthrough.py +77 -0
- lfx/components/deactivated/should_run_next.py +40 -0
- lfx/components/deactivated/split_text.py +63 -0
- lfx/components/deactivated/store_message.py +24 -0
- lfx/components/deactivated/sub_flow.py +124 -0
- lfx/components/deactivated/vectara_self_query.py +76 -0
- lfx/components/deactivated/vector_store.py +24 -0
- lfx/components/deepseek/__init__.py +34 -0
- lfx/components/deepseek/deepseek.py +136 -0
- lfx/components/docling/__init__.py +43 -0
- lfx/components/docling/chunk_docling_document.py +186 -0
- lfx/components/docling/docling_inline.py +238 -0
- lfx/components/docling/docling_remote.py +195 -0
- lfx/components/docling/export_docling_document.py +117 -0
- lfx/components/documentloaders/__init__.py +3 -0
- lfx/components/duckduckgo/__init__.py +3 -0
- lfx/components/duckduckgo/duck_duck_go_search_run.py +92 -0
- lfx/components/elastic/__init__.py +37 -0
- lfx/components/elastic/elasticsearch.py +267 -0
- lfx/components/elastic/opensearch.py +789 -0
- lfx/components/elastic/opensearch_multimodal.py +1575 -0
- lfx/components/embeddings/__init__.py +37 -0
- lfx/components/embeddings/similarity.py +77 -0
- lfx/components/embeddings/text_embedder.py +65 -0
- lfx/components/exa/__init__.py +3 -0
- lfx/components/exa/exa_search.py +68 -0
- lfx/components/files_and_knowledge/__init__.py +47 -0
- lfx/components/files_and_knowledge/directory.py +113 -0
- lfx/components/files_and_knowledge/file.py +841 -0
- lfx/components/files_and_knowledge/ingestion.py +694 -0
- lfx/components/files_and_knowledge/retrieval.py +264 -0
- lfx/components/files_and_knowledge/save_file.py +746 -0
- lfx/components/firecrawl/__init__.py +43 -0
- lfx/components/firecrawl/firecrawl_crawl_api.py +88 -0
- lfx/components/firecrawl/firecrawl_extract_api.py +136 -0
- lfx/components/firecrawl/firecrawl_map_api.py +89 -0
- lfx/components/firecrawl/firecrawl_scrape_api.py +73 -0
- lfx/components/flow_controls/__init__.py +58 -0
- lfx/components/flow_controls/conditional_router.py +208 -0
- lfx/components/flow_controls/data_conditional_router.py +126 -0
- lfx/components/flow_controls/flow_tool.py +111 -0
- lfx/components/flow_controls/listen.py +29 -0
- lfx/components/flow_controls/loop.py +163 -0
- lfx/components/flow_controls/notify.py +88 -0
- lfx/components/flow_controls/pass_message.py +36 -0
- lfx/components/flow_controls/run_flow.py +108 -0
- lfx/components/flow_controls/sub_flow.py +115 -0
- lfx/components/git/__init__.py +4 -0
- lfx/components/git/git.py +262 -0
- lfx/components/git/gitextractor.py +196 -0
- lfx/components/glean/__init__.py +3 -0
- lfx/components/glean/glean_search_api.py +173 -0
- lfx/components/google/__init__.py +17 -0
- lfx/components/google/gmail.py +193 -0
- lfx/components/google/google_bq_sql_executor.py +157 -0
- lfx/components/google/google_drive.py +92 -0
- lfx/components/google/google_drive_search.py +152 -0
- lfx/components/google/google_generative_ai.py +144 -0
- lfx/components/google/google_generative_ai_embeddings.py +141 -0
- lfx/components/google/google_oauth_token.py +89 -0
- lfx/components/google/google_search_api_core.py +68 -0
- lfx/components/google/google_serper_api_core.py +74 -0
- lfx/components/groq/__init__.py +34 -0
- lfx/components/groq/groq.py +143 -0
- lfx/components/helpers/__init__.py +154 -0
- lfx/components/homeassistant/__init__.py +7 -0
- lfx/components/homeassistant/home_assistant_control.py +152 -0
- lfx/components/homeassistant/list_home_assistant_states.py +137 -0
- lfx/components/huggingface/__init__.py +37 -0
- lfx/components/huggingface/huggingface.py +199 -0
- lfx/components/huggingface/huggingface_inference_api.py +106 -0
- lfx/components/ibm/__init__.py +34 -0
- lfx/components/ibm/watsonx.py +207 -0
- lfx/components/ibm/watsonx_embeddings.py +135 -0
- lfx/components/icosacomputing/__init__.py +5 -0
- lfx/components/icosacomputing/combinatorial_reasoner.py +84 -0
- lfx/components/input_output/__init__.py +40 -0
- lfx/components/input_output/chat.py +109 -0
- lfx/components/input_output/chat_output.py +184 -0
- lfx/components/input_output/text.py +27 -0
- lfx/components/input_output/text_output.py +29 -0
- lfx/components/input_output/webhook.py +56 -0
- lfx/components/jigsawstack/__init__.py +23 -0
- lfx/components/jigsawstack/ai_scrape.py +126 -0
- lfx/components/jigsawstack/ai_web_search.py +136 -0
- lfx/components/jigsawstack/file_read.py +115 -0
- lfx/components/jigsawstack/file_upload.py +94 -0
- lfx/components/jigsawstack/image_generation.py +205 -0
- lfx/components/jigsawstack/nsfw.py +60 -0
- lfx/components/jigsawstack/object_detection.py +124 -0
- lfx/components/jigsawstack/sentiment.py +112 -0
- lfx/components/jigsawstack/text_to_sql.py +90 -0
- lfx/components/jigsawstack/text_translate.py +77 -0
- lfx/components/jigsawstack/vocr.py +107 -0
- lfx/components/knowledge_bases/__init__.py +89 -0
- lfx/components/langchain_utilities/__init__.py +109 -0
- lfx/components/langchain_utilities/character.py +53 -0
- lfx/components/langchain_utilities/conversation.py +59 -0
- lfx/components/langchain_utilities/csv_agent.py +175 -0
- lfx/components/langchain_utilities/fake_embeddings.py +26 -0
- lfx/components/langchain_utilities/html_link_extractor.py +35 -0
- lfx/components/langchain_utilities/json_agent.py +100 -0
- lfx/components/langchain_utilities/langchain_hub.py +126 -0
- lfx/components/langchain_utilities/language_recursive.py +49 -0
- lfx/components/langchain_utilities/language_semantic.py +138 -0
- lfx/components/langchain_utilities/llm_checker.py +39 -0
- lfx/components/langchain_utilities/llm_math.py +42 -0
- lfx/components/langchain_utilities/natural_language.py +61 -0
- lfx/components/langchain_utilities/openai_tools.py +53 -0
- lfx/components/langchain_utilities/openapi.py +48 -0
- lfx/components/langchain_utilities/recursive_character.py +60 -0
- lfx/components/langchain_utilities/retrieval_qa.py +83 -0
- lfx/components/langchain_utilities/runnable_executor.py +137 -0
- lfx/components/langchain_utilities/self_query.py +80 -0
- lfx/components/langchain_utilities/spider.py +142 -0
- lfx/components/langchain_utilities/sql.py +40 -0
- lfx/components/langchain_utilities/sql_database.py +35 -0
- lfx/components/langchain_utilities/sql_generator.py +78 -0
- lfx/components/langchain_utilities/tool_calling.py +59 -0
- lfx/components/langchain_utilities/vector_store_info.py +49 -0
- lfx/components/langchain_utilities/vector_store_router.py +33 -0
- lfx/components/langchain_utilities/xml_agent.py +71 -0
- lfx/components/langwatch/__init__.py +3 -0
- lfx/components/langwatch/langwatch.py +278 -0
- lfx/components/link_extractors/__init__.py +3 -0
- lfx/components/llm_operations/__init__.py +46 -0
- lfx/components/llm_operations/batch_run.py +205 -0
- lfx/components/llm_operations/lambda_filter.py +218 -0
- lfx/components/llm_operations/llm_conditional_router.py +421 -0
- lfx/components/llm_operations/llm_selector.py +499 -0
- lfx/components/llm_operations/structured_output.py +244 -0
- lfx/components/lmstudio/__init__.py +34 -0
- lfx/components/lmstudio/lmstudioembeddings.py +89 -0
- lfx/components/lmstudio/lmstudiomodel.py +133 -0
- lfx/components/logic/__init__.py +181 -0
- lfx/components/maritalk/__init__.py +32 -0
- lfx/components/maritalk/maritalk.py +52 -0
- lfx/components/mem0/__init__.py +3 -0
- lfx/components/mem0/mem0_chat_memory.py +147 -0
- lfx/components/milvus/__init__.py +34 -0
- lfx/components/milvus/milvus.py +115 -0
- lfx/components/mistral/__init__.py +37 -0
- lfx/components/mistral/mistral.py +114 -0
- lfx/components/mistral/mistral_embeddings.py +58 -0
- lfx/components/models/__init__.py +89 -0
- lfx/components/models_and_agents/__init__.py +49 -0
- lfx/components/models_and_agents/agent.py +644 -0
- lfx/components/models_and_agents/embedding_model.py +423 -0
- lfx/components/models_and_agents/language_model.py +398 -0
- lfx/components/models_and_agents/mcp_component.py +594 -0
- lfx/components/models_and_agents/memory.py +268 -0
- lfx/components/models_and_agents/prompt.py +67 -0
- lfx/components/mongodb/__init__.py +34 -0
- lfx/components/mongodb/mongodb_atlas.py +213 -0
- lfx/components/needle/__init__.py +3 -0
- lfx/components/needle/needle.py +104 -0
- lfx/components/notdiamond/__init__.py +34 -0
- lfx/components/notdiamond/notdiamond.py +228 -0
- lfx/components/novita/__init__.py +32 -0
- lfx/components/novita/novita.py +130 -0
- lfx/components/nvidia/__init__.py +57 -0
- lfx/components/nvidia/nvidia.py +151 -0
- lfx/components/nvidia/nvidia_embedding.py +77 -0
- lfx/components/nvidia/nvidia_ingest.py +317 -0
- lfx/components/nvidia/nvidia_rerank.py +63 -0
- lfx/components/nvidia/system_assist.py +65 -0
- lfx/components/olivya/__init__.py +3 -0
- lfx/components/olivya/olivya.py +116 -0
- lfx/components/ollama/__init__.py +37 -0
- lfx/components/ollama/ollama.py +548 -0
- lfx/components/ollama/ollama_embeddings.py +103 -0
- lfx/components/openai/__init__.py +37 -0
- lfx/components/openai/openai.py +100 -0
- lfx/components/openai/openai_chat_model.py +176 -0
- lfx/components/openrouter/__init__.py +32 -0
- lfx/components/openrouter/openrouter.py +104 -0
- lfx/components/output_parsers/__init__.py +3 -0
- lfx/components/perplexity/__init__.py +34 -0
- lfx/components/perplexity/perplexity.py +75 -0
- lfx/components/pgvector/__init__.py +34 -0
- lfx/components/pgvector/pgvector.py +72 -0
- lfx/components/pinecone/__init__.py +34 -0
- lfx/components/pinecone/pinecone.py +134 -0
- lfx/components/processing/__init__.py +72 -0
- lfx/components/processing/alter_metadata.py +109 -0
- lfx/components/processing/combine_text.py +40 -0
- lfx/components/processing/converter.py +248 -0
- lfx/components/processing/create_data.py +111 -0
- lfx/components/processing/create_list.py +40 -0
- lfx/components/processing/data_operations.py +528 -0
- lfx/components/processing/data_to_dataframe.py +71 -0
- lfx/components/processing/dataframe_operations.py +313 -0
- lfx/components/processing/dataframe_to_toolset.py +259 -0
- lfx/components/processing/dynamic_create_data.py +357 -0
- lfx/components/processing/extract_key.py +54 -0
- lfx/components/processing/filter_data.py +43 -0
- lfx/components/processing/filter_data_values.py +89 -0
- lfx/components/processing/json_cleaner.py +104 -0
- lfx/components/processing/merge_data.py +91 -0
- lfx/components/processing/message_to_data.py +37 -0
- lfx/components/processing/output_parser.py +46 -0
- lfx/components/processing/parse_data.py +71 -0
- lfx/components/processing/parse_dataframe.py +69 -0
- lfx/components/processing/parse_json_data.py +91 -0
- lfx/components/processing/parser.py +148 -0
- lfx/components/processing/regex.py +83 -0
- lfx/components/processing/select_data.py +49 -0
- lfx/components/processing/split_text.py +141 -0
- lfx/components/processing/store_message.py +91 -0
- lfx/components/processing/update_data.py +161 -0
- lfx/components/prototypes/__init__.py +35 -0
- lfx/components/prototypes/python_function.py +73 -0
- lfx/components/qdrant/__init__.py +34 -0
- lfx/components/qdrant/qdrant.py +109 -0
- lfx/components/redis/__init__.py +37 -0
- lfx/components/redis/redis.py +89 -0
- lfx/components/redis/redis_chat.py +43 -0
- lfx/components/sambanova/__init__.py +32 -0
- lfx/components/sambanova/sambanova.py +84 -0
- lfx/components/scrapegraph/__init__.py +40 -0
- lfx/components/scrapegraph/scrapegraph_markdownify_api.py +64 -0
- lfx/components/scrapegraph/scrapegraph_search_api.py +64 -0
- lfx/components/scrapegraph/scrapegraph_smart_scraper_api.py +71 -0
- lfx/components/searchapi/__init__.py +34 -0
- lfx/components/searchapi/search.py +79 -0
- lfx/components/serpapi/__init__.py +3 -0
- lfx/components/serpapi/serp.py +115 -0
- lfx/components/supabase/__init__.py +34 -0
- lfx/components/supabase/supabase.py +76 -0
- lfx/components/tavily/__init__.py +4 -0
- lfx/components/tavily/tavily_extract.py +117 -0
- lfx/components/tavily/tavily_search.py +212 -0
- lfx/components/textsplitters/__init__.py +3 -0
- lfx/components/toolkits/__init__.py +3 -0
- lfx/components/tools/__init__.py +66 -0
- lfx/components/tools/calculator.py +109 -0
- lfx/components/tools/google_search_api.py +45 -0
- lfx/components/tools/google_serper_api.py +115 -0
- lfx/components/tools/python_code_structured_tool.py +328 -0
- lfx/components/tools/python_repl.py +98 -0
- lfx/components/tools/search_api.py +88 -0
- lfx/components/tools/searxng.py +145 -0
- lfx/components/tools/serp_api.py +120 -0
- lfx/components/tools/tavily_search_tool.py +345 -0
- lfx/components/tools/wikidata_api.py +103 -0
- lfx/components/tools/wikipedia_api.py +50 -0
- lfx/components/tools/yahoo_finance.py +130 -0
- lfx/components/twelvelabs/__init__.py +52 -0
- lfx/components/twelvelabs/convert_astra_results.py +84 -0
- lfx/components/twelvelabs/pegasus_index.py +311 -0
- lfx/components/twelvelabs/split_video.py +301 -0
- lfx/components/twelvelabs/text_embeddings.py +57 -0
- lfx/components/twelvelabs/twelvelabs_pegasus.py +408 -0
- lfx/components/twelvelabs/video_embeddings.py +100 -0
- lfx/components/twelvelabs/video_file.py +191 -0
- lfx/components/unstructured/__init__.py +3 -0
- lfx/components/unstructured/unstructured.py +121 -0
- lfx/components/upstash/__init__.py +34 -0
- lfx/components/upstash/upstash.py +124 -0
- lfx/components/utilities/__init__.py +43 -0
- lfx/components/utilities/calculator_core.py +89 -0
- lfx/components/utilities/current_date.py +42 -0
- lfx/components/utilities/id_generator.py +42 -0
- lfx/components/utilities/python_repl_core.py +98 -0
- lfx/components/vectara/__init__.py +37 -0
- lfx/components/vectara/vectara.py +97 -0
- lfx/components/vectara/vectara_rag.py +164 -0
- lfx/components/vectorstores/__init__.py +34 -0
- lfx/components/vectorstores/local_db.py +270 -0
- lfx/components/vertexai/__init__.py +37 -0
- lfx/components/vertexai/vertexai.py +71 -0
- lfx/components/vertexai/vertexai_embeddings.py +67 -0
- lfx/components/vlmrun/__init__.py +34 -0
- lfx/components/vlmrun/vlmrun_transcription.py +224 -0
- lfx/components/weaviate/__init__.py +34 -0
- lfx/components/weaviate/weaviate.py +89 -0
- lfx/components/wikipedia/__init__.py +4 -0
- lfx/components/wikipedia/wikidata.py +86 -0
- lfx/components/wikipedia/wikipedia.py +53 -0
- lfx/components/wolframalpha/__init__.py +3 -0
- lfx/components/wolframalpha/wolfram_alpha_api.py +54 -0
- lfx/components/xai/__init__.py +32 -0
- lfx/components/xai/xai.py +167 -0
- lfx/components/yahoosearch/__init__.py +3 -0
- lfx/components/yahoosearch/yahoo.py +137 -0
- lfx/components/youtube/__init__.py +52 -0
- lfx/components/youtube/channel.py +227 -0
- lfx/components/youtube/comments.py +231 -0
- lfx/components/youtube/playlist.py +33 -0
- lfx/components/youtube/search.py +120 -0
- lfx/components/youtube/trending.py +285 -0
- lfx/components/youtube/video_details.py +263 -0
- lfx/components/youtube/youtube_transcripts.py +206 -0
- lfx/components/zep/__init__.py +3 -0
- lfx/components/zep/zep.py +45 -0
- lfx/constants.py +6 -0
- lfx/custom/__init__.py +7 -0
- lfx/custom/attributes.py +87 -0
- lfx/custom/code_parser/__init__.py +3 -0
- lfx/custom/code_parser/code_parser.py +361 -0
- lfx/custom/custom_component/__init__.py +0 -0
- lfx/custom/custom_component/base_component.py +128 -0
- lfx/custom/custom_component/component.py +1890 -0
- lfx/custom/custom_component/component_with_cache.py +8 -0
- lfx/custom/custom_component/custom_component.py +650 -0
- lfx/custom/dependency_analyzer.py +165 -0
- lfx/custom/directory_reader/__init__.py +3 -0
- lfx/custom/directory_reader/directory_reader.py +359 -0
- lfx/custom/directory_reader/utils.py +171 -0
- lfx/custom/eval.py +12 -0
- lfx/custom/schema.py +32 -0
- lfx/custom/tree_visitor.py +21 -0
- lfx/custom/utils.py +877 -0
- lfx/custom/validate.py +523 -0
- lfx/events/__init__.py +1 -0
- lfx/events/event_manager.py +110 -0
- lfx/exceptions/__init__.py +0 -0
- lfx/exceptions/component.py +15 -0
- lfx/field_typing/__init__.py +91 -0
- lfx/field_typing/constants.py +216 -0
- lfx/field_typing/range_spec.py +35 -0
- lfx/graph/__init__.py +6 -0
- lfx/graph/edge/__init__.py +0 -0
- lfx/graph/edge/base.py +300 -0
- lfx/graph/edge/schema.py +119 -0
- lfx/graph/edge/utils.py +0 -0
- lfx/graph/graph/__init__.py +0 -0
- lfx/graph/graph/ascii.py +202 -0
- lfx/graph/graph/base.py +2298 -0
- lfx/graph/graph/constants.py +63 -0
- lfx/graph/graph/runnable_vertices_manager.py +133 -0
- lfx/graph/graph/schema.py +53 -0
- lfx/graph/graph/state_model.py +66 -0
- lfx/graph/graph/utils.py +1024 -0
- lfx/graph/schema.py +75 -0
- lfx/graph/state/__init__.py +0 -0
- lfx/graph/state/model.py +250 -0
- lfx/graph/utils.py +206 -0
- lfx/graph/vertex/__init__.py +0 -0
- lfx/graph/vertex/base.py +826 -0
- lfx/graph/vertex/constants.py +0 -0
- lfx/graph/vertex/exceptions.py +4 -0
- lfx/graph/vertex/param_handler.py +316 -0
- lfx/graph/vertex/schema.py +26 -0
- lfx/graph/vertex/utils.py +19 -0
- lfx/graph/vertex/vertex_types.py +489 -0
- lfx/helpers/__init__.py +141 -0
- lfx/helpers/base_model.py +71 -0
- lfx/helpers/custom.py +13 -0
- lfx/helpers/data.py +167 -0
- lfx/helpers/flow.py +308 -0
- lfx/inputs/__init__.py +68 -0
- lfx/inputs/constants.py +2 -0
- lfx/inputs/input_mixin.py +352 -0
- lfx/inputs/inputs.py +718 -0
- lfx/inputs/validators.py +19 -0
- lfx/interface/__init__.py +6 -0
- lfx/interface/components.py +897 -0
- lfx/interface/importing/__init__.py +5 -0
- lfx/interface/importing/utils.py +39 -0
- lfx/interface/initialize/__init__.py +3 -0
- lfx/interface/initialize/loading.py +317 -0
- lfx/interface/listing.py +26 -0
- lfx/interface/run.py +16 -0
- lfx/interface/utils.py +111 -0
- lfx/io/__init__.py +63 -0
- lfx/io/schema.py +295 -0
- lfx/load/__init__.py +8 -0
- lfx/load/load.py +256 -0
- lfx/load/utils.py +99 -0
- lfx/log/__init__.py +5 -0
- lfx/log/logger.py +411 -0
- lfx/logging/__init__.py +11 -0
- lfx/logging/logger.py +24 -0
- lfx/memory/__init__.py +70 -0
- lfx/memory/stubs.py +302 -0
- lfx/processing/__init__.py +1 -0
- lfx/processing/process.py +238 -0
- lfx/processing/utils.py +25 -0
- lfx/py.typed +0 -0
- lfx/schema/__init__.py +66 -0
- lfx/schema/artifact.py +83 -0
- lfx/schema/content_block.py +62 -0
- lfx/schema/content_types.py +91 -0
- lfx/schema/cross_module.py +80 -0
- lfx/schema/data.py +309 -0
- lfx/schema/dataframe.py +210 -0
- lfx/schema/dotdict.py +74 -0
- lfx/schema/encoders.py +13 -0
- lfx/schema/graph.py +47 -0
- lfx/schema/image.py +184 -0
- lfx/schema/json_schema.py +186 -0
- lfx/schema/log.py +62 -0
- lfx/schema/message.py +493 -0
- lfx/schema/openai_responses_schemas.py +74 -0
- lfx/schema/properties.py +41 -0
- lfx/schema/schema.py +180 -0
- lfx/schema/serialize.py +13 -0
- lfx/schema/table.py +142 -0
- lfx/schema/validators.py +114 -0
- lfx/serialization/__init__.py +5 -0
- lfx/serialization/constants.py +2 -0
- lfx/serialization/serialization.py +314 -0
- lfx/services/__init__.py +26 -0
- lfx/services/base.py +28 -0
- lfx/services/cache/__init__.py +6 -0
- lfx/services/cache/base.py +183 -0
- lfx/services/cache/service.py +166 -0
- lfx/services/cache/utils.py +169 -0
- lfx/services/chat/__init__.py +1 -0
- lfx/services/chat/config.py +2 -0
- lfx/services/chat/schema.py +10 -0
- lfx/services/database/__init__.py +5 -0
- lfx/services/database/service.py +25 -0
- lfx/services/deps.py +194 -0
- lfx/services/factory.py +19 -0
- lfx/services/initialize.py +19 -0
- lfx/services/interfaces.py +103 -0
- lfx/services/manager.py +185 -0
- lfx/services/mcp_composer/__init__.py +6 -0
- lfx/services/mcp_composer/factory.py +16 -0
- lfx/services/mcp_composer/service.py +1441 -0
- lfx/services/schema.py +21 -0
- lfx/services/session.py +87 -0
- lfx/services/settings/__init__.py +3 -0
- lfx/services/settings/auth.py +133 -0
- lfx/services/settings/base.py +668 -0
- lfx/services/settings/constants.py +43 -0
- lfx/services/settings/factory.py +23 -0
- lfx/services/settings/feature_flags.py +11 -0
- lfx/services/settings/service.py +35 -0
- lfx/services/settings/utils.py +40 -0
- lfx/services/shared_component_cache/__init__.py +1 -0
- lfx/services/shared_component_cache/factory.py +30 -0
- lfx/services/shared_component_cache/service.py +9 -0
- lfx/services/storage/__init__.py +5 -0
- lfx/services/storage/local.py +185 -0
- lfx/services/storage/service.py +177 -0
- lfx/services/tracing/__init__.py +1 -0
- lfx/services/tracing/service.py +21 -0
- lfx/settings.py +6 -0
- lfx/template/__init__.py +6 -0
- lfx/template/field/__init__.py +0 -0
- lfx/template/field/base.py +260 -0
- lfx/template/field/prompt.py +15 -0
- lfx/template/frontend_node/__init__.py +6 -0
- lfx/template/frontend_node/base.py +214 -0
- lfx/template/frontend_node/constants.py +65 -0
- lfx/template/frontend_node/custom_components.py +79 -0
- lfx/template/template/__init__.py +0 -0
- lfx/template/template/base.py +100 -0
- lfx/template/utils.py +217 -0
- lfx/type_extraction/__init__.py +19 -0
- lfx/type_extraction/type_extraction.py +75 -0
- lfx/type_extraction.py +80 -0
- lfx/utils/__init__.py +1 -0
- lfx/utils/async_helpers.py +42 -0
- lfx/utils/component_utils.py +154 -0
- lfx/utils/concurrency.py +60 -0
- lfx/utils/connection_string_parser.py +11 -0
- lfx/utils/constants.py +233 -0
- lfx/utils/data_structure.py +212 -0
- lfx/utils/exceptions.py +22 -0
- lfx/utils/helpers.py +34 -0
- lfx/utils/image.py +79 -0
- lfx/utils/langflow_utils.py +52 -0
- lfx/utils/lazy_load.py +15 -0
- lfx/utils/request_utils.py +18 -0
- lfx/utils/schemas.py +139 -0
- lfx/utils/ssrf_protection.py +384 -0
- lfx/utils/util.py +626 -0
- lfx/utils/util_strings.py +56 -0
- lfx/utils/validate_cloud.py +26 -0
- lfx/utils/version.py +24 -0
- lfx_nightly-0.2.0.dev25.dist-info/METADATA +312 -0
- lfx_nightly-0.2.0.dev25.dist-info/RECORD +769 -0
- lfx_nightly-0.2.0.dev25.dist-info/WHEEL +4 -0
- lfx_nightly-0.2.0.dev25.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Langflow environment utility functions."""
|
|
2
|
+
|
|
3
|
+
import importlib.util
|
|
4
|
+
|
|
5
|
+
from lfx.log.logger import logger
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class _LangflowModule:
|
|
9
|
+
# Static variable
|
|
10
|
+
# Tri-state:
|
|
11
|
+
# - None: Langflow check not performed yet
|
|
12
|
+
# - True: Langflow is available
|
|
13
|
+
# - False: Langflow is not available
|
|
14
|
+
_available = None
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def is_available(cls):
|
|
18
|
+
return cls._available
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def set_available(cls, value):
|
|
22
|
+
cls._available = value
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def has_langflow_memory():
|
|
26
|
+
"""Check if langflow.memory (with database support) and MessageTable are available."""
|
|
27
|
+
# TODO: REVISIT: Optimize this implementation later
|
|
28
|
+
# - Consider refactoring to use lazy loading or a more robust service discovery mechanism
|
|
29
|
+
# that can handle runtime availability changes.
|
|
30
|
+
|
|
31
|
+
# Use cached check from previous invocation (if applicable)
|
|
32
|
+
|
|
33
|
+
is_langflow_available = _LangflowModule.is_available()
|
|
34
|
+
|
|
35
|
+
if is_langflow_available is not None:
|
|
36
|
+
return is_langflow_available
|
|
37
|
+
|
|
38
|
+
# First check (lazy load and cache check)
|
|
39
|
+
|
|
40
|
+
module_spec = None
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
module_spec = importlib.util.find_spec("langflow")
|
|
44
|
+
except ImportError:
|
|
45
|
+
pass
|
|
46
|
+
except (TypeError, ValueError) as e:
|
|
47
|
+
logger.error(f"Error encountered checking for langflow.memory: {e}")
|
|
48
|
+
|
|
49
|
+
is_langflow_available = module_spec is not None
|
|
50
|
+
_LangflowModule.set_available(is_langflow_available)
|
|
51
|
+
|
|
52
|
+
return is_langflow_available
|
lfx/utils/lazy_load.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class LazyLoadDictBase:
|
|
2
|
+
def __init__(self) -> None:
|
|
3
|
+
self._all_types_dict = None
|
|
4
|
+
|
|
5
|
+
@property
|
|
6
|
+
def all_types_dict(self):
|
|
7
|
+
if self._all_types_dict is None:
|
|
8
|
+
self._all_types_dict = self._build_dict()
|
|
9
|
+
return self._all_types_dict
|
|
10
|
+
|
|
11
|
+
def _build_dict(self):
|
|
12
|
+
raise NotImplementedError
|
|
13
|
+
|
|
14
|
+
def get_type_dict(self):
|
|
15
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from lfx.services.deps import get_settings_service
|
|
2
|
+
|
|
3
|
+
DEFAULT_USER_AGENT = "Langflow"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_user_agent():
|
|
7
|
+
"""Get user agent with fallback."""
|
|
8
|
+
try:
|
|
9
|
+
settings_service = get_settings_service()
|
|
10
|
+
if (
|
|
11
|
+
settings_service
|
|
12
|
+
and hasattr(settings_service, "settings")
|
|
13
|
+
and hasattr(settings_service.settings, "user_agent")
|
|
14
|
+
):
|
|
15
|
+
return settings_service.settings.user_agent
|
|
16
|
+
except (AttributeError, TypeError):
|
|
17
|
+
pass
|
|
18
|
+
return DEFAULT_USER_AGENT
|
lfx/utils/schemas.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import enum
|
|
2
|
+
|
|
3
|
+
from langchain_core.messages import BaseMessage
|
|
4
|
+
from pydantic import BaseModel, field_validator, model_validator
|
|
5
|
+
from typing_extensions import TypedDict
|
|
6
|
+
|
|
7
|
+
from .constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_AI
|
|
8
|
+
|
|
9
|
+
# File types moved from lfx.base.data.utils
|
|
10
|
+
TEXT_FILE_TYPES = [
|
|
11
|
+
"txt",
|
|
12
|
+
"md",
|
|
13
|
+
"mdx",
|
|
14
|
+
"csv",
|
|
15
|
+
"json",
|
|
16
|
+
"yaml",
|
|
17
|
+
"yml",
|
|
18
|
+
"xml",
|
|
19
|
+
"html",
|
|
20
|
+
"htm",
|
|
21
|
+
"pdf",
|
|
22
|
+
"docx",
|
|
23
|
+
"py",
|
|
24
|
+
"sh",
|
|
25
|
+
"sql",
|
|
26
|
+
"js",
|
|
27
|
+
"ts",
|
|
28
|
+
"tsx",
|
|
29
|
+
]
|
|
30
|
+
IMG_FILE_TYPES = ["jpg", "jpeg", "png", "bmp", "image"]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class File(TypedDict):
|
|
34
|
+
"""File schema."""
|
|
35
|
+
|
|
36
|
+
path: str
|
|
37
|
+
name: str
|
|
38
|
+
type: str
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class ChatOutputResponse(BaseModel):
|
|
42
|
+
"""Chat output response schema."""
|
|
43
|
+
|
|
44
|
+
message: str | list[str | dict]
|
|
45
|
+
sender: str | None = MESSAGE_SENDER_AI
|
|
46
|
+
sender_name: str | None = MESSAGE_SENDER_NAME_AI
|
|
47
|
+
session_id: str | None = None
|
|
48
|
+
stream_url: str | None = None
|
|
49
|
+
component_id: str | None = None
|
|
50
|
+
files: list[File] = []
|
|
51
|
+
type: str
|
|
52
|
+
|
|
53
|
+
@field_validator("files", mode="before")
|
|
54
|
+
@classmethod
|
|
55
|
+
def validate_files(cls, files):
|
|
56
|
+
"""Validate files."""
|
|
57
|
+
if not files:
|
|
58
|
+
return files
|
|
59
|
+
|
|
60
|
+
for file in files:
|
|
61
|
+
if not isinstance(file, dict):
|
|
62
|
+
msg = "Files must be a list of dictionaries."
|
|
63
|
+
raise ValueError(msg) # noqa: TRY004
|
|
64
|
+
|
|
65
|
+
if not all(key in file for key in ["path", "name", "type"]):
|
|
66
|
+
# If any of the keys are missing, we should extract the
|
|
67
|
+
# values from the file path
|
|
68
|
+
path = file.get("path")
|
|
69
|
+
if not path:
|
|
70
|
+
msg = "File path is required."
|
|
71
|
+
raise ValueError(msg)
|
|
72
|
+
|
|
73
|
+
name = file.get("name")
|
|
74
|
+
if not name:
|
|
75
|
+
name = path.split("/")[-1]
|
|
76
|
+
file["name"] = name
|
|
77
|
+
type_ = file.get("type")
|
|
78
|
+
if not type_:
|
|
79
|
+
# get the file type from the path
|
|
80
|
+
extension = path.split(".")[-1]
|
|
81
|
+
file_types = set(TEXT_FILE_TYPES + IMG_FILE_TYPES)
|
|
82
|
+
if extension and extension in file_types:
|
|
83
|
+
type_ = extension
|
|
84
|
+
else:
|
|
85
|
+
for file_type in file_types:
|
|
86
|
+
if file_type in path:
|
|
87
|
+
type_ = file_type
|
|
88
|
+
break
|
|
89
|
+
if not type_:
|
|
90
|
+
msg = "File type is required."
|
|
91
|
+
raise ValueError(msg)
|
|
92
|
+
file["type"] = type_
|
|
93
|
+
|
|
94
|
+
return files
|
|
95
|
+
|
|
96
|
+
@classmethod
|
|
97
|
+
def from_message(
|
|
98
|
+
cls,
|
|
99
|
+
message: BaseMessage,
|
|
100
|
+
sender: str | None = MESSAGE_SENDER_AI,
|
|
101
|
+
sender_name: str | None = MESSAGE_SENDER_NAME_AI,
|
|
102
|
+
):
|
|
103
|
+
"""Build chat output response from message."""
|
|
104
|
+
content = message.content
|
|
105
|
+
return cls(message=content, sender=sender, sender_name=sender_name)
|
|
106
|
+
|
|
107
|
+
@model_validator(mode="after")
|
|
108
|
+
def validate_message(self):
|
|
109
|
+
"""Validate message."""
|
|
110
|
+
# The idea here is ensure the \n in message
|
|
111
|
+
# is compliant with markdown if sender is machine
|
|
112
|
+
# so, for example:
|
|
113
|
+
# \n\n -> \n\n
|
|
114
|
+
# \n -> \n\n
|
|
115
|
+
|
|
116
|
+
if self.sender != MESSAGE_SENDER_AI:
|
|
117
|
+
return self
|
|
118
|
+
|
|
119
|
+
# We need to make sure we don't duplicate \n
|
|
120
|
+
# in the message
|
|
121
|
+
message = self.message.replace("\n\n", "\n")
|
|
122
|
+
self.message = message.replace("\n", "\n\n")
|
|
123
|
+
return self
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class DataOutputResponse(BaseModel):
|
|
127
|
+
"""Data output response schema."""
|
|
128
|
+
|
|
129
|
+
data: list[dict | None]
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class ContainsEnumMeta(enum.EnumMeta):
|
|
133
|
+
def __contains__(cls, item) -> bool:
|
|
134
|
+
try:
|
|
135
|
+
cls(item)
|
|
136
|
+
except ValueError:
|
|
137
|
+
return False
|
|
138
|
+
else:
|
|
139
|
+
return True
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
"""SSRF (Server-Side Request Forgery) protection utilities.
|
|
2
|
+
|
|
3
|
+
This module provides validation to prevent SSRF attacks by blocking requests to:
|
|
4
|
+
- Private IP ranges (RFC 1918)
|
|
5
|
+
- Loopback addresses
|
|
6
|
+
- Cloud metadata endpoints (169.254.169.254)
|
|
7
|
+
- Other internal/special-use addresses
|
|
8
|
+
|
|
9
|
+
IMPORTANT: HTTP Redirects
|
|
10
|
+
According to OWASP SSRF Prevention Cheat Sheet, HTTP redirects should be DISABLED
|
|
11
|
+
to prevent bypass attacks where a public URL redirects to internal resources.
|
|
12
|
+
The API Request component has (as of v1.7.0) follow_redirects=False by default.
|
|
13
|
+
See: https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html
|
|
14
|
+
|
|
15
|
+
Configuration:
|
|
16
|
+
LANGFLOW_SSRF_PROTECTION_ENABLED: Enable/disable SSRF protection (default: false)
|
|
17
|
+
TODO: Change default to true in next major version (2.0)
|
|
18
|
+
LANGFLOW_SSRF_ALLOWED_HOSTS: Comma-separated list of allowed hosts/CIDR ranges
|
|
19
|
+
Examples: "192.168.1.0/24,internal-api.company.local,10.0.0.5"
|
|
20
|
+
|
|
21
|
+
TODO: In next major version (2.0):
|
|
22
|
+
- Change LANGFLOW_SSRF_PROTECTION_ENABLED default to "true"
|
|
23
|
+
- Remove warning-only mode and enforce blocking
|
|
24
|
+
- Update documentation to reflect breaking change
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import functools
|
|
28
|
+
import ipaddress
|
|
29
|
+
import socket
|
|
30
|
+
from urllib.parse import urlparse
|
|
31
|
+
|
|
32
|
+
from lfx.logging import logger
|
|
33
|
+
from lfx.services.deps import get_settings_service
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class SSRFProtectionError(ValueError):
|
|
37
|
+
"""Raised when a URL is blocked due to SSRF protection."""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@functools.cache
|
|
41
|
+
def get_blocked_ip_ranges() -> list[ipaddress.IPv4Network | ipaddress.IPv6Network]:
|
|
42
|
+
"""Get the list of blocked IP ranges, initializing lazily on first access.
|
|
43
|
+
|
|
44
|
+
This lazy loading avoids the startup cost of creating all ip_network objects
|
|
45
|
+
at module import time.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
list: List of blocked IPv4 and IPv6 network ranges.
|
|
49
|
+
"""
|
|
50
|
+
return [
|
|
51
|
+
# IPv4 ranges
|
|
52
|
+
ipaddress.ip_network("0.0.0.0/8"), # Current network (only valid as source)
|
|
53
|
+
ipaddress.ip_network("10.0.0.0/8"), # Private network (RFC 1918)
|
|
54
|
+
ipaddress.ip_network("100.64.0.0/10"), # Carrier-grade NAT (RFC 6598)
|
|
55
|
+
ipaddress.ip_network("127.0.0.0/8"), # Loopback
|
|
56
|
+
ipaddress.ip_network("169.254.0.0/16"), # Link-local / AWS metadata
|
|
57
|
+
ipaddress.ip_network("172.16.0.0/12"), # Private network (RFC 1918)
|
|
58
|
+
ipaddress.ip_network("192.0.0.0/24"), # IETF Protocol Assignments
|
|
59
|
+
ipaddress.ip_network("192.0.2.0/24"), # Documentation (TEST-NET-1)
|
|
60
|
+
ipaddress.ip_network("192.168.0.0/16"), # Private network (RFC 1918)
|
|
61
|
+
ipaddress.ip_network("198.18.0.0/15"), # Benchmarking
|
|
62
|
+
ipaddress.ip_network("198.51.100.0/24"), # Documentation (TEST-NET-2)
|
|
63
|
+
ipaddress.ip_network("203.0.113.0/24"), # Documentation (TEST-NET-3)
|
|
64
|
+
ipaddress.ip_network("224.0.0.0/4"), # Multicast
|
|
65
|
+
ipaddress.ip_network("240.0.0.0/4"), # Reserved
|
|
66
|
+
ipaddress.ip_network("255.255.255.255/32"), # Broadcast
|
|
67
|
+
# IPv6 ranges
|
|
68
|
+
ipaddress.ip_network("::1/128"), # Loopback
|
|
69
|
+
ipaddress.ip_network("::/128"), # Unspecified address
|
|
70
|
+
ipaddress.ip_network("::ffff:0:0/96"), # IPv4-mapped IPv6 addresses
|
|
71
|
+
ipaddress.ip_network("100::/64"), # Discard prefix
|
|
72
|
+
ipaddress.ip_network("2001::/23"), # IETF Protocol Assignments
|
|
73
|
+
ipaddress.ip_network("2001:db8::/32"), # Documentation
|
|
74
|
+
ipaddress.ip_network("fc00::/7"), # Unique local addresses (ULA)
|
|
75
|
+
ipaddress.ip_network("fe80::/10"), # Link-local
|
|
76
|
+
ipaddress.ip_network("ff00::/8"), # Multicast
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def is_ssrf_protection_enabled() -> bool:
|
|
81
|
+
"""Check if SSRF protection is enabled in settings.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
bool: True if SSRF protection is enabled, False otherwise.
|
|
85
|
+
"""
|
|
86
|
+
return get_settings_service().settings.ssrf_protection_enabled
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def get_allowed_hosts() -> list[str]:
|
|
90
|
+
"""Get list of allowed hosts and/or CIDR ranges for SSRF protection.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
list[str]: Stripped hostnames or CIDR blocks from settings, or empty list if unset.
|
|
94
|
+
"""
|
|
95
|
+
allowed_hosts = get_settings_service().settings.ssrf_allowed_hosts
|
|
96
|
+
if not allowed_hosts:
|
|
97
|
+
return []
|
|
98
|
+
# ssrf_allowed_hosts is already a list[str], just clean and filter entries
|
|
99
|
+
return [host.strip() for host in allowed_hosts if host and host.strip()]
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def is_host_allowed(hostname: str, ip: str | None = None) -> bool:
|
|
103
|
+
"""Check if a hostname or IP is in the allowed hosts list.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
hostname: Hostname to check
|
|
107
|
+
ip: Optional IP address to check
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
bool: True if hostname or IP is in the allowed list, False otherwise.
|
|
111
|
+
"""
|
|
112
|
+
allowed_hosts = get_allowed_hosts()
|
|
113
|
+
if not allowed_hosts:
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
# Check hostname match
|
|
117
|
+
if hostname in allowed_hosts:
|
|
118
|
+
return True
|
|
119
|
+
|
|
120
|
+
# Check if hostname matches any wildcard patterns
|
|
121
|
+
for allowed in allowed_hosts:
|
|
122
|
+
if allowed.startswith("*."):
|
|
123
|
+
# Wildcard domain matching
|
|
124
|
+
domain_suffix = allowed[1:] # Remove the *
|
|
125
|
+
if hostname.endswith(domain_suffix) or hostname == domain_suffix[1:]:
|
|
126
|
+
return True
|
|
127
|
+
|
|
128
|
+
# Check IP-based matching if IP is provided
|
|
129
|
+
if ip:
|
|
130
|
+
try:
|
|
131
|
+
ip_obj = ipaddress.ip_address(ip)
|
|
132
|
+
|
|
133
|
+
# Check exact IP match
|
|
134
|
+
if ip in allowed_hosts:
|
|
135
|
+
return True
|
|
136
|
+
|
|
137
|
+
# Check CIDR range match
|
|
138
|
+
for allowed in allowed_hosts:
|
|
139
|
+
try:
|
|
140
|
+
# Try to parse as CIDR network
|
|
141
|
+
if "/" in allowed:
|
|
142
|
+
network = ipaddress.ip_network(allowed, strict=False)
|
|
143
|
+
if ip_obj in network:
|
|
144
|
+
return True
|
|
145
|
+
except (ValueError, ipaddress.AddressValueError):
|
|
146
|
+
# Not a valid CIDR, skip
|
|
147
|
+
continue
|
|
148
|
+
|
|
149
|
+
except (ValueError, ipaddress.AddressValueError):
|
|
150
|
+
# Invalid IP, skip IP-based checks
|
|
151
|
+
pass
|
|
152
|
+
|
|
153
|
+
return False
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def is_ip_blocked(ip: str | ipaddress.IPv4Address | ipaddress.IPv6Address) -> bool:
|
|
157
|
+
"""Check if an IP address is in a blocked range.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
ip: IP address to check (string or ipaddress object)
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
bool: True if IP is in a blocked range, False otherwise.
|
|
164
|
+
"""
|
|
165
|
+
try:
|
|
166
|
+
ip_obj = ipaddress.ip_address(ip) if isinstance(ip, str) else ip
|
|
167
|
+
|
|
168
|
+
# Check against all blocked ranges
|
|
169
|
+
return any(ip_obj in blocked_range for blocked_range in get_blocked_ip_ranges())
|
|
170
|
+
except (ValueError, ipaddress.AddressValueError):
|
|
171
|
+
# If we can't parse the IP, treat it as blocked for safety
|
|
172
|
+
return True
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def resolve_hostname(hostname: str) -> list[str]:
|
|
176
|
+
"""Resolve a hostname to its IP addresses.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
hostname: Hostname to resolve
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
list[str]: List of resolved IP addresses
|
|
183
|
+
|
|
184
|
+
Raises:
|
|
185
|
+
SSRFProtectionError: If hostname cannot be resolved
|
|
186
|
+
"""
|
|
187
|
+
try:
|
|
188
|
+
# Get address info for both IPv4 and IPv6
|
|
189
|
+
addr_info = socket.getaddrinfo(hostname, None)
|
|
190
|
+
|
|
191
|
+
# Extract unique IP addresses
|
|
192
|
+
ips = []
|
|
193
|
+
for info in addr_info:
|
|
194
|
+
ip = info[4][0]
|
|
195
|
+
# Remove IPv6 zone ID if present (e.g., "fe80::1%eth0" -> "fe80::1")
|
|
196
|
+
if "%" in ip:
|
|
197
|
+
ip = ip.split("%")[0]
|
|
198
|
+
if ip not in ips:
|
|
199
|
+
ips.append(ip)
|
|
200
|
+
|
|
201
|
+
if not ips:
|
|
202
|
+
msg = f"Unable to resolve hostname: {hostname}"
|
|
203
|
+
raise SSRFProtectionError(msg)
|
|
204
|
+
except socket.gaierror as e:
|
|
205
|
+
msg = f"DNS resolution failed for {hostname}: {e}"
|
|
206
|
+
raise SSRFProtectionError(msg) from e
|
|
207
|
+
except Exception as e:
|
|
208
|
+
msg = f"Error resolving hostname {hostname}: {e}"
|
|
209
|
+
raise SSRFProtectionError(msg) from e
|
|
210
|
+
|
|
211
|
+
return ips
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _validate_url_scheme(scheme: str) -> None:
|
|
215
|
+
"""Validate that URL scheme is http or https.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
scheme: URL scheme to validate
|
|
219
|
+
|
|
220
|
+
Raises:
|
|
221
|
+
SSRFProtectionError: If scheme is invalid
|
|
222
|
+
"""
|
|
223
|
+
if scheme not in ("http", "https"):
|
|
224
|
+
msg = f"Invalid URL scheme '{scheme}'. Only http and https are allowed."
|
|
225
|
+
raise SSRFProtectionError(msg)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _validate_hostname_exists(hostname: str | None) -> str:
|
|
229
|
+
"""Validate that hostname exists in the URL.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
hostname: Hostname to validate (may be None)
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
str: The validated hostname
|
|
236
|
+
|
|
237
|
+
Raises:
|
|
238
|
+
SSRFProtectionError: If hostname is missing
|
|
239
|
+
"""
|
|
240
|
+
if not hostname:
|
|
241
|
+
msg = "URL must contain a valid hostname"
|
|
242
|
+
raise SSRFProtectionError(msg)
|
|
243
|
+
return hostname
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _validate_direct_ip_address(hostname: str) -> bool:
|
|
247
|
+
"""Validate a direct IP address in the URL.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
hostname: Hostname that may be an IP address
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
bool: True if hostname is a direct IP and validation passed,
|
|
254
|
+
False if hostname is not an IP (caller should continue with DNS resolution)
|
|
255
|
+
|
|
256
|
+
Raises:
|
|
257
|
+
SSRFProtectionError: If IP is blocked
|
|
258
|
+
"""
|
|
259
|
+
try:
|
|
260
|
+
ip_obj = ipaddress.ip_address(hostname)
|
|
261
|
+
except ValueError:
|
|
262
|
+
# Not an IP address, it's a hostname - caller should continue with DNS resolution
|
|
263
|
+
return False
|
|
264
|
+
|
|
265
|
+
# It's a direct IP address
|
|
266
|
+
# Check if IP is in allowlist
|
|
267
|
+
if is_host_allowed(hostname, str(ip_obj)):
|
|
268
|
+
logger.debug("IP address %s is in allowlist, bypassing SSRF checks", hostname)
|
|
269
|
+
return True
|
|
270
|
+
|
|
271
|
+
if is_ip_blocked(ip_obj):
|
|
272
|
+
msg = (
|
|
273
|
+
f"Access to IP address {hostname} is blocked by SSRF protection. "
|
|
274
|
+
"Requests to private/internal IP ranges are not allowed for security reasons. "
|
|
275
|
+
"To allow this IP, add it to LANGFLOW_SSRF_ALLOWED_HOSTS environment variable."
|
|
276
|
+
)
|
|
277
|
+
raise SSRFProtectionError(msg)
|
|
278
|
+
|
|
279
|
+
# Direct IP is allowed (public IP)
|
|
280
|
+
return True
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _validate_hostname_resolution(hostname: str) -> None:
|
|
284
|
+
"""Resolve hostname and validate resolved IPs are not blocked.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
hostname: Hostname to resolve and validate
|
|
288
|
+
|
|
289
|
+
Raises:
|
|
290
|
+
SSRFProtectionError: If resolved IPs are blocked
|
|
291
|
+
"""
|
|
292
|
+
# Resolve hostname to IP addresses
|
|
293
|
+
try:
|
|
294
|
+
resolved_ips = resolve_hostname(hostname)
|
|
295
|
+
except SSRFProtectionError:
|
|
296
|
+
# Re-raise SSRF errors as-is
|
|
297
|
+
raise
|
|
298
|
+
except Exception as e:
|
|
299
|
+
msg = f"Failed to resolve hostname {hostname}: {e}"
|
|
300
|
+
raise SSRFProtectionError(msg) from e
|
|
301
|
+
|
|
302
|
+
# Check if any resolved IP is blocked
|
|
303
|
+
blocked_ips = []
|
|
304
|
+
for ip in resolved_ips:
|
|
305
|
+
# Check if this specific IP is in the allowlist
|
|
306
|
+
if is_host_allowed(hostname, ip):
|
|
307
|
+
logger.debug("Resolved IP %s for hostname %s is in allowlist, bypassing SSRF checks", ip, hostname)
|
|
308
|
+
return
|
|
309
|
+
|
|
310
|
+
if is_ip_blocked(ip):
|
|
311
|
+
blocked_ips.append(ip)
|
|
312
|
+
|
|
313
|
+
if blocked_ips:
|
|
314
|
+
msg = (
|
|
315
|
+
f"Hostname {hostname} resolves to blocked IP address(es): {', '.join(blocked_ips)}. "
|
|
316
|
+
"Requests to private/internal IP ranges are not allowed for security reasons. "
|
|
317
|
+
"This protection prevents access to internal services, cloud metadata endpoints "
|
|
318
|
+
"(e.g., AWS 169.254.169.254), and other sensitive internal resources. "
|
|
319
|
+
"To allow this hostname, add it to LANGFLOW_SSRF_ALLOWED_HOSTS environment variable."
|
|
320
|
+
)
|
|
321
|
+
raise SSRFProtectionError(msg)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def validate_url_for_ssrf(url: str, *, warn_only: bool = True) -> None:
|
|
325
|
+
"""Validate a URL to prevent SSRF attacks.
|
|
326
|
+
|
|
327
|
+
This function performs the following checks:
|
|
328
|
+
1. Validates the URL scheme (only http/https allowed)
|
|
329
|
+
2. Validates hostname exists
|
|
330
|
+
3. Checks if hostname/IP is in allowlist
|
|
331
|
+
4. If direct IP: validates it's not in blocked ranges
|
|
332
|
+
5. If hostname: resolves to IPs and validates they're not in blocked ranges
|
|
333
|
+
|
|
334
|
+
Args:
|
|
335
|
+
url: URL to validate
|
|
336
|
+
warn_only: If True, only log warnings instead of raising errors (default: True)
|
|
337
|
+
TODO: Change default to False in next major version (2.0)
|
|
338
|
+
|
|
339
|
+
Raises:
|
|
340
|
+
SSRFProtectionError: If the URL is blocked due to SSRF protection (only if warn_only=False)
|
|
341
|
+
ValueError: If the URL is malformed
|
|
342
|
+
"""
|
|
343
|
+
# Skip validation if SSRF protection is disabled
|
|
344
|
+
if not is_ssrf_protection_enabled():
|
|
345
|
+
return
|
|
346
|
+
|
|
347
|
+
# Parse URL
|
|
348
|
+
try:
|
|
349
|
+
parsed = urlparse(url)
|
|
350
|
+
except Exception as e:
|
|
351
|
+
msg = f"Invalid URL format: {e}"
|
|
352
|
+
raise ValueError(msg) from e
|
|
353
|
+
|
|
354
|
+
try:
|
|
355
|
+
# Validate scheme
|
|
356
|
+
_validate_url_scheme(parsed.scheme)
|
|
357
|
+
if parsed.scheme not in ("http", "https"):
|
|
358
|
+
return
|
|
359
|
+
|
|
360
|
+
# Validate hostname exists
|
|
361
|
+
hostname = _validate_hostname_exists(parsed.hostname)
|
|
362
|
+
|
|
363
|
+
# Check if hostname/IP is in allowlist (early return if allowed)
|
|
364
|
+
if is_host_allowed(hostname):
|
|
365
|
+
logger.debug("Hostname %s is in allowlist, bypassing SSRF checks", hostname)
|
|
366
|
+
return
|
|
367
|
+
|
|
368
|
+
# Validate direct IP address or resolve hostname
|
|
369
|
+
is_direct_ip = _validate_direct_ip_address(hostname)
|
|
370
|
+
if is_direct_ip:
|
|
371
|
+
# Direct IP was handled (allowed or exception raised)
|
|
372
|
+
return
|
|
373
|
+
|
|
374
|
+
# Not a direct IP, resolve hostname and validate
|
|
375
|
+
_validate_hostname_resolution(hostname)
|
|
376
|
+
except SSRFProtectionError as e:
|
|
377
|
+
if warn_only:
|
|
378
|
+
logger.warning("SSRF Protection Warning: %s [URL: %s]", str(e), url)
|
|
379
|
+
logger.warning(
|
|
380
|
+
"This request will be blocked when SSRF protection is enforced in the next major version. "
|
|
381
|
+
"Please review your API Request components."
|
|
382
|
+
)
|
|
383
|
+
return
|
|
384
|
+
raise
|