lfx-nightly 0.2.0.dev0__py3-none-any.whl → 0.2.0.dev41__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lfx/_assets/component_index.json +1 -1
- lfx/base/agents/agent.py +21 -4
- lfx/base/agents/altk_base_agent.py +393 -0
- lfx/base/agents/altk_tool_wrappers.py +565 -0
- lfx/base/agents/events.py +2 -1
- lfx/base/composio/composio_base.py +159 -224
- lfx/base/data/base_file.py +97 -20
- lfx/base/data/docling_utils.py +61 -10
- lfx/base/data/storage_utils.py +301 -0
- lfx/base/data/utils.py +178 -14
- lfx/base/mcp/util.py +2 -2
- lfx/base/models/anthropic_constants.py +21 -12
- lfx/base/models/groq_constants.py +74 -58
- lfx/base/models/groq_model_discovery.py +265 -0
- lfx/base/models/model.py +1 -1
- lfx/base/models/model_utils.py +100 -0
- lfx/base/models/openai_constants.py +7 -0
- lfx/base/models/watsonx_constants.py +32 -8
- lfx/base/tools/run_flow.py +601 -129
- lfx/cli/commands.py +9 -4
- lfx/cli/common.py +2 -2
- lfx/cli/run.py +1 -1
- lfx/cli/script_loader.py +53 -11
- lfx/components/Notion/create_page.py +1 -1
- lfx/components/Notion/list_database_properties.py +1 -1
- lfx/components/Notion/list_pages.py +1 -1
- lfx/components/Notion/list_users.py +1 -1
- lfx/components/Notion/page_content_viewer.py +1 -1
- lfx/components/Notion/search.py +1 -1
- lfx/components/Notion/update_page_property.py +1 -1
- lfx/components/__init__.py +19 -5
- lfx/components/{agents → altk}/__init__.py +5 -9
- lfx/components/altk/altk_agent.py +193 -0
- lfx/components/apify/apify_actor.py +1 -1
- lfx/components/composio/__init__.py +70 -18
- lfx/components/composio/apollo_composio.py +11 -0
- lfx/components/composio/bitbucket_composio.py +11 -0
- lfx/components/composio/canva_composio.py +11 -0
- lfx/components/composio/coda_composio.py +11 -0
- lfx/components/composio/composio_api.py +10 -0
- lfx/components/composio/discord_composio.py +1 -1
- lfx/components/composio/elevenlabs_composio.py +11 -0
- lfx/components/composio/exa_composio.py +11 -0
- lfx/components/composio/firecrawl_composio.py +11 -0
- lfx/components/composio/fireflies_composio.py +11 -0
- lfx/components/composio/gmail_composio.py +1 -1
- lfx/components/composio/googlebigquery_composio.py +11 -0
- lfx/components/composio/googlecalendar_composio.py +1 -1
- lfx/components/composio/googledocs_composio.py +1 -1
- lfx/components/composio/googlemeet_composio.py +1 -1
- lfx/components/composio/googlesheets_composio.py +1 -1
- lfx/components/composio/googletasks_composio.py +1 -1
- lfx/components/composio/heygen_composio.py +11 -0
- lfx/components/composio/mem0_composio.py +11 -0
- lfx/components/composio/peopledatalabs_composio.py +11 -0
- lfx/components/composio/perplexityai_composio.py +11 -0
- lfx/components/composio/serpapi_composio.py +11 -0
- lfx/components/composio/slack_composio.py +3 -574
- lfx/components/composio/slackbot_composio.py +1 -1
- lfx/components/composio/snowflake_composio.py +11 -0
- lfx/components/composio/tavily_composio.py +11 -0
- lfx/components/composio/youtube_composio.py +2 -2
- lfx/components/cuga/__init__.py +34 -0
- lfx/components/cuga/cuga_agent.py +730 -0
- lfx/components/data/__init__.py +78 -28
- lfx/components/data_source/__init__.py +58 -0
- lfx/components/{data → data_source}/api_request.py +26 -3
- lfx/components/{data → data_source}/csv_to_data.py +15 -10
- lfx/components/{data → data_source}/json_to_data.py +15 -8
- lfx/components/{data → data_source}/news_search.py +1 -1
- lfx/components/{data → data_source}/rss.py +1 -1
- lfx/components/{data → data_source}/sql_executor.py +1 -1
- lfx/components/{data → data_source}/url.py +1 -1
- lfx/components/{data → data_source}/web_search.py +1 -1
- lfx/components/datastax/astradb_cql.py +1 -1
- lfx/components/datastax/astradb_graph.py +1 -1
- lfx/components/datastax/astradb_tool.py +1 -1
- lfx/components/datastax/astradb_vectorstore.py +1 -1
- lfx/components/datastax/hcd.py +1 -1
- lfx/components/deactivated/json_document_builder.py +1 -1
- lfx/components/docling/__init__.py +0 -3
- lfx/components/docling/chunk_docling_document.py +3 -1
- lfx/components/docling/export_docling_document.py +3 -1
- lfx/components/elastic/elasticsearch.py +1 -1
- lfx/components/files_and_knowledge/__init__.py +47 -0
- lfx/components/{data → files_and_knowledge}/directory.py +1 -1
- lfx/components/{data → files_and_knowledge}/file.py +304 -24
- lfx/components/{knowledge_bases → files_and_knowledge}/retrieval.py +2 -2
- lfx/components/{data → files_and_knowledge}/save_file.py +218 -31
- lfx/components/flow_controls/__init__.py +58 -0
- lfx/components/{logic → flow_controls}/conditional_router.py +1 -1
- lfx/components/{logic → flow_controls}/loop.py +43 -9
- lfx/components/flow_controls/run_flow.py +108 -0
- lfx/components/glean/glean_search_api.py +1 -1
- lfx/components/groq/groq.py +35 -28
- lfx/components/helpers/__init__.py +102 -0
- lfx/components/ibm/watsonx.py +7 -1
- lfx/components/input_output/__init__.py +3 -1
- lfx/components/input_output/chat.py +4 -3
- lfx/components/input_output/chat_output.py +10 -4
- lfx/components/input_output/text.py +1 -1
- lfx/components/input_output/text_output.py +1 -1
- lfx/components/{data → input_output}/webhook.py +1 -1
- lfx/components/knowledge_bases/__init__.py +59 -4
- lfx/components/langchain_utilities/character.py +1 -1
- lfx/components/langchain_utilities/csv_agent.py +84 -16
- lfx/components/langchain_utilities/json_agent.py +67 -12
- lfx/components/langchain_utilities/language_recursive.py +1 -1
- lfx/components/llm_operations/__init__.py +46 -0
- lfx/components/{processing → llm_operations}/batch_run.py +17 -8
- lfx/components/{processing → llm_operations}/lambda_filter.py +1 -1
- lfx/components/{logic → llm_operations}/llm_conditional_router.py +1 -1
- lfx/components/{processing/llm_router.py → llm_operations/llm_selector.py} +3 -3
- lfx/components/{processing → llm_operations}/structured_output.py +1 -1
- lfx/components/logic/__init__.py +126 -0
- lfx/components/mem0/mem0_chat_memory.py +11 -0
- lfx/components/models/__init__.py +64 -9
- lfx/components/models_and_agents/__init__.py +49 -0
- lfx/components/{agents → models_and_agents}/agent.py +6 -4
- lfx/components/models_and_agents/embedding_model.py +353 -0
- lfx/components/models_and_agents/language_model.py +398 -0
- lfx/components/{agents → models_and_agents}/mcp_component.py +53 -44
- lfx/components/{helpers → models_and_agents}/memory.py +1 -1
- lfx/components/nvidia/system_assist.py +1 -1
- lfx/components/olivya/olivya.py +1 -1
- lfx/components/ollama/ollama.py +24 -5
- lfx/components/processing/__init__.py +9 -60
- lfx/components/processing/converter.py +1 -1
- lfx/components/processing/dataframe_operations.py +1 -1
- lfx/components/processing/parse_json_data.py +2 -2
- lfx/components/processing/parser.py +1 -1
- lfx/components/processing/split_text.py +1 -1
- lfx/components/qdrant/qdrant.py +1 -1
- lfx/components/redis/redis.py +1 -1
- lfx/components/twelvelabs/split_video.py +10 -0
- lfx/components/twelvelabs/video_file.py +12 -0
- lfx/components/utilities/__init__.py +43 -0
- lfx/components/{helpers → utilities}/calculator_core.py +1 -1
- lfx/components/{helpers → utilities}/current_date.py +1 -1
- lfx/components/{processing → utilities}/python_repl_core.py +1 -1
- lfx/components/vectorstores/local_db.py +9 -0
- lfx/components/youtube/youtube_transcripts.py +118 -30
- lfx/custom/custom_component/component.py +57 -1
- lfx/custom/custom_component/custom_component.py +68 -6
- lfx/custom/directory_reader/directory_reader.py +5 -2
- lfx/graph/edge/base.py +43 -20
- lfx/graph/state/model.py +15 -2
- lfx/graph/utils.py +6 -0
- lfx/graph/vertex/param_handler.py +10 -7
- lfx/helpers/__init__.py +12 -0
- lfx/helpers/flow.py +117 -0
- lfx/inputs/input_mixin.py +24 -1
- lfx/inputs/inputs.py +13 -1
- lfx/interface/components.py +161 -83
- lfx/log/logger.py +5 -3
- lfx/schema/image.py +2 -12
- lfx/services/database/__init__.py +5 -0
- lfx/services/database/service.py +25 -0
- lfx/services/deps.py +87 -22
- lfx/services/interfaces.py +5 -0
- lfx/services/manager.py +24 -10
- lfx/services/mcp_composer/service.py +1029 -162
- lfx/services/session.py +5 -0
- lfx/services/settings/auth.py +18 -11
- lfx/services/settings/base.py +56 -30
- lfx/services/settings/constants.py +8 -0
- lfx/services/storage/local.py +108 -46
- lfx/services/storage/service.py +171 -29
- lfx/template/field/base.py +3 -0
- lfx/utils/image.py +29 -11
- lfx/utils/ssrf_protection.py +384 -0
- lfx/utils/validate_cloud.py +26 -0
- {lfx_nightly-0.2.0.dev0.dist-info → lfx_nightly-0.2.0.dev41.dist-info}/METADATA +38 -22
- {lfx_nightly-0.2.0.dev0.dist-info → lfx_nightly-0.2.0.dev41.dist-info}/RECORD +189 -160
- {lfx_nightly-0.2.0.dev0.dist-info → lfx_nightly-0.2.0.dev41.dist-info}/WHEEL +1 -1
- lfx/components/agents/altk_agent.py +0 -366
- lfx/components/agents/cuga_agent.py +0 -1013
- lfx/components/docling/docling_remote_vlm.py +0 -284
- lfx/components/logic/run_flow.py +0 -71
- lfx/components/models/embedding_model.py +0 -195
- lfx/components/models/language_model.py +0 -144
- lfx/components/processing/dataframe_to_toolset.py +0 -259
- /lfx/components/{data → data_source}/mock_data.py +0 -0
- /lfx/components/{knowledge_bases → files_and_knowledge}/ingestion.py +0 -0
- /lfx/components/{logic → flow_controls}/data_conditional_router.py +0 -0
- /lfx/components/{logic → flow_controls}/flow_tool.py +0 -0
- /lfx/components/{logic → flow_controls}/listen.py +0 -0
- /lfx/components/{logic → flow_controls}/notify.py +0 -0
- /lfx/components/{logic → flow_controls}/pass_message.py +0 -0
- /lfx/components/{logic → flow_controls}/sub_flow.py +0 -0
- /lfx/components/{processing → models_and_agents}/prompt.py +0 -0
- /lfx/components/{helpers → processing}/create_list.py +0 -0
- /lfx/components/{helpers → processing}/output_parser.py +0 -0
- /lfx/components/{helpers → processing}/store_message.py +0 -0
- /lfx/components/{helpers → utilities}/id_generator.py +0 -0
- {lfx_nightly-0.2.0.dev0.dist-info → lfx_nightly-0.2.0.dev41.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,730 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import traceback
|
|
4
|
+
import uuid
|
|
5
|
+
from collections.abc import AsyncIterator
|
|
6
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
7
|
+
|
|
8
|
+
from langchain_core.agents import AgentFinish
|
|
9
|
+
from langchain_core.messages import AIMessage, HumanMessage
|
|
10
|
+
from langchain_core.tools import StructuredTool
|
|
11
|
+
|
|
12
|
+
from lfx.base.agents.agent import LCToolsAgentComponent
|
|
13
|
+
from lfx.base.models.model_input_constants import (
|
|
14
|
+
ALL_PROVIDER_FIELDS,
|
|
15
|
+
MODEL_DYNAMIC_UPDATE_FIELDS,
|
|
16
|
+
MODEL_PROVIDERS,
|
|
17
|
+
MODEL_PROVIDERS_DICT,
|
|
18
|
+
MODELS_METADATA,
|
|
19
|
+
)
|
|
20
|
+
from lfx.base.models.model_utils import get_model_name
|
|
21
|
+
from lfx.components.helpers import CurrentDateComponent
|
|
22
|
+
from lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent
|
|
23
|
+
from lfx.components.models_and_agents.memory import MemoryComponent
|
|
24
|
+
from lfx.custom.custom_component.component import _get_component_toolkit
|
|
25
|
+
from lfx.custom.utils import update_component_build_config
|
|
26
|
+
from lfx.field_typing import Tool
|
|
27
|
+
from lfx.io import BoolInput, DropdownInput, IntInput, MultilineInput, Output
|
|
28
|
+
from lfx.log.logger import logger
|
|
29
|
+
from lfx.schema.dotdict import dotdict
|
|
30
|
+
from lfx.schema.message import Message
|
|
31
|
+
|
|
32
|
+
if TYPE_CHECKING:
|
|
33
|
+
from lfx.schema.log import SendMessageFunctionType
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def set_advanced_true(component_input):
|
|
37
|
+
"""Set the advanced flag to True for a component input.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
component_input: The component input to modify
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
The modified component input with advanced=True
|
|
44
|
+
"""
|
|
45
|
+
component_input.advanced = True
|
|
46
|
+
return component_input
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
MODEL_PROVIDERS_LIST = ["OpenAI"]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class CugaComponent(ToolCallingAgentComponent):
|
|
53
|
+
"""Cuga Agent Component for advanced AI task execution.
|
|
54
|
+
|
|
55
|
+
The Cuga component is an advanced AI agent that can execute complex tasks using
|
|
56
|
+
various tools and browser automation. It supports custom policies, web applications,
|
|
57
|
+
and API interactions.
|
|
58
|
+
|
|
59
|
+
Attributes:
|
|
60
|
+
display_name: Human-readable name for the component
|
|
61
|
+
description: Brief description of the component's purpose
|
|
62
|
+
documentation: URL to component documentation
|
|
63
|
+
icon: Icon identifier for the UI
|
|
64
|
+
name: Internal component name
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
display_name: str = "Cuga"
|
|
68
|
+
description: str = "Define the Cuga agent's policies, then assign it a task."
|
|
69
|
+
documentation: str = "https://docs.langflow.org/bundles-cuga"
|
|
70
|
+
icon = "bot"
|
|
71
|
+
name = "Cuga"
|
|
72
|
+
|
|
73
|
+
memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]
|
|
74
|
+
|
|
75
|
+
inputs = [
|
|
76
|
+
DropdownInput(
|
|
77
|
+
name="agent_llm",
|
|
78
|
+
display_name="Model Provider",
|
|
79
|
+
info="The provider of the language model that the agent will use to generate responses.",
|
|
80
|
+
options=[*MODEL_PROVIDERS_LIST, "Custom"],
|
|
81
|
+
value="OpenAI",
|
|
82
|
+
real_time_refresh=True,
|
|
83
|
+
input_types=[],
|
|
84
|
+
options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST] + [{"icon": "brain"}],
|
|
85
|
+
),
|
|
86
|
+
*MODEL_PROVIDERS_DICT["OpenAI"]["inputs"],
|
|
87
|
+
MultilineInput(
|
|
88
|
+
name="policies",
|
|
89
|
+
display_name="Policies",
|
|
90
|
+
info=(
|
|
91
|
+
"Custom instructions or policies for the agent to adhere to during its operation.\n"
|
|
92
|
+
"Example:\n"
|
|
93
|
+
"## Plan\n"
|
|
94
|
+
"< planning instructions e.g. which tools and when to use>\n"
|
|
95
|
+
"## Answer\n"
|
|
96
|
+
"< final answer instructions how to answer>"
|
|
97
|
+
),
|
|
98
|
+
value="",
|
|
99
|
+
advanced=False,
|
|
100
|
+
),
|
|
101
|
+
IntInput(
|
|
102
|
+
name="n_messages",
|
|
103
|
+
display_name="Number of Chat History Messages",
|
|
104
|
+
value=100,
|
|
105
|
+
info="Number of chat history messages to retrieve.",
|
|
106
|
+
advanced=True,
|
|
107
|
+
show=True,
|
|
108
|
+
),
|
|
109
|
+
*LCToolsAgentComponent.get_base_inputs(),
|
|
110
|
+
BoolInput(
|
|
111
|
+
name="add_current_date_tool",
|
|
112
|
+
display_name="Current Date",
|
|
113
|
+
advanced=True,
|
|
114
|
+
info="If true, will add a tool to the agent that returns the current date.",
|
|
115
|
+
value=True,
|
|
116
|
+
),
|
|
117
|
+
BoolInput(
|
|
118
|
+
name="lite_mode",
|
|
119
|
+
display_name="Enable CugaLite",
|
|
120
|
+
info="Enable CugaLite for simple API tasks (faster execution).",
|
|
121
|
+
value=True,
|
|
122
|
+
advanced=False,
|
|
123
|
+
),
|
|
124
|
+
IntInput(
|
|
125
|
+
name="lite_mode_tool_threshold",
|
|
126
|
+
display_name="CugaLite Tool Threshold",
|
|
127
|
+
info="Route to CugaLite if app has fewer than this many tools.",
|
|
128
|
+
value=25,
|
|
129
|
+
advanced=False,
|
|
130
|
+
),
|
|
131
|
+
DropdownInput(
|
|
132
|
+
name="decomposition_strategy",
|
|
133
|
+
display_name="Decomposition Strategy",
|
|
134
|
+
info="Strategy for task decomposition: 'flexible' allows multiple subtasks per app,\n"
|
|
135
|
+
" 'exact' enforces one subtask per app.",
|
|
136
|
+
options=["flexible", "exact"],
|
|
137
|
+
value="flexible",
|
|
138
|
+
advanced=True,
|
|
139
|
+
),
|
|
140
|
+
BoolInput(
|
|
141
|
+
name="browser_enabled",
|
|
142
|
+
display_name="Enable Browser",
|
|
143
|
+
info="Toggle to enable a built-in browser tool for web scraping and searching.",
|
|
144
|
+
value=False,
|
|
145
|
+
advanced=False,
|
|
146
|
+
),
|
|
147
|
+
MultilineInput(
|
|
148
|
+
name="web_apps",
|
|
149
|
+
display_name="Web applications",
|
|
150
|
+
info=(
|
|
151
|
+
"Define a list of web applications that cuga will open when enable browser is true. "
|
|
152
|
+
"Currently only supports one web application. Example: https://example.com"
|
|
153
|
+
),
|
|
154
|
+
value="",
|
|
155
|
+
advanced=False,
|
|
156
|
+
),
|
|
157
|
+
]
|
|
158
|
+
outputs = [
|
|
159
|
+
Output(name="response", display_name="Response", method="message_response"),
|
|
160
|
+
]
|
|
161
|
+
|
|
162
|
+
async def call_agent(
|
|
163
|
+
self, current_input: str, tools: list[Tool], history_messages: list[Message], llm
|
|
164
|
+
) -> AsyncIterator[dict[str, Any]]:
|
|
165
|
+
"""Execute the Cuga agent with the given input and tools.
|
|
166
|
+
|
|
167
|
+
This method initializes and runs the Cuga agent, processing the input through
|
|
168
|
+
the agent's workflow and yielding events for real-time monitoring.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
current_input: The user input to process
|
|
172
|
+
tools: List of available tools for the agent
|
|
173
|
+
history_messages: Previous conversation history
|
|
174
|
+
llm: The language model instance to use
|
|
175
|
+
|
|
176
|
+
Yields:
|
|
177
|
+
dict: Agent events including tool usage, thinking, and final results
|
|
178
|
+
|
|
179
|
+
Raises:
|
|
180
|
+
ValueError: If there's an error in agent initialization
|
|
181
|
+
TypeError: If there's a type error in processing
|
|
182
|
+
RuntimeError: If there's a runtime error during execution
|
|
183
|
+
ConnectionError: If there's a connection issue
|
|
184
|
+
"""
|
|
185
|
+
yield {
|
|
186
|
+
"event": "on_chain_start",
|
|
187
|
+
"run_id": str(uuid.uuid4()),
|
|
188
|
+
"name": "CUGA_initializing",
|
|
189
|
+
"data": {"input": {"input": current_input, "chat_history": []}},
|
|
190
|
+
}
|
|
191
|
+
logger.debug(f"[CUGA] LLM MODEL TYPE: {type(llm)}")
|
|
192
|
+
if current_input:
|
|
193
|
+
# Import settings first
|
|
194
|
+
from cuga.config import settings
|
|
195
|
+
|
|
196
|
+
# Use Dynaconf's set() method to update settings dynamically
|
|
197
|
+
# This properly updates the settings object without corruption
|
|
198
|
+
logger.debug("[CUGA] Updating CUGA settings via Dynaconf set() method")
|
|
199
|
+
|
|
200
|
+
settings.advanced_features.registry = False
|
|
201
|
+
settings.advanced_features.lite_mode = self.lite_mode
|
|
202
|
+
settings.advanced_features.lite_mode_tool_threshold = self.lite_mode_tool_threshold
|
|
203
|
+
settings.advanced_features.decomposition_strategy = self.decomposition_strategy
|
|
204
|
+
|
|
205
|
+
if self.browser_enabled:
|
|
206
|
+
logger.debug("[CUGA] browser_enabled is true, setting mode to hybrid")
|
|
207
|
+
settings.advanced_features.mode = "hybrid"
|
|
208
|
+
settings.advanced_features.use_vision = False
|
|
209
|
+
else:
|
|
210
|
+
logger.debug("[CUGA] browser_enabled is false, setting mode to api")
|
|
211
|
+
settings.advanced_features.mode = "api"
|
|
212
|
+
|
|
213
|
+
from cuga.backend.activity_tracker.tracker import ActivityTracker
|
|
214
|
+
from cuga.backend.cuga_graph.nodes.api.variables_manager.manager import VariablesManager
|
|
215
|
+
from cuga.backend.cuga_graph.utils.agent_loop import StreamEvent
|
|
216
|
+
from cuga.backend.cuga_graph.utils.controller import (
|
|
217
|
+
AgentRunner as CugaAgent,
|
|
218
|
+
)
|
|
219
|
+
from cuga.backend.cuga_graph.utils.controller import (
|
|
220
|
+
ExperimentResult as AgentResult,
|
|
221
|
+
)
|
|
222
|
+
from cuga.backend.llm.models import LLMManager
|
|
223
|
+
from cuga.configurations.instructions_manager import InstructionsManager
|
|
224
|
+
|
|
225
|
+
var_manager = VariablesManager()
|
|
226
|
+
|
|
227
|
+
# Reset var_manager if this is the first message in history
|
|
228
|
+
logger.debug(f"[CUGA] Checking history_messages: count={len(history_messages) if history_messages else 0}")
|
|
229
|
+
if not history_messages or len(history_messages) == 0:
|
|
230
|
+
logger.debug("[CUGA] First message in history detected, resetting var_manager")
|
|
231
|
+
var_manager.reset()
|
|
232
|
+
else:
|
|
233
|
+
logger.debug(f"[CUGA] Continuing conversation with {len(history_messages)} previous messages")
|
|
234
|
+
|
|
235
|
+
llm_manager = LLMManager()
|
|
236
|
+
llm_manager.set_llm(llm)
|
|
237
|
+
instructions_manager = InstructionsManager()
|
|
238
|
+
|
|
239
|
+
policies_to_use = self.policies or ""
|
|
240
|
+
logger.debug(f"[CUGA] policies are: {policies_to_use}")
|
|
241
|
+
instructions_manager.set_instructions_from_one_file(policies_to_use)
|
|
242
|
+
tracker = ActivityTracker()
|
|
243
|
+
tracker.set_tools(tools)
|
|
244
|
+
cuga_agent = CugaAgent(browser_enabled=self.browser_enabled)
|
|
245
|
+
if self.browser_enabled:
|
|
246
|
+
await cuga_agent.initialize_freemode_env(start_url=self.web_apps.strip(), interface_mode="browser_only")
|
|
247
|
+
else:
|
|
248
|
+
await cuga_agent.initialize_appworld_env()
|
|
249
|
+
|
|
250
|
+
yield {
|
|
251
|
+
"event": "on_chain_start",
|
|
252
|
+
"run_id": str(uuid.uuid4()),
|
|
253
|
+
"name": "CUGA_thinking...",
|
|
254
|
+
"data": {"input": {"input": current_input, "chat_history": []}},
|
|
255
|
+
}
|
|
256
|
+
logger.debug(f"[CUGA] current web apps are {self.web_apps}")
|
|
257
|
+
logger.debug(f"[CUGA] Processing input: {current_input}")
|
|
258
|
+
try:
|
|
259
|
+
# Convert history to LangChain format for the event
|
|
260
|
+
lc_messages = []
|
|
261
|
+
for msg in history_messages:
|
|
262
|
+
if hasattr(msg, "sender") and msg.sender == "Human":
|
|
263
|
+
lc_messages.append(HumanMessage(content=msg.text))
|
|
264
|
+
else:
|
|
265
|
+
lc_messages.append(AIMessage(content=msg.text))
|
|
266
|
+
|
|
267
|
+
await asyncio.sleep(0.5)
|
|
268
|
+
|
|
269
|
+
# 2. Build final response
|
|
270
|
+
response_parts = []
|
|
271
|
+
|
|
272
|
+
response_parts.append(f"Processed input: '{current_input}'")
|
|
273
|
+
response_parts.append(f"Available tools: {len(tools)}")
|
|
274
|
+
last_event: StreamEvent | None = None
|
|
275
|
+
tool_run_id: str | None = None
|
|
276
|
+
# 3. Chain end event with AgentFinish
|
|
277
|
+
async for event in cuga_agent.run_task_generic_yield(eval_mode=False, goal=current_input):
|
|
278
|
+
logger.debug(f"[CUGA] recieved event {event}")
|
|
279
|
+
if last_event is not None and tool_run_id is not None:
|
|
280
|
+
logger.debug(f"[CUGA] last event {last_event}")
|
|
281
|
+
try:
|
|
282
|
+
# TODO: Extract data
|
|
283
|
+
data_dict = json.loads(last_event.data)
|
|
284
|
+
except json.JSONDecodeError:
|
|
285
|
+
data_dict = last_event.data
|
|
286
|
+
if last_event.name == "CodeAgent":
|
|
287
|
+
data_dict = data_dict["code"]
|
|
288
|
+
yield {
|
|
289
|
+
"event": "on_tool_end",
|
|
290
|
+
"run_id": tool_run_id,
|
|
291
|
+
"name": last_event.name,
|
|
292
|
+
"data": {"output": data_dict},
|
|
293
|
+
}
|
|
294
|
+
if isinstance(event, StreamEvent):
|
|
295
|
+
tool_run_id = str(uuid.uuid4())
|
|
296
|
+
last_event = StreamEvent(name=event.name, data=event.data)
|
|
297
|
+
tool_event = {
|
|
298
|
+
"event": "on_tool_start",
|
|
299
|
+
"run_id": tool_run_id,
|
|
300
|
+
"name": event.name,
|
|
301
|
+
"data": {"input": {}},
|
|
302
|
+
}
|
|
303
|
+
logger.debug(f"[CUGA] Yielding tool_start event: {event.name}")
|
|
304
|
+
yield tool_event
|
|
305
|
+
|
|
306
|
+
if isinstance(event, AgentResult):
|
|
307
|
+
task_result = event
|
|
308
|
+
end_event = {
|
|
309
|
+
"event": "on_chain_end",
|
|
310
|
+
"run_id": str(uuid.uuid4()),
|
|
311
|
+
"name": "CugaAgent",
|
|
312
|
+
"data": {"output": AgentFinish(return_values={"output": task_result.answer}, log="")},
|
|
313
|
+
}
|
|
314
|
+
answer_preview = task_result.answer[:100] if task_result.answer else "None"
|
|
315
|
+
logger.info(f"[CUGA] Yielding chain_end event with answer: {answer_preview}...")
|
|
316
|
+
yield end_event
|
|
317
|
+
|
|
318
|
+
except (ValueError, TypeError, RuntimeError, ConnectionError) as e:
|
|
319
|
+
logger.error(f"[CUGA] An error occurred: {e!s}")
|
|
320
|
+
logger.error(f"[CUGA] Traceback: {traceback.format_exc()}")
|
|
321
|
+
error_msg = f"CUGA Agent error: {e!s}"
|
|
322
|
+
logger.error(f"[CUGA] Error occurred: {error_msg}")
|
|
323
|
+
|
|
324
|
+
# Emit error event
|
|
325
|
+
yield {
|
|
326
|
+
"event": "on_chain_error",
|
|
327
|
+
"run_id": str(uuid.uuid4()),
|
|
328
|
+
"name": "CugaAgent",
|
|
329
|
+
"data": {"error": error_msg},
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
async def message_response(self) -> Message:
|
|
333
|
+
"""Generate a message response using the Cuga agent.
|
|
334
|
+
|
|
335
|
+
This method processes the input through the Cuga agent and returns a structured
|
|
336
|
+
message response. It handles agent initialization, tool setup, and event processing.
|
|
337
|
+
|
|
338
|
+
Returns:
|
|
339
|
+
Message: The agent's response message
|
|
340
|
+
|
|
341
|
+
Raises:
|
|
342
|
+
Exception: If there's an error during agent execution
|
|
343
|
+
"""
|
|
344
|
+
logger.debug("[CUGA] Starting Cuga agent run for message_response.")
|
|
345
|
+
logger.debug(f"[CUGA] Agent input value: {self.input_value}")
|
|
346
|
+
|
|
347
|
+
# Validate input is not empty
|
|
348
|
+
if not self.input_value or not str(self.input_value).strip():
|
|
349
|
+
msg = "Message cannot be empty. Please provide a valid message."
|
|
350
|
+
raise ValueError(msg)
|
|
351
|
+
|
|
352
|
+
try:
|
|
353
|
+
llm_model, self.chat_history, self.tools = await self.get_agent_requirements()
|
|
354
|
+
|
|
355
|
+
# Create agent message for event processing
|
|
356
|
+
from lfx.schema.content_block import ContentBlock
|
|
357
|
+
from lfx.schema.message import MESSAGE_SENDER_AI
|
|
358
|
+
|
|
359
|
+
agent_message = Message(
|
|
360
|
+
sender=MESSAGE_SENDER_AI,
|
|
361
|
+
sender_name="Cuga",
|
|
362
|
+
properties={"icon": "Bot", "state": "partial"},
|
|
363
|
+
content_blocks=[ContentBlock(title="Agent Steps", contents=[])],
|
|
364
|
+
session_id=self.graph.session_id,
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
# Pre-assign an ID for event processing, following the base agent pattern
|
|
368
|
+
# This ensures streaming works even when not connected to ChatOutput
|
|
369
|
+
if not self.is_connected_to_chat_output():
|
|
370
|
+
# When not connected to ChatOutput, assign ID upfront for streaming support
|
|
371
|
+
agent_message.data["id"] = str(uuid.uuid4())
|
|
372
|
+
|
|
373
|
+
# Get input text
|
|
374
|
+
input_text = self.input_value.text if hasattr(self.input_value, "text") else str(self.input_value)
|
|
375
|
+
|
|
376
|
+
# Create event iterator from call_agent
|
|
377
|
+
event_iterator = self.call_agent(
|
|
378
|
+
current_input=input_text, tools=self.tools or [], history_messages=self.chat_history, llm=llm_model
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
# Process events using the existing event processing system
|
|
382
|
+
from lfx.base.agents.events import process_agent_events
|
|
383
|
+
|
|
384
|
+
# Create a wrapper that forces DB updates for event handlers
|
|
385
|
+
# This ensures the UI can see loading steps in real-time via polling
|
|
386
|
+
async def force_db_update_send_message(message, id_=None, *, skip_db_update=False): # noqa: ARG001
|
|
387
|
+
# Always persist to DB so polling-based UI shows loading steps in real-time
|
|
388
|
+
content_blocks_len = len(message.content_blocks[0].contents) if message.content_blocks else 0
|
|
389
|
+
logger.debug(
|
|
390
|
+
f"[CUGA] Sending message update - state: {message.properties.state}, "
|
|
391
|
+
f"content_blocks: {content_blocks_len}"
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
result = await self.send_message(message, id_=id_, skip_db_update=False)
|
|
395
|
+
|
|
396
|
+
logger.debug(f"[CUGA] Message processed with ID: {result.id}")
|
|
397
|
+
return result
|
|
398
|
+
|
|
399
|
+
result = await process_agent_events(
|
|
400
|
+
event_iterator, agent_message, cast("SendMessageFunctionType", force_db_update_send_message)
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
logger.debug("[CUGA] Agent run finished successfully.")
|
|
404
|
+
logger.debug(f"[CUGA] Agent output: {result}")
|
|
405
|
+
|
|
406
|
+
except Exception as e:
|
|
407
|
+
logger.error(f"[CUGA] Error in message_response: {e}")
|
|
408
|
+
logger.error(f"[CUGA] An error occurred: {e!s}")
|
|
409
|
+
logger.error(f"[CUGA] Traceback: {traceback.format_exc()}")
|
|
410
|
+
|
|
411
|
+
# Check if error is related to Playwright installation
|
|
412
|
+
error_str = str(e).lower()
|
|
413
|
+
if "playwright install" in error_str:
|
|
414
|
+
msg = (
|
|
415
|
+
"Playwright is not installed. Please install Playwright Chromium using: "
|
|
416
|
+
"uv run -m playwright install chromium"
|
|
417
|
+
)
|
|
418
|
+
raise ValueError(msg) from e
|
|
419
|
+
|
|
420
|
+
raise
|
|
421
|
+
else:
|
|
422
|
+
return result
|
|
423
|
+
|
|
424
|
+
async def get_agent_requirements(self):
|
|
425
|
+
"""Get the agent requirements for the Cuga agent.
|
|
426
|
+
|
|
427
|
+
This method retrieves and configures all necessary components for the agent
|
|
428
|
+
including the language model, chat history, and tools.
|
|
429
|
+
|
|
430
|
+
Returns:
|
|
431
|
+
tuple: A tuple containing (llm_model, chat_history, tools)
|
|
432
|
+
|
|
433
|
+
Raises:
|
|
434
|
+
ValueError: If no language model is selected or if there's an error
|
|
435
|
+
in model initialization
|
|
436
|
+
"""
|
|
437
|
+
llm_model, display_name = await self.get_llm()
|
|
438
|
+
if llm_model is None:
|
|
439
|
+
msg = "No language model selected. Please choose a model to proceed."
|
|
440
|
+
raise ValueError(msg)
|
|
441
|
+
self.model_name = get_model_name(llm_model, display_name=display_name)
|
|
442
|
+
|
|
443
|
+
# Get memory data
|
|
444
|
+
self.chat_history = await self.get_memory_data()
|
|
445
|
+
if isinstance(self.chat_history, Message):
|
|
446
|
+
self.chat_history = [self.chat_history]
|
|
447
|
+
|
|
448
|
+
# Add current date tool if enabled
|
|
449
|
+
if self.add_current_date_tool:
|
|
450
|
+
if not isinstance(self.tools, list):
|
|
451
|
+
self.tools = []
|
|
452
|
+
current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)
|
|
453
|
+
if not isinstance(current_date_tool, StructuredTool):
|
|
454
|
+
msg = "CurrentDateComponent must be converted to a StructuredTool"
|
|
455
|
+
raise TypeError(msg)
|
|
456
|
+
self.tools.append(current_date_tool)
|
|
457
|
+
|
|
458
|
+
# --- ADDED LOGGING START ---
|
|
459
|
+
logger.debug("[CUGA] Retrieved agent requirements: LLM, chat history, and tools.")
|
|
460
|
+
logger.debug(f"[CUGA] LLM model: {self.model_name}")
|
|
461
|
+
logger.debug(f"[CUGA] Number of chat history messages: {len(self.chat_history)}")
|
|
462
|
+
logger.debug(f"[CUGA] Tools available: {[tool.name for tool in self.tools]}")
|
|
463
|
+
logger.debug(f"[CUGA] metadata: {[tool.metadata for tool in self.tools]}")
|
|
464
|
+
# --- ADDED LOGGING END ---
|
|
465
|
+
|
|
466
|
+
return llm_model, self.chat_history, self.tools
|
|
467
|
+
|
|
468
|
+
async def get_memory_data(self):
|
|
469
|
+
"""Retrieve chat history messages.
|
|
470
|
+
|
|
471
|
+
This method fetches the conversation history from memory, excluding the current
|
|
472
|
+
input message to avoid duplication.
|
|
473
|
+
|
|
474
|
+
Returns:
|
|
475
|
+
list: List of Message objects representing the chat history
|
|
476
|
+
"""
|
|
477
|
+
logger.debug("[CUGA] Retrieving chat history messages.")
|
|
478
|
+
logger.debug(f"[CUGA] Session ID: {self.graph.session_id}")
|
|
479
|
+
messages = (
|
|
480
|
+
await MemoryComponent(**self.get_base_args())
|
|
481
|
+
.set(session_id=self.graph.session_id, order="Ascending", n_messages=self.n_messages)
|
|
482
|
+
.retrieve_messages()
|
|
483
|
+
)
|
|
484
|
+
logger.debug(f"[CUGA] Retrieved {len(messages)} messages from memory")
|
|
485
|
+
return [
|
|
486
|
+
message for message in messages if getattr(message, "id", None) != getattr(self.input_value, "id", None)
|
|
487
|
+
]
|
|
488
|
+
|
|
489
|
+
async def get_llm(self):
|
|
490
|
+
"""Get language model for the Cuga agent.
|
|
491
|
+
|
|
492
|
+
This method initializes and configures the language model based on the
|
|
493
|
+
selected provider and parameters.
|
|
494
|
+
|
|
495
|
+
Returns:
|
|
496
|
+
tuple: A tuple containing (llm_model, display_name)
|
|
497
|
+
|
|
498
|
+
Raises:
|
|
499
|
+
ValueError: If the model provider is invalid or model initialization fails
|
|
500
|
+
"""
|
|
501
|
+
logger.debug("[CUGA] Getting language model for the agent.")
|
|
502
|
+
logger.debug(f"[CUGA] Requested LLM provider: {self.agent_llm}")
|
|
503
|
+
|
|
504
|
+
if not isinstance(self.agent_llm, str):
|
|
505
|
+
logger.debug("[CUGA] Agent LLM is already a model instance.")
|
|
506
|
+
return self.agent_llm, None
|
|
507
|
+
|
|
508
|
+
try:
|
|
509
|
+
provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)
|
|
510
|
+
if not provider_info:
|
|
511
|
+
msg = f"Invalid model provider: {self.agent_llm}"
|
|
512
|
+
raise ValueError(msg)
|
|
513
|
+
|
|
514
|
+
component_class = provider_info.get("component_class")
|
|
515
|
+
display_name = component_class.display_name
|
|
516
|
+
inputs = provider_info.get("inputs")
|
|
517
|
+
prefix = provider_info.get("prefix", "")
|
|
518
|
+
logger.debug(f"[CUGA] Successfully built LLM model from provider: {self.agent_llm}")
|
|
519
|
+
return self._build_llm_model(component_class, inputs, prefix), display_name
|
|
520
|
+
|
|
521
|
+
except (AttributeError, ValueError, TypeError, RuntimeError) as e:
|
|
522
|
+
await logger.aerror(f"[CUGA] Error building {self.agent_llm} language model: {e!s}")
|
|
523
|
+
msg = f"Failed to initialize language model: {e!s}"
|
|
524
|
+
raise ValueError(msg) from e
|
|
525
|
+
|
|
526
|
+
def _build_llm_model(self, component, inputs, prefix=""):
|
|
527
|
+
"""Build LLM model with parameters.
|
|
528
|
+
|
|
529
|
+
This method constructs a language model instance using the provided component
|
|
530
|
+
class and input parameters.
|
|
531
|
+
|
|
532
|
+
Args:
|
|
533
|
+
component: The LLM component class to instantiate
|
|
534
|
+
inputs: List of input field definitions
|
|
535
|
+
prefix: Optional prefix for parameter names
|
|
536
|
+
|
|
537
|
+
Returns:
|
|
538
|
+
The configured LLM model instance
|
|
539
|
+
"""
|
|
540
|
+
model_kwargs = {}
|
|
541
|
+
for input_ in inputs:
|
|
542
|
+
if hasattr(self, f"{prefix}{input_.name}"):
|
|
543
|
+
model_kwargs[input_.name] = getattr(self, f"{prefix}{input_.name}")
|
|
544
|
+
return component.set(**model_kwargs).build_model()
|
|
545
|
+
|
|
546
|
+
def set_component_params(self, component):
|
|
547
|
+
"""Set component parameters based on provider.
|
|
548
|
+
|
|
549
|
+
This method configures component parameters according to the selected
|
|
550
|
+
model provider's requirements.
|
|
551
|
+
|
|
552
|
+
Args:
|
|
553
|
+
component: The component to configure
|
|
554
|
+
|
|
555
|
+
Returns:
|
|
556
|
+
The configured component
|
|
557
|
+
"""
|
|
558
|
+
provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)
|
|
559
|
+
if provider_info:
|
|
560
|
+
inputs = provider_info.get("inputs")
|
|
561
|
+
prefix = provider_info.get("prefix")
|
|
562
|
+
model_kwargs = {}
|
|
563
|
+
for input_ in inputs:
|
|
564
|
+
if hasattr(self, f"{prefix}{input_.name}"):
|
|
565
|
+
model_kwargs[input_.name] = getattr(self, f"{prefix}{input_.name}")
|
|
566
|
+
return component.set(**model_kwargs)
|
|
567
|
+
return component
|
|
568
|
+
|
|
569
|
+
def delete_fields(self, build_config: dotdict, fields: dict | list[str]) -> None:
|
|
570
|
+
"""Delete specified fields from build_config.
|
|
571
|
+
|
|
572
|
+
This method removes unwanted fields from the build configuration.
|
|
573
|
+
|
|
574
|
+
Args:
|
|
575
|
+
build_config: The build configuration dictionary
|
|
576
|
+
fields: Fields to remove (can be dict or list of strings)
|
|
577
|
+
"""
|
|
578
|
+
for field in fields:
|
|
579
|
+
build_config.pop(field, None)
|
|
580
|
+
|
|
581
|
+
def update_input_types(self, build_config: dotdict) -> dotdict:
|
|
582
|
+
"""Update input types for all fields in build_config.
|
|
583
|
+
|
|
584
|
+
This method ensures all fields in the build configuration have proper
|
|
585
|
+
input types defined.
|
|
586
|
+
|
|
587
|
+
Args:
|
|
588
|
+
build_config: The build configuration to update
|
|
589
|
+
|
|
590
|
+
Returns:
|
|
591
|
+
dotdict: Updated build configuration with input types
|
|
592
|
+
"""
|
|
593
|
+
for key, value in build_config.items():
|
|
594
|
+
if isinstance(value, dict):
|
|
595
|
+
if value.get("input_types") is None:
|
|
596
|
+
build_config[key]["input_types"] = []
|
|
597
|
+
elif hasattr(value, "input_types") and value.input_types is None:
|
|
598
|
+
value.input_types = []
|
|
599
|
+
return build_config
|
|
600
|
+
|
|
601
|
+
async def update_build_config(
|
|
602
|
+
self, build_config: dotdict, field_value: str, field_name: str | None = None
|
|
603
|
+
) -> dotdict:
|
|
604
|
+
"""Update build configuration based on field changes.
|
|
605
|
+
|
|
606
|
+
This method dynamically updates the component's build configuration when
|
|
607
|
+
certain fields change, particularly the model provider selection.
|
|
608
|
+
|
|
609
|
+
Args:
|
|
610
|
+
build_config: The current build configuration
|
|
611
|
+
field_value: The new value for the field
|
|
612
|
+
field_name: The name of the field being changed
|
|
613
|
+
|
|
614
|
+
Returns:
|
|
615
|
+
dotdict: Updated build configuration
|
|
616
|
+
|
|
617
|
+
Raises:
|
|
618
|
+
ValueError: If required keys are missing from the configuration
|
|
619
|
+
"""
|
|
620
|
+
if field_name in ("agent_llm",):
|
|
621
|
+
build_config["agent_llm"]["value"] = field_value
|
|
622
|
+
provider_info = MODEL_PROVIDERS_DICT.get(field_value)
|
|
623
|
+
if provider_info:
|
|
624
|
+
component_class = provider_info.get("component_class")
|
|
625
|
+
if component_class and hasattr(component_class, "update_build_config"):
|
|
626
|
+
build_config = await update_component_build_config(
|
|
627
|
+
component_class, build_config, field_value, "model_name"
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
provider_configs: dict[str, tuple[dict, list[dict]]] = {
|
|
631
|
+
provider: (
|
|
632
|
+
MODEL_PROVIDERS_DICT[provider]["fields"],
|
|
633
|
+
[
|
|
634
|
+
MODEL_PROVIDERS_DICT[other_provider]["fields"]
|
|
635
|
+
for other_provider in MODEL_PROVIDERS_DICT
|
|
636
|
+
if other_provider != provider
|
|
637
|
+
],
|
|
638
|
+
)
|
|
639
|
+
for provider in MODEL_PROVIDERS_DICT
|
|
640
|
+
}
|
|
641
|
+
if field_value in provider_configs:
|
|
642
|
+
fields_to_add, fields_to_delete = provider_configs[field_value]
|
|
643
|
+
|
|
644
|
+
# Delete fields from other providers
|
|
645
|
+
for fields in fields_to_delete:
|
|
646
|
+
self.delete_fields(build_config, fields)
|
|
647
|
+
|
|
648
|
+
# Add provider-specific fields
|
|
649
|
+
if field_value == "OpenAI" and not any(field in build_config for field in fields_to_add):
|
|
650
|
+
build_config.update(fields_to_add)
|
|
651
|
+
else:
|
|
652
|
+
build_config.update(fields_to_add)
|
|
653
|
+
build_config["agent_llm"]["input_types"] = []
|
|
654
|
+
elif field_value == "Custom":
|
|
655
|
+
# Delete all provider fields
|
|
656
|
+
self.delete_fields(build_config, ALL_PROVIDER_FIELDS)
|
|
657
|
+
# Update with custom component
|
|
658
|
+
custom_component = DropdownInput(
|
|
659
|
+
name="agent_llm",
|
|
660
|
+
display_name="Language Model",
|
|
661
|
+
options=[*sorted(MODEL_PROVIDERS), "Custom"],
|
|
662
|
+
value="Custom",
|
|
663
|
+
real_time_refresh=True,
|
|
664
|
+
input_types=["LanguageModel"],
|
|
665
|
+
options_metadata=[MODELS_METADATA[key] for key in sorted(MODELS_METADATA.keys())]
|
|
666
|
+
+ [{"icon": "brain"}],
|
|
667
|
+
)
|
|
668
|
+
build_config.update({"agent_llm": custom_component.to_dict()})
|
|
669
|
+
|
|
670
|
+
# Update input types for all fields
|
|
671
|
+
build_config = self.update_input_types(build_config)
|
|
672
|
+
|
|
673
|
+
# Validate required keys
|
|
674
|
+
default_keys = [
|
|
675
|
+
"code",
|
|
676
|
+
"_type",
|
|
677
|
+
"agent_llm",
|
|
678
|
+
"tools",
|
|
679
|
+
"input_value",
|
|
680
|
+
"add_current_date_tool",
|
|
681
|
+
"policies",
|
|
682
|
+
"agent_description",
|
|
683
|
+
"max_iterations",
|
|
684
|
+
"handle_parsing_errors",
|
|
685
|
+
"verbose",
|
|
686
|
+
]
|
|
687
|
+
missing_keys = [key for key in default_keys if key not in build_config]
|
|
688
|
+
if missing_keys:
|
|
689
|
+
msg = f"Missing required keys in build_config: {missing_keys}"
|
|
690
|
+
raise ValueError(msg)
|
|
691
|
+
|
|
692
|
+
if (
|
|
693
|
+
isinstance(self.agent_llm, str)
|
|
694
|
+
and self.agent_llm in MODEL_PROVIDERS_DICT
|
|
695
|
+
and field_name in MODEL_DYNAMIC_UPDATE_FIELDS
|
|
696
|
+
):
|
|
697
|
+
provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)
|
|
698
|
+
if provider_info:
|
|
699
|
+
component_class = provider_info.get("component_class")
|
|
700
|
+
component_class = self.set_component_params(component_class)
|
|
701
|
+
prefix = provider_info.get("prefix")
|
|
702
|
+
if component_class and hasattr(component_class, "update_build_config"):
|
|
703
|
+
if isinstance(field_name, str) and isinstance(prefix, str):
|
|
704
|
+
field_name = field_name.replace(prefix, "")
|
|
705
|
+
build_config = await update_component_build_config(
|
|
706
|
+
component_class, build_config, field_value, "model_name"
|
|
707
|
+
)
|
|
708
|
+
return dotdict({k: v.to_dict() if hasattr(v, "to_dict") else v for k, v in build_config.items()})
|
|
709
|
+
|
|
710
|
+
async def _get_tools(self) -> list[Tool]:
|
|
711
|
+
"""Build agent tools.
|
|
712
|
+
|
|
713
|
+
This method constructs the list of tools available to the Cuga agent,
|
|
714
|
+
including component tools and any additional configured tools.
|
|
715
|
+
|
|
716
|
+
Returns:
|
|
717
|
+
list[Tool]: List of available tools for the agent
|
|
718
|
+
"""
|
|
719
|
+
logger.debug("[CUGA] Building agent tools.")
|
|
720
|
+
component_toolkit = _get_component_toolkit()
|
|
721
|
+
tools_names = self._build_tools_names()
|
|
722
|
+
agent_description = self.get_tool_description()
|
|
723
|
+
description = f"{agent_description}{tools_names}"
|
|
724
|
+
tools = component_toolkit(component=self).get_tools(
|
|
725
|
+
tool_name="Call_CugaAgent", tool_description=description, callbacks=self.get_langchain_callbacks()
|
|
726
|
+
)
|
|
727
|
+
if hasattr(self, "tools_metadata"):
|
|
728
|
+
tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)
|
|
729
|
+
logger.debug(f"[CUGA] Tools built: {[tool.name for tool in tools]}")
|
|
730
|
+
return tools
|