lfx-nightly 0.1.13.dev0__py3-none-any.whl → 0.2.0.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.
Files changed (86) hide show
  1. lfx/_assets/component_index.json +1 -1
  2. lfx/base/agents/agent.py +109 -29
  3. lfx/base/agents/events.py +102 -35
  4. lfx/base/agents/utils.py +15 -2
  5. lfx/base/composio/composio_base.py +24 -9
  6. lfx/base/datastax/__init__.py +5 -0
  7. lfx/{components/vectorstores/astradb.py → base/datastax/astradb_base.py} +84 -473
  8. lfx/base/io/chat.py +5 -4
  9. lfx/base/mcp/util.py +101 -15
  10. lfx/base/models/model_input_constants.py +74 -7
  11. lfx/base/models/ollama_constants.py +3 -0
  12. lfx/base/models/watsonx_constants.py +12 -0
  13. lfx/cli/commands.py +1 -1
  14. lfx/components/agents/__init__.py +3 -1
  15. lfx/components/agents/agent.py +47 -4
  16. lfx/components/agents/altk_agent.py +366 -0
  17. lfx/components/agents/cuga_agent.py +1 -1
  18. lfx/components/agents/mcp_component.py +32 -2
  19. lfx/components/amazon/amazon_bedrock_converse.py +1 -1
  20. lfx/components/apify/apify_actor.py +3 -3
  21. lfx/components/datastax/__init__.py +12 -6
  22. lfx/components/datastax/{astra_assistant_manager.py → astradb_assistant_manager.py} +1 -0
  23. lfx/components/datastax/astradb_chatmemory.py +40 -0
  24. lfx/components/datastax/astradb_cql.py +5 -31
  25. lfx/components/datastax/astradb_graph.py +9 -123
  26. lfx/components/datastax/astradb_tool.py +12 -52
  27. lfx/components/datastax/astradb_vectorstore.py +133 -976
  28. lfx/components/datastax/create_assistant.py +1 -0
  29. lfx/components/datastax/create_thread.py +1 -0
  30. lfx/components/datastax/dotenv.py +1 -0
  31. lfx/components/datastax/get_assistant.py +1 -0
  32. lfx/components/datastax/getenvvar.py +1 -0
  33. lfx/components/datastax/graph_rag.py +1 -1
  34. lfx/components/datastax/list_assistants.py +1 -0
  35. lfx/components/datastax/run.py +1 -0
  36. lfx/components/docling/__init__.py +3 -0
  37. lfx/components/docling/docling_remote_vlm.py +284 -0
  38. lfx/components/ibm/watsonx.py +25 -21
  39. lfx/components/input_output/chat.py +8 -0
  40. lfx/components/input_output/chat_output.py +8 -0
  41. lfx/components/knowledge_bases/ingestion.py +17 -9
  42. lfx/components/knowledge_bases/retrieval.py +16 -8
  43. lfx/components/logic/loop.py +4 -0
  44. lfx/components/mistral/mistral_embeddings.py +1 -1
  45. lfx/components/models/embedding_model.py +88 -7
  46. lfx/components/ollama/ollama.py +221 -14
  47. lfx/components/openrouter/openrouter.py +49 -147
  48. lfx/components/processing/parser.py +6 -1
  49. lfx/components/processing/structured_output.py +55 -17
  50. lfx/components/vectorstores/__init__.py +0 -6
  51. lfx/custom/custom_component/component.py +3 -2
  52. lfx/field_typing/constants.py +1 -0
  53. lfx/graph/edge/base.py +2 -2
  54. lfx/graph/graph/base.py +1 -1
  55. lfx/graph/graph/schema.py +3 -2
  56. lfx/graph/vertex/vertex_types.py +1 -1
  57. lfx/io/schema.py +6 -0
  58. lfx/schema/schema.py +5 -0
  59. {lfx_nightly-0.1.13.dev0.dist-info → lfx_nightly-0.2.0.dev0.dist-info}/METADATA +1 -1
  60. {lfx_nightly-0.1.13.dev0.dist-info → lfx_nightly-0.2.0.dev0.dist-info}/RECORD +63 -81
  61. lfx/components/datastax/astra_db.py +0 -77
  62. lfx/components/datastax/cassandra.py +0 -92
  63. lfx/components/vectorstores/astradb_graph.py +0 -326
  64. lfx/components/vectorstores/cassandra.py +0 -264
  65. lfx/components/vectorstores/cassandra_graph.py +0 -238
  66. lfx/components/vectorstores/chroma.py +0 -167
  67. lfx/components/vectorstores/clickhouse.py +0 -135
  68. lfx/components/vectorstores/couchbase.py +0 -102
  69. lfx/components/vectorstores/elasticsearch.py +0 -267
  70. lfx/components/vectorstores/faiss.py +0 -111
  71. lfx/components/vectorstores/graph_rag.py +0 -141
  72. lfx/components/vectorstores/hcd.py +0 -314
  73. lfx/components/vectorstores/milvus.py +0 -115
  74. lfx/components/vectorstores/mongodb_atlas.py +0 -213
  75. lfx/components/vectorstores/opensearch.py +0 -243
  76. lfx/components/vectorstores/pgvector.py +0 -72
  77. lfx/components/vectorstores/pinecone.py +0 -134
  78. lfx/components/vectorstores/qdrant.py +0 -109
  79. lfx/components/vectorstores/supabase.py +0 -76
  80. lfx/components/vectorstores/upstash.py +0 -124
  81. lfx/components/vectorstores/vectara.py +0 -97
  82. lfx/components/vectorstores/vectara_rag.py +0 -164
  83. lfx/components/vectorstores/weaviate.py +0 -89
  84. /lfx/components/datastax/{astra_vectorize.py → astradb_vectorize.py} +0 -0
  85. {lfx_nightly-0.1.13.dev0.dist-info → lfx_nightly-0.2.0.dev0.dist-info}/WHEEL +0 -0
  86. {lfx_nightly-0.1.13.dev0.dist-info → lfx_nightly-0.2.0.dev0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,366 @@
1
+ import ast
2
+ import json
3
+ import uuid
4
+ from collections.abc import Sequence
5
+ from typing import TYPE_CHECKING, Any, cast
6
+
7
+ from altk.core.llm import get_llm
8
+ from altk.core.toolkit import AgentPhase
9
+ from altk.post_tool.code_generation.code_generation import (
10
+ CodeGenerationComponent,
11
+ CodeGenerationComponentConfig,
12
+ )
13
+ from altk.post_tool.core.toolkit import CodeGenerationRunInput
14
+ from langchain.agents import AgentExecutor, BaseMultiActionAgent, BaseSingleActionAgent
15
+ from langchain_anthropic.chat_models import ChatAnthropic
16
+ from langchain_core.language_models.chat_models import BaseChatModel
17
+ from langchain_core.messages import BaseMessage, HumanMessage
18
+ from langchain_core.runnables import Runnable, RunnableBinding
19
+ from langchain_core.tools import BaseTool
20
+ from langchain_openai.chat_models.base import ChatOpenAI
21
+ from pydantic import Field
22
+
23
+ from lfx.base.agents.callback import AgentAsyncHandler
24
+ from lfx.base.agents.events import ExceptionWithMessageError, process_agent_events
25
+ from lfx.base.agents.utils import data_to_messages, get_chat_output_sender_name
26
+ from lfx.base.models.model_input_constants import (
27
+ MODEL_PROVIDERS_DICT,
28
+ MODELS_METADATA,
29
+ )
30
+ from lfx.components.agents import AgentComponent
31
+ from lfx.inputs.inputs import BoolInput
32
+ from lfx.io import DropdownInput, IntInput, Output
33
+ from lfx.log.logger import logger
34
+ from lfx.memory import delete_message
35
+ from lfx.schema.content_block import ContentBlock
36
+ from lfx.schema.data import Data
37
+ from lfx.schema.message import Message
38
+ from lfx.utils.constants import MESSAGE_SENDER_AI
39
+
40
+ if TYPE_CHECKING:
41
+ from lfx.schema.log import SendMessageFunctionType
42
+
43
+
44
+ def set_advanced_true(component_input):
45
+ component_input.advanced = True
46
+ return component_input
47
+
48
+
49
+ MODEL_PROVIDERS_LIST = ["Anthropic", "OpenAI"]
50
+ INPUT_NAMES_TO_BE_OVERRIDDEN = ["agent_llm"]
51
+
52
+
53
+ def get_parent_agent_inputs():
54
+ return [
55
+ input_field for input_field in AgentComponent.inputs if input_field.name not in INPUT_NAMES_TO_BE_OVERRIDDEN
56
+ ]
57
+
58
+
59
+ class PostToolProcessor(BaseTool):
60
+ """A tool output processor to process tool outputs.
61
+
62
+ This wrapper intercepts the tool execution output and
63
+ if the tool output is a JSON, it invokes an ALTK component
64
+ to extract information from the JSON by generating Python code.
65
+ """
66
+
67
+ name: str = Field(...)
68
+ description: str = Field(...)
69
+ wrapped_tool: BaseTool = Field(...)
70
+ user_query: str = Field(...)
71
+ agent: Runnable | BaseSingleActionAgent | BaseMultiActionAgent | AgentExecutor = Field(...)
72
+ response_processing_size_threshold: int = Field(...)
73
+
74
+ def __init__(
75
+ self, wrapped_tool: BaseTool, user_query: str, agent, response_processing_size_threshold: int, **kwargs
76
+ ):
77
+ super().__init__(
78
+ name=wrapped_tool.name,
79
+ description=wrapped_tool.description,
80
+ wrapped_tool=wrapped_tool,
81
+ user_query=user_query,
82
+ agent=agent,
83
+ response_processing_size_threshold=response_processing_size_threshold,
84
+ **kwargs,
85
+ )
86
+
87
+ def _execute_tool(self, *args, **kwargs) -> str:
88
+ """Execute the wrapped tool with proper error handling."""
89
+ try:
90
+ # Try with config parameter first (newer LangChain versions)
91
+ if hasattr(self.wrapped_tool, "_run"):
92
+ # Ensure config is provided for StructuredTool
93
+ if "config" not in kwargs:
94
+ kwargs["config"] = {}
95
+ return self.wrapped_tool._run(*args, **kwargs) # noqa: SLF001
96
+ return self.wrapped_tool.run(*args, **kwargs)
97
+ except TypeError as e:
98
+ if "config" in str(e):
99
+ # Fallback: try without config for older tools
100
+ kwargs.pop("config", None)
101
+ if hasattr(self.wrapped_tool, "_run"):
102
+ return self.wrapped_tool._run(*args, **kwargs) # noqa: SLF001
103
+ return self.wrapped_tool.run(*args, **kwargs)
104
+ raise
105
+
106
+ def _run(self, *args: Any, **kwargs: Any) -> str:
107
+ # Run the wrapped tool
108
+ result = self._execute_tool(*args, **kwargs)
109
+
110
+ # Run postprocessing and return the output
111
+ return self.process_tool_response(result)
112
+
113
+ def _get_tool_response_str(self, tool_response) -> str | None:
114
+ if isinstance(tool_response, str):
115
+ tool_response_str = tool_response
116
+ elif isinstance(tool_response, Data):
117
+ tool_response_str = str(tool_response.data)
118
+ elif isinstance(tool_response, list) and all(isinstance(item, Data) for item in tool_response):
119
+ # get only the first element, not 100% sure if it should be the first or the last
120
+ tool_response_str = str(tool_response[0].data)
121
+ elif isinstance(tool_response, (dict, list)):
122
+ tool_response_str = str(tool_response)
123
+ else:
124
+ tool_response_str = None
125
+ return tool_response_str
126
+
127
+ def _get_altk_llm_object(self) -> Any:
128
+ # Extract the LLM model and map it to altk model inputs
129
+ llm_object: BaseChatModel | None = None
130
+ steps = getattr(self.agent, "steps", None)
131
+ if steps:
132
+ for step in steps:
133
+ if isinstance(step, RunnableBinding) and isinstance(step.bound, BaseChatModel):
134
+ llm_object = step.bound
135
+ break
136
+ if isinstance(llm_object, ChatAnthropic):
137
+ # litellm needs the prefix to the model name for anthropic
138
+ model_name = f"anthropic/{llm_object.model}"
139
+ api_key = llm_object.anthropic_api_key.get_secret_value()
140
+ llm_client = get_llm("litellm")
141
+ llm_client_obj = llm_client(model_name=model_name, api_key=api_key)
142
+ elif isinstance(llm_object, ChatOpenAI):
143
+ model_name = llm_object.model_name
144
+ api_key = llm_object.openai_api_key.get_secret_value()
145
+ llm_client = get_llm("openai.sync")
146
+ llm_client_obj = llm_client(model=model_name, api_key=api_key)
147
+ else:
148
+ logger.info("ALTK currently only supports OpenAI and Anthropic models through Langflow.")
149
+ llm_client_obj = None
150
+
151
+ return llm_client_obj
152
+
153
+ def process_tool_response(self, tool_response: str, **_kwargs):
154
+ logger.info("Calling process_tool_response of PostToolProcessor")
155
+ tool_response_str = self._get_tool_response_str(tool_response)
156
+
157
+ try:
158
+ tool_response_json = ast.literal_eval(tool_response_str)
159
+ if not isinstance(tool_response_json, (list, dict)):
160
+ tool_response_json = None
161
+ except (json.JSONDecodeError, TypeError) as e:
162
+ logger.info(
163
+ f"An error in converting the tool response to json, this will skip the code generation component: {e}"
164
+ )
165
+ tool_response_json = None
166
+
167
+ if tool_response_json is not None and len(str(tool_response_json)) > self.response_processing_size_threshold:
168
+ llm_client_obj = self._get_altk_llm_object()
169
+ if llm_client_obj is not None:
170
+ config = CodeGenerationComponentConfig(llm_client=llm_client_obj, use_docker_sandbox=False)
171
+
172
+ middleware = CodeGenerationComponent(config=config)
173
+ input_data = CodeGenerationRunInput(
174
+ messages=[], nl_query=self.user_query, tool_response=tool_response_json
175
+ )
176
+ output = None
177
+ try:
178
+ output = middleware.process(input_data, AgentPhase.RUNTIME)
179
+ except Exception as e: # noqa: BLE001
180
+ logger.error(f"Exception in executing CodeGenerationComponent: {e}")
181
+ logger.info(f"Output of CodeGenerationComponent: {output.result}")
182
+ return output.result
183
+ return tool_response
184
+
185
+
186
+ class ALTKAgentComponent(AgentComponent):
187
+ """An advanced tool calling agent.
188
+
189
+ The ALTKAgent is an advanced AI agent that enhances the tool calling capabilities of LLMs
190
+ by performing special checks and processing around tool calls.
191
+ It uses components from the Agent Lifecycle ToolKit (https://github.com/AgentToolkit/agent-lifecycle-toolkit)
192
+ """
193
+
194
+ display_name: str = "ALTK Agent"
195
+ description: str = "Agent with enhanced tool calling capabilities. For more information on ALTK, visit https://github.com/AgentToolkit/agent-lifecycle-toolkit"
196
+ documentation: str = "https://docs.langflow.org/agents"
197
+ icon = "bot"
198
+ beta = True
199
+ name = "ALTKAgent"
200
+
201
+ # Filter out json_mode from OpenAI inputs since we handle structured output differently
202
+ if "OpenAI" in MODEL_PROVIDERS_DICT:
203
+ openai_inputs_filtered = [
204
+ input_field
205
+ for input_field in MODEL_PROVIDERS_DICT["OpenAI"]["inputs"]
206
+ if not (hasattr(input_field, "name") and input_field.name == "json_mode")
207
+ ]
208
+ else:
209
+ openai_inputs_filtered = []
210
+
211
+ inputs = [
212
+ DropdownInput(
213
+ name="agent_llm",
214
+ display_name="Model Provider",
215
+ info="The provider of the language model that the agent will use to generate responses.",
216
+ options=[*MODEL_PROVIDERS_LIST],
217
+ value="OpenAI",
218
+ real_time_refresh=True,
219
+ refresh_button=False,
220
+ input_types=[],
221
+ options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],
222
+ ),
223
+ *get_parent_agent_inputs(),
224
+ BoolInput(
225
+ name="enable_post_tool_reflection",
226
+ display_name="Post Tool JSON Processing",
227
+ info="If true, it passes the tool output to a json processing (if json) step.",
228
+ value=True,
229
+ ),
230
+ # Post Tool Processing is applied only when the number of characters in the response
231
+ # exceed the following threshold
232
+ IntInput(
233
+ name="response_processing_size_threshold",
234
+ display_name="Response Processing Size Threshold",
235
+ value=100,
236
+ info="Tool output is post-processed only if the response length exceeds a specified character threshold.",
237
+ advanced=True,
238
+ show=True,
239
+ ),
240
+ ]
241
+ outputs = [
242
+ Output(name="response", display_name="Response", method="message_response"),
243
+ ]
244
+
245
+ def update_runnable_instance(
246
+ self, agent: AgentExecutor, runnable: AgentExecutor, tools: Sequence[BaseTool]
247
+ ) -> AgentExecutor:
248
+ user_query = self.input_value.get_text() if hasattr(self.input_value, "get_text") else self.input_value
249
+ if self.enable_post_tool_reflection:
250
+ wrapped_tools = [
251
+ PostToolProcessor(
252
+ wrapped_tool=tool,
253
+ user_query=user_query,
254
+ agent=agent,
255
+ response_processing_size_threshold=self.response_processing_size_threshold,
256
+ )
257
+ if not isinstance(tool, PostToolProcessor)
258
+ else tool
259
+ for tool in tools
260
+ ]
261
+ else:
262
+ wrapped_tools = tools
263
+
264
+ runnable.tools = wrapped_tools
265
+
266
+ return runnable
267
+
268
+ async def run_agent(
269
+ self,
270
+ agent: Runnable | BaseSingleActionAgent | BaseMultiActionAgent | AgentExecutor,
271
+ ) -> Message:
272
+ if isinstance(agent, AgentExecutor):
273
+ runnable = agent
274
+ else:
275
+ # note the tools are not required to run the agent, hence the validation removed.
276
+ handle_parsing_errors = hasattr(self, "handle_parsing_errors") and self.handle_parsing_errors
277
+ verbose = hasattr(self, "verbose") and self.verbose
278
+ max_iterations = hasattr(self, "max_iterations") and self.max_iterations
279
+ runnable = AgentExecutor.from_agent_and_tools(
280
+ agent=agent,
281
+ tools=self.tools or [],
282
+ handle_parsing_errors=handle_parsing_errors,
283
+ verbose=verbose,
284
+ max_iterations=max_iterations,
285
+ )
286
+ runnable = self.update_runnable_instance(agent, runnable, self.tools)
287
+
288
+ # Convert input_value to proper format for agent
289
+ if hasattr(self.input_value, "to_lc_message") and callable(self.input_value.to_lc_message):
290
+ lc_message = self.input_value.to_lc_message()
291
+ input_text = lc_message.content if hasattr(lc_message, "content") else str(lc_message)
292
+ else:
293
+ lc_message = None
294
+ input_text = self.input_value
295
+
296
+ input_dict: dict[str, str | list[BaseMessage]] = {}
297
+ if hasattr(self, "system_prompt"):
298
+ input_dict["system_prompt"] = self.system_prompt
299
+ if hasattr(self, "chat_history") and self.chat_history:
300
+ if (
301
+ hasattr(self.chat_history, "to_data")
302
+ and callable(self.chat_history.to_data)
303
+ and self.chat_history.__class__.__name__ == "Data"
304
+ ):
305
+ input_dict["chat_history"] = data_to_messages(self.chat_history)
306
+ # Handle both lfx.schema.message.Message and langflow.schema.message.Message types
307
+ if all(hasattr(m, "to_data") and callable(m.to_data) and "text" in m.data for m in self.chat_history):
308
+ input_dict["chat_history"] = data_to_messages(self.chat_history)
309
+ if all(isinstance(m, Message) for m in self.chat_history):
310
+ input_dict["chat_history"] = data_to_messages([m.to_data() for m in self.chat_history])
311
+ if hasattr(lc_message, "content") and isinstance(lc_message.content, list):
312
+ # ! Because the input has to be a string, we must pass the images in the chat_history
313
+
314
+ image_dicts = [item for item in lc_message.content if item.get("type") == "image"]
315
+ lc_message.content = [item for item in lc_message.content if item.get("type") != "image"]
316
+
317
+ if "chat_history" not in input_dict:
318
+ input_dict["chat_history"] = []
319
+ if isinstance(input_dict["chat_history"], list):
320
+ input_dict["chat_history"].extend(HumanMessage(content=[image_dict]) for image_dict in image_dicts)
321
+ else:
322
+ input_dict["chat_history"] = [HumanMessage(content=[image_dict]) for image_dict in image_dicts]
323
+ input_dict["input"] = input_text
324
+ if hasattr(self, "graph"):
325
+ session_id = self.graph.session_id
326
+ elif hasattr(self, "_session_id"):
327
+ session_id = self._session_id
328
+ else:
329
+ session_id = None
330
+
331
+ try:
332
+ sender_name = get_chat_output_sender_name(self)
333
+ except AttributeError:
334
+ sender_name = self.display_name or "AI"
335
+
336
+ agent_message = Message(
337
+ sender=MESSAGE_SENDER_AI,
338
+ sender_name=sender_name,
339
+ properties={"icon": "Bot", "state": "partial"},
340
+ content_blocks=[ContentBlock(title="Agent Steps", contents=[])],
341
+ session_id=session_id or uuid.uuid4(),
342
+ )
343
+ try:
344
+ result = await process_agent_events(
345
+ runnable.astream_events(
346
+ input_dict,
347
+ config={"callbacks": [AgentAsyncHandler(self.log), *self.get_langchain_callbacks()]},
348
+ version="v2",
349
+ ),
350
+ agent_message,
351
+ cast("SendMessageFunctionType", self.send_message),
352
+ )
353
+ except ExceptionWithMessageError as e:
354
+ if hasattr(e, "agent_message") and hasattr(e.agent_message, "id"):
355
+ msg_id = e.agent_message.id
356
+ await delete_message(id_=msg_id)
357
+ await self._send_message_event(e.agent_message, category="remove_message")
358
+ logger.error(f"ExceptionWithMessageError: {e}")
359
+ raise
360
+ except Exception as e:
361
+ # Log or handle any other exceptions
362
+ logger.error(f"Error: {e}")
363
+ raise
364
+
365
+ self.status = result
366
+ return result
@@ -185,7 +185,7 @@ class CugaComponent(ToolCallingAgentComponent):
185
185
  },
186
186
  ],
187
187
  ),
188
- *LCToolsAgentComponent._base_inputs,
188
+ *LCToolsAgentComponent.get_base_inputs(),
189
189
  BoolInput(
190
190
  name="add_current_date_tool",
191
191
  display_name="Current Date",
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
+ import json
4
5
  import uuid
5
6
  from typing import Any
6
7
 
@@ -61,6 +62,7 @@ class MCPToolsComponent(ComponentWithCache):
61
62
  "mcp_server",
62
63
  "tool",
63
64
  "use_cache",
65
+ "verify_ssl",
64
66
  ]
65
67
 
66
68
  display_name = "MCP Tools"
@@ -86,6 +88,16 @@ class MCPToolsComponent(ComponentWithCache):
86
88
  value=False,
87
89
  advanced=True,
88
90
  ),
91
+ BoolInput(
92
+ name="verify_ssl",
93
+ display_name="Verify SSL Certificate",
94
+ info=(
95
+ "Enable SSL certificate verification for HTTPS connections. "
96
+ "Disable only for development/testing with self-signed certificates."
97
+ ),
98
+ value=True,
99
+ advanced=True,
100
+ ),
89
101
  DropdownInput(
90
102
  name="tool",
91
103
  display_name="Tool",
@@ -210,6 +222,11 @@ class MCPToolsComponent(ComponentWithCache):
210
222
  self.tools = []
211
223
  return [], {"name": server_name, "config": server_config}
212
224
 
225
+ # Add verify_ssl option to server config if not present
226
+ if "verify_ssl" not in server_config:
227
+ verify_ssl = getattr(self, "verify_ssl", True)
228
+ server_config["verify_ssl"] = verify_ssl
229
+
213
230
  _, tool_list, tool_cache = await update_tools(
214
231
  server_name=server_name,
215
232
  server_config=server_config,
@@ -504,7 +521,6 @@ class MCPToolsComponent(ComponentWithCache):
504
521
  if session_context:
505
522
  self.stdio_client.set_session_context(session_context)
506
523
  self.streamable_http_client.set_session_context(session_context)
507
-
508
524
  exec_tool = self._tool_cache[self.tool]
509
525
  tool_args = self.get_inputs_for_all_tools(self.tools)[self.tool]
510
526
  kwargs = {}
@@ -519,11 +535,14 @@ class MCPToolsComponent(ComponentWithCache):
519
535
  unflattened_kwargs = maybe_unflatten_dict(kwargs)
520
536
 
521
537
  output = await exec_tool.coroutine(**unflattened_kwargs)
522
-
523
538
  tool_content = []
524
539
  for item in output.content:
525
540
  item_dict = item.model_dump()
541
+ item_dict = self.process_output_item(item_dict)
526
542
  tool_content.append(item_dict)
543
+
544
+ if isinstance(tool_content, list) and all(isinstance(x, dict) for x in tool_content):
545
+ return DataFrame(tool_content)
527
546
  return DataFrame(data=tool_content)
528
547
  return DataFrame(data=[{"error": "You must select a tool"}])
529
548
  except Exception as e:
@@ -531,6 +550,17 @@ class MCPToolsComponent(ComponentWithCache):
531
550
  await logger.aexception(msg)
532
551
  raise ValueError(msg) from e
533
552
 
553
+ def process_output_item(self, item_dict):
554
+ """Process the output of a tool."""
555
+ if item_dict.get("type") == "text":
556
+ text = item_dict.get("text")
557
+ try:
558
+ return json.loads(text)
559
+ # convert it to dict
560
+ except json.JSONDecodeError:
561
+ return item_dict
562
+ return item_dict
563
+
534
564
  def _get_session_context(self) -> str | None:
535
565
  """Get the Langflow session ID for MCP session caching."""
536
566
  # Try to get session ID from the component's execution context
@@ -16,7 +16,7 @@ class AmazonBedrockConverseComponent(LCModelComponent):
16
16
  beta = True
17
17
 
18
18
  inputs = [
19
- *LCModelComponent._base_inputs,
19
+ *LCModelComponent.get_base_inputs(),
20
20
  DropdownInput(
21
21
  name="model_id",
22
22
  display_name="Model ID",
@@ -92,7 +92,7 @@ class ApifyActorsComponent(Component):
92
92
  """Run the Actor and return node output."""
93
93
  input_ = json.loads(self.run_input)
94
94
  fields = ApifyActorsComponent.parse_dataset_fields(self.dataset_fields) if self.dataset_fields else None
95
- res = self._run_actor(self.actor_id, input_, fields=fields)
95
+ res = self.run_actor(self.actor_id, input_, fields=fields)
96
96
  if self.flatten_dataset:
97
97
  res = [ApifyActorsComponent.flatten(item) for item in res]
98
98
  data = [Data(data=item) for item in res]
@@ -159,7 +159,7 @@ class ApifyActorsComponent(Component):
159
159
  # retrieve if nested, just in case
160
160
  input_dict = input_dict.get("run_input", input_dict)
161
161
 
162
- res = parent._run_actor(actor_id, input_dict)
162
+ res = parent.run_actor(actor_id, input_dict)
163
163
  return "\n\n".join([ApifyActorsComponent.dict_to_json_str(item) for item in res])
164
164
 
165
165
  return ApifyActorRun
@@ -256,7 +256,7 @@ class ApifyActorsComponent(Component):
256
256
  valid_chars = string.ascii_letters + string.digits + "_-"
257
257
  return "".join(char if char in valid_chars else "_" for char in actor_id)
258
258
 
259
- def _run_actor(self, actor_id: str, run_input: dict, fields: list[str] | None = None) -> list[dict]:
259
+ def run_actor(self, actor_id: str, run_input: dict, fields: list[str] | None = None) -> list[dict]:
260
260
  """Run an Apify Actor and return the output dataset.
261
261
 
262
262
  Args:
@@ -5,17 +5,19 @@ from typing import TYPE_CHECKING, Any
5
5
  from lfx.components._importing import import_mod
6
6
 
7
7
  if TYPE_CHECKING:
8
- from .astra_assistant_manager import AstraAssistantManager
9
- from .astra_db import AstraDBChatMemory
10
- from .astra_vectorize import AstraVectorizeComponent
8
+ from .astradb_assistant_manager import AstraAssistantManager
9
+ from .astradb_chatmemory import AstraDBChatMemory
11
10
  from .astradb_cql import AstraDBCQLToolComponent
11
+ from .astradb_graph import AstraDBGraphVectorStoreComponent
12
12
  from .astradb_tool import AstraDBToolComponent
13
+ from .astradb_vectorize import AstraVectorizeComponent
13
14
  from .astradb_vectorstore import AstraDBVectorStoreComponent
14
15
  from .create_assistant import AssistantsCreateAssistant
15
16
  from .create_thread import AssistantsCreateThread
16
17
  from .dotenv import Dotenv
17
18
  from .get_assistant import AssistantsGetAssistantName
18
19
  from .getenvvar import GetEnvVar
20
+ from .graph_rag import GraphRAGComponent
19
21
  from .list_assistants import AssistantsListAssistants
20
22
  from .run import AssistantsRun
21
23
 
@@ -25,14 +27,16 @@ _dynamic_imports = {
25
27
  "AssistantsGetAssistantName": "get_assistant",
26
28
  "AssistantsListAssistants": "list_assistants",
27
29
  "AssistantsRun": "run",
28
- "AstraAssistantManager": "astra_assistant_manager",
30
+ "AstraAssistantManager": "astradb_assistant_manager",
29
31
  "AstraDBCQLToolComponent": "astradb_cql",
30
- "AstraDBChatMemory": "astra_db",
32
+ "AstraDBChatMemory": "astradb_chatmemory",
33
+ "AstraDBGraphVectorStoreComponent": "astradb_graph",
31
34
  "AstraDBToolComponent": "astradb_tool",
32
35
  "AstraDBVectorStoreComponent": "astradb_vectorstore",
33
- "AstraVectorizeComponent": "astra_vectorize",
36
+ "AstraVectorizeComponent": "astradb_vectorize",
34
37
  "Dotenv": "dotenv",
35
38
  "GetEnvVar": "getenvvar",
39
+ "GraphRAGComponent": "graph_rag",
36
40
  }
37
41
 
38
42
  __all__ = [
@@ -44,11 +48,13 @@ __all__ = [
44
48
  "AstraAssistantManager",
45
49
  "AstraDBCQLToolComponent",
46
50
  "AstraDBChatMemory",
51
+ "AstraDBGraphVectorStoreComponent",
47
52
  "AstraDBToolComponent",
48
53
  "AstraDBVectorStoreComponent",
49
54
  "AstraVectorizeComponent",
50
55
  "Dotenv",
51
56
  "GetEnvVar",
57
+ "GraphRAGComponent",
52
58
  ]
53
59
 
54
60
 
@@ -30,6 +30,7 @@ class AstraAssistantManager(ComponentWithCache):
30
30
  name = "Astra Assistant Agent"
31
31
  description = "Manages Assistant Interactions"
32
32
  icon = "AstraDB"
33
+ legacy = True
33
34
 
34
35
  inputs = [
35
36
  DropdownInput(
@@ -0,0 +1,40 @@
1
+ from lfx.base.datastax.astradb_base import AstraDBBaseComponent
2
+ from lfx.base.memory.model import LCChatMemoryComponent
3
+ from lfx.field_typing.constants import Memory
4
+ from lfx.inputs.inputs import MessageTextInput
5
+
6
+
7
+ class AstraDBChatMemory(AstraDBBaseComponent, LCChatMemoryComponent):
8
+ display_name = "Astra DB Chat Memory"
9
+ description = "Retrieves and stores chat messages from Astra DB."
10
+ name = "AstraDBChatMemory"
11
+ icon: str = "AstraDB"
12
+
13
+ inputs = [
14
+ *AstraDBBaseComponent.inputs,
15
+ MessageTextInput(
16
+ name="session_id",
17
+ display_name="Session ID",
18
+ info="The session ID of the chat. If empty, the current session ID parameter will be used.",
19
+ advanced=True,
20
+ ),
21
+ ]
22
+
23
+ def build_message_history(self) -> Memory:
24
+ try:
25
+ from langchain_astradb.chat_message_histories import AstraDBChatMessageHistory
26
+ except ImportError as e:
27
+ msg = (
28
+ "Could not import langchain Astra DB integration package. "
29
+ "Please install it with `uv pip install langchain-astradb`."
30
+ )
31
+ raise ImportError(msg) from e
32
+
33
+ return AstraDBChatMessageHistory(
34
+ session_id=self.session_id,
35
+ collection_name=self.collection_name,
36
+ token=self.token,
37
+ api_endpoint=self.get_api_endpoint(),
38
+ namespace=self.get_keyspace(),
39
+ environment=self.environment,
40
+ )
@@ -8,20 +8,22 @@ import requests
8
8
  from langchain_core.tools import StructuredTool, Tool
9
9
  from pydantic import BaseModel, Field, create_model
10
10
 
11
+ from lfx.base.datastax.astradb_base import AstraDBBaseComponent
11
12
  from lfx.base.langchain_utilities.model import LCToolComponent
12
- from lfx.io import DictInput, IntInput, SecretStrInput, StrInput, TableInput
13
+ from lfx.io import DictInput, IntInput, StrInput, TableInput
13
14
  from lfx.log.logger import logger
14
15
  from lfx.schema.data import Data
15
16
  from lfx.schema.table import EditMode
16
17
 
17
18
 
18
- class AstraDBCQLToolComponent(LCToolComponent):
19
+ class AstraDBCQLToolComponent(AstraDBBaseComponent, LCToolComponent):
19
20
  display_name: str = "Astra DB CQL"
20
21
  description: str = "Create a tool to get transactional data from DataStax Astra DB CQL Table"
21
22
  documentation: str = "https://docs.langflow.org/bundles-datastax#astra-db-cql"
22
23
  icon: str = "AstraDB"
23
24
 
24
25
  inputs = [
26
+ *AstraDBBaseComponent.inputs,
25
27
  StrInput(name="tool_name", display_name="Tool Name", info="The name of the tool.", required=True),
26
28
  StrInput(
27
29
  name="tool_description",
@@ -29,34 +31,6 @@ class AstraDBCQLToolComponent(LCToolComponent):
29
31
  info="The tool description to be passed to the model.",
30
32
  required=True,
31
33
  ),
32
- StrInput(
33
- name="keyspace",
34
- display_name="Keyspace",
35
- value="default_keyspace",
36
- info="The keyspace name within Astra DB where the data is stored.",
37
- required=True,
38
- advanced=True,
39
- ),
40
- StrInput(
41
- name="table_name",
42
- display_name="Table Name",
43
- info="The name of the table within Astra DB where the data is stored.",
44
- required=True,
45
- ),
46
- SecretStrInput(
47
- name="token",
48
- display_name="Astra DB Application Token",
49
- info="Authentication token for accessing Astra DB.",
50
- value="ASTRA_DB_APPLICATION_TOKEN",
51
- required=True,
52
- ),
53
- StrInput(
54
- name="api_endpoint",
55
- display_name="API Endpoint",
56
- info="API endpoint URL for the Astra DB service.",
57
- value="ASTRA_DB_API_ENDPOINT",
58
- required=True,
59
- ),
60
34
  StrInput(
61
35
  name="projection_fields",
62
36
  display_name="Projection fields",
@@ -204,7 +178,7 @@ class AstraDBCQLToolComponent(LCToolComponent):
204
178
 
205
179
  def astra_rest(self, args):
206
180
  headers = {"Accept": "application/json", "X-Cassandra-Token": f"{self.token}"}
207
- astra_url = f"{self.api_endpoint}/api/rest/v2/keyspaces/{self.keyspace}/{self.table_name}/"
181
+ astra_url = f"{self.get_api_endpoint()}/api/rest/v2/keyspaces/{self.get_keyspace()}/{self.collection_name}/"
208
182
  where = {}
209
183
 
210
184
  for param in self.tools_params: