lfx-nightly 0.2.0.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 (188) hide show
  1. lfx/_assets/component_index.json +1 -1
  2. lfx/base/agents/agent.py +13 -1
  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 +2 -1
  6. lfx/base/composio/composio_base.py +159 -224
  7. lfx/base/data/base_file.py +88 -21
  8. lfx/base/data/storage_utils.py +192 -0
  9. lfx/base/data/utils.py +178 -14
  10. lfx/base/embeddings/embeddings_class.py +113 -0
  11. lfx/base/models/groq_constants.py +74 -58
  12. lfx/base/models/groq_model_discovery.py +265 -0
  13. lfx/base/models/model.py +1 -1
  14. lfx/base/models/model_utils.py +100 -0
  15. lfx/base/models/openai_constants.py +7 -0
  16. lfx/base/models/watsonx_constants.py +32 -8
  17. lfx/base/tools/run_flow.py +601 -129
  18. lfx/cli/commands.py +6 -3
  19. lfx/cli/common.py +2 -2
  20. lfx/cli/run.py +1 -1
  21. lfx/cli/script_loader.py +53 -11
  22. lfx/components/Notion/create_page.py +1 -1
  23. lfx/components/Notion/list_database_properties.py +1 -1
  24. lfx/components/Notion/list_pages.py +1 -1
  25. lfx/components/Notion/list_users.py +1 -1
  26. lfx/components/Notion/page_content_viewer.py +1 -1
  27. lfx/components/Notion/search.py +1 -1
  28. lfx/components/Notion/update_page_property.py +1 -1
  29. lfx/components/__init__.py +19 -5
  30. lfx/components/{agents → altk}/__init__.py +5 -9
  31. lfx/components/altk/altk_agent.py +193 -0
  32. lfx/components/apify/apify_actor.py +1 -1
  33. lfx/components/composio/__init__.py +70 -18
  34. lfx/components/composio/apollo_composio.py +11 -0
  35. lfx/components/composio/bitbucket_composio.py +11 -0
  36. lfx/components/composio/canva_composio.py +11 -0
  37. lfx/components/composio/coda_composio.py +11 -0
  38. lfx/components/composio/composio_api.py +10 -0
  39. lfx/components/composio/discord_composio.py +1 -1
  40. lfx/components/composio/elevenlabs_composio.py +11 -0
  41. lfx/components/composio/exa_composio.py +11 -0
  42. lfx/components/composio/firecrawl_composio.py +11 -0
  43. lfx/components/composio/fireflies_composio.py +11 -0
  44. lfx/components/composio/gmail_composio.py +1 -1
  45. lfx/components/composio/googlebigquery_composio.py +11 -0
  46. lfx/components/composio/googlecalendar_composio.py +1 -1
  47. lfx/components/composio/googledocs_composio.py +1 -1
  48. lfx/components/composio/googlemeet_composio.py +1 -1
  49. lfx/components/composio/googlesheets_composio.py +1 -1
  50. lfx/components/composio/googletasks_composio.py +1 -1
  51. lfx/components/composio/heygen_composio.py +11 -0
  52. lfx/components/composio/mem0_composio.py +11 -0
  53. lfx/components/composio/peopledatalabs_composio.py +11 -0
  54. lfx/components/composio/perplexityai_composio.py +11 -0
  55. lfx/components/composio/serpapi_composio.py +11 -0
  56. lfx/components/composio/slack_composio.py +3 -574
  57. lfx/components/composio/slackbot_composio.py +1 -1
  58. lfx/components/composio/snowflake_composio.py +11 -0
  59. lfx/components/composio/tavily_composio.py +11 -0
  60. lfx/components/composio/youtube_composio.py +2 -2
  61. lfx/components/cuga/__init__.py +34 -0
  62. lfx/components/cuga/cuga_agent.py +730 -0
  63. lfx/components/data/__init__.py +78 -28
  64. lfx/components/data_source/__init__.py +58 -0
  65. lfx/components/{data → data_source}/api_request.py +26 -3
  66. lfx/components/{data → data_source}/csv_to_data.py +15 -10
  67. lfx/components/{data → data_source}/json_to_data.py +15 -8
  68. lfx/components/{data → data_source}/news_search.py +1 -1
  69. lfx/components/{data → data_source}/rss.py +1 -1
  70. lfx/components/{data → data_source}/sql_executor.py +1 -1
  71. lfx/components/{data → data_source}/url.py +1 -1
  72. lfx/components/{data → data_source}/web_search.py +1 -1
  73. lfx/components/datastax/astradb_cql.py +1 -1
  74. lfx/components/datastax/astradb_graph.py +1 -1
  75. lfx/components/datastax/astradb_tool.py +1 -1
  76. lfx/components/datastax/astradb_vectorstore.py +1 -1
  77. lfx/components/datastax/hcd.py +1 -1
  78. lfx/components/deactivated/json_document_builder.py +1 -1
  79. lfx/components/docling/__init__.py +0 -3
  80. lfx/components/elastic/elasticsearch.py +1 -1
  81. lfx/components/elastic/opensearch_multimodal.py +1575 -0
  82. lfx/components/files_and_knowledge/__init__.py +47 -0
  83. lfx/components/{data → files_and_knowledge}/directory.py +1 -1
  84. lfx/components/{data → files_and_knowledge}/file.py +246 -18
  85. lfx/components/{knowledge_bases → files_and_knowledge}/retrieval.py +2 -2
  86. lfx/components/{data → files_and_knowledge}/save_file.py +142 -22
  87. lfx/components/flow_controls/__init__.py +58 -0
  88. lfx/components/{logic → flow_controls}/conditional_router.py +1 -1
  89. lfx/components/{logic → flow_controls}/loop.py +43 -9
  90. lfx/components/flow_controls/run_flow.py +108 -0
  91. lfx/components/glean/glean_search_api.py +1 -1
  92. lfx/components/groq/groq.py +35 -28
  93. lfx/components/helpers/__init__.py +102 -0
  94. lfx/components/input_output/__init__.py +3 -1
  95. lfx/components/input_output/chat.py +4 -3
  96. lfx/components/input_output/chat_output.py +4 -4
  97. lfx/components/input_output/text.py +1 -1
  98. lfx/components/input_output/text_output.py +1 -1
  99. lfx/components/{data → input_output}/webhook.py +1 -1
  100. lfx/components/knowledge_bases/__init__.py +59 -4
  101. lfx/components/langchain_utilities/character.py +1 -1
  102. lfx/components/langchain_utilities/csv_agent.py +84 -16
  103. lfx/components/langchain_utilities/json_agent.py +67 -12
  104. lfx/components/langchain_utilities/language_recursive.py +1 -1
  105. lfx/components/llm_operations/__init__.py +46 -0
  106. lfx/components/{processing → llm_operations}/batch_run.py +1 -1
  107. lfx/components/{processing → llm_operations}/lambda_filter.py +1 -1
  108. lfx/components/{logic → llm_operations}/llm_conditional_router.py +1 -1
  109. lfx/components/{processing/llm_router.py → llm_operations/llm_selector.py} +3 -3
  110. lfx/components/{processing → llm_operations}/structured_output.py +1 -1
  111. lfx/components/logic/__init__.py +126 -0
  112. lfx/components/mem0/mem0_chat_memory.py +11 -0
  113. lfx/components/models/__init__.py +64 -9
  114. lfx/components/models_and_agents/__init__.py +49 -0
  115. lfx/components/{agents → models_and_agents}/agent.py +2 -2
  116. lfx/components/models_and_agents/embedding_model.py +423 -0
  117. lfx/components/models_and_agents/language_model.py +398 -0
  118. lfx/components/{agents → models_and_agents}/mcp_component.py +53 -44
  119. lfx/components/{helpers → models_and_agents}/memory.py +1 -1
  120. lfx/components/nvidia/system_assist.py +1 -1
  121. lfx/components/olivya/olivya.py +1 -1
  122. lfx/components/ollama/ollama.py +17 -3
  123. lfx/components/processing/__init__.py +9 -57
  124. lfx/components/processing/converter.py +1 -1
  125. lfx/components/processing/dataframe_operations.py +1 -1
  126. lfx/components/processing/parse_json_data.py +2 -2
  127. lfx/components/processing/parser.py +1 -1
  128. lfx/components/processing/split_text.py +1 -1
  129. lfx/components/qdrant/qdrant.py +1 -1
  130. lfx/components/redis/redis.py +1 -1
  131. lfx/components/twelvelabs/split_video.py +10 -0
  132. lfx/components/twelvelabs/video_file.py +12 -0
  133. lfx/components/utilities/__init__.py +43 -0
  134. lfx/components/{helpers → utilities}/calculator_core.py +1 -1
  135. lfx/components/{helpers → utilities}/current_date.py +1 -1
  136. lfx/components/{processing → utilities}/python_repl_core.py +1 -1
  137. lfx/components/vectorstores/local_db.py +9 -0
  138. lfx/components/youtube/youtube_transcripts.py +118 -30
  139. lfx/custom/custom_component/component.py +57 -1
  140. lfx/custom/custom_component/custom_component.py +68 -6
  141. lfx/graph/edge/base.py +43 -20
  142. lfx/graph/graph/base.py +4 -1
  143. lfx/graph/state/model.py +15 -2
  144. lfx/graph/utils.py +6 -0
  145. lfx/graph/vertex/base.py +4 -1
  146. lfx/graph/vertex/param_handler.py +10 -7
  147. lfx/helpers/__init__.py +12 -0
  148. lfx/helpers/flow.py +117 -0
  149. lfx/inputs/input_mixin.py +24 -1
  150. lfx/inputs/inputs.py +13 -1
  151. lfx/interface/components.py +161 -83
  152. lfx/log/logger.py +5 -3
  153. lfx/services/database/__init__.py +5 -0
  154. lfx/services/database/service.py +25 -0
  155. lfx/services/deps.py +87 -22
  156. lfx/services/manager.py +19 -6
  157. lfx/services/mcp_composer/service.py +998 -157
  158. lfx/services/session.py +5 -0
  159. lfx/services/settings/base.py +51 -7
  160. lfx/services/settings/constants.py +8 -0
  161. lfx/services/storage/local.py +76 -46
  162. lfx/services/storage/service.py +152 -29
  163. lfx/template/field/base.py +3 -0
  164. lfx/utils/ssrf_protection.py +384 -0
  165. lfx/utils/validate_cloud.py +26 -0
  166. {lfx_nightly-0.2.0.dev0.dist-info → lfx_nightly-0.2.0.dev26.dist-info}/METADATA +38 -22
  167. {lfx_nightly-0.2.0.dev0.dist-info → lfx_nightly-0.2.0.dev26.dist-info}/RECORD +182 -150
  168. {lfx_nightly-0.2.0.dev0.dist-info → lfx_nightly-0.2.0.dev26.dist-info}/WHEEL +1 -1
  169. lfx/components/agents/altk_agent.py +0 -366
  170. lfx/components/agents/cuga_agent.py +0 -1013
  171. lfx/components/docling/docling_remote_vlm.py +0 -284
  172. lfx/components/logic/run_flow.py +0 -71
  173. lfx/components/models/embedding_model.py +0 -195
  174. lfx/components/models/language_model.py +0 -144
  175. /lfx/components/{data → data_source}/mock_data.py +0 -0
  176. /lfx/components/{knowledge_bases → files_and_knowledge}/ingestion.py +0 -0
  177. /lfx/components/{logic → flow_controls}/data_conditional_router.py +0 -0
  178. /lfx/components/{logic → flow_controls}/flow_tool.py +0 -0
  179. /lfx/components/{logic → flow_controls}/listen.py +0 -0
  180. /lfx/components/{logic → flow_controls}/notify.py +0 -0
  181. /lfx/components/{logic → flow_controls}/pass_message.py +0 -0
  182. /lfx/components/{logic → flow_controls}/sub_flow.py +0 -0
  183. /lfx/components/{processing → models_and_agents}/prompt.py +0 -0
  184. /lfx/components/{helpers → processing}/create_list.py +0 -0
  185. /lfx/components/{helpers → processing}/output_parser.py +0 -0
  186. /lfx/components/{helpers → processing}/store_message.py +0 -0
  187. /lfx/components/{helpers → utilities}/id_generator.py +0 -0
  188. {lfx_nightly-0.2.0.dev0.dist-info → lfx_nightly-0.2.0.dev26.dist-info}/entry_points.txt +0 -0
lfx/base/agents/agent.py CHANGED
@@ -165,7 +165,19 @@ class LCAgentComponent(Component):
165
165
  lc_message = None
166
166
  if isinstance(self.input_value, Message):
167
167
  lc_message = self.input_value.to_lc_message()
168
- input_dict: dict[str, str | list[BaseMessage] | BaseMessage] = {"input": lc_message}
168
+ # Extract text content from the LangChain message for agent input
169
+ # Agents expect a string input, not a Message object
170
+ if hasattr(lc_message, "content"):
171
+ if isinstance(lc_message.content, str):
172
+ input_dict: dict[str, str | list[BaseMessage] | BaseMessage] = {"input": lc_message.content}
173
+ elif isinstance(lc_message.content, list):
174
+ # For multimodal content, extract text parts
175
+ text_parts = [item.get("text", "") for item in lc_message.content if item.get("type") == "text"]
176
+ input_dict = {"input": " ".join(text_parts) if text_parts else ""}
177
+ else:
178
+ input_dict = {"input": str(lc_message.content)}
179
+ else:
180
+ input_dict = {"input": str(lc_message)}
169
181
  else:
170
182
  input_dict = {"input": self.input_value}
171
183
 
@@ -0,0 +1,380 @@
1
+ """Reusable base classes for ALTK agent components and tool wrappers.
2
+
3
+ This module abstracts common orchestration so concrete components can focus
4
+ on user-facing configuration and small customizations.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import uuid
10
+ from abc import ABC, abstractmethod
11
+ from typing import TYPE_CHECKING, Any, cast
12
+
13
+ from altk.core.llm import get_llm
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.components.models_and_agents import AgentComponent
27
+ from lfx.log.logger import logger
28
+ from lfx.memory import delete_message
29
+ from lfx.schema.content_block import ContentBlock
30
+ from lfx.schema.data import Data
31
+
32
+ if TYPE_CHECKING:
33
+ from collections.abc import Sequence
34
+
35
+ from lfx.schema.log import SendMessageFunctionType
36
+
37
+ from lfx.schema.message import Message
38
+ from lfx.utils.constants import MESSAGE_SENDER_AI
39
+
40
+
41
+ def normalize_message_content(message: BaseMessage) -> str:
42
+ """Normalize message content to handle inconsistent formats from Data.to_lc_message().
43
+
44
+ Args:
45
+ message: A BaseMessage that may have content as either:
46
+ - str (for AI messages)
47
+ - list[dict] (for User messages in format [{"type": "text", "text": "..."}])
48
+
49
+ Returns:
50
+ str: The extracted text content
51
+
52
+ Note:
53
+ This addresses the inconsistency in lfx.schema.data.Data.to_lc_message() where:
54
+ - User messages: content = [{"type": "text", "text": text}] (list format)
55
+ - AI messages: content = text (string format)
56
+ """
57
+ content = message.content
58
+
59
+ # Handle string format (AI messages)
60
+ if isinstance(content, str):
61
+ return content
62
+
63
+ # Handle list format (User messages)
64
+ if isinstance(content, list) and len(content) > 0:
65
+ # Extract text from first content block that has 'text' field
66
+ for item in content:
67
+ if isinstance(item, dict) and item.get("type") == "text" and "text" in item:
68
+ return item["text"]
69
+ # If no text found, return empty string (e.g., image-only messages)
70
+ return ""
71
+
72
+ # Handle empty list or other formats
73
+ if isinstance(content, list):
74
+ return ""
75
+
76
+ # Fallback for any other format
77
+ return str(content)
78
+
79
+
80
+ # === Base Tool Wrapper Architecture ===
81
+
82
+
83
+ class BaseToolWrapper(ABC):
84
+ """Base class for all tool wrappers in the pipeline.
85
+
86
+ Tool wrappers can enhance tools by adding pre-execution validation,
87
+ post-execution processing, or other capabilities.
88
+ """
89
+
90
+ @abstractmethod
91
+ def wrap_tool(self, tool: BaseTool, **kwargs) -> BaseTool:
92
+ """Wrap a tool with enhanced functionality."""
93
+
94
+ def initialize(self, **_kwargs) -> bool: # pragma: no cover - trivial
95
+ """Initialize any resources needed by the wrapper."""
96
+ return True
97
+
98
+ @property
99
+ def is_available(self) -> bool: # pragma: no cover - trivial
100
+ """Check if the wrapper is available for use."""
101
+ return True
102
+
103
+
104
+ class ALTKBaseTool(BaseTool):
105
+ """Base class for tools that need agent interaction and ALTK LLM access.
106
+
107
+ Provides common functionality for tool execution and ALTK LLM object creation.
108
+ """
109
+
110
+ name: str = Field(...)
111
+ description: str = Field(...)
112
+ wrapped_tool: BaseTool = Field(...)
113
+ agent: Runnable | BaseSingleActionAgent | BaseMultiActionAgent | AgentExecutor = Field(...)
114
+
115
+ def _run(self, *args, **kwargs) -> str:
116
+ """Abstract method implementation that uses the wrapped tool execution."""
117
+ return self._execute_tool(*args, **kwargs)
118
+
119
+ def _execute_tool(self, *args, **kwargs) -> str:
120
+ """Execute the wrapped tool with compatibility across LC versions."""
121
+ # BaseTool.run() expects tool_input as first argument
122
+ if args:
123
+ # Use first arg as tool_input, pass remaining args
124
+ tool_input = args[0]
125
+ return self.wrapped_tool.run(tool_input, *args[1:])
126
+ if kwargs:
127
+ # Use kwargs dict as tool_input
128
+ return self.wrapped_tool.run(kwargs)
129
+ # No arguments - pass empty dict as tool_input
130
+ return self.wrapped_tool.run({})
131
+
132
+ def _get_altk_llm_object(self, *, use_output_val: bool = True) -> Any:
133
+ """Extract the underlying LLM and map it to an ALTK client object."""
134
+ llm_object: BaseChatModel | None = None
135
+ steps = getattr(self.agent, "steps", None)
136
+ if steps:
137
+ for step in steps:
138
+ if isinstance(step, RunnableBinding) and isinstance(step.bound, BaseChatModel):
139
+ llm_object = step.bound
140
+ break
141
+
142
+ if isinstance(llm_object, ChatAnthropic):
143
+ model_name = f"anthropic/{llm_object.model}"
144
+ api_key = llm_object.anthropic_api_key.get_secret_value()
145
+ llm_client_type = "litellm.output_val" if use_output_val else "litellm"
146
+ llm_client = get_llm(llm_client_type)
147
+ llm_client_obj = llm_client(model_name=model_name, api_key=api_key)
148
+ elif isinstance(llm_object, ChatOpenAI):
149
+ model_name = llm_object.model_name
150
+ api_key = llm_object.openai_api_key.get_secret_value()
151
+ llm_client_type = "openai.sync.output_val" if use_output_val else "openai.sync"
152
+ llm_client = get_llm(llm_client_type)
153
+ llm_client_obj = llm_client(model=model_name, api_key=api_key)
154
+ else:
155
+ logger.info("ALTK currently only supports OpenAI and Anthropic models through Langflow.")
156
+ llm_client_obj = None
157
+
158
+ return llm_client_obj
159
+
160
+
161
+ class ToolPipelineManager:
162
+ """Manages a sequence of tool wrappers and applies them to tools."""
163
+
164
+ def __init__(self):
165
+ self.wrappers: list[BaseToolWrapper] = []
166
+
167
+ def clear(self) -> None:
168
+ self.wrappers.clear()
169
+
170
+ def add_wrapper(self, wrapper: BaseToolWrapper) -> None:
171
+ self.wrappers.append(wrapper)
172
+
173
+ def configure_wrappers(self, wrappers: list[BaseToolWrapper]) -> None:
174
+ """Replace current wrappers with new configuration."""
175
+ self.clear()
176
+ for wrapper in wrappers:
177
+ self.add_wrapper(wrapper)
178
+
179
+ def process_tools(self, tools: list[BaseTool], **kwargs) -> list[BaseTool]:
180
+ return [self._apply_wrappers_to_tool(tool, **kwargs) for tool in tools]
181
+
182
+ def _apply_wrappers_to_tool(self, tool: BaseTool, **kwargs) -> BaseTool:
183
+ wrapped_tool = tool
184
+ for wrapper in reversed(self.wrappers):
185
+ if wrapper.is_available:
186
+ wrapped_tool = wrapper.wrap_tool(wrapped_tool, **kwargs)
187
+ return wrapped_tool
188
+
189
+
190
+ # === Base Agent Component Orchestration ===
191
+
192
+
193
+ class ALTKBaseAgentComponent(AgentComponent):
194
+ """Base agent component that centralizes orchestration and hooks.
195
+
196
+ Subclasses should override `get_tool_wrappers` to provide their wrappers
197
+ and can customize context building if needed.
198
+ """
199
+
200
+ def __init__(self, **kwargs):
201
+ super().__init__(**kwargs)
202
+ self.pipeline_manager = ToolPipelineManager()
203
+
204
+ # ---- Hooks for subclasses ----
205
+ def configure_tool_pipeline(self) -> None:
206
+ """Configure the tool pipeline with wrappers. Subclasses override this."""
207
+ # Default: no wrappers
208
+ self.pipeline_manager.clear()
209
+
210
+ def build_conversation_context(self) -> list[BaseMessage]:
211
+ """Create conversation context from input and chat history."""
212
+ context: list[BaseMessage] = []
213
+
214
+ # Add chat history to maintain chronological order
215
+ if hasattr(self, "chat_history") and self.chat_history:
216
+ if isinstance(self.chat_history, Data):
217
+ context.append(self.chat_history.to_lc_message())
218
+ elif isinstance(self.chat_history, list):
219
+ if all(isinstance(m, Message) for m in self.chat_history):
220
+ context.extend([m.to_lc_message() for m in self.chat_history])
221
+ else:
222
+ # Assume list of Data objects, let data_to_messages handle validation
223
+ try:
224
+ context.extend(data_to_messages(self.chat_history))
225
+ except (AttributeError, TypeError) as e:
226
+ error_message = f"Invalid chat_history list contents: {e}"
227
+ raise ValueError(error_message) from e
228
+ else:
229
+ # Reject all other types (strings, numbers, etc.)
230
+ type_name = type(self.chat_history).__name__
231
+ error_message = (
232
+ f"chat_history must be a Data object, list of Data/Message objects, or None. Got: {type_name}"
233
+ )
234
+ raise ValueError(error_message)
235
+
236
+ # Then add current input to maintain chronological order
237
+ if hasattr(self, "input_value") and self.input_value:
238
+ if isinstance(self.input_value, Message):
239
+ context.append(self.input_value.to_lc_message())
240
+ else:
241
+ context.append(HumanMessage(content=str(self.input_value)))
242
+
243
+ return context
244
+
245
+ def get_user_query(self) -> str:
246
+ if hasattr(self.input_value, "get_text") and callable(self.input_value.get_text):
247
+ return self.input_value.get_text()
248
+ return str(self.input_value)
249
+
250
+ # ---- Internal helpers reused by run/update ----
251
+ def _initialize_tool_pipeline(self) -> None:
252
+ """Initialize the tool pipeline by calling the subclass configuration."""
253
+ self.configure_tool_pipeline()
254
+
255
+ def update_runnable_instance(
256
+ self, agent: AgentExecutor, runnable: AgentExecutor, tools: Sequence[BaseTool]
257
+ ) -> AgentExecutor:
258
+ """Update the runnable instance with processed tools.
259
+
260
+ Subclasses can override this method to customize tool processing.
261
+ The default implementation applies the tool wrapper pipeline.
262
+ """
263
+ user_query = self.get_user_query()
264
+ conversation_context = self.build_conversation_context()
265
+
266
+ self._initialize_tool_pipeline()
267
+ processed_tools = self.pipeline_manager.process_tools(
268
+ list(tools or []),
269
+ agent=agent,
270
+ user_query=user_query,
271
+ conversation_context=conversation_context,
272
+ )
273
+
274
+ runnable.tools = processed_tools
275
+ return runnable
276
+
277
+ async def run_agent(
278
+ self,
279
+ agent: Runnable | BaseSingleActionAgent | BaseMultiActionAgent | AgentExecutor,
280
+ ) -> Message:
281
+ if isinstance(agent, AgentExecutor):
282
+ runnable = agent
283
+ else:
284
+ # note the tools are not required to run the agent, hence the validation removed.
285
+ handle_parsing_errors = hasattr(self, "handle_parsing_errors") and self.handle_parsing_errors
286
+ verbose = hasattr(self, "verbose") and self.verbose
287
+ max_iterations = hasattr(self, "max_iterations") and self.max_iterations
288
+ runnable = AgentExecutor.from_agent_and_tools(
289
+ agent=agent,
290
+ tools=self.tools or [],
291
+ handle_parsing_errors=handle_parsing_errors,
292
+ verbose=verbose,
293
+ max_iterations=max_iterations,
294
+ )
295
+ runnable = self.update_runnable_instance(agent, runnable, self.tools)
296
+
297
+ # Convert input_value to proper format for agent
298
+ if hasattr(self.input_value, "to_lc_message") and callable(self.input_value.to_lc_message):
299
+ lc_message = self.input_value.to_lc_message()
300
+ input_text = lc_message.content if hasattr(lc_message, "content") else str(lc_message)
301
+ else:
302
+ lc_message = None
303
+ input_text = self.input_value
304
+
305
+ input_dict: dict[str, str | list[BaseMessage]] = {}
306
+ if hasattr(self, "system_prompt"):
307
+ input_dict["system_prompt"] = self.system_prompt
308
+ if hasattr(self, "chat_history") and self.chat_history:
309
+ if (
310
+ hasattr(self.chat_history, "to_data")
311
+ and callable(self.chat_history.to_data)
312
+ and self.chat_history.__class__.__name__ == "Data"
313
+ ):
314
+ input_dict["chat_history"] = data_to_messages(self.chat_history)
315
+ # Handle both lfx.schema.message.Message and langflow.schema.message.Message types
316
+ if all(hasattr(m, "to_data") and callable(m.to_data) and "text" in m.data for m in self.chat_history):
317
+ input_dict["chat_history"] = data_to_messages(self.chat_history)
318
+ if all(isinstance(m, Message) for m in self.chat_history):
319
+ input_dict["chat_history"] = data_to_messages([m.to_data() for m in self.chat_history])
320
+ if hasattr(lc_message, "content") and isinstance(lc_message.content, list):
321
+ # ! Because the input has to be a string, we must pass the images in the chat_history
322
+
323
+ image_dicts = [item for item in lc_message.content if item.get("type") == "image"]
324
+ lc_message.content = [item for item in lc_message.content if item.get("type") != "image"]
325
+
326
+ if "chat_history" not in input_dict:
327
+ input_dict["chat_history"] = []
328
+ if isinstance(input_dict["chat_history"], list):
329
+ input_dict["chat_history"].extend(HumanMessage(content=[image_dict]) for image_dict in image_dicts)
330
+ else:
331
+ input_dict["chat_history"] = [HumanMessage(content=[image_dict]) for image_dict in image_dicts]
332
+ input_dict["input"] = input_text
333
+ if hasattr(self, "graph"):
334
+ session_id = self.graph.session_id
335
+ elif hasattr(self, "_session_id"):
336
+ session_id = self._session_id
337
+ else:
338
+ session_id = None
339
+
340
+ try:
341
+ sender_name = get_chat_output_sender_name(self)
342
+ except AttributeError:
343
+ sender_name = self.display_name or "AI"
344
+
345
+ agent_message = Message(
346
+ sender=MESSAGE_SENDER_AI,
347
+ sender_name=sender_name,
348
+ properties={"icon": "Bot", "state": "partial"},
349
+ content_blocks=[ContentBlock(title="Agent Steps", contents=[])],
350
+ session_id=session_id or uuid.uuid4(),
351
+ )
352
+ try:
353
+ result = await process_agent_events(
354
+ runnable.astream_events(
355
+ input_dict,
356
+ config={
357
+ "callbacks": [
358
+ AgentAsyncHandler(self.log),
359
+ *self.get_langchain_callbacks(),
360
+ ]
361
+ },
362
+ version="v2",
363
+ ),
364
+ agent_message,
365
+ cast("SendMessageFunctionType", self.send_message),
366
+ )
367
+ except ExceptionWithMessageError as e:
368
+ if hasattr(e, "agent_message") and hasattr(e.agent_message, "id"):
369
+ msg_id = e.agent_message.id
370
+ await delete_message(id_=msg_id)
371
+ await self._send_message_event(e.agent_message, category="remove_message")
372
+ logger.error(f"ExceptionWithMessageError: {e}")
373
+ raise
374
+ except Exception as e:
375
+ # Log or handle any other exceptions
376
+ logger.error(f"Error: {e}")
377
+ raise
378
+
379
+ self.status = result
380
+ return result