lfx-nightly 0.1.11.dev0__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.
- lfx/__init__.py +0 -0
- lfx/__main__.py +25 -0
- lfx/base/__init__.py +0 -0
- lfx/base/agents/__init__.py +0 -0
- lfx/base/agents/agent.py +268 -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 +346 -0
- lfx/base/agents/utils.py +205 -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 +1291 -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 +685 -0
- lfx/base/data/docling_utils.py +245 -0
- lfx/base/data/utils.py +198 -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/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 +20 -0
- lfx/base/io/text.py +22 -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 +1398 -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 +47 -0
- lfx/base/models/aws_constants.py +151 -0
- lfx/base/models/chat_result.py +76 -0
- lfx/base/models/google_generative_ai_constants.py +70 -0
- lfx/base/models/groq_constants.py +134 -0
- lfx/base/models/model.py +375 -0
- lfx/base/models/model_input_constants.py +307 -0
- lfx/base/models/model_metadata.py +41 -0
- lfx/base/models/model_utils.py +8 -0
- lfx/base/models/novita_constants.py +35 -0
- lfx/base/models/ollama_constants.py +49 -0
- lfx/base/models/openai_constants.py +122 -0
- lfx/base/models/sambanova_constants.py +18 -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 +224 -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 +319 -0
- lfx/cli/common.py +650 -0
- lfx/cli/run.py +441 -0
- lfx/cli/script_loader.py +247 -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 +411 -0
- lfx/components/_importing.py +42 -0
- lfx/components/agentql/__init__.py +3 -0
- lfx/components/agentql/agentql_api.py +151 -0
- lfx/components/agents/__init__.py +34 -0
- lfx/components/agents/agent.py +558 -0
- lfx/components/agents/mcp_component.py +501 -0
- lfx/components/aiml/__init__.py +37 -0
- lfx/components/aiml/aiml.py +112 -0
- lfx/components/aiml/aiml_embeddings.py +37 -0
- lfx/components/amazon/__init__.py +36 -0
- lfx/components/amazon/amazon_bedrock_embedding.py +109 -0
- lfx/components/amazon/amazon_bedrock_model.py +124 -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 +163 -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 +167 -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/composio/__init__.py +74 -0
- lfx/components/composio/composio_api.py +268 -0
- lfx/components/composio/dropbox_compnent.py +11 -0
- lfx/components/composio/github_composio.py +11 -0
- lfx/components/composio/gmail_composio.py +38 -0
- lfx/components/composio/googlecalendar_composio.py +11 -0
- lfx/components/composio/googlemeet_composio.py +11 -0
- lfx/components/composio/googletasks_composio.py +8 -0
- lfx/components/composio/linear_composio.py +11 -0
- lfx/components/composio/outlook_composio.py +11 -0
- lfx/components/composio/reddit_composio.py +11 -0
- lfx/components/composio/slack_composio.py +582 -0
- lfx/components/composio/slackbot_composio.py +11 -0
- lfx/components/composio/supabase_composio.py +11 -0
- lfx/components/composio/todoist_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 +107 -0
- lfx/components/crewai/hierarchical_crew.py +46 -0
- lfx/components/crewai/hierarchical_task.py +44 -0
- lfx/components/crewai/sequential_crew.py +52 -0
- lfx/components/crewai/sequential_task.py +73 -0
- lfx/components/crewai/sequential_task_agent.py +143 -0
- lfx/components/custom_component/__init__.py +34 -0
- lfx/components/custom_component/custom_component.py +31 -0
- lfx/components/data/__init__.py +64 -0
- lfx/components/data/api_request.py +544 -0
- lfx/components/data/csv_to_data.py +95 -0
- lfx/components/data/directory.py +113 -0
- lfx/components/data/file.py +577 -0
- lfx/components/data/json_to_data.py +98 -0
- lfx/components/data/news_search.py +164 -0
- lfx/components/data/rss.py +69 -0
- lfx/components/data/sql_executor.py +101 -0
- lfx/components/data/url.py +311 -0
- lfx/components/data/web_search.py +112 -0
- lfx/components/data/webhook.py +56 -0
- lfx/components/datastax/__init__.py +70 -0
- lfx/components/datastax/astra_assistant_manager.py +306 -0
- lfx/components/datastax/astra_db.py +75 -0
- lfx/components/datastax/astra_vectorize.py +124 -0
- lfx/components/datastax/astradb.py +1285 -0
- lfx/components/datastax/astradb_cql.py +314 -0
- lfx/components/datastax/astradb_graph.py +330 -0
- lfx/components/datastax/astradb_tool.py +414 -0
- lfx/components/datastax/astradb_vectorstore.py +1285 -0
- lfx/components/datastax/cassandra.py +92 -0
- lfx/components/datastax/create_assistant.py +58 -0
- lfx/components/datastax/create_thread.py +32 -0
- lfx/components/datastax/dotenv.py +35 -0
- lfx/components/datastax/get_assistant.py +37 -0
- lfx/components/datastax/getenvvar.py +30 -0
- lfx/components/datastax/graph_rag.py +141 -0
- lfx/components/datastax/hcd.py +314 -0
- lfx/components/datastax/list_assistants.py +25 -0
- lfx/components/datastax/run.py +89 -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 +231 -0
- lfx/components/docling/docling_remote.py +193 -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 +243 -0
- lfx/components/embeddings/__init__.py +37 -0
- lfx/components/embeddings/similarity.py +76 -0
- lfx/components/embeddings/text_embedder.py +64 -0
- lfx/components/exa/__init__.py +3 -0
- lfx/components/exa/exa_search.py +68 -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/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 +192 -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 +147 -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 +136 -0
- lfx/components/helpers/__init__.py +52 -0
- lfx/components/helpers/calculator_core.py +89 -0
- lfx/components/helpers/create_list.py +40 -0
- lfx/components/helpers/current_date.py +42 -0
- lfx/components/helpers/id_generator.py +42 -0
- lfx/components/helpers/memory.py +251 -0
- lfx/components/helpers/output_parser.py +45 -0
- lfx/components/helpers/store_message.py +90 -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 +197 -0
- lfx/components/huggingface/huggingface_inference_api.py +106 -0
- lfx/components/ibm/__init__.py +34 -0
- lfx/components/ibm/watsonx.py +203 -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 +38 -0
- lfx/components/input_output/chat.py +120 -0
- lfx/components/input_output/chat_output.py +200 -0
- lfx/components/input_output/text.py +27 -0
- lfx/components/input_output/text_output.py +29 -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/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 +107 -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 +45 -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/lmstudio/__init__.py +34 -0
- lfx/components/lmstudio/lmstudioembeddings.py +89 -0
- lfx/components/lmstudio/lmstudiomodel.py +129 -0
- lfx/components/logic/__init__.py +52 -0
- lfx/components/logic/conditional_router.py +171 -0
- lfx/components/logic/data_conditional_router.py +125 -0
- lfx/components/logic/flow_tool.py +110 -0
- lfx/components/logic/listen.py +29 -0
- lfx/components/logic/loop.py +125 -0
- lfx/components/logic/notify.py +88 -0
- lfx/components/logic/pass_message.py +35 -0
- lfx/components/logic/run_flow.py +71 -0
- lfx/components/logic/sub_flow.py +114 -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 +136 -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 +34 -0
- lfx/components/models/embedding_model.py +114 -0
- lfx/components/models/language_model.py +144 -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 +157 -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 +330 -0
- lfx/components/ollama/ollama_embeddings.py +106 -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 +202 -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 +117 -0
- lfx/components/processing/alter_metadata.py +108 -0
- lfx/components/processing/batch_run.py +205 -0
- lfx/components/processing/combine_text.py +39 -0
- lfx/components/processing/converter.py +159 -0
- lfx/components/processing/create_data.py +110 -0
- lfx/components/processing/data_operations.py +438 -0
- lfx/components/processing/data_to_dataframe.py +70 -0
- lfx/components/processing/dataframe_operations.py +313 -0
- lfx/components/processing/extract_key.py +53 -0
- lfx/components/processing/filter_data.py +42 -0
- lfx/components/processing/filter_data_values.py +88 -0
- lfx/components/processing/json_cleaner.py +103 -0
- lfx/components/processing/lambda_filter.py +154 -0
- lfx/components/processing/llm_router.py +499 -0
- lfx/components/processing/merge_data.py +90 -0
- lfx/components/processing/message_to_data.py +36 -0
- lfx/components/processing/parse_data.py +70 -0
- lfx/components/processing/parse_dataframe.py +68 -0
- lfx/components/processing/parse_json_data.py +90 -0
- lfx/components/processing/parser.py +143 -0
- lfx/components/processing/prompt.py +67 -0
- lfx/components/processing/python_repl_core.py +98 -0
- lfx/components/processing/regex.py +82 -0
- lfx/components/processing/save_file.py +225 -0
- lfx/components/processing/select_data.py +48 -0
- lfx/components/processing/split_text.py +141 -0
- lfx/components/processing/structured_output.py +202 -0
- lfx/components/processing/update_data.py +160 -0
- lfx/components/prototypes/__init__.py +34 -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 +72 -0
- lfx/components/tools/calculator.py +108 -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 +327 -0
- lfx/components/tools/python_repl.py +97 -0
- lfx/components/tools/search_api.py +87 -0
- lfx/components/tools/searxng.py +145 -0
- lfx/components/tools/serp_api.py +119 -0
- lfx/components/tools/tavily_search_tool.py +344 -0
- lfx/components/tools/wikidata_api.py +102 -0
- lfx/components/tools/wikipedia_api.py +49 -0
- lfx/components/tools/yahoo_finance.py +129 -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 +291 -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 +179 -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/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 +40 -0
- lfx/components/vectorstores/astradb.py +1285 -0
- lfx/components/vectorstores/astradb_graph.py +319 -0
- lfx/components/vectorstores/cassandra.py +264 -0
- lfx/components/vectorstores/cassandra_graph.py +238 -0
- lfx/components/vectorstores/chroma.py +167 -0
- lfx/components/vectorstores/clickhouse.py +135 -0
- lfx/components/vectorstores/couchbase.py +102 -0
- lfx/components/vectorstores/elasticsearch.py +267 -0
- lfx/components/vectorstores/faiss.py +111 -0
- lfx/components/vectorstores/graph_rag.py +141 -0
- lfx/components/vectorstores/hcd.py +314 -0
- lfx/components/vectorstores/local_db.py +261 -0
- lfx/components/vectorstores/milvus.py +115 -0
- lfx/components/vectorstores/mongodb_atlas.py +213 -0
- lfx/components/vectorstores/opensearch.py +243 -0
- lfx/components/vectorstores/pgvector.py +72 -0
- lfx/components/vectorstores/pinecone.py +134 -0
- lfx/components/vectorstores/qdrant.py +109 -0
- lfx/components/vectorstores/supabase.py +76 -0
- lfx/components/vectorstores/upstash.py +124 -0
- lfx/components/vectorstores/vectara.py +97 -0
- lfx/components/vectorstores/vectara_rag.py +164 -0
- lfx/components/vectorstores/weaviate.py +89 -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/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 +118 -0
- lfx/components/zep/__init__.py +3 -0
- lfx/components/zep/zep.py +44 -0
- lfx/constants.py +6 -0
- lfx/custom/__init__.py +7 -0
- lfx/custom/attributes.py +86 -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 +1808 -0
- lfx/custom/custom_component/component_with_cache.py +8 -0
- lfx/custom/custom_component/custom_component.py +588 -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 +488 -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 +215 -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 +277 -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 +2238 -0
- lfx/graph/graph/constants.py +63 -0
- lfx/graph/graph/runnable_vertices_manager.py +133 -0
- lfx/graph/graph/schema.py +52 -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 +237 -0
- lfx/graph/utils.py +200 -0
- lfx/graph/vertex/__init__.py +0 -0
- lfx/graph/vertex/base.py +823 -0
- lfx/graph/vertex/constants.py +0 -0
- lfx/graph/vertex/exceptions.py +4 -0
- lfx/graph/vertex/param_handler.py +264 -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 +1 -0
- lfx/helpers/base_model.py +71 -0
- lfx/helpers/custom.py +13 -0
- lfx/helpers/data.py +167 -0
- lfx/helpers/flow.py +194 -0
- lfx/inputs/__init__.py +68 -0
- lfx/inputs/constants.py +2 -0
- lfx/inputs/input_mixin.py +328 -0
- lfx/inputs/inputs.py +714 -0
- lfx/inputs/validators.py +19 -0
- lfx/interface/__init__.py +6 -0
- lfx/interface/components.py +489 -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 +224 -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 +289 -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 +385 -0
- lfx/memory/__init__.py +90 -0
- lfx/memory/stubs.py +283 -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/data.py +308 -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 +131 -0
- lfx/schema/json_schema.py +141 -0
- lfx/schema/log.py +61 -0
- lfx/schema/message.py +473 -0
- lfx/schema/openai_responses_schemas.py +74 -0
- lfx/schema/properties.py +41 -0
- lfx/schema/schema.py +171 -0
- lfx/schema/serialize.py +13 -0
- lfx/schema/table.py +140 -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 +23 -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/deps.py +129 -0
- lfx/services/factory.py +19 -0
- lfx/services/initialize.py +19 -0
- lfx/services/interfaces.py +103 -0
- lfx/services/manager.py +172 -0
- lfx/services/schema.py +20 -0
- lfx/services/session.py +82 -0
- lfx/services/settings/__init__.py +3 -0
- lfx/services/settings/auth.py +130 -0
- lfx/services/settings/base.py +539 -0
- lfx/services/settings/constants.py +31 -0
- lfx/services/settings/factory.py +23 -0
- lfx/services/settings/feature_flags.py +12 -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 +155 -0
- lfx/services/storage/service.py +54 -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 +257 -0
- lfx/template/field/prompt.py +15 -0
- lfx/template/frontend_node/__init__.py +6 -0
- lfx/template/frontend_node/base.py +212 -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 +205 -0
- lfx/utils/data_structure.py +212 -0
- lfx/utils/exceptions.py +22 -0
- lfx/utils/helpers.py +28 -0
- lfx/utils/image.py +73 -0
- lfx/utils/lazy_load.py +15 -0
- lfx/utils/request_utils.py +18 -0
- lfx/utils/schemas.py +139 -0
- lfx/utils/util.py +481 -0
- lfx/utils/util_strings.py +56 -0
- lfx/utils/version.py +24 -0
- lfx_nightly-0.1.11.dev0.dist-info/METADATA +293 -0
- lfx_nightly-0.1.11.dev0.dist-info/RECORD +699 -0
- lfx_nightly-0.1.11.dev0.dist-info/WHEEL +4 -0
- lfx_nightly-0.1.11.dev0.dist-info/entry_points.txt +2 -0
lfx/cli/serve_app.py
ADDED
@@ -0,0 +1,546 @@
|
|
1
|
+
"""FastAPI application factory for serving **multiple** LFX graphs at once.
|
2
|
+
|
3
|
+
This module is used by the CLI *serve* command when the provided path is a
|
4
|
+
folder containing multiple ``*.json`` flow files. Each flow is exposed under
|
5
|
+
its own router prefix::
|
6
|
+
|
7
|
+
/flows/{flow_id}/run - POST - execute the flow
|
8
|
+
/flows/{flow_id}/info - GET - metadata
|
9
|
+
|
10
|
+
A global ``/flows`` endpoint lists all available flows and returns a JSON array
|
11
|
+
of metadata objects, allowing API consumers to discover IDs without guessing.
|
12
|
+
|
13
|
+
Authentication behaves exactly like the single-flow serving: all execution
|
14
|
+
endpoints require the ``x-api-key`` header (or query parameter) validated by
|
15
|
+
:func:`lfx.cli.commands.verify_api_key`.
|
16
|
+
"""
|
17
|
+
|
18
|
+
from __future__ import annotations
|
19
|
+
|
20
|
+
import asyncio
|
21
|
+
import time
|
22
|
+
from copy import deepcopy
|
23
|
+
from typing import TYPE_CHECKING, Annotated, Any
|
24
|
+
|
25
|
+
from fastapi import APIRouter, Depends, FastAPI, HTTPException, Security
|
26
|
+
from fastapi.responses import StreamingResponse
|
27
|
+
from fastapi.security import APIKeyHeader, APIKeyQuery
|
28
|
+
from pydantic import BaseModel, Field
|
29
|
+
|
30
|
+
from lfx.cli.common import execute_graph_with_capture, extract_result_data, get_api_key
|
31
|
+
from lfx.log.logger import logger
|
32
|
+
|
33
|
+
if TYPE_CHECKING:
|
34
|
+
from collections.abc import AsyncGenerator, Callable
|
35
|
+
from pathlib import Path
|
36
|
+
|
37
|
+
from lfx.graph import Graph
|
38
|
+
|
39
|
+
# Security - use the same pattern as Langflow main API
|
40
|
+
API_KEY_NAME = "x-api-key"
|
41
|
+
api_key_query = APIKeyQuery(name=API_KEY_NAME, scheme_name="API key query", auto_error=False)
|
42
|
+
api_key_header = APIKeyHeader(name=API_KEY_NAME, scheme_name="API key header", auto_error=False)
|
43
|
+
|
44
|
+
|
45
|
+
def verify_api_key(
|
46
|
+
query_param: Annotated[str | None, Security(api_key_query)],
|
47
|
+
header_param: Annotated[str | None, Security(api_key_header)],
|
48
|
+
) -> str:
|
49
|
+
"""Verify API key from query parameter or header."""
|
50
|
+
provided_key = query_param or header_param
|
51
|
+
if not provided_key:
|
52
|
+
raise HTTPException(status_code=401, detail="API key required")
|
53
|
+
|
54
|
+
try:
|
55
|
+
expected_key = get_api_key()
|
56
|
+
if provided_key != expected_key:
|
57
|
+
raise HTTPException(status_code=401, detail="Invalid API key")
|
58
|
+
except ValueError as e:
|
59
|
+
raise HTTPException(status_code=500, detail=str(e)) from e
|
60
|
+
|
61
|
+
return provided_key
|
62
|
+
|
63
|
+
|
64
|
+
def _analyze_graph_structure(graph: Graph) -> dict[str, Any]:
|
65
|
+
"""Analyze the graph structure to extract dynamic documentation information.
|
66
|
+
|
67
|
+
Args:
|
68
|
+
graph: The LFX graph to analyze
|
69
|
+
|
70
|
+
Returns:
|
71
|
+
dict: Graph analysis including components, input/output types, and flow details
|
72
|
+
"""
|
73
|
+
analysis: dict[str, Any] = {
|
74
|
+
"components": [],
|
75
|
+
"input_types": set(),
|
76
|
+
"output_types": set(),
|
77
|
+
"node_count": 0,
|
78
|
+
"edge_count": 0,
|
79
|
+
"entry_points": [],
|
80
|
+
"exit_points": [],
|
81
|
+
}
|
82
|
+
|
83
|
+
try:
|
84
|
+
# Analyze nodes
|
85
|
+
for node_id, node in graph.nodes.items():
|
86
|
+
analysis["node_count"] += 1
|
87
|
+
component_info = {
|
88
|
+
"id": node_id,
|
89
|
+
"type": node.data.get("type", "Unknown"),
|
90
|
+
"name": node.data.get("display_name", node.data.get("type", "Unknown")),
|
91
|
+
"description": node.data.get("description", ""),
|
92
|
+
"template": node.data.get("template", {}),
|
93
|
+
}
|
94
|
+
analysis["components"].append(component_info)
|
95
|
+
|
96
|
+
# Identify entry points (nodes with no incoming edges)
|
97
|
+
if not any(edge.source == node_id for edge in graph.edges):
|
98
|
+
analysis["entry_points"].append(component_info)
|
99
|
+
|
100
|
+
# Identify exit points (nodes with no outgoing edges)
|
101
|
+
if not any(edge.target == node_id for edge in graph.edges):
|
102
|
+
analysis["exit_points"].append(component_info)
|
103
|
+
|
104
|
+
# Analyze edges
|
105
|
+
analysis["edge_count"] = len(graph.edges)
|
106
|
+
|
107
|
+
# Try to determine input/output types from entry/exit points
|
108
|
+
for entry in analysis["entry_points"]:
|
109
|
+
template = entry.get("template", {})
|
110
|
+
for field_config in template.values():
|
111
|
+
if field_config.get("type") in ["str", "text", "string"]:
|
112
|
+
analysis["input_types"].add("text")
|
113
|
+
elif field_config.get("type") in ["int", "float", "number"]:
|
114
|
+
analysis["input_types"].add("numeric")
|
115
|
+
elif field_config.get("type") in ["file", "path"]:
|
116
|
+
analysis["input_types"].add("file")
|
117
|
+
|
118
|
+
for exit_point in analysis["exit_points"]:
|
119
|
+
template = exit_point.get("template", {})
|
120
|
+
for field_config in template.values():
|
121
|
+
if field_config.get("type") in ["str", "text", "string"]:
|
122
|
+
analysis["output_types"].add("text")
|
123
|
+
elif field_config.get("type") in ["int", "float", "number"]:
|
124
|
+
analysis["output_types"].add("numeric")
|
125
|
+
elif field_config.get("type") in ["file", "path"]:
|
126
|
+
analysis["output_types"].add("file")
|
127
|
+
|
128
|
+
except (KeyError, AttributeError):
|
129
|
+
# If analysis fails, provide basic info
|
130
|
+
analysis["components"] = [{"type": "Unknown", "name": "Graph Component"}]
|
131
|
+
analysis["input_types"] = {"text"}
|
132
|
+
analysis["output_types"] = {"text"}
|
133
|
+
|
134
|
+
# Convert sets to lists for JSON serialization
|
135
|
+
analysis["input_types"] = list(analysis["input_types"])
|
136
|
+
analysis["output_types"] = list(analysis["output_types"])
|
137
|
+
|
138
|
+
return analysis
|
139
|
+
|
140
|
+
|
141
|
+
def _generate_dynamic_run_description(graph: Graph) -> str:
|
142
|
+
"""Generate dynamic description for the /run endpoint based on graph analysis.
|
143
|
+
|
144
|
+
Args:
|
145
|
+
graph: The LFX graph
|
146
|
+
|
147
|
+
Returns:
|
148
|
+
str: Dynamic description for the /run endpoint
|
149
|
+
"""
|
150
|
+
analysis = _analyze_graph_structure(graph)
|
151
|
+
|
152
|
+
# Determine input examples based on entry points
|
153
|
+
input_examples = []
|
154
|
+
for entry in analysis["entry_points"]:
|
155
|
+
template = entry.get("template", {})
|
156
|
+
for field_name, field_config in template.items():
|
157
|
+
if field_config.get("type") in ["str", "text", "string"]:
|
158
|
+
input_examples.append(f'"{field_name}": "Your input text here"')
|
159
|
+
elif field_config.get("type") in ["int", "float", "number"]:
|
160
|
+
input_examples.append(f'"{field_name}": 42')
|
161
|
+
elif field_config.get("type") in ["file", "path"]:
|
162
|
+
input_examples.append(f'"{field_name}": "/path/to/file.txt"')
|
163
|
+
|
164
|
+
if not input_examples:
|
165
|
+
input_examples = ['"input_value": "Your input text here"']
|
166
|
+
|
167
|
+
# Determine output examples based on exit points
|
168
|
+
output_examples = []
|
169
|
+
for exit_point in analysis["exit_points"]:
|
170
|
+
template = exit_point.get("template", {})
|
171
|
+
for field_name, field_config in template.items():
|
172
|
+
if field_config.get("type") in ["str", "text", "string"]:
|
173
|
+
output_examples.append(f'"{field_name}": "Processed result"')
|
174
|
+
elif field_config.get("type") in ["int", "float", "number"]:
|
175
|
+
output_examples.append(f'"{field_name}": 123')
|
176
|
+
elif field_config.get("type") in ["file", "path"]:
|
177
|
+
output_examples.append(f'"{field_name}": "/path/to/output.txt"')
|
178
|
+
|
179
|
+
if not output_examples:
|
180
|
+
output_examples = ['"result": "Processed result"']
|
181
|
+
|
182
|
+
description_parts = [
|
183
|
+
f"Execute the deployed LFX graph with {analysis['node_count']} components.",
|
184
|
+
"",
|
185
|
+
"**Graph Analysis**:",
|
186
|
+
f"- Entry points: {len(analysis['entry_points'])}",
|
187
|
+
f"- Exit points: {len(analysis['exit_points'])}",
|
188
|
+
f"- Input types: {', '.join(analysis['input_types']) if analysis['input_types'] else 'text'}",
|
189
|
+
f"- Output types: {', '.join(analysis['output_types']) if analysis['output_types'] else 'text'}",
|
190
|
+
"",
|
191
|
+
"**Authentication Required**: Include your API key in the `x-api-key` header or as a query parameter.",
|
192
|
+
"",
|
193
|
+
"**Example Request**:",
|
194
|
+
"```json",
|
195
|
+
"{",
|
196
|
+
f" {', '.join(input_examples)}",
|
197
|
+
"}",
|
198
|
+
"```",
|
199
|
+
"",
|
200
|
+
"**Example Response**:",
|
201
|
+
"```json",
|
202
|
+
"{",
|
203
|
+
f" {', '.join(output_examples)},",
|
204
|
+
' "success": true,',
|
205
|
+
' "logs": "Graph execution completed successfully",',
|
206
|
+
' "type": "message",',
|
207
|
+
' "component": "FinalComponent"',
|
208
|
+
"}",
|
209
|
+
"```",
|
210
|
+
]
|
211
|
+
|
212
|
+
return "\n".join(description_parts)
|
213
|
+
|
214
|
+
|
215
|
+
class FlowMeta(BaseModel):
|
216
|
+
"""Metadata returned by the ``/flows`` endpoint."""
|
217
|
+
|
218
|
+
id: str = Field(..., description="Deterministic flow identifier (UUIDv5)")
|
219
|
+
relative_path: str = Field(..., description="Path of the flow JSON relative to the deployed folder")
|
220
|
+
title: str = Field(..., description="Human-readable title (filename stem if unknown)")
|
221
|
+
description: str | None = Field(None, description="Optional flow description")
|
222
|
+
|
223
|
+
|
224
|
+
class RunRequest(BaseModel):
|
225
|
+
"""Request model for executing a LFX flow."""
|
226
|
+
|
227
|
+
input_value: str = Field(..., description="Input value passed to the flow")
|
228
|
+
|
229
|
+
|
230
|
+
class StreamRequest(BaseModel):
|
231
|
+
"""Request model for streaming execution of a LFX flow."""
|
232
|
+
|
233
|
+
input_value: str = Field(..., description="Input value passed to the flow")
|
234
|
+
input_type: str = Field(default="chat", description="Type of input (chat, text)")
|
235
|
+
output_type: str = Field(default="chat", description="Type of output (chat, text, debug, any)")
|
236
|
+
output_component: str | None = Field(default=None, description="Specific output component to stream from")
|
237
|
+
session_id: str | None = Field(default=None, description="Session ID for maintaining conversation state")
|
238
|
+
tweaks: dict[str, Any] | None = Field(default=None, description="Optional tweaks to modify flow behavior")
|
239
|
+
|
240
|
+
|
241
|
+
class RunResponse(BaseModel):
|
242
|
+
"""Response model mirroring the single-flow server."""
|
243
|
+
|
244
|
+
result: str = Field(..., description="The output result from the flow execution")
|
245
|
+
success: bool = Field(..., description="Whether execution was successful")
|
246
|
+
logs: str = Field("", description="Captured logs from execution")
|
247
|
+
type: str = Field("message", description="Type of result")
|
248
|
+
component: str = Field("", description="Component that generated the result")
|
249
|
+
|
250
|
+
|
251
|
+
class ErrorResponse(BaseModel):
|
252
|
+
error: str = Field(..., description="Error message")
|
253
|
+
success: bool = Field(default=False, description="Always false for errors")
|
254
|
+
|
255
|
+
|
256
|
+
# -----------------------------------------------------------------------------
|
257
|
+
# Streaming helper functions
|
258
|
+
# -----------------------------------------------------------------------------
|
259
|
+
|
260
|
+
|
261
|
+
async def consume_and_yield(queue: asyncio.Queue, client_consumed_queue: asyncio.Queue) -> AsyncGenerator:
|
262
|
+
"""Consumes events from a queue and yields them to the client while tracking timing metrics.
|
263
|
+
|
264
|
+
This coroutine continuously pulls events from the input queue and yields them to the client.
|
265
|
+
It tracks timing metrics for how long events spend in the queue and how long the client takes
|
266
|
+
to process them.
|
267
|
+
|
268
|
+
Args:
|
269
|
+
queue (asyncio.Queue): The queue containing events to be consumed and yielded
|
270
|
+
client_consumed_queue (asyncio.Queue): A queue for tracking when the client has consumed events
|
271
|
+
|
272
|
+
Yields:
|
273
|
+
The value from each event in the queue
|
274
|
+
|
275
|
+
Notes:
|
276
|
+
- Events are tuples of (event_id, value, put_time)
|
277
|
+
- Breaks the loop when receiving a None value, signaling completion
|
278
|
+
- Tracks and logs timing metrics for queue time and client processing time
|
279
|
+
- Notifies client consumption via client_consumed_queue
|
280
|
+
"""
|
281
|
+
while True:
|
282
|
+
event_id, value, put_time = await queue.get()
|
283
|
+
if value is None:
|
284
|
+
break
|
285
|
+
get_time = time.time()
|
286
|
+
yield value
|
287
|
+
get_time_yield = time.time()
|
288
|
+
client_consumed_queue.put_nowait(event_id)
|
289
|
+
logger.debug(
|
290
|
+
f"consumed event {event_id} "
|
291
|
+
f"(time in queue, {get_time - put_time:.4f}, "
|
292
|
+
f"client {get_time_yield - get_time:.4f})"
|
293
|
+
)
|
294
|
+
|
295
|
+
|
296
|
+
async def run_flow_generator_for_serve(
|
297
|
+
graph: Graph,
|
298
|
+
input_request: StreamRequest,
|
299
|
+
flow_id: str,
|
300
|
+
event_manager,
|
301
|
+
client_consumed_queue: asyncio.Queue,
|
302
|
+
) -> None:
|
303
|
+
"""Executes a flow asynchronously and manages event streaming to the client.
|
304
|
+
|
305
|
+
This coroutine runs a flow with streaming enabled and handles the event lifecycle,
|
306
|
+
including success completion and error scenarios.
|
307
|
+
|
308
|
+
Args:
|
309
|
+
graph (Graph): The graph to execute
|
310
|
+
input_request (StreamRequest): The input parameters for the flow
|
311
|
+
flow_id (str): The ID of the flow being executed
|
312
|
+
event_manager: Manages the streaming of events to the client
|
313
|
+
client_consumed_queue (asyncio.Queue): Tracks client consumption of events
|
314
|
+
|
315
|
+
Events Generated:
|
316
|
+
- "add_message": Sent when new messages are added during flow execution
|
317
|
+
- "token": Sent for each token generated during streaming
|
318
|
+
- "end": Sent when flow execution completes, includes final result
|
319
|
+
- "error": Sent if an error occurs during execution
|
320
|
+
|
321
|
+
Notes:
|
322
|
+
- Runs the flow with streaming enabled via execute_graph_with_capture()
|
323
|
+
- On success, sends the final result via event_manager.on_end()
|
324
|
+
- On error, logs the error and sends it via event_manager.on_error()
|
325
|
+
- Always sends a final None event to signal completion
|
326
|
+
"""
|
327
|
+
try:
|
328
|
+
# For the serve app, we'll use execute_graph_with_capture with streaming
|
329
|
+
# Note: This is a simplified version. In a full implementation, you might want
|
330
|
+
# to integrate with the full LFX streaming pipeline from endpoints.py
|
331
|
+
results, logs = await execute_graph_with_capture(graph, input_request.input_value)
|
332
|
+
result_data = extract_result_data(results, logs)
|
333
|
+
|
334
|
+
# Send the final result
|
335
|
+
event_manager.on_end(data={"result": result_data})
|
336
|
+
await client_consumed_queue.get()
|
337
|
+
except Exception as e: # noqa: BLE001
|
338
|
+
logger.error(f"Error running flow {flow_id}: {e}")
|
339
|
+
event_manager.on_error(data={"error": str(e)})
|
340
|
+
finally:
|
341
|
+
await event_manager.queue.put((None, None, time.time()))
|
342
|
+
|
343
|
+
|
344
|
+
# -----------------------------------------------------------------------------
|
345
|
+
# Application factory
|
346
|
+
# -----------------------------------------------------------------------------
|
347
|
+
|
348
|
+
|
349
|
+
def create_multi_serve_app(
|
350
|
+
*,
|
351
|
+
root_dir: Path, # noqa: ARG001
|
352
|
+
graphs: dict[str, Graph],
|
353
|
+
metas: dict[str, FlowMeta],
|
354
|
+
verbose_print: Callable[[str], None], # noqa: ARG001
|
355
|
+
) -> FastAPI:
|
356
|
+
"""Create a FastAPI app exposing multiple LFX flows.
|
357
|
+
|
358
|
+
Parameters
|
359
|
+
----------
|
360
|
+
root_dir
|
361
|
+
Folder originally supplied to the serve command. All *relative_path*
|
362
|
+
values are relative to this directory.
|
363
|
+
graphs
|
364
|
+
Mapping ``flow_id -> Graph`` containing prepared graph objects.
|
365
|
+
metas
|
366
|
+
Mapping ``flow_id -> FlowMeta`` containing metadata for each flow.
|
367
|
+
verbose_print
|
368
|
+
Diagnostic printer inherited from the CLI (unused, kept for backward compatibility).
|
369
|
+
"""
|
370
|
+
if set(graphs) != set(metas): # pragma: no cover - sanity check
|
371
|
+
msg = "graphs and metas must contain the same keys"
|
372
|
+
raise ValueError(msg)
|
373
|
+
|
374
|
+
app = FastAPI(
|
375
|
+
title=f"LFX Multi-Flow Server ({len(graphs)})",
|
376
|
+
description=(
|
377
|
+
"This server hosts multiple LFX graphs under the `/flows/{id}` prefix. "
|
378
|
+
"Use `/flows` to list available IDs then POST your input to `/flows/{id}/run`."
|
379
|
+
),
|
380
|
+
version="1.0.0",
|
381
|
+
)
|
382
|
+
|
383
|
+
# ------------------------------------------------------------------
|
384
|
+
# Global endpoints
|
385
|
+
# ------------------------------------------------------------------
|
386
|
+
|
387
|
+
@app.get("/flows", response_model=list[FlowMeta], tags=["info"], summary="List available flows")
|
388
|
+
async def list_flows():
|
389
|
+
"""Return metadata for all flows hosted in this server."""
|
390
|
+
return list(metas.values())
|
391
|
+
|
392
|
+
@app.get("/health", tags=["info"], summary="Global health check")
|
393
|
+
async def global_health():
|
394
|
+
return {"status": "healthy", "flow_count": len(graphs)}
|
395
|
+
|
396
|
+
# ------------------------------------------------------------------
|
397
|
+
# Per-flow routers
|
398
|
+
# ------------------------------------------------------------------
|
399
|
+
|
400
|
+
def create_flow_router(flow_id: str, graph: Graph, meta: FlowMeta) -> APIRouter:
|
401
|
+
"""Create a router for a specific flow to avoid loop variable binding issues."""
|
402
|
+
analysis = _analyze_graph_structure(graph)
|
403
|
+
run_description = _generate_dynamic_run_description(graph)
|
404
|
+
|
405
|
+
router = APIRouter(
|
406
|
+
prefix=f"/flows/{flow_id}",
|
407
|
+
tags=[meta.title or flow_id],
|
408
|
+
dependencies=[Depends(verify_api_key)], # Auth for all routes inside
|
409
|
+
)
|
410
|
+
|
411
|
+
@router.post(
|
412
|
+
"/run",
|
413
|
+
response_model=RunResponse,
|
414
|
+
responses={500: {"model": ErrorResponse}},
|
415
|
+
summary="Execute flow",
|
416
|
+
description=run_description,
|
417
|
+
)
|
418
|
+
async def run_flow(
|
419
|
+
request: RunRequest,
|
420
|
+
) -> RunResponse:
|
421
|
+
try:
|
422
|
+
graph_copy = deepcopy(graph)
|
423
|
+
results, logs = await execute_graph_with_capture(graph_copy, request.input_value)
|
424
|
+
result_data = extract_result_data(results, logs)
|
425
|
+
|
426
|
+
# Debug logging
|
427
|
+
logger.debug(f"Flow {flow_id} execution completed: {len(results)} results, {len(logs)} log chars")
|
428
|
+
logger.debug(f"Flow {flow_id} result data: {result_data}")
|
429
|
+
|
430
|
+
# Check if the execution was successful
|
431
|
+
if not result_data.get("success", True):
|
432
|
+
# If the flow execution failed, return error details in the response
|
433
|
+
error_message = result_data.get("result", result_data.get("text", "No response generated"))
|
434
|
+
|
435
|
+
# Add more context to the logs when there's an error
|
436
|
+
error_logs = logs
|
437
|
+
if not error_logs.strip():
|
438
|
+
error_logs = (
|
439
|
+
f"Flow execution completed but no valid result was produced.\nResult data: {result_data}"
|
440
|
+
)
|
441
|
+
|
442
|
+
return RunResponse(
|
443
|
+
result=error_message,
|
444
|
+
success=False,
|
445
|
+
logs=error_logs,
|
446
|
+
type="error",
|
447
|
+
component=result_data.get("component", ""),
|
448
|
+
)
|
449
|
+
|
450
|
+
return RunResponse(
|
451
|
+
result=result_data.get("result", result_data.get("text", "")),
|
452
|
+
success=result_data.get("success", True),
|
453
|
+
logs=logs,
|
454
|
+
type=result_data.get("type", "message"),
|
455
|
+
component=result_data.get("component", ""),
|
456
|
+
)
|
457
|
+
except Exception as exc: # noqa: BLE001
|
458
|
+
import traceback
|
459
|
+
|
460
|
+
# Capture the full traceback for debugging
|
461
|
+
error_traceback = traceback.format_exc()
|
462
|
+
error_message = f"Flow execution failed: {exc!s}"
|
463
|
+
|
464
|
+
# Log to server console for debugging
|
465
|
+
logger.error(f"Error running flow {flow_id}: {exc}")
|
466
|
+
logger.debug(f"Full traceback for flow {flow_id}:\n{error_traceback}")
|
467
|
+
|
468
|
+
# Return error details in the API response instead of raising HTTPException
|
469
|
+
return RunResponse(
|
470
|
+
result=error_message,
|
471
|
+
success=False,
|
472
|
+
logs=f"ERROR: {error_message}\n\nFull traceback:\n{error_traceback}",
|
473
|
+
type="error",
|
474
|
+
component="",
|
475
|
+
)
|
476
|
+
|
477
|
+
@router.post(
|
478
|
+
"/stream",
|
479
|
+
response_model=None,
|
480
|
+
summary="Stream flow execution",
|
481
|
+
description=f"Stream the execution of {meta.title or flow_id} with real-time events and token streaming.",
|
482
|
+
)
|
483
|
+
async def stream_flow(
|
484
|
+
request: StreamRequest,
|
485
|
+
) -> StreamingResponse:
|
486
|
+
"""Stream the execution of the flow with real-time events."""
|
487
|
+
try:
|
488
|
+
# Import here to avoid potential circular imports
|
489
|
+
from lfx.events.event_manager import create_stream_tokens_event_manager
|
490
|
+
|
491
|
+
asyncio_queue: asyncio.Queue = asyncio.Queue()
|
492
|
+
asyncio_queue_client_consumed: asyncio.Queue = asyncio.Queue()
|
493
|
+
event_manager = create_stream_tokens_event_manager(queue=asyncio_queue)
|
494
|
+
|
495
|
+
main_task = asyncio.create_task(
|
496
|
+
run_flow_generator_for_serve(
|
497
|
+
graph=graph,
|
498
|
+
input_request=request,
|
499
|
+
flow_id=flow_id,
|
500
|
+
event_manager=event_manager,
|
501
|
+
client_consumed_queue=asyncio_queue_client_consumed,
|
502
|
+
)
|
503
|
+
)
|
504
|
+
|
505
|
+
async def on_disconnect() -> None:
|
506
|
+
logger.debug(f"Client disconnected from flow {flow_id}, closing tasks")
|
507
|
+
main_task.cancel()
|
508
|
+
|
509
|
+
return StreamingResponse(
|
510
|
+
consume_and_yield(asyncio_queue, asyncio_queue_client_consumed),
|
511
|
+
background=on_disconnect,
|
512
|
+
media_type="text/event-stream",
|
513
|
+
)
|
514
|
+
except Exception as exc: # noqa: BLE001
|
515
|
+
logger.error(f"Error setting up streaming for flow {flow_id}: {exc}")
|
516
|
+
# Return a simple error stream
|
517
|
+
error_message = f"Failed to start streaming: {exc!s}"
|
518
|
+
|
519
|
+
async def error_stream():
|
520
|
+
yield f'data: {{"error": "{error_message}", "success": false}}\n\n'
|
521
|
+
|
522
|
+
return StreamingResponse(
|
523
|
+
error_stream(),
|
524
|
+
media_type="text/event-stream",
|
525
|
+
)
|
526
|
+
|
527
|
+
@router.get("/info", summary="Flow metadata", response_model=FlowMeta)
|
528
|
+
async def flow_info():
|
529
|
+
"""Return metadata and basic analysis for this flow."""
|
530
|
+
# Enrich meta with analysis data for convenience
|
531
|
+
return {
|
532
|
+
**meta.model_dump(),
|
533
|
+
"components": analysis["node_count"],
|
534
|
+
"connections": analysis["edge_count"],
|
535
|
+
"input_types": analysis["input_types"],
|
536
|
+
"output_types": analysis["output_types"],
|
537
|
+
}
|
538
|
+
|
539
|
+
return router
|
540
|
+
|
541
|
+
for flow_id, graph in graphs.items():
|
542
|
+
meta = metas[flow_id]
|
543
|
+
router = create_flow_router(flow_id, graph, meta)
|
544
|
+
app.include_router(router)
|
545
|
+
|
546
|
+
return app
|
lfx/cli/validation.py
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
"""Validation utilities for CLI commands."""
|
2
|
+
|
3
|
+
import re
|
4
|
+
|
5
|
+
from lfx.graph.graph.base import Graph
|
6
|
+
from lfx.services.deps import get_settings_service
|
7
|
+
|
8
|
+
|
9
|
+
def is_valid_env_var_name(name: str) -> bool:
|
10
|
+
"""Check if a string is a valid environment variable name.
|
11
|
+
|
12
|
+
Environment variable names should:
|
13
|
+
- Start with a letter or underscore
|
14
|
+
- Contain only letters, numbers, and underscores
|
15
|
+
- Not contain spaces or special characters
|
16
|
+
|
17
|
+
Args:
|
18
|
+
name: The string to validate
|
19
|
+
|
20
|
+
Returns:
|
21
|
+
bool: True if valid, False otherwise
|
22
|
+
"""
|
23
|
+
# Pattern for valid environment variable names
|
24
|
+
# Must start with letter or underscore, followed by letters, numbers, or underscores
|
25
|
+
pattern = r"^[a-zA-Z_][a-zA-Z0-9_]*$"
|
26
|
+
return bool(re.match(pattern, name))
|
27
|
+
|
28
|
+
|
29
|
+
def validate_global_variables_for_env(graph: Graph) -> list[str]:
|
30
|
+
"""Validate that all global variables with load_from_db=True can be used as environment variables.
|
31
|
+
|
32
|
+
When the database is not available (noop mode), global variables with load_from_db=True
|
33
|
+
are loaded from environment variables. This function checks that all such variables
|
34
|
+
have names that are valid for environment variables.
|
35
|
+
|
36
|
+
Args:
|
37
|
+
graph: The graph to validate
|
38
|
+
|
39
|
+
Returns:
|
40
|
+
list[str]: List of error messages for invalid variable names
|
41
|
+
"""
|
42
|
+
errors = []
|
43
|
+
settings_service = get_settings_service()
|
44
|
+
|
45
|
+
# Check if we're in noop mode (no database)
|
46
|
+
is_noop_mode = settings_service and settings_service.settings.use_noop_database
|
47
|
+
|
48
|
+
if not is_noop_mode:
|
49
|
+
# If database is available, no need to validate
|
50
|
+
return errors
|
51
|
+
|
52
|
+
# Check all vertices for fields with load_from_db=True
|
53
|
+
for vertex in graph.vertices:
|
54
|
+
# Get the fields that have load_from_db=True
|
55
|
+
load_from_db_fields = getattr(vertex, "load_from_db_fields", [])
|
56
|
+
|
57
|
+
for field_name in load_from_db_fields:
|
58
|
+
# Get the value of the field (which should be the variable name)
|
59
|
+
field_value = vertex.params.get(field_name)
|
60
|
+
|
61
|
+
if field_value and isinstance(field_value, str) and not is_valid_env_var_name(field_value):
|
62
|
+
errors.append(
|
63
|
+
f"Component '{vertex.display_name}' (id: {vertex.id}) has field '{field_name}' "
|
64
|
+
f"with value '{field_value}' that contains invalid characters for an environment "
|
65
|
+
f"variable name. Environment variable names must start with a letter or underscore "
|
66
|
+
f"and contain only letters, numbers, and underscores (no spaces or special characters)."
|
67
|
+
)
|
68
|
+
|
69
|
+
return errors
|
@@ -0,0 +1,34 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import TYPE_CHECKING, Any
|
4
|
+
|
5
|
+
from lfx.components._importing import import_mod
|
6
|
+
|
7
|
+
if TYPE_CHECKING:
|
8
|
+
from .faiss import FaissVectorStoreComponent
|
9
|
+
|
10
|
+
_dynamic_imports = {
|
11
|
+
"FaissVectorStoreComponent": "faiss",
|
12
|
+
}
|
13
|
+
|
14
|
+
__all__ = [
|
15
|
+
"FaissVectorStoreComponent",
|
16
|
+
]
|
17
|
+
|
18
|
+
|
19
|
+
def __getattr__(attr_name: str) -> Any:
|
20
|
+
"""Lazily import FAISS components on attribute access."""
|
21
|
+
if attr_name not in _dynamic_imports:
|
22
|
+
msg = f"module '{__name__}' has no attribute '{attr_name}'"
|
23
|
+
raise AttributeError(msg)
|
24
|
+
try:
|
25
|
+
result = import_mod(attr_name, _dynamic_imports[attr_name], __spec__.parent)
|
26
|
+
except (ModuleNotFoundError, ImportError, AttributeError) as e:
|
27
|
+
msg = f"Could not import '{attr_name}' from '{__name__}': {e}"
|
28
|
+
raise AttributeError(msg) from e
|
29
|
+
globals()[attr_name] = result
|
30
|
+
return result
|
31
|
+
|
32
|
+
|
33
|
+
def __dir__() -> list[str]:
|
34
|
+
return list(__all__)
|