lfx-nightly 0.1.13.dev0__py3-none-any.whl → 0.2.0.dev26__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.
Files changed (237) hide show
  1. lfx/_assets/component_index.json +1 -1
  2. lfx/base/agents/agent.py +121 -29
  3. lfx/base/agents/altk_base_agent.py +380 -0
  4. lfx/base/agents/altk_tool_wrappers.py +565 -0
  5. lfx/base/agents/events.py +103 -35
  6. lfx/base/agents/utils.py +15 -2
  7. lfx/base/composio/composio_base.py +183 -233
  8. lfx/base/data/base_file.py +88 -21
  9. lfx/base/data/storage_utils.py +192 -0
  10. lfx/base/data/utils.py +178 -14
  11. lfx/base/datastax/__init__.py +5 -0
  12. lfx/{components/vectorstores/astradb.py → base/datastax/astradb_base.py} +84 -473
  13. lfx/base/embeddings/embeddings_class.py +113 -0
  14. lfx/base/io/chat.py +5 -4
  15. lfx/base/mcp/util.py +101 -15
  16. lfx/base/models/groq_constants.py +74 -58
  17. lfx/base/models/groq_model_discovery.py +265 -0
  18. lfx/base/models/model.py +1 -1
  19. lfx/base/models/model_input_constants.py +74 -7
  20. lfx/base/models/model_utils.py +100 -0
  21. lfx/base/models/ollama_constants.py +3 -0
  22. lfx/base/models/openai_constants.py +7 -0
  23. lfx/base/models/watsonx_constants.py +36 -0
  24. lfx/base/tools/run_flow.py +601 -129
  25. lfx/cli/commands.py +7 -4
  26. lfx/cli/common.py +2 -2
  27. lfx/cli/run.py +1 -1
  28. lfx/cli/script_loader.py +53 -11
  29. lfx/components/Notion/create_page.py +1 -1
  30. lfx/components/Notion/list_database_properties.py +1 -1
  31. lfx/components/Notion/list_pages.py +1 -1
  32. lfx/components/Notion/list_users.py +1 -1
  33. lfx/components/Notion/page_content_viewer.py +1 -1
  34. lfx/components/Notion/search.py +1 -1
  35. lfx/components/Notion/update_page_property.py +1 -1
  36. lfx/components/__init__.py +19 -5
  37. lfx/components/altk/__init__.py +34 -0
  38. lfx/components/altk/altk_agent.py +193 -0
  39. lfx/components/amazon/amazon_bedrock_converse.py +1 -1
  40. lfx/components/apify/apify_actor.py +4 -4
  41. lfx/components/composio/__init__.py +70 -18
  42. lfx/components/composio/apollo_composio.py +11 -0
  43. lfx/components/composio/bitbucket_composio.py +11 -0
  44. lfx/components/composio/canva_composio.py +11 -0
  45. lfx/components/composio/coda_composio.py +11 -0
  46. lfx/components/composio/composio_api.py +10 -0
  47. lfx/components/composio/discord_composio.py +1 -1
  48. lfx/components/composio/elevenlabs_composio.py +11 -0
  49. lfx/components/composio/exa_composio.py +11 -0
  50. lfx/components/composio/firecrawl_composio.py +11 -0
  51. lfx/components/composio/fireflies_composio.py +11 -0
  52. lfx/components/composio/gmail_composio.py +1 -1
  53. lfx/components/composio/googlebigquery_composio.py +11 -0
  54. lfx/components/composio/googlecalendar_composio.py +1 -1
  55. lfx/components/composio/googledocs_composio.py +1 -1
  56. lfx/components/composio/googlemeet_composio.py +1 -1
  57. lfx/components/composio/googlesheets_composio.py +1 -1
  58. lfx/components/composio/googletasks_composio.py +1 -1
  59. lfx/components/composio/heygen_composio.py +11 -0
  60. lfx/components/composio/mem0_composio.py +11 -0
  61. lfx/components/composio/peopledatalabs_composio.py +11 -0
  62. lfx/components/composio/perplexityai_composio.py +11 -0
  63. lfx/components/composio/serpapi_composio.py +11 -0
  64. lfx/components/composio/slack_composio.py +3 -574
  65. lfx/components/composio/slackbot_composio.py +1 -1
  66. lfx/components/composio/snowflake_composio.py +11 -0
  67. lfx/components/composio/tavily_composio.py +11 -0
  68. lfx/components/composio/youtube_composio.py +2 -2
  69. lfx/components/{agents → cuga}/__init__.py +5 -7
  70. lfx/components/cuga/cuga_agent.py +730 -0
  71. lfx/components/data/__init__.py +78 -28
  72. lfx/components/data_source/__init__.py +58 -0
  73. lfx/components/{data → data_source}/api_request.py +26 -3
  74. lfx/components/{data → data_source}/csv_to_data.py +15 -10
  75. lfx/components/{data → data_source}/json_to_data.py +15 -8
  76. lfx/components/{data → data_source}/news_search.py +1 -1
  77. lfx/components/{data → data_source}/rss.py +1 -1
  78. lfx/components/{data → data_source}/sql_executor.py +1 -1
  79. lfx/components/{data → data_source}/url.py +1 -1
  80. lfx/components/{data → data_source}/web_search.py +1 -1
  81. lfx/components/datastax/__init__.py +12 -6
  82. lfx/components/datastax/{astra_assistant_manager.py → astradb_assistant_manager.py} +1 -0
  83. lfx/components/datastax/astradb_chatmemory.py +40 -0
  84. lfx/components/datastax/astradb_cql.py +6 -32
  85. lfx/components/datastax/astradb_graph.py +10 -124
  86. lfx/components/datastax/astradb_tool.py +13 -53
  87. lfx/components/datastax/astradb_vectorstore.py +134 -977
  88. lfx/components/datastax/create_assistant.py +1 -0
  89. lfx/components/datastax/create_thread.py +1 -0
  90. lfx/components/datastax/dotenv.py +1 -0
  91. lfx/components/datastax/get_assistant.py +1 -0
  92. lfx/components/datastax/getenvvar.py +1 -0
  93. lfx/components/datastax/graph_rag.py +1 -1
  94. lfx/components/datastax/hcd.py +1 -1
  95. lfx/components/datastax/list_assistants.py +1 -0
  96. lfx/components/datastax/run.py +1 -0
  97. lfx/components/deactivated/json_document_builder.py +1 -1
  98. lfx/components/elastic/elasticsearch.py +1 -1
  99. lfx/components/elastic/opensearch_multimodal.py +1575 -0
  100. lfx/components/files_and_knowledge/__init__.py +47 -0
  101. lfx/components/{data → files_and_knowledge}/directory.py +1 -1
  102. lfx/components/{data → files_and_knowledge}/file.py +246 -18
  103. lfx/components/{knowledge_bases → files_and_knowledge}/ingestion.py +17 -9
  104. lfx/components/{knowledge_bases → files_and_knowledge}/retrieval.py +18 -10
  105. lfx/components/{data → files_and_knowledge}/save_file.py +142 -22
  106. lfx/components/flow_controls/__init__.py +58 -0
  107. lfx/components/{logic → flow_controls}/conditional_router.py +1 -1
  108. lfx/components/{logic → flow_controls}/loop.py +47 -9
  109. lfx/components/flow_controls/run_flow.py +108 -0
  110. lfx/components/glean/glean_search_api.py +1 -1
  111. lfx/components/groq/groq.py +35 -28
  112. lfx/components/helpers/__init__.py +102 -0
  113. lfx/components/ibm/watsonx.py +25 -21
  114. lfx/components/input_output/__init__.py +3 -1
  115. lfx/components/input_output/chat.py +12 -3
  116. lfx/components/input_output/chat_output.py +12 -4
  117. lfx/components/input_output/text.py +1 -1
  118. lfx/components/input_output/text_output.py +1 -1
  119. lfx/components/{data → input_output}/webhook.py +1 -1
  120. lfx/components/knowledge_bases/__init__.py +59 -4
  121. lfx/components/langchain_utilities/character.py +1 -1
  122. lfx/components/langchain_utilities/csv_agent.py +84 -16
  123. lfx/components/langchain_utilities/json_agent.py +67 -12
  124. lfx/components/langchain_utilities/language_recursive.py +1 -1
  125. lfx/components/llm_operations/__init__.py +46 -0
  126. lfx/components/{processing → llm_operations}/batch_run.py +1 -1
  127. lfx/components/{processing → llm_operations}/lambda_filter.py +1 -1
  128. lfx/components/{logic → llm_operations}/llm_conditional_router.py +1 -1
  129. lfx/components/{processing/llm_router.py → llm_operations/llm_selector.py} +3 -3
  130. lfx/components/{processing → llm_operations}/structured_output.py +56 -18
  131. lfx/components/logic/__init__.py +126 -0
  132. lfx/components/mem0/mem0_chat_memory.py +11 -0
  133. lfx/components/mistral/mistral_embeddings.py +1 -1
  134. lfx/components/models/__init__.py +64 -9
  135. lfx/components/models_and_agents/__init__.py +49 -0
  136. lfx/components/{agents → models_and_agents}/agent.py +49 -6
  137. lfx/components/models_and_agents/embedding_model.py +423 -0
  138. lfx/components/models_and_agents/language_model.py +398 -0
  139. lfx/components/{agents → models_and_agents}/mcp_component.py +84 -45
  140. lfx/components/{helpers → models_and_agents}/memory.py +1 -1
  141. lfx/components/nvidia/system_assist.py +1 -1
  142. lfx/components/olivya/olivya.py +1 -1
  143. lfx/components/ollama/ollama.py +235 -14
  144. lfx/components/openrouter/openrouter.py +49 -147
  145. lfx/components/processing/__init__.py +9 -57
  146. lfx/components/processing/converter.py +1 -1
  147. lfx/components/processing/dataframe_operations.py +1 -1
  148. lfx/components/processing/parse_json_data.py +2 -2
  149. lfx/components/processing/parser.py +7 -2
  150. lfx/components/processing/split_text.py +1 -1
  151. lfx/components/qdrant/qdrant.py +1 -1
  152. lfx/components/redis/redis.py +1 -1
  153. lfx/components/twelvelabs/split_video.py +10 -0
  154. lfx/components/twelvelabs/video_file.py +12 -0
  155. lfx/components/utilities/__init__.py +43 -0
  156. lfx/components/{helpers → utilities}/calculator_core.py +1 -1
  157. lfx/components/{helpers → utilities}/current_date.py +1 -1
  158. lfx/components/{processing → utilities}/python_repl_core.py +1 -1
  159. lfx/components/vectorstores/__init__.py +0 -6
  160. lfx/components/vectorstores/local_db.py +9 -0
  161. lfx/components/youtube/youtube_transcripts.py +118 -30
  162. lfx/custom/custom_component/component.py +60 -3
  163. lfx/custom/custom_component/custom_component.py +68 -6
  164. lfx/field_typing/constants.py +1 -0
  165. lfx/graph/edge/base.py +45 -22
  166. lfx/graph/graph/base.py +5 -2
  167. lfx/graph/graph/schema.py +3 -2
  168. lfx/graph/state/model.py +15 -2
  169. lfx/graph/utils.py +6 -0
  170. lfx/graph/vertex/base.py +4 -1
  171. lfx/graph/vertex/param_handler.py +10 -7
  172. lfx/graph/vertex/vertex_types.py +1 -1
  173. lfx/helpers/__init__.py +12 -0
  174. lfx/helpers/flow.py +117 -0
  175. lfx/inputs/input_mixin.py +24 -1
  176. lfx/inputs/inputs.py +13 -1
  177. lfx/interface/components.py +161 -83
  178. lfx/io/schema.py +6 -0
  179. lfx/log/logger.py +5 -3
  180. lfx/schema/schema.py +5 -0
  181. lfx/services/database/__init__.py +5 -0
  182. lfx/services/database/service.py +25 -0
  183. lfx/services/deps.py +87 -22
  184. lfx/services/manager.py +19 -6
  185. lfx/services/mcp_composer/service.py +998 -157
  186. lfx/services/session.py +5 -0
  187. lfx/services/settings/base.py +51 -7
  188. lfx/services/settings/constants.py +8 -0
  189. lfx/services/storage/local.py +76 -46
  190. lfx/services/storage/service.py +152 -29
  191. lfx/template/field/base.py +3 -0
  192. lfx/utils/ssrf_protection.py +384 -0
  193. lfx/utils/validate_cloud.py +26 -0
  194. {lfx_nightly-0.1.13.dev0.dist-info → lfx_nightly-0.2.0.dev26.dist-info}/METADATA +38 -22
  195. {lfx_nightly-0.1.13.dev0.dist-info → lfx_nightly-0.2.0.dev26.dist-info}/RECORD +210 -196
  196. {lfx_nightly-0.1.13.dev0.dist-info → lfx_nightly-0.2.0.dev26.dist-info}/WHEEL +1 -1
  197. lfx/components/agents/cuga_agent.py +0 -1013
  198. lfx/components/datastax/astra_db.py +0 -77
  199. lfx/components/datastax/cassandra.py +0 -92
  200. lfx/components/logic/run_flow.py +0 -71
  201. lfx/components/models/embedding_model.py +0 -114
  202. lfx/components/models/language_model.py +0 -144
  203. lfx/components/vectorstores/astradb_graph.py +0 -326
  204. lfx/components/vectorstores/cassandra.py +0 -264
  205. lfx/components/vectorstores/cassandra_graph.py +0 -238
  206. lfx/components/vectorstores/chroma.py +0 -167
  207. lfx/components/vectorstores/clickhouse.py +0 -135
  208. lfx/components/vectorstores/couchbase.py +0 -102
  209. lfx/components/vectorstores/elasticsearch.py +0 -267
  210. lfx/components/vectorstores/faiss.py +0 -111
  211. lfx/components/vectorstores/graph_rag.py +0 -141
  212. lfx/components/vectorstores/hcd.py +0 -314
  213. lfx/components/vectorstores/milvus.py +0 -115
  214. lfx/components/vectorstores/mongodb_atlas.py +0 -213
  215. lfx/components/vectorstores/opensearch.py +0 -243
  216. lfx/components/vectorstores/pgvector.py +0 -72
  217. lfx/components/vectorstores/pinecone.py +0 -134
  218. lfx/components/vectorstores/qdrant.py +0 -109
  219. lfx/components/vectorstores/supabase.py +0 -76
  220. lfx/components/vectorstores/upstash.py +0 -124
  221. lfx/components/vectorstores/vectara.py +0 -97
  222. lfx/components/vectorstores/vectara_rag.py +0 -164
  223. lfx/components/vectorstores/weaviate.py +0 -89
  224. /lfx/components/{data → data_source}/mock_data.py +0 -0
  225. /lfx/components/datastax/{astra_vectorize.py → astradb_vectorize.py} +0 -0
  226. /lfx/components/{logic → flow_controls}/data_conditional_router.py +0 -0
  227. /lfx/components/{logic → flow_controls}/flow_tool.py +0 -0
  228. /lfx/components/{logic → flow_controls}/listen.py +0 -0
  229. /lfx/components/{logic → flow_controls}/notify.py +0 -0
  230. /lfx/components/{logic → flow_controls}/pass_message.py +0 -0
  231. /lfx/components/{logic → flow_controls}/sub_flow.py +0 -0
  232. /lfx/components/{processing → models_and_agents}/prompt.py +0 -0
  233. /lfx/components/{helpers → processing}/create_list.py +0 -0
  234. /lfx/components/{helpers → processing}/output_parser.py +0 -0
  235. /lfx/components/{helpers → processing}/store_message.py +0 -0
  236. /lfx/components/{helpers → utilities}/id_generator.py +0 -0
  237. {lfx_nightly-0.1.13.dev0.dist-info → lfx_nightly-0.2.0.dev26.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,265 @@
1
+ """Dynamic Groq model discovery and tool calling detection.
2
+
3
+ This module fetches available models directly from the Groq API
4
+ and tests their tool calling capabilities programmatically,
5
+ eliminating the need for manual metadata updates.
6
+ """
7
+
8
+ import json
9
+ from datetime import datetime, timedelta, timezone
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+ import requests
14
+
15
+ from lfx.log.logger import logger
16
+
17
+
18
+ class GroqModelDiscovery:
19
+ """Discovers and caches Groq model capabilities dynamically."""
20
+
21
+ # Cache file location - use local cache directory within models
22
+ CACHE_FILE = Path(__file__).parent / ".cache" / "groq_models_cache.json"
23
+ CACHE_DURATION = timedelta(hours=24) # Refresh cache every 24 hours
24
+
25
+ # Models to skip from LLM list (audio, TTS, guards)
26
+ SKIP_PATTERNS = ["whisper", "tts", "guard", "safeguard", "prompt-guard", "saba"]
27
+
28
+ def __init__(self, api_key: str | None = None, base_url: str = "https://api.groq.com"):
29
+ """Initialize discovery with optional API key for testing.
30
+
31
+ Args:
32
+ api_key: Groq API key. If None, only cached data will be used.
33
+ base_url: Groq API base URL
34
+ """
35
+ self.api_key = api_key
36
+ self.base_url = base_url
37
+
38
+ def get_models(self, *, force_refresh: bool = False) -> dict[str, dict[str, Any]]:
39
+ """Get available models with their capabilities.
40
+
41
+ Args:
42
+ force_refresh: If True, bypass cache and fetch fresh data
43
+
44
+ Returns:
45
+ Dictionary mapping model IDs to their metadata:
46
+ {
47
+ "model-id": {
48
+ "name": "model-id",
49
+ "provider": "Provider Name",
50
+ "tool_calling": True/False,
51
+ "preview": True/False,
52
+ "not_supported": True/False, # for non-LLM models
53
+ "last_tested": "2025-01-06T10:30:00"
54
+ }
55
+ }
56
+ """
57
+ # Try to load from cache first
58
+ if not force_refresh:
59
+ cached = self._load_cache()
60
+ if cached:
61
+ logger.info("Using cached Groq model metadata")
62
+ return cached
63
+
64
+ # Fetch fresh data from API
65
+ if not self.api_key:
66
+ logger.warning("No API key provided, using minimal fallback list")
67
+ return self._get_fallback_models()
68
+
69
+ try:
70
+ models_metadata = {}
71
+
72
+ # Step 1: Get list of available models
73
+ available_models = self._fetch_available_models()
74
+ logger.info(f"Found {len(available_models)} models from Groq API")
75
+
76
+ # Step 2: Categorize models
77
+ llm_models = []
78
+ non_llm_models = []
79
+
80
+ for model_id in available_models:
81
+ if any(pattern in model_id.lower() for pattern in self.SKIP_PATTERNS):
82
+ non_llm_models.append(model_id)
83
+ else:
84
+ llm_models.append(model_id)
85
+
86
+ # Step 3: Test LLM models for tool calling
87
+ logger.info(f"Testing {len(llm_models)} LLM models for tool calling support...")
88
+ for model_id in llm_models:
89
+ supports_tools = self._test_tool_calling(model_id)
90
+ models_metadata[model_id] = {
91
+ "name": model_id,
92
+ "provider": self._get_provider_name(model_id),
93
+ "tool_calling": supports_tools,
94
+ "preview": "preview" in model_id.lower() or "/" in model_id,
95
+ "last_tested": datetime.now(timezone.utc).isoformat(),
96
+ }
97
+ logger.debug(f"{model_id}: tool_calling={supports_tools}")
98
+
99
+ # Step 4: Add non-LLM models as unsupported
100
+ for model_id in non_llm_models:
101
+ models_metadata[model_id] = {
102
+ "name": model_id,
103
+ "provider": self._get_provider_name(model_id),
104
+ "not_supported": True,
105
+ "last_tested": datetime.now(timezone.utc).isoformat(),
106
+ }
107
+
108
+ # Save to cache
109
+ self._save_cache(models_metadata)
110
+
111
+ except (requests.RequestException, KeyError, ValueError, ImportError) as e:
112
+ logger.exception(f"Error discovering models: {e}")
113
+ return self._get_fallback_models()
114
+ else:
115
+ return models_metadata
116
+
117
+ def _fetch_available_models(self) -> list[str]:
118
+ """Fetch list of available models from Groq API."""
119
+ url = f"{self.base_url}/openai/v1/models"
120
+ headers = {"Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"}
121
+
122
+ response = requests.get(url, headers=headers, timeout=10)
123
+ response.raise_for_status()
124
+
125
+ model_list = response.json()
126
+ # Use direct access to raise KeyError if 'data' is missing
127
+ return [model["id"] for model in model_list["data"]]
128
+
129
+ def _test_tool_calling(self, model_id: str) -> bool:
130
+ """Test if a model supports tool calling.
131
+
132
+ Args:
133
+ model_id: The model ID to test
134
+
135
+ Returns:
136
+ True if model supports tool calling, False otherwise
137
+ """
138
+ try:
139
+ import groq
140
+
141
+ client = groq.Groq(api_key=self.api_key)
142
+
143
+ # Simple tool definition
144
+ tools = [
145
+ {
146
+ "type": "function",
147
+ "function": {
148
+ "name": "test_tool",
149
+ "description": "A test tool",
150
+ "parameters": {
151
+ "type": "object",
152
+ "properties": {"x": {"type": "string"}},
153
+ "required": ["x"],
154
+ },
155
+ },
156
+ }
157
+ ]
158
+
159
+ messages = [{"role": "user", "content": "test"}]
160
+
161
+ # Try to make a request with tools
162
+ client.chat.completions.create(
163
+ model=model_id, messages=messages, tools=tools, tool_choice="auto", max_tokens=10
164
+ )
165
+
166
+ except (ImportError, AttributeError, TypeError, ValueError, RuntimeError, KeyError) as e:
167
+ error_msg = str(e).lower()
168
+ # If error mentions tool calling, model doesn't support it
169
+ if "tool" in error_msg:
170
+ return False
171
+ # Other errors might be rate limits, etc - be conservative
172
+ logger.warning(f"Error testing {model_id}: {e}")
173
+ return False
174
+ else:
175
+ return True
176
+
177
+ def _get_provider_name(self, model_id: str) -> str:
178
+ """Extract provider name from model ID."""
179
+ if "/" in model_id:
180
+ provider_map = {
181
+ "meta-llama": "Meta",
182
+ "openai": "OpenAI",
183
+ "groq": "Groq",
184
+ "moonshotai": "Moonshot AI",
185
+ "qwen": "Alibaba Cloud",
186
+ }
187
+ prefix = model_id.split("/")[0]
188
+ return provider_map.get(prefix, prefix.title())
189
+
190
+ # Common patterns
191
+ if model_id.startswith("llama"):
192
+ return "Meta"
193
+ if model_id.startswith("qwen"):
194
+ return "Alibaba Cloud"
195
+ if model_id.startswith("allam"):
196
+ return "SDAIA"
197
+
198
+ return "Groq"
199
+
200
+ def _load_cache(self) -> dict[str, dict] | None:
201
+ """Load cached model metadata if it exists and is fresh."""
202
+ if not self.CACHE_FILE.exists():
203
+ return None
204
+
205
+ try:
206
+ with self.CACHE_FILE.open() as f:
207
+ cache_data = json.load(f)
208
+
209
+ # Check cache age
210
+ cache_time = datetime.fromisoformat(cache_data["cached_at"])
211
+ if datetime.now(timezone.utc) - cache_time > self.CACHE_DURATION:
212
+ logger.info("Cache expired, will fetch fresh data")
213
+ return None
214
+
215
+ return cache_data["models"]
216
+
217
+ except (json.JSONDecodeError, KeyError, ValueError) as e:
218
+ logger.warning(f"Invalid cache file: {e}")
219
+ return None
220
+
221
+ def _save_cache(self, models_metadata: dict[str, dict]) -> None:
222
+ """Save model metadata to cache."""
223
+ try:
224
+ cache_data = {"cached_at": datetime.now(timezone.utc).isoformat(), "models": models_metadata}
225
+
226
+ self.CACHE_FILE.parent.mkdir(parents=True, exist_ok=True)
227
+ with self.CACHE_FILE.open("w") as f:
228
+ json.dump(cache_data, f, indent=2)
229
+
230
+ logger.info(f"Cached {len(models_metadata)} models to {self.CACHE_FILE}")
231
+
232
+ except (OSError, TypeError, ValueError) as e:
233
+ logger.warning(f"Failed to save cache: {e}")
234
+
235
+ def _get_fallback_models(self) -> dict[str, dict]:
236
+ """Return minimal fallback list when API is unavailable."""
237
+ return {
238
+ "llama-3.1-8b-instant": {
239
+ "name": "llama-3.1-8b-instant",
240
+ "provider": "Meta",
241
+ "tool_calling": True,
242
+ "preview": False,
243
+ },
244
+ "llama-3.3-70b-versatile": {
245
+ "name": "llama-3.3-70b-versatile",
246
+ "provider": "Meta",
247
+ "tool_calling": True,
248
+ "preview": False,
249
+ },
250
+ }
251
+
252
+
253
+ # Convenience function for use in other modules
254
+ def get_groq_models(api_key: str | None = None, *, force_refresh: bool = False) -> dict[str, dict]:
255
+ """Get Groq models with their capabilities.
256
+
257
+ Args:
258
+ api_key: Optional API key for testing. If None, uses cached data.
259
+ force_refresh: If True, bypass cache and fetch fresh data.
260
+
261
+ Returns:
262
+ Dictionary of model metadata
263
+ """
264
+ discovery = GroqModelDiscovery(api_key=api_key)
265
+ return discovery.get_models(force_refresh=force_refresh)
lfx/base/models/model.py CHANGED
@@ -296,7 +296,7 @@ class LCModelComponent(Component):
296
296
  )
297
297
  model_message.properties.source = self._build_source(self._id, self.display_name, self)
298
298
  lf_message = await self.send_message(model_message)
299
- result = lf_message.text
299
+ result = lf_message.text or ""
300
300
  else:
301
301
  message = await runnable.ainvoke(inputs)
302
302
  result = message.content if hasattr(message, "content") else message
@@ -14,14 +14,18 @@ class ModelProvidersDict(TypedDict):
14
14
  is_active: bool
15
15
 
16
16
 
17
- def get_filtered_inputs(component_class):
17
+ def get_filtered_inputs(component_class, provider_name: str | None = None):
18
18
  base_input_names = {field.name for field in LCModelComponent.get_base_inputs()}
19
19
  component_instance = component_class()
20
20
 
21
- return [process_inputs(input_) for input_ in component_instance.inputs if input_.name not in base_input_names]
21
+ return [
22
+ process_inputs(input_, provider_name)
23
+ for input_ in component_instance.inputs
24
+ if input_.name not in base_input_names
25
+ ]
22
26
 
23
27
 
24
- def process_inputs(component_data: Input):
28
+ def process_inputs(component_data: Input, provider_name: str | None = None):
25
29
  """Processes and modifies an input configuration based on its type or name.
26
30
 
27
31
  Adjusts properties such as value, advanced status, real-time refresh, and additional information for specific
@@ -29,6 +33,7 @@ def process_inputs(component_data: Input):
29
33
 
30
34
  Args:
31
35
  component_data: The input configuration to process.
36
+ provider_name: The name of the provider to process the inputs for.
32
37
 
33
38
  Returns:
34
39
  The modified input configuration.
@@ -43,9 +48,11 @@ def process_inputs(component_data: Input):
43
48
  component_data.advanced = True
44
49
  component_data.value = True
45
50
  elif component_data.name in {"temperature", "base_url"}:
46
- component_data = set_advanced_true(component_data)
51
+ if provider_name not in ["IBM watsonx.ai", "Ollama"]:
52
+ component_data = set_advanced_true(component_data)
47
53
  elif component_data.name == "model_name":
48
- component_data = set_real_time_refresh_false(component_data)
54
+ if provider_name not in ["IBM watsonx.ai"]:
55
+ component_data = set_real_time_refresh_false(component_data)
49
56
  component_data = add_combobox_true(component_data)
50
57
  component_data = add_info(
51
58
  component_data,
@@ -79,6 +86,28 @@ def create_input_fields_dict(inputs: list[Input], prefix: str) -> dict[str, Inpu
79
86
  return {f"{prefix}{input_.name}": input_.to_dict() for input_ in inputs}
80
87
 
81
88
 
89
+ def _get_ollama_inputs_and_fields():
90
+ try:
91
+ from lfx.components.ollama.ollama import ChatOllamaComponent
92
+
93
+ ollama_inputs = get_filtered_inputs(ChatOllamaComponent, provider_name="Ollama")
94
+ except ImportError as e:
95
+ msg = "Ollama is not installed. Please install it with `pip install langchain-ollama`."
96
+ raise ImportError(msg) from e
97
+ return ollama_inputs, create_input_fields_dict(ollama_inputs, "")
98
+
99
+
100
+ def _get_watsonx_inputs_and_fields():
101
+ try:
102
+ from lfx.components.ibm.watsonx import WatsonxAIComponent
103
+
104
+ watsonx_inputs = get_filtered_inputs(WatsonxAIComponent, provider_name="IBM watsonx.ai")
105
+ except ImportError as e:
106
+ msg = "IBM watsonx.ai is not installed. Please install it with `pip install langchain-ibm-watsonx`."
107
+ raise ImportError(msg) from e
108
+ return watsonx_inputs, create_input_fields_dict(watsonx_inputs, "")
109
+
110
+
82
111
  def _get_google_generative_ai_inputs_and_fields():
83
112
  try:
84
113
  from lfx.components.google.google_generative_ai import GoogleGenerativeAIComponent
@@ -293,6 +322,36 @@ try:
293
322
  except ImportError:
294
323
  pass
295
324
 
325
+ try:
326
+ from lfx.components.ibm.watsonx import WatsonxAIComponent
327
+
328
+ watsonx_inputs, watsonx_fields = _get_watsonx_inputs_and_fields()
329
+ MODEL_PROVIDERS_DICT["IBM watsonx.ai"] = {
330
+ "fields": watsonx_fields,
331
+ "inputs": watsonx_inputs,
332
+ "prefix": "",
333
+ "component_class": WatsonxAIComponent(),
334
+ "icon": WatsonxAIComponent.icon,
335
+ "is_active": True,
336
+ }
337
+ except ImportError:
338
+ pass
339
+
340
+ try:
341
+ from lfx.components.ollama.ollama import ChatOllamaComponent
342
+
343
+ ollama_inputs, ollama_fields = _get_ollama_inputs_and_fields()
344
+ MODEL_PROVIDERS_DICT["Ollama"] = {
345
+ "fields": ollama_fields,
346
+ "inputs": ollama_inputs,
347
+ "prefix": "",
348
+ "component_class": ChatOllamaComponent(),
349
+ "icon": ChatOllamaComponent.icon,
350
+ "is_active": True,
351
+ }
352
+ except ImportError:
353
+ pass
354
+
296
355
  # Expose only active providers ----------------------------------------------
297
356
  ACTIVE_MODEL_PROVIDERS_DICT: dict[str, ModelProvidersDict] = {
298
357
  name: prov for name, prov in MODEL_PROVIDERS_DICT.items() if prov.get("is_active", True)
@@ -302,10 +361,18 @@ MODEL_PROVIDERS: list[str] = list(ACTIVE_MODEL_PROVIDERS_DICT.keys())
302
361
 
303
362
  ALL_PROVIDER_FIELDS: list[str] = [field for prov in ACTIVE_MODEL_PROVIDERS_DICT.values() for field in prov["fields"]]
304
363
 
305
- MODEL_DYNAMIC_UPDATE_FIELDS = ["api_key", "model", "tool_model_enabled", "base_url", "model_name"]
364
+ MODEL_DYNAMIC_UPDATE_FIELDS = [
365
+ "api_key",
366
+ "model",
367
+ "tool_model_enabled",
368
+ "base_url",
369
+ "model_name",
370
+ "watsonx_endpoint",
371
+ "url",
372
+ ]
306
373
 
307
374
  MODELS_METADATA = {name: {"icon": prov["icon"]} for name, prov in ACTIVE_MODEL_PROVIDERS_DICT.items()}
308
375
 
309
- MODEL_PROVIDERS_LIST = ["Anthropic", "Google Generative AI", "OpenAI"]
376
+ MODEL_PROVIDERS_LIST = ["Anthropic", "Google Generative AI", "OpenAI", "IBM watsonx.ai", "Ollama"]
310
377
 
311
378
  MODEL_OPTIONS_METADATA = [MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA]
@@ -1,3 +1,14 @@
1
+ import asyncio
2
+ from urllib.parse import urljoin
3
+
4
+ import httpx
5
+
6
+ from lfx.log.logger import logger
7
+ from lfx.utils.util import transform_localhost_url
8
+
9
+ HTTP_STATUS_OK = 200
10
+
11
+
1
12
  def get_model_name(llm, display_name: str | None = "Custom"):
2
13
  attributes_to_check = ["model_name", "model", "model_id", "deployment_name"]
3
14
 
@@ -6,3 +17,92 @@ def get_model_name(llm, display_name: str | None = "Custom"):
6
17
 
7
18
  # If no matching attribute is found, return the class name as a fallback
8
19
  return model_name if model_name is not None else display_name
20
+
21
+
22
+ async def is_valid_ollama_url(url: str) -> bool:
23
+ """Check if the provided URL is a valid Ollama API endpoint."""
24
+ try:
25
+ url = transform_localhost_url(url)
26
+ if not url:
27
+ return False
28
+ # Strip /v1 suffix if present, as Ollama API endpoints are at root level
29
+ url = url.rstrip("/").removesuffix("/v1")
30
+ if not url.endswith("/"):
31
+ url = url + "/"
32
+ async with httpx.AsyncClient() as client:
33
+ return (await client.get(url=urljoin(url, "api/tags"))).status_code == HTTP_STATUS_OK
34
+ except httpx.RequestError:
35
+ logger.debug(f"Invalid Ollama URL: {url}")
36
+ return False
37
+
38
+
39
+ async def get_ollama_models(
40
+ base_url_value: str, desired_capability: str, json_models_key: str, json_name_key: str, json_capabilities_key: str
41
+ ) -> list[str]:
42
+ """Fetch available completion models from the Ollama API.
43
+
44
+ Filters out embedding models and only returns models with completion capability.
45
+
46
+ Args:
47
+ base_url_value (str): The base URL of the Ollama API.
48
+ desired_capability (str): The desired capability of the model.
49
+ json_models_key (str): The key in the JSON response that contains the models.
50
+ json_name_key (str): The key in the JSON response that contains the model names.
51
+ json_capabilities_key (str): The key in the JSON response that contains the model capabilities.
52
+
53
+ Returns:
54
+ list[str]: A sorted list of model names that support completion.
55
+
56
+ Raises:
57
+ ValueError: If there is an issue with the API request or response.
58
+ """
59
+ try:
60
+ # Strip /v1 suffix if present, as Ollama API endpoints are at root level
61
+ base_url = base_url_value.rstrip("/").removesuffix("/v1")
62
+ if not base_url.endswith("/"):
63
+ base_url = base_url + "/"
64
+ base_url = transform_localhost_url(base_url)
65
+
66
+ # Ollama REST API to return models
67
+ tags_url = urljoin(base_url, "api/tags")
68
+
69
+ # Ollama REST API to return model capabilities
70
+ show_url = urljoin(base_url, "api/show")
71
+ tags_response = None
72
+
73
+ async with httpx.AsyncClient() as client:
74
+ # Fetch available models
75
+ tags_response = await client.get(url=tags_url)
76
+ tags_response.raise_for_status()
77
+ models = tags_response.json()
78
+ if asyncio.iscoroutine(models):
79
+ models = await models
80
+ await logger.adebug(f"Available models: {models}")
81
+
82
+ # Filter models that are NOT embedding models
83
+ model_ids = []
84
+ for model in models.get(json_models_key, []):
85
+ model_name = model.get(json_name_key)
86
+ if not model_name:
87
+ continue
88
+ await logger.adebug(f"Checking model: {model_name}")
89
+
90
+ payload = {"model": model_name}
91
+ show_response = await client.post(url=show_url, json=payload)
92
+ show_response.raise_for_status()
93
+ json_data = show_response.json()
94
+ if asyncio.iscoroutine(json_data):
95
+ json_data = await json_data
96
+
97
+ capabilities = json_data.get(json_capabilities_key, [])
98
+ await logger.adebug(f"Model: {model_name}, Capabilities: {capabilities}")
99
+
100
+ if desired_capability in capabilities:
101
+ model_ids.append(model_name)
102
+
103
+ return sorted(model_ids)
104
+
105
+ except (httpx.RequestError, ValueError) as e:
106
+ msg = "Could not get model names from Ollama."
107
+ await logger.aexception(msg)
108
+ raise ValueError(msg) from e
@@ -47,3 +47,6 @@ URL_LIST = [
47
47
  "http://127.0.0.1:11434",
48
48
  "http://0.0.0.0:11434",
49
49
  ]
50
+
51
+
52
+ DEFAULT_OLLAMA_API_URL = "https://ollama.com"
@@ -3,6 +3,13 @@ from .model_metadata import create_model_metadata
3
3
  # Unified model metadata - single source of truth
4
4
  OPENAI_MODELS_DETAILED = [
5
5
  # GPT-5 Series
6
+ create_model_metadata(
7
+ provider="OpenAI",
8
+ name="gpt-5.1",
9
+ icon="OpenAI",
10
+ tool_calling=True,
11
+ reasoning=True,
12
+ ),
6
13
  create_model_metadata(
7
14
  provider="OpenAI",
8
15
  name="gpt-5",
@@ -0,0 +1,36 @@
1
+ from .model_metadata import create_model_metadata
2
+
3
+ WATSONX_DEFAULT_EMBEDDING_MODELS = [
4
+ create_model_metadata(
5
+ provider="IBM Watsonx",
6
+ name="sentence-transformers/all-minilm-l12-v2",
7
+ icon="WatsonxAI",
8
+ ),
9
+ create_model_metadata(
10
+ provider="IBM Watsonx",
11
+ name="ibm/slate-125m-english-rtrvr-v2",
12
+ icon="WatsonxAI",
13
+ ),
14
+ create_model_metadata(
15
+ provider="IBM Watsonx",
16
+ name="ibm/slate-30m-english-rtrvr-v2",
17
+ icon="WatsonxAI",
18
+ ),
19
+ create_model_metadata(
20
+ provider="IBM Watsonx",
21
+ name="intfloat/multilingual-e5-large",
22
+ icon="WatsonxAI",
23
+ ),
24
+ ]
25
+
26
+
27
+ WATSONX_EMBEDDING_MODEL_NAMES = [metadata["name"] for metadata in WATSONX_DEFAULT_EMBEDDING_MODELS]
28
+
29
+ IBM_WATSONX_URLS = [
30
+ "https://us-south.ml.cloud.ibm.com",
31
+ "https://eu-de.ml.cloud.ibm.com",
32
+ "https://eu-gb.ml.cloud.ibm.com",
33
+ "https://au-syd.ml.cloud.ibm.com",
34
+ "https://jp-tok.ml.cloud.ibm.com",
35
+ "https://ca-tor.ml.cloud.ibm.com",
36
+ ]