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.
- lfx/_assets/component_index.json +1 -1
- lfx/base/agents/agent.py +13 -1
- lfx/base/agents/altk_base_agent.py +380 -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 +88 -21
- lfx/base/data/storage_utils.py +192 -0
- lfx/base/data/utils.py +178 -14
- lfx/base/embeddings/embeddings_class.py +113 -0
- 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 +6 -3
- 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/elastic/elasticsearch.py +1 -1
- lfx/components/elastic/opensearch_multimodal.py +1575 -0
- 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 +246 -18
- lfx/components/{knowledge_bases → files_and_knowledge}/retrieval.py +2 -2
- lfx/components/{data → files_and_knowledge}/save_file.py +142 -22
- 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/input_output/__init__.py +3 -1
- lfx/components/input_output/chat.py +4 -3
- lfx/components/input_output/chat_output.py +4 -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 +1 -1
- 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 +2 -2
- lfx/components/models_and_agents/embedding_model.py +423 -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 +17 -3
- lfx/components/processing/__init__.py +9 -57
- 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/graph/edge/base.py +43 -20
- lfx/graph/graph/base.py +4 -1
- lfx/graph/state/model.py +15 -2
- lfx/graph/utils.py +6 -0
- lfx/graph/vertex/base.py +4 -1
- 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/services/database/__init__.py +5 -0
- lfx/services/database/service.py +25 -0
- lfx/services/deps.py +87 -22
- lfx/services/manager.py +19 -6
- lfx/services/mcp_composer/service.py +998 -157
- lfx/services/session.py +5 -0
- lfx/services/settings/base.py +51 -7
- lfx/services/settings/constants.py +8 -0
- lfx/services/storage/local.py +76 -46
- lfx/services/storage/service.py +152 -29
- lfx/template/field/base.py +3 -0
- 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.dev26.dist-info}/METADATA +38 -22
- {lfx_nightly-0.2.0.dev0.dist-info → lfx_nightly-0.2.0.dev26.dist-info}/RECORD +182 -150
- {lfx_nightly-0.2.0.dev0.dist-info → lfx_nightly-0.2.0.dev26.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/{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.dev26.dist-info}/entry_points.txt +0 -0
|
@@ -1,1013 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import json
|
|
3
|
-
import os
|
|
4
|
-
import re
|
|
5
|
-
import traceback
|
|
6
|
-
import uuid
|
|
7
|
-
from collections.abc import AsyncIterator
|
|
8
|
-
from typing import TYPE_CHECKING, Any, cast
|
|
9
|
-
|
|
10
|
-
from langchain_core.agents import AgentFinish
|
|
11
|
-
from langchain_core.messages import AIMessage, HumanMessage
|
|
12
|
-
from langchain_core.tools import StructuredTool
|
|
13
|
-
from langflow.field_typing import Tool
|
|
14
|
-
from langflow.io import BoolInput, DropdownInput, IntInput, MultilineInput, Output, TableInput
|
|
15
|
-
|
|
16
|
-
# from langflow.logging import logger
|
|
17
|
-
from langflow.schema.data import Data
|
|
18
|
-
from langflow.schema.dotdict import dotdict
|
|
19
|
-
from langflow.schema.message import Message
|
|
20
|
-
from langflow.schema.table import EditMode
|
|
21
|
-
from pydantic import ValidationError
|
|
22
|
-
|
|
23
|
-
from lfx.base.agents.events import ExceptionWithMessageError
|
|
24
|
-
from lfx.base.models.model_input_constants import (
|
|
25
|
-
ALL_PROVIDER_FIELDS,
|
|
26
|
-
MODEL_DYNAMIC_UPDATE_FIELDS,
|
|
27
|
-
MODEL_PROVIDERS,
|
|
28
|
-
MODEL_PROVIDERS_DICT,
|
|
29
|
-
MODELS_METADATA,
|
|
30
|
-
)
|
|
31
|
-
from lfx.base.models.model_utils import get_model_name
|
|
32
|
-
from lfx.components.agents.agent import LCToolsAgentComponent
|
|
33
|
-
from lfx.components.helpers.current_date import CurrentDateComponent
|
|
34
|
-
from lfx.components.helpers.memory import MemoryComponent
|
|
35
|
-
from lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent
|
|
36
|
-
from lfx.custom.custom_component.component import _get_component_toolkit
|
|
37
|
-
from lfx.custom.utils import update_component_build_config
|
|
38
|
-
from lfx.helpers.base_model import build_model_from_schema
|
|
39
|
-
from lfx.log.logger import logger
|
|
40
|
-
|
|
41
|
-
if TYPE_CHECKING:
|
|
42
|
-
from langflow.schema.log import SendMessageFunctionType
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def set_advanced_true(component_input):
|
|
46
|
-
"""Set the advanced flag to True for a component input.
|
|
47
|
-
|
|
48
|
-
Args:
|
|
49
|
-
component_input: The component input to modify
|
|
50
|
-
|
|
51
|
-
Returns:
|
|
52
|
-
The modified component input with advanced=True
|
|
53
|
-
"""
|
|
54
|
-
component_input.advanced = True
|
|
55
|
-
return component_input
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
MODEL_PROVIDERS_LIST = ["OpenAI"]
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
class CugaComponent(ToolCallingAgentComponent):
|
|
62
|
-
"""Cuga Agent Component for advanced AI task execution.
|
|
63
|
-
|
|
64
|
-
The Cuga component is an advanced AI agent that can execute complex tasks using
|
|
65
|
-
various tools, browser automation, and structured output generation. It supports
|
|
66
|
-
custom policies, web applications, and API interactions.
|
|
67
|
-
|
|
68
|
-
Attributes:
|
|
69
|
-
display_name: Human-readable name for the component
|
|
70
|
-
description: Brief description of the component's purpose
|
|
71
|
-
documentation: URL to component documentation
|
|
72
|
-
icon: Icon identifier for the UI
|
|
73
|
-
beta: Whether the component is in beta status
|
|
74
|
-
name: Internal component name
|
|
75
|
-
"""
|
|
76
|
-
|
|
77
|
-
display_name: str = "Cuga"
|
|
78
|
-
description: str = "Define the Cuga agent's policies, then assign it a task."
|
|
79
|
-
documentation: str = "https://docs.langflow.org/agents"
|
|
80
|
-
icon = "bot"
|
|
81
|
-
beta = True
|
|
82
|
-
name = "Cuga"
|
|
83
|
-
|
|
84
|
-
memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]
|
|
85
|
-
|
|
86
|
-
# Filter out json_mode from OpenAI inputs since we handle structured output differently
|
|
87
|
-
openai_inputs_filtered = [
|
|
88
|
-
input_field
|
|
89
|
-
for input_field in MODEL_PROVIDERS_DICT["OpenAI"]["inputs"]
|
|
90
|
-
if not (hasattr(input_field, "name") and input_field.name == "json_mode")
|
|
91
|
-
]
|
|
92
|
-
|
|
93
|
-
inputs = [
|
|
94
|
-
DropdownInput(
|
|
95
|
-
name="agent_llm",
|
|
96
|
-
display_name="Model Provider",
|
|
97
|
-
info="The provider of the language model that the agent will use to generate responses.",
|
|
98
|
-
options=[*MODEL_PROVIDERS_LIST, "Custom"],
|
|
99
|
-
value="OpenAI",
|
|
100
|
-
real_time_refresh=True,
|
|
101
|
-
input_types=[],
|
|
102
|
-
options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST] + [{"icon": "brain"}],
|
|
103
|
-
),
|
|
104
|
-
*openai_inputs_filtered,
|
|
105
|
-
MultilineInput(
|
|
106
|
-
name="policies",
|
|
107
|
-
display_name="Policies",
|
|
108
|
-
info=(
|
|
109
|
-
"Custom instructions or policies for the agent to adhere to during its operation.\n"
|
|
110
|
-
"Example:\n"
|
|
111
|
-
"# Plan\n"
|
|
112
|
-
"< planning instructions e.g. which tools and when to use>\n"
|
|
113
|
-
"# Answer\n"
|
|
114
|
-
"< final answer instructions how to answer>"
|
|
115
|
-
),
|
|
116
|
-
value="",
|
|
117
|
-
advanced=False,
|
|
118
|
-
),
|
|
119
|
-
IntInput(
|
|
120
|
-
name="n_messages",
|
|
121
|
-
display_name="Number of Chat History Messages",
|
|
122
|
-
value=100,
|
|
123
|
-
info="Number of chat history messages to retrieve.",
|
|
124
|
-
advanced=True,
|
|
125
|
-
show=True,
|
|
126
|
-
),
|
|
127
|
-
MultilineInput(
|
|
128
|
-
name="format_instructions",
|
|
129
|
-
display_name="Output Format Instructions",
|
|
130
|
-
info="Generic Template for structured output formatting. Valid only with Structured response.",
|
|
131
|
-
value=(
|
|
132
|
-
"You are an AI that extracts structured JSON objects from unstructured text. "
|
|
133
|
-
"Use a predefined schema with expected types (str, int, float, bool, dict). "
|
|
134
|
-
"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. "
|
|
135
|
-
"Fill missing or ambiguous values with defaults: null for missing values. "
|
|
136
|
-
"Remove exact duplicates but keep variations that have different field values. "
|
|
137
|
-
"Always return valid JSON in the expected format, never throw errors. "
|
|
138
|
-
"If multiple objects can be extracted, return them all in the structured format."
|
|
139
|
-
),
|
|
140
|
-
advanced=True,
|
|
141
|
-
),
|
|
142
|
-
TableInput(
|
|
143
|
-
name="output_schema",
|
|
144
|
-
display_name="Output Schema",
|
|
145
|
-
info=(
|
|
146
|
-
"Schema Validation: Define the structure and data types for structured output. "
|
|
147
|
-
"No validation if no output schema."
|
|
148
|
-
),
|
|
149
|
-
advanced=True,
|
|
150
|
-
required=False,
|
|
151
|
-
value=[],
|
|
152
|
-
table_schema=[
|
|
153
|
-
{
|
|
154
|
-
"name": "name",
|
|
155
|
-
"display_name": "Name",
|
|
156
|
-
"type": "str",
|
|
157
|
-
"description": "Specify the name of the output field.",
|
|
158
|
-
"default": "field",
|
|
159
|
-
"edit_mode": EditMode.INLINE,
|
|
160
|
-
},
|
|
161
|
-
{
|
|
162
|
-
"name": "description",
|
|
163
|
-
"display_name": "Description",
|
|
164
|
-
"type": "str",
|
|
165
|
-
"description": "Describe the purpose of the output field.",
|
|
166
|
-
"default": "description of field",
|
|
167
|
-
"edit_mode": EditMode.POPOVER,
|
|
168
|
-
},
|
|
169
|
-
{
|
|
170
|
-
"name": "type",
|
|
171
|
-
"display_name": "Type",
|
|
172
|
-
"type": "str",
|
|
173
|
-
"edit_mode": EditMode.INLINE,
|
|
174
|
-
"description": ("Indicate the data type of the output field (e.g., str, int, float, bool, dict)."),
|
|
175
|
-
"options": ["str", "int", "float", "bool", "dict"],
|
|
176
|
-
"default": "str",
|
|
177
|
-
},
|
|
178
|
-
{
|
|
179
|
-
"name": "multiple",
|
|
180
|
-
"display_name": "As List",
|
|
181
|
-
"type": "boolean",
|
|
182
|
-
"description": "Set to True if this output field should be a list of the specified type.",
|
|
183
|
-
"default": "False",
|
|
184
|
-
"edit_mode": EditMode.INLINE,
|
|
185
|
-
},
|
|
186
|
-
],
|
|
187
|
-
),
|
|
188
|
-
*LCToolsAgentComponent.get_base_inputs(),
|
|
189
|
-
BoolInput(
|
|
190
|
-
name="add_current_date_tool",
|
|
191
|
-
display_name="Current Date",
|
|
192
|
-
advanced=True,
|
|
193
|
-
info="If true, will add a tool to the agent that returns the current date.",
|
|
194
|
-
value=True,
|
|
195
|
-
),
|
|
196
|
-
BoolInput(
|
|
197
|
-
name="browser_enabled",
|
|
198
|
-
display_name="Enable Browser",
|
|
199
|
-
info="Toggle to enable a built-in browser tool for web scraping and searching.",
|
|
200
|
-
value=False,
|
|
201
|
-
advanced=False,
|
|
202
|
-
),
|
|
203
|
-
MultilineInput(
|
|
204
|
-
name="web_apps",
|
|
205
|
-
display_name="Web applications",
|
|
206
|
-
info=(
|
|
207
|
-
"Define a list of web applications that cuga will open when enable browser is true. "
|
|
208
|
-
"Currently only supports one web application. Example: https://example.com"
|
|
209
|
-
),
|
|
210
|
-
value="",
|
|
211
|
-
advanced=False,
|
|
212
|
-
),
|
|
213
|
-
BoolInput(
|
|
214
|
-
name="API",
|
|
215
|
-
display_name="Enable API Sub-agent",
|
|
216
|
-
info="Toggle to enable a built-in sub-agent specialized for API interactions.",
|
|
217
|
-
value=False,
|
|
218
|
-
advanced=True,
|
|
219
|
-
),
|
|
220
|
-
]
|
|
221
|
-
outputs = [
|
|
222
|
-
Output(name="response", display_name="Response", method="message_response"),
|
|
223
|
-
Output(name="structured_response", display_name="Structured Response", method="json_response", tool_mode=False),
|
|
224
|
-
]
|
|
225
|
-
|
|
226
|
-
async def call_agent(
|
|
227
|
-
self, current_input: str, tools: list[Tool], history_messages: list[Message], llm
|
|
228
|
-
) -> AsyncIterator[dict[str, Any]]:
|
|
229
|
-
"""Execute the Cuga agent with the given input and tools.
|
|
230
|
-
|
|
231
|
-
This method initializes and runs the Cuga agent, processing the input through
|
|
232
|
-
the agent's workflow and yielding events for real-time monitoring.
|
|
233
|
-
|
|
234
|
-
Args:
|
|
235
|
-
current_input: The user input to process
|
|
236
|
-
tools: List of available tools for the agent
|
|
237
|
-
history_messages: Previous conversation history
|
|
238
|
-
llm: The language model instance to use
|
|
239
|
-
|
|
240
|
-
Yields:
|
|
241
|
-
dict: Agent events including tool usage, thinking, and final results
|
|
242
|
-
|
|
243
|
-
Raises:
|
|
244
|
-
ValueError: If there's an error in agent initialization
|
|
245
|
-
TypeError: If there's a type error in processing
|
|
246
|
-
RuntimeError: If there's a runtime error during execution
|
|
247
|
-
ConnectionError: If there's a connection issue
|
|
248
|
-
"""
|
|
249
|
-
yield {
|
|
250
|
-
"event": "on_chain_start",
|
|
251
|
-
"run_id": str(uuid.uuid4()),
|
|
252
|
-
"name": "CUGA_initializing",
|
|
253
|
-
"data": {"input": {"input": current_input, "chat_history": []}},
|
|
254
|
-
}
|
|
255
|
-
logger.debug(f"LLM MODEL TYPE: {type(llm)}")
|
|
256
|
-
if current_input:
|
|
257
|
-
try:
|
|
258
|
-
from cuga.config import settings as cuga_settings
|
|
259
|
-
|
|
260
|
-
logger.info("Updating cuga settings programmatically")
|
|
261
|
-
cuga_settings.set("advanced_features.registry", False) # noqa: FBT003
|
|
262
|
-
|
|
263
|
-
if self.browser_enabled:
|
|
264
|
-
logger.info("browser_enabled is true, setting mode to hybrid")
|
|
265
|
-
cuga_settings.set("advanced_features.mode", "hybrid")
|
|
266
|
-
cuga_settings.set("advanced_features.use_vision", False) # noqa: FBT003
|
|
267
|
-
else:
|
|
268
|
-
logger.info("browser_enabled is false, setting mode to api")
|
|
269
|
-
cuga_settings.set("advanced_features.mode", "api")
|
|
270
|
-
|
|
271
|
-
logger.info(f"Cuga settings updated - MODE: {cuga_settings.get('advanced_features.mode')}")
|
|
272
|
-
except (ImportError, AttributeError) as e:
|
|
273
|
-
logger.warning(f"Could not update cuga settings: {e}")
|
|
274
|
-
os.environ["DYNACONF_ADVANCED_FEATURES__REGISTRY"] = "false"
|
|
275
|
-
if self.browser_enabled:
|
|
276
|
-
logger.info("browser_enabled is true, setting env to hybrid")
|
|
277
|
-
os.environ["DYNACONF_ADVANCED_FEATURES__MODE"] = "hybrid"
|
|
278
|
-
os.environ["DYNACONF_ADVANCED_FEATURES__USE_VISION"] = "false"
|
|
279
|
-
else:
|
|
280
|
-
logger.info("browser_enabled is false, setting env to api")
|
|
281
|
-
os.environ["DYNACONF_ADVANCED_FEATURES__MODE"] = "api"
|
|
282
|
-
|
|
283
|
-
from cuga.backend.activity_tracker.tracker import ActivityTracker
|
|
284
|
-
from cuga.backend.cuga_graph.utils.agent_loop import StreamEvent
|
|
285
|
-
from cuga.backend.cuga_graph.utils.controller import (
|
|
286
|
-
AgentRunner as CugaAgent,
|
|
287
|
-
)
|
|
288
|
-
from cuga.backend.cuga_graph.utils.controller import (
|
|
289
|
-
ExperimentResult as AgentResult,
|
|
290
|
-
)
|
|
291
|
-
from cuga.backend.llm.models import LLMManager
|
|
292
|
-
from cuga.configurations.instructions_manager import InstructionsManager
|
|
293
|
-
|
|
294
|
-
llm_manager = LLMManager()
|
|
295
|
-
llm_manager.set_llm(llm)
|
|
296
|
-
instructions_manager = InstructionsManager()
|
|
297
|
-
if self.policies:
|
|
298
|
-
logger.info(f"policies are: {self.policies}")
|
|
299
|
-
instructions_manager.set_instructions_from_one_file(self.policies)
|
|
300
|
-
tracker = ActivityTracker()
|
|
301
|
-
tracker.set_tools(tools)
|
|
302
|
-
cuga_agent = CugaAgent(browser_enabled=self.browser_enabled)
|
|
303
|
-
if self.browser_enabled:
|
|
304
|
-
await cuga_agent.initialize_freemode_env(start_url=self.web_apps.strip(), interface_mode="browser_only")
|
|
305
|
-
else:
|
|
306
|
-
await cuga_agent.initialize_appworld_env()
|
|
307
|
-
yield {
|
|
308
|
-
"event": "on_chain_start",
|
|
309
|
-
"run_id": str(uuid.uuid4()),
|
|
310
|
-
"name": "CUGA_thinking...",
|
|
311
|
-
"data": {"input": {"input": current_input, "chat_history": []}},
|
|
312
|
-
}
|
|
313
|
-
logger.info(f"[CUGA] current web apps are {self.web_apps}")
|
|
314
|
-
logger.info(f"[CUGA] Processing input: {current_input}")
|
|
315
|
-
try:
|
|
316
|
-
# Convert history to LangChain format for the event
|
|
317
|
-
lc_messages = []
|
|
318
|
-
for msg in history_messages:
|
|
319
|
-
if hasattr(msg, "sender") and msg.sender == "Human":
|
|
320
|
-
lc_messages.append(HumanMessage(content=msg.text))
|
|
321
|
-
else:
|
|
322
|
-
lc_messages.append(AIMessage(content=msg.text))
|
|
323
|
-
|
|
324
|
-
await asyncio.sleep(0.5)
|
|
325
|
-
|
|
326
|
-
tools_used = []
|
|
327
|
-
|
|
328
|
-
# Simulate browser tool usage
|
|
329
|
-
if getattr(self, "BROWSER", False) and any(
|
|
330
|
-
word in current_input.lower() for word in ["search", "web", "browse"]
|
|
331
|
-
):
|
|
332
|
-
tool_run_id = str(uuid.uuid4())
|
|
333
|
-
|
|
334
|
-
yield {
|
|
335
|
-
"event": "on_tool_start",
|
|
336
|
-
"run_id": tool_run_id,
|
|
337
|
-
"name": "BrowserTool",
|
|
338
|
-
"data": {"input": {"query": current_input}},
|
|
339
|
-
}
|
|
340
|
-
await asyncio.sleep(0.3)
|
|
341
|
-
|
|
342
|
-
yield {
|
|
343
|
-
"event": "on_tool_end",
|
|
344
|
-
"run_id": tool_run_id,
|
|
345
|
-
"name": "BrowserTool",
|
|
346
|
-
"data": {"output": "Simulated web search results for: " + current_input},
|
|
347
|
-
}
|
|
348
|
-
tools_used.append("Performed web search")
|
|
349
|
-
|
|
350
|
-
# 2. Build final response
|
|
351
|
-
response_parts = []
|
|
352
|
-
|
|
353
|
-
response_parts.append(f"Processed input: '{current_input}'")
|
|
354
|
-
response_parts.append(f"Available tools: {len(tools)}")
|
|
355
|
-
# final_response = "CUGA Agent Response:\n" + "\n".join(response_parts)
|
|
356
|
-
last_event: StreamEvent = None
|
|
357
|
-
tool_run_id = None
|
|
358
|
-
# 3. Chain end event with AgentFinish
|
|
359
|
-
async for event in cuga_agent.run_task_generic_yield(eval_mode=False, goal=current_input):
|
|
360
|
-
logger.debug(f"recieved event {event}")
|
|
361
|
-
if last_event is not None and tool_run_id is not None:
|
|
362
|
-
logger.debug(f"last event {last_event}")
|
|
363
|
-
try:
|
|
364
|
-
# TODO: Extract data
|
|
365
|
-
data_dict = json.loads(last_event.data)
|
|
366
|
-
except json.JSONDecodeError:
|
|
367
|
-
data_dict = last_event.data
|
|
368
|
-
if last_event.name == "CodeAgent":
|
|
369
|
-
data_dict = data_dict["code"]
|
|
370
|
-
yield {
|
|
371
|
-
"event": "on_tool_end",
|
|
372
|
-
"run_id": tool_run_id,
|
|
373
|
-
"name": last_event.name,
|
|
374
|
-
"data": {"output": data_dict},
|
|
375
|
-
}
|
|
376
|
-
if isinstance(event, StreamEvent):
|
|
377
|
-
tool_run_id = str(uuid.uuid4())
|
|
378
|
-
last_event = StreamEvent(name=event.name, data=event.data)
|
|
379
|
-
tool_event = {
|
|
380
|
-
"event": "on_tool_start",
|
|
381
|
-
"run_id": tool_run_id,
|
|
382
|
-
"name": event.name,
|
|
383
|
-
"data": {"input": {}},
|
|
384
|
-
}
|
|
385
|
-
logger.debug(f"[CUGA] Yielding tool_start event: {event.name}")
|
|
386
|
-
yield tool_event
|
|
387
|
-
|
|
388
|
-
if isinstance(event, AgentResult):
|
|
389
|
-
task_result = event
|
|
390
|
-
end_event = {
|
|
391
|
-
"event": "on_chain_end",
|
|
392
|
-
"run_id": str(uuid.uuid4()),
|
|
393
|
-
"name": "CugaAgent",
|
|
394
|
-
"data": {"output": AgentFinish(return_values={"output": task_result.answer}, log="")},
|
|
395
|
-
}
|
|
396
|
-
answer_preview = task_result.answer[:100] if task_result.answer else "None"
|
|
397
|
-
logger.info(f"[CUGA] Yielding chain_end event with answer: {answer_preview}...")
|
|
398
|
-
yield end_event
|
|
399
|
-
# task_result: AgentResult = await cuga_agent.run_task_generic_yield(
|
|
400
|
-
# eval_mode=False, goal=current_input, on_progress=on_progress
|
|
401
|
-
# )
|
|
402
|
-
|
|
403
|
-
except (ValueError, TypeError, RuntimeError, ConnectionError) as e:
|
|
404
|
-
logger.error(f"An error occurred: {e!s}")
|
|
405
|
-
logger.error(f"Traceback: {traceback.format_exc()}")
|
|
406
|
-
error_msg = f"CUGA Agent error: {e!s}"
|
|
407
|
-
logger.error(f"[CUGA] Error occurred: {error_msg}")
|
|
408
|
-
|
|
409
|
-
# Emit error event
|
|
410
|
-
yield {
|
|
411
|
-
"event": "on_chain_error",
|
|
412
|
-
"run_id": str(uuid.uuid4()),
|
|
413
|
-
"name": "CugaAgent",
|
|
414
|
-
"data": {"error": error_msg},
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
async def message_response(self) -> Message:
|
|
418
|
-
"""Generate a message response using the Cuga agent.
|
|
419
|
-
|
|
420
|
-
This method processes the input through the Cuga agent and returns a structured
|
|
421
|
-
message response. It handles agent initialization, tool setup, and event processing.
|
|
422
|
-
|
|
423
|
-
Returns:
|
|
424
|
-
Message: The agent's response message
|
|
425
|
-
|
|
426
|
-
Raises:
|
|
427
|
-
Exception: If there's an error during agent execution
|
|
428
|
-
"""
|
|
429
|
-
logger.info("[CUGA] Starting Cuga agent run for message_response.")
|
|
430
|
-
logger.info(f"[CUGA] Agent input value: {self.input_value}")
|
|
431
|
-
|
|
432
|
-
# Validate input is not empty
|
|
433
|
-
if not self.input_value or not str(self.input_value).strip():
|
|
434
|
-
msg = "Message cannot be empty. Please provide a valid message."
|
|
435
|
-
raise ValueError(msg)
|
|
436
|
-
|
|
437
|
-
try:
|
|
438
|
-
llm_model, self.chat_history, self.tools = await self.get_agent_requirements()
|
|
439
|
-
|
|
440
|
-
# Create agent message for event processing
|
|
441
|
-
from lfx.schema.content_block import ContentBlock
|
|
442
|
-
from lfx.schema.message import MESSAGE_SENDER_AI
|
|
443
|
-
|
|
444
|
-
agent_message = Message(
|
|
445
|
-
sender=MESSAGE_SENDER_AI,
|
|
446
|
-
sender_name="Cuga",
|
|
447
|
-
properties={"icon": "Bot", "state": "partial"},
|
|
448
|
-
content_blocks=[ContentBlock(title="Agent Steps", contents=[])],
|
|
449
|
-
session_id=self.graph.session_id,
|
|
450
|
-
)
|
|
451
|
-
|
|
452
|
-
# Get input text
|
|
453
|
-
input_text = self.input_value.text if hasattr(self.input_value, "text") else str(self.input_value)
|
|
454
|
-
|
|
455
|
-
# Create event iterator from call_agent
|
|
456
|
-
event_iterator = self.call_agent(
|
|
457
|
-
current_input=input_text, tools=self.tools or [], history_messages=self.chat_history, llm=llm_model
|
|
458
|
-
)
|
|
459
|
-
|
|
460
|
-
# Process events using the existing event processing system
|
|
461
|
-
from lfx.base.agents.events import process_agent_events
|
|
462
|
-
|
|
463
|
-
# Create a wrapper that forces DB updates for event handlers
|
|
464
|
-
# This ensures the UI can see loading steps in real-time via polling
|
|
465
|
-
async def force_db_update_send_message(message, id_=None, *, skip_db_update=False): # noqa: ARG001
|
|
466
|
-
# Always persist to DB so polling-based UI shows loading steps in real-time
|
|
467
|
-
content_blocks_len = len(message.content_blocks[0].contents) if message.content_blocks else 0
|
|
468
|
-
logger.debug(
|
|
469
|
-
f"[CUGA] Sending message update - state: {message.properties.state}, "
|
|
470
|
-
f"content_blocks: {content_blocks_len}"
|
|
471
|
-
)
|
|
472
|
-
result = await self.send_message(message, id_=id_, skip_db_update=False)
|
|
473
|
-
logger.debug(f"[CUGA] Message saved to DB with ID: {result.id if result else 'None'}")
|
|
474
|
-
return result
|
|
475
|
-
|
|
476
|
-
result = await process_agent_events(
|
|
477
|
-
event_iterator, agent_message, cast("SendMessageFunctionType", force_db_update_send_message)
|
|
478
|
-
)
|
|
479
|
-
|
|
480
|
-
logger.info("[CUGA] Agent run finished successfully.")
|
|
481
|
-
logger.info(f"[CUGA] Agent output: {result}")
|
|
482
|
-
|
|
483
|
-
# Store result for potential JSON output
|
|
484
|
-
self._agent_result = result
|
|
485
|
-
|
|
486
|
-
except Exception as e:
|
|
487
|
-
logger.error(f"[CUGA] Error in message_response: {e}")
|
|
488
|
-
logger.error(f"An error occurred: {e!s}")
|
|
489
|
-
logger.error(f"Traceback: {traceback.format_exc()}")
|
|
490
|
-
|
|
491
|
-
# Check if error is related to Playwright installation
|
|
492
|
-
error_str = str(e).lower()
|
|
493
|
-
if "playwright install" in error_str:
|
|
494
|
-
msg = (
|
|
495
|
-
"Playwright is not installed. Please install Playwright Chromium using: "
|
|
496
|
-
"uv run -m playwright install chromium"
|
|
497
|
-
)
|
|
498
|
-
raise ValueError(msg) from e
|
|
499
|
-
|
|
500
|
-
raise
|
|
501
|
-
else:
|
|
502
|
-
return result
|
|
503
|
-
|
|
504
|
-
async def get_agent_requirements(self):
|
|
505
|
-
"""Get the agent requirements for the Cuga agent.
|
|
506
|
-
|
|
507
|
-
This method retrieves and configures all necessary components for the agent
|
|
508
|
-
including the language model, chat history, and tools.
|
|
509
|
-
|
|
510
|
-
Returns:
|
|
511
|
-
tuple: A tuple containing (llm_model, chat_history, tools)
|
|
512
|
-
|
|
513
|
-
Raises:
|
|
514
|
-
ValueError: If no language model is selected or if there's an error
|
|
515
|
-
in model initialization
|
|
516
|
-
"""
|
|
517
|
-
llm_model, display_name = await self.get_llm()
|
|
518
|
-
if llm_model is None:
|
|
519
|
-
msg = "No language model selected. Please choose a model to proceed."
|
|
520
|
-
raise ValueError(msg)
|
|
521
|
-
self.model_name = get_model_name(llm_model, display_name=display_name)
|
|
522
|
-
|
|
523
|
-
# Get memory data
|
|
524
|
-
self.chat_history = await self.get_memory_data()
|
|
525
|
-
if isinstance(self.chat_history, Message):
|
|
526
|
-
self.chat_history = [self.chat_history]
|
|
527
|
-
|
|
528
|
-
# Add current date tool if enabled
|
|
529
|
-
if self.add_current_date_tool:
|
|
530
|
-
if not isinstance(self.tools, list):
|
|
531
|
-
self.tools = []
|
|
532
|
-
current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)
|
|
533
|
-
if not isinstance(current_date_tool, StructuredTool):
|
|
534
|
-
msg = "CurrentDateComponent must be converted to a StructuredTool"
|
|
535
|
-
raise TypeError(msg)
|
|
536
|
-
self.tools.append(current_date_tool)
|
|
537
|
-
|
|
538
|
-
# --- ADDED LOGGING START ---
|
|
539
|
-
logger.info("[CUGA] Retrieved agent requirements: LLM, chat history, and tools.")
|
|
540
|
-
logger.info(f"[CUGA] LLM model: {self.model_name}")
|
|
541
|
-
logger.info(f"[CUGA] Number of chat history messages: {len(self.chat_history)}")
|
|
542
|
-
logger.info(f"[CUGA] Tools available: {[tool.name for tool in self.tools]}")
|
|
543
|
-
logger.info(f"[CUGA] metadata: {[tool.metadata for tool in self.tools]}")
|
|
544
|
-
# --- ADDED LOGGING END ---
|
|
545
|
-
|
|
546
|
-
return llm_model, self.chat_history, self.tools
|
|
547
|
-
|
|
548
|
-
def _preprocess_schema(self, schema):
|
|
549
|
-
"""Preprocess schema to ensure correct data types for build_model_from_schema.
|
|
550
|
-
|
|
551
|
-
This method validates and normalizes the output schema to ensure it's compatible
|
|
552
|
-
with the Pydantic model building process.
|
|
553
|
-
|
|
554
|
-
Args:
|
|
555
|
-
schema: List of schema field definitions
|
|
556
|
-
|
|
557
|
-
Returns:
|
|
558
|
-
list: Processed schema with validated data types
|
|
559
|
-
"""
|
|
560
|
-
processed_schema = []
|
|
561
|
-
for field in schema:
|
|
562
|
-
processed_field = {
|
|
563
|
-
"name": str(field.get("name", "field")),
|
|
564
|
-
"type": str(field.get("type", "str")),
|
|
565
|
-
"description": str(field.get("description", "")),
|
|
566
|
-
"multiple": field.get("multiple", False),
|
|
567
|
-
}
|
|
568
|
-
# Ensure multiple is handled correctly
|
|
569
|
-
if isinstance(processed_field["multiple"], str):
|
|
570
|
-
processed_field["multiple"] = processed_field["multiple"].lower() in ["true", "1", "t", "y", "yes"]
|
|
571
|
-
processed_schema.append(processed_field)
|
|
572
|
-
return processed_schema
|
|
573
|
-
|
|
574
|
-
async def build_structured_output_base(self, content: str):
|
|
575
|
-
"""Build structured output with optional BaseModel validation.
|
|
576
|
-
|
|
577
|
-
This method parses JSON content from the agent response and optionally validates
|
|
578
|
-
it against a provided schema using Pydantic models.
|
|
579
|
-
|
|
580
|
-
Args:
|
|
581
|
-
content: The raw content from the agent response
|
|
582
|
-
|
|
583
|
-
Returns:
|
|
584
|
-
dict or list: Parsed and optionally validated JSON data
|
|
585
|
-
"""
|
|
586
|
-
# --- ADDED LOGGING START ---
|
|
587
|
-
logger.info(f"[CUGA] Attempting to build structured output from content: {content}")
|
|
588
|
-
# --- ADDED LOGGING END ---
|
|
589
|
-
|
|
590
|
-
json_pattern = r"\{.*\}"
|
|
591
|
-
schema_error_msg = "Try setting an output schema"
|
|
592
|
-
|
|
593
|
-
# Try to parse content as JSON first
|
|
594
|
-
json_data = None
|
|
595
|
-
try:
|
|
596
|
-
json_data = json.loads(content)
|
|
597
|
-
except json.JSONDecodeError:
|
|
598
|
-
json_match = re.search(json_pattern, content, re.DOTALL)
|
|
599
|
-
if json_match:
|
|
600
|
-
try:
|
|
601
|
-
json_data = json.loads(json_match.group())
|
|
602
|
-
except json.JSONDecodeError:
|
|
603
|
-
logger.warning("[CUGA] Could not parse content as JSON even with regex match.")
|
|
604
|
-
return {"content": content, "error": schema_error_msg}
|
|
605
|
-
else:
|
|
606
|
-
logger.warning("[CUGA] No JSON pattern found in the content.")
|
|
607
|
-
return {"content": content, "error": schema_error_msg}
|
|
608
|
-
|
|
609
|
-
# If no output schema provided, return parsed JSON without validation
|
|
610
|
-
if not hasattr(self, "output_schema") or not self.output_schema or len(self.output_schema) == 0:
|
|
611
|
-
logger.info("[CUGA] No output schema provided. Returning parsed JSON without validation.")
|
|
612
|
-
return json_data
|
|
613
|
-
|
|
614
|
-
# Use BaseModel validation with schema
|
|
615
|
-
try:
|
|
616
|
-
logger.info("[CUGA] Output schema detected. Validating structured output against schema.")
|
|
617
|
-
processed_schema = self._preprocess_schema(self.output_schema)
|
|
618
|
-
output_model = build_model_from_schema(processed_schema)
|
|
619
|
-
|
|
620
|
-
# Validate against the schema
|
|
621
|
-
if isinstance(json_data, list):
|
|
622
|
-
# Multiple objects
|
|
623
|
-
validated_objects = []
|
|
624
|
-
for item in json_data:
|
|
625
|
-
try:
|
|
626
|
-
validated_obj = output_model.model_validate(item)
|
|
627
|
-
validated_objects.append(validated_obj.model_dump())
|
|
628
|
-
except ValidationError as e:
|
|
629
|
-
await logger.aerror(f"[CUGA] Validation error for item: {e}")
|
|
630
|
-
validated_objects.append({"data": item, "validation_error": str(e)})
|
|
631
|
-
return validated_objects
|
|
632
|
-
|
|
633
|
-
# Single object
|
|
634
|
-
try:
|
|
635
|
-
validated_obj = output_model.model_validate(json_data)
|
|
636
|
-
return [validated_obj.model_dump()]
|
|
637
|
-
except ValidationError as e:
|
|
638
|
-
await logger.aerror(f"[CUGA] Validation error: {e}")
|
|
639
|
-
return [{"data": json_data, "validation_error": str(e)}]
|
|
640
|
-
|
|
641
|
-
except (TypeError, ValueError) as e:
|
|
642
|
-
await logger.aerror(f"[CUGA] Error building structured output: {e}")
|
|
643
|
-
return json_data
|
|
644
|
-
|
|
645
|
-
async def json_response(self) -> Data:
|
|
646
|
-
"""Convert agent response to structured JSON Data output with schema validation.
|
|
647
|
-
|
|
648
|
-
This method generates a structured JSON response by combining system instructions,
|
|
649
|
-
format instructions, and schema information, then processing the agent's response
|
|
650
|
-
through structured output validation.
|
|
651
|
-
|
|
652
|
-
Returns:
|
|
653
|
-
Data: Structured data object containing the validated JSON response
|
|
654
|
-
|
|
655
|
-
Raises:
|
|
656
|
-
ExceptionWithMessageError: If there's an error in structured processing
|
|
657
|
-
ValueError: If there's a validation error
|
|
658
|
-
TypeError: If there's a type error in processing
|
|
659
|
-
"""
|
|
660
|
-
# --- ADDED LOGGING START ---
|
|
661
|
-
logger.info("[CUGA] Starting Cuga agent run for json_response.")
|
|
662
|
-
logger.info(f"[CUGA] Agent input value: {self.input_value}")
|
|
663
|
-
# --- ADDED LOGGING END ---
|
|
664
|
-
|
|
665
|
-
try:
|
|
666
|
-
system_components = []
|
|
667
|
-
|
|
668
|
-
# 1. Agent Instructions
|
|
669
|
-
agent_instructions = getattr(self, "instructions", "") or ""
|
|
670
|
-
if agent_instructions:
|
|
671
|
-
system_components.append(f"{agent_instructions}")
|
|
672
|
-
|
|
673
|
-
# 3. Format Instructions
|
|
674
|
-
format_instructions = getattr(self, "format_instructions", "") or ""
|
|
675
|
-
if format_instructions:
|
|
676
|
-
system_components.append(f"Format instructions: {format_instructions}")
|
|
677
|
-
|
|
678
|
-
# 4. Schema Information
|
|
679
|
-
if hasattr(self, "output_schema") and self.output_schema and len(self.output_schema) > 0:
|
|
680
|
-
try:
|
|
681
|
-
processed_schema = self._preprocess_schema(self.output_schema)
|
|
682
|
-
output_model = build_model_from_schema(processed_schema)
|
|
683
|
-
schema_dict = output_model.model_json_schema()
|
|
684
|
-
schema_info = (
|
|
685
|
-
"You are given some text that may include format instructions, "
|
|
686
|
-
"explanations, or other content alongside a JSON schema.\n\n"
|
|
687
|
-
"Your task:\n"
|
|
688
|
-
"- Extract only the JSON schema.\n"
|
|
689
|
-
"- Return it as valid JSON.\n"
|
|
690
|
-
"- Do not include format instructions, explanations, or extra text.\n\n"
|
|
691
|
-
"Input:\n"
|
|
692
|
-
f"{json.dumps(schema_dict, indent=2)}\n\n"
|
|
693
|
-
"Output (only JSON schema):"
|
|
694
|
-
)
|
|
695
|
-
system_components.append(schema_info)
|
|
696
|
-
except (ValidationError, ValueError, TypeError, KeyError) as e:
|
|
697
|
-
await logger.aerror(f"[CUGA] Could not build schema for prompt: {e}", exc_info=True)
|
|
698
|
-
|
|
699
|
-
# Combine all components
|
|
700
|
-
combined_instructions = "\n\n".join(system_components) if system_components else ""
|
|
701
|
-
|
|
702
|
-
llm_model, self.chat_history, self.tools = await self.get_agent_requirements()
|
|
703
|
-
|
|
704
|
-
# Use call_agent for structured response
|
|
705
|
-
input_text = self.input_value.text if hasattr(self.input_value, "text") else str(self.input_value)
|
|
706
|
-
|
|
707
|
-
# Modify the input to include structured output requirements
|
|
708
|
-
structured_input = (
|
|
709
|
-
f"{combined_instructions}\n\nUser Input: {input_text}\n\nPlease provide a structured JSON response."
|
|
710
|
-
)
|
|
711
|
-
|
|
712
|
-
logger.info(f"[CUGA] Combined system prompt for structured agent: {combined_instructions}")
|
|
713
|
-
|
|
714
|
-
content = await self.call_agent(
|
|
715
|
-
current_input=structured_input,
|
|
716
|
-
tools=self.tools or [],
|
|
717
|
-
history_messages=self.chat_history,
|
|
718
|
-
llm=llm_model,
|
|
719
|
-
)
|
|
720
|
-
|
|
721
|
-
logger.info(f"[CUGA] Structured agent result: {content}")
|
|
722
|
-
|
|
723
|
-
except (ExceptionWithMessageError, ValueError, TypeError, NotImplementedError, AttributeError) as e:
|
|
724
|
-
await logger.aerror(f"[CUGA] Error with structured agent: {e}")
|
|
725
|
-
content_str = "No content returned from Cuga agent"
|
|
726
|
-
return Data(data={"content": content_str, "error": str(e)})
|
|
727
|
-
|
|
728
|
-
# Process with structured output validation
|
|
729
|
-
try:
|
|
730
|
-
structured_output = await self.build_structured_output_base(content)
|
|
731
|
-
|
|
732
|
-
# Handle different output formats
|
|
733
|
-
if isinstance(structured_output, list) and structured_output:
|
|
734
|
-
if len(structured_output) == 1:
|
|
735
|
-
logger.info("[CUGA] Structured output is a single object in a list.")
|
|
736
|
-
logger.info(f"[CUGA] Final structured output: {structured_output[0]}")
|
|
737
|
-
return Data(data=structured_output[0])
|
|
738
|
-
logger.info("[CUGA] Structured output is a list of multiple objects.")
|
|
739
|
-
logger.info(f"[CUGA] Final structured output: {structured_output}")
|
|
740
|
-
return Data(data={"results": structured_output})
|
|
741
|
-
if isinstance(structured_output, dict):
|
|
742
|
-
logger.info("[CUGA] Structured output is a single dictionary.")
|
|
743
|
-
logger.info(f"[CUGA] Final structured output: {structured_output}")
|
|
744
|
-
return Data(data=structured_output)
|
|
745
|
-
logger.info("[CUGA] Structured output is not a list or dictionary. Returning raw content.")
|
|
746
|
-
logger.info(f"[CUGA] Final output content: {content}")
|
|
747
|
-
return Data(data={"content": content})
|
|
748
|
-
|
|
749
|
-
except (ValueError, TypeError) as e:
|
|
750
|
-
await logger.aerror(f"[CUGA] Error in structured output processing: {e}")
|
|
751
|
-
return Data(data={"content": content, "error": str(e)})
|
|
752
|
-
|
|
753
|
-
async def get_memory_data(self):
|
|
754
|
-
"""Retrieve chat history messages.
|
|
755
|
-
|
|
756
|
-
This method fetches the conversation history from memory, excluding the current
|
|
757
|
-
input message to avoid duplication.
|
|
758
|
-
|
|
759
|
-
Returns:
|
|
760
|
-
list: List of Message objects representing the chat history
|
|
761
|
-
"""
|
|
762
|
-
logger.info("[CUGA] Retrieving chat history messages.")
|
|
763
|
-
messages = (
|
|
764
|
-
await MemoryComponent(**self.get_base_args())
|
|
765
|
-
.set(session_id=self.graph.session_id, order="Ascending", n_messages=self.n_messages)
|
|
766
|
-
.retrieve_messages()
|
|
767
|
-
)
|
|
768
|
-
return [
|
|
769
|
-
message for message in messages if getattr(message, "id", None) != getattr(self.input_value, "id", None)
|
|
770
|
-
]
|
|
771
|
-
|
|
772
|
-
async def get_llm(self):
|
|
773
|
-
"""Get language model for the Cuga agent.
|
|
774
|
-
|
|
775
|
-
This method initializes and configures the language model based on the
|
|
776
|
-
selected provider and parameters.
|
|
777
|
-
|
|
778
|
-
Returns:
|
|
779
|
-
tuple: A tuple containing (llm_model, display_name)
|
|
780
|
-
|
|
781
|
-
Raises:
|
|
782
|
-
ValueError: If the model provider is invalid or model initialization fails
|
|
783
|
-
"""
|
|
784
|
-
logger.info("[CUGA] Getting language model for the agent.")
|
|
785
|
-
logger.info(f"[CUGA] Requested LLM provider: {self.agent_llm}")
|
|
786
|
-
|
|
787
|
-
if not isinstance(self.agent_llm, str):
|
|
788
|
-
logger.info("[CUGA] Agent LLM is already a model instance.")
|
|
789
|
-
return self.agent_llm, None
|
|
790
|
-
|
|
791
|
-
try:
|
|
792
|
-
provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)
|
|
793
|
-
if not provider_info:
|
|
794
|
-
msg = f"Invalid model provider: {self.agent_llm}"
|
|
795
|
-
raise ValueError(msg)
|
|
796
|
-
|
|
797
|
-
component_class = provider_info.get("component_class")
|
|
798
|
-
display_name = component_class.display_name
|
|
799
|
-
inputs = provider_info.get("inputs")
|
|
800
|
-
prefix = provider_info.get("prefix", "")
|
|
801
|
-
logger.info(f"[CUGA] Successfully built LLM model from provider: {self.agent_llm}")
|
|
802
|
-
return self._build_llm_model(component_class, inputs, prefix), display_name
|
|
803
|
-
|
|
804
|
-
except (AttributeError, ValueError, TypeError, RuntimeError) as e:
|
|
805
|
-
await logger.aerror(f"[CUGA] Error building {self.agent_llm} language model: {e!s}")
|
|
806
|
-
msg = f"Failed to initialize language model: {e!s}"
|
|
807
|
-
raise ValueError(msg) from e
|
|
808
|
-
|
|
809
|
-
def _build_llm_model(self, component, inputs, prefix=""):
|
|
810
|
-
"""Build LLM model with parameters.
|
|
811
|
-
|
|
812
|
-
This method constructs a language model instance using the provided component
|
|
813
|
-
class and input parameters.
|
|
814
|
-
|
|
815
|
-
Args:
|
|
816
|
-
component: The LLM component class to instantiate
|
|
817
|
-
inputs: List of input field definitions
|
|
818
|
-
prefix: Optional prefix for parameter names
|
|
819
|
-
|
|
820
|
-
Returns:
|
|
821
|
-
The configured LLM model instance
|
|
822
|
-
"""
|
|
823
|
-
model_kwargs = {}
|
|
824
|
-
for input_ in inputs:
|
|
825
|
-
if hasattr(self, f"{prefix}{input_.name}"):
|
|
826
|
-
model_kwargs[input_.name] = getattr(self, f"{prefix}{input_.name}")
|
|
827
|
-
return component.set(**model_kwargs).build_model()
|
|
828
|
-
|
|
829
|
-
def set_component_params(self, component):
|
|
830
|
-
"""Set component parameters based on provider.
|
|
831
|
-
|
|
832
|
-
This method configures component parameters according to the selected
|
|
833
|
-
model provider's requirements.
|
|
834
|
-
|
|
835
|
-
Args:
|
|
836
|
-
component: The component to configure
|
|
837
|
-
|
|
838
|
-
Returns:
|
|
839
|
-
The configured component
|
|
840
|
-
"""
|
|
841
|
-
provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)
|
|
842
|
-
if provider_info:
|
|
843
|
-
inputs = provider_info.get("inputs")
|
|
844
|
-
prefix = provider_info.get("prefix")
|
|
845
|
-
model_kwargs = {}
|
|
846
|
-
for input_ in inputs:
|
|
847
|
-
if hasattr(self, f"{prefix}{input_.name}"):
|
|
848
|
-
model_kwargs[input_.name] = getattr(self, f"{prefix}{input_.name}")
|
|
849
|
-
return component.set(**model_kwargs)
|
|
850
|
-
return component
|
|
851
|
-
|
|
852
|
-
def delete_fields(self, build_config: dotdict, fields: dict | list[str]) -> None:
|
|
853
|
-
"""Delete specified fields from build_config.
|
|
854
|
-
|
|
855
|
-
This method removes unwanted fields from the build configuration.
|
|
856
|
-
|
|
857
|
-
Args:
|
|
858
|
-
build_config: The build configuration dictionary
|
|
859
|
-
fields: Fields to remove (can be dict or list of strings)
|
|
860
|
-
"""
|
|
861
|
-
for field in fields:
|
|
862
|
-
build_config.pop(field, None)
|
|
863
|
-
|
|
864
|
-
def update_input_types(self, build_config: dotdict) -> dotdict:
|
|
865
|
-
"""Update input types for all fields in build_config.
|
|
866
|
-
|
|
867
|
-
This method ensures all fields in the build configuration have proper
|
|
868
|
-
input types defined.
|
|
869
|
-
|
|
870
|
-
Args:
|
|
871
|
-
build_config: The build configuration to update
|
|
872
|
-
|
|
873
|
-
Returns:
|
|
874
|
-
dotdict: Updated build configuration with input types
|
|
875
|
-
"""
|
|
876
|
-
for key, value in build_config.items():
|
|
877
|
-
if isinstance(value, dict):
|
|
878
|
-
if value.get("input_types") is None:
|
|
879
|
-
build_config[key]["input_types"] = []
|
|
880
|
-
elif hasattr(value, "input_types") and value.input_types is None:
|
|
881
|
-
value.input_types = []
|
|
882
|
-
return build_config
|
|
883
|
-
|
|
884
|
-
async def update_build_config(
|
|
885
|
-
self, build_config: dotdict, field_value: str, field_name: str | None = None
|
|
886
|
-
) -> dotdict:
|
|
887
|
-
"""Update build configuration based on field changes.
|
|
888
|
-
|
|
889
|
-
This method dynamically updates the component's build configuration when
|
|
890
|
-
certain fields change, particularly the model provider selection.
|
|
891
|
-
|
|
892
|
-
Args:
|
|
893
|
-
build_config: The current build configuration
|
|
894
|
-
field_value: The new value for the field
|
|
895
|
-
field_name: The name of the field being changed
|
|
896
|
-
|
|
897
|
-
Returns:
|
|
898
|
-
dotdict: Updated build configuration
|
|
899
|
-
|
|
900
|
-
Raises:
|
|
901
|
-
ValueError: If required keys are missing from the configuration
|
|
902
|
-
"""
|
|
903
|
-
if field_name in ("agent_llm",):
|
|
904
|
-
build_config["agent_llm"]["value"] = field_value
|
|
905
|
-
provider_info = MODEL_PROVIDERS_DICT.get(field_value)
|
|
906
|
-
if provider_info:
|
|
907
|
-
component_class = provider_info.get("component_class")
|
|
908
|
-
if component_class and hasattr(component_class, "update_build_config"):
|
|
909
|
-
build_config = await update_component_build_config(
|
|
910
|
-
component_class, build_config, field_value, "model_name"
|
|
911
|
-
)
|
|
912
|
-
|
|
913
|
-
provider_configs: dict[str, tuple[dict, list[dict]]] = {
|
|
914
|
-
provider: (
|
|
915
|
-
MODEL_PROVIDERS_DICT[provider]["fields"],
|
|
916
|
-
[
|
|
917
|
-
MODEL_PROVIDERS_DICT[other_provider]["fields"]
|
|
918
|
-
for other_provider in MODEL_PROVIDERS_DICT
|
|
919
|
-
if other_provider != provider
|
|
920
|
-
],
|
|
921
|
-
)
|
|
922
|
-
for provider in MODEL_PROVIDERS_DICT
|
|
923
|
-
}
|
|
924
|
-
if field_value in provider_configs:
|
|
925
|
-
fields_to_add, fields_to_delete = provider_configs[field_value]
|
|
926
|
-
|
|
927
|
-
# Delete fields from other providers
|
|
928
|
-
for fields in fields_to_delete:
|
|
929
|
-
self.delete_fields(build_config, fields)
|
|
930
|
-
|
|
931
|
-
# Add provider-specific fields
|
|
932
|
-
if field_value == "OpenAI" and not any(field in build_config for field in fields_to_add):
|
|
933
|
-
build_config.update(fields_to_add)
|
|
934
|
-
else:
|
|
935
|
-
build_config.update(fields_to_add)
|
|
936
|
-
build_config["agent_llm"]["input_types"] = []
|
|
937
|
-
elif field_value == "Custom":
|
|
938
|
-
# Delete all provider fields
|
|
939
|
-
self.delete_fields(build_config, ALL_PROVIDER_FIELDS)
|
|
940
|
-
# Update with custom component
|
|
941
|
-
custom_component = DropdownInput(
|
|
942
|
-
name="agent_llm",
|
|
943
|
-
display_name="Language Model",
|
|
944
|
-
options=[*sorted(MODEL_PROVIDERS), "Custom"],
|
|
945
|
-
value="Custom",
|
|
946
|
-
real_time_refresh=True,
|
|
947
|
-
input_types=["LanguageModel"],
|
|
948
|
-
options_metadata=[MODELS_METADATA[key] for key in sorted(MODELS_METADATA.keys())]
|
|
949
|
-
+ [{"icon": "brain"}],
|
|
950
|
-
)
|
|
951
|
-
build_config.update({"agent_llm": custom_component.to_dict()})
|
|
952
|
-
|
|
953
|
-
# Update input types for all fields
|
|
954
|
-
build_config = self.update_input_types(build_config)
|
|
955
|
-
|
|
956
|
-
# Validate required keys
|
|
957
|
-
default_keys = [
|
|
958
|
-
"code",
|
|
959
|
-
"_type",
|
|
960
|
-
"agent_llm",
|
|
961
|
-
"tools",
|
|
962
|
-
"input_value",
|
|
963
|
-
"add_current_date_tool",
|
|
964
|
-
"policies",
|
|
965
|
-
"agent_description",
|
|
966
|
-
"max_iterations",
|
|
967
|
-
"handle_parsing_errors",
|
|
968
|
-
"verbose",
|
|
969
|
-
]
|
|
970
|
-
missing_keys = [key for key in default_keys if key not in build_config]
|
|
971
|
-
if missing_keys:
|
|
972
|
-
msg = f"Missing required keys in build_config: {missing_keys}"
|
|
973
|
-
raise ValueError(msg)
|
|
974
|
-
|
|
975
|
-
if (
|
|
976
|
-
isinstance(self.agent_llm, str)
|
|
977
|
-
and self.agent_llm in MODEL_PROVIDERS_DICT
|
|
978
|
-
and field_name in MODEL_DYNAMIC_UPDATE_FIELDS
|
|
979
|
-
):
|
|
980
|
-
provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)
|
|
981
|
-
if provider_info:
|
|
982
|
-
component_class = provider_info.get("component_class")
|
|
983
|
-
component_class = self.set_component_params(component_class)
|
|
984
|
-
prefix = provider_info.get("prefix")
|
|
985
|
-
if component_class and hasattr(component_class, "update_build_config"):
|
|
986
|
-
if isinstance(field_name, str) and isinstance(prefix, str):
|
|
987
|
-
field_name = field_name.replace(prefix, "")
|
|
988
|
-
build_config = await update_component_build_config(
|
|
989
|
-
component_class, build_config, field_value, "model_name"
|
|
990
|
-
)
|
|
991
|
-
return dotdict({k: v.to_dict() if hasattr(v, "to_dict") else v for k, v in build_config.items()})
|
|
992
|
-
|
|
993
|
-
async def _get_tools(self) -> list[Tool]:
|
|
994
|
-
"""Build agent tools.
|
|
995
|
-
|
|
996
|
-
This method constructs the list of tools available to the Cuga agent,
|
|
997
|
-
including component tools and any additional configured tools.
|
|
998
|
-
|
|
999
|
-
Returns:
|
|
1000
|
-
list[Tool]: List of available tools for the agent
|
|
1001
|
-
"""
|
|
1002
|
-
logger.info("[CUGA] Building agent tools.")
|
|
1003
|
-
component_toolkit = _get_component_toolkit()
|
|
1004
|
-
tools_names = self._build_tools_names()
|
|
1005
|
-
agent_description = self.get_tool_description()
|
|
1006
|
-
description = f"{agent_description}{tools_names}"
|
|
1007
|
-
tools = component_toolkit(component=self).get_tools(
|
|
1008
|
-
tool_name="Call_CugaAgent", tool_description=description, callbacks=self.get_langchain_callbacks()
|
|
1009
|
-
)
|
|
1010
|
-
if hasattr(self, "tools_metadata"):
|
|
1011
|
-
tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)
|
|
1012
|
-
logger.info(f"[CUGA] Tools built: {[tool.name for tool in tools]}")
|
|
1013
|
-
return tools
|