feat: add CUGA integration with agent component and tests#9907
Conversation
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the WalkthroughIntroduces a new Cuga agent component with provider-based LLM setup, memory/tools integration, event-driven execution, and structured JSON output support. Exposes CugaComponent via agents package. Adds comprehensive unit tests covering configuration, outputs, JSON/schema handling, and client scenarios. Updates pyproject to include a new dependency. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant CugaComponent
participant LLM
participant Tools
participant Memory
User->>CugaComponent: message_response()
CugaComponent->>CugaComponent: get_agent_requirements()
CugaComponent->>LLM: get_llm()
CugaComponent->>Memory: get_memory_data()
CugaComponent->>Tools: _get_tools()
note over CugaComponent: Assemble history, tools, llm
CugaComponent->>CugaComponent: call_agent(input, tools, history, llm)
activate CugaComponent
CugaComponent-->>User: on_chain_start (event)
alt tool invoked
CugaComponent-->>User: on_tool_start (event)
Tools-->>CugaComponent: tool result
CugaComponent-->>User: on_tool_end (event)
end
CugaComponent-->>User: on_chain_end (AgentFinish)
deactivate CugaComponent
CugaComponent-->>User: AI Message
sequenceDiagram
autonumber
actor User
participant CugaComponent
participant LLM
participant Validator as Schema Validator
User->>CugaComponent: json_response()
CugaComponent->>CugaComponent: build prompt (instructions + format + schema)
CugaComponent->>LLM: call_agent(...)
CugaComponent-->>CugaComponent: receive content
CugaComponent->>Validator: build_structured_output_base(content, schema?)
alt valid JSON (matches schema)
Validator-->>CugaComponent: validated object(s)
CugaComponent-->>User: Data (structured)
else invalid/absent JSON
Validator-->>CugaComponent: error structure
CugaComponent-->>User: Data (error details)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60–90 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (13)
src/lfx/src/lfx/components/agents/cuga_agent.py (10)
211-211: Suspicious env var name (likely typo).
DYNA_ADVANCED_FEATURES__REGISRYlooks misspelled (“REGISTRY”). Confirm expected key; otherwise features won’t toggle.- os.environ["DYNA_ADVANCED_FEATURES__REGISRY"] = "false" + os.environ["DYNA_ADVANCED_FEATURES__REGISTRY"] = "false"If the original is required by CUGA, ignore this change.
285-287: Type annotation assigns None to non‑optional.
last_event: StreamEvent = Noneis invalid for type checkers.- last_event: StreamEvent = None + last_event: StreamEvent | None = None + tool_run_id: str | None = None
288-288: Nit: log message typo.“recieved” → “received”.
- logger.debug(f"recieved event {event}") + logger.debug(f"received event {event}")
310-319: Missing final on_tool_end for the last StreamEvent.If the stream ends without another event, the last tool never emits
on_tool_end.async for event in cuga_agent.run_task_generic_yield(eval_mode=False, goal=current_input): ... - logger.info(f"[CUGA] Generated response: {final_response}") + logger.info(f"[CUGA] Generated response.") + # Close the last tool if pending + if last_event is not None and tool_run_id is not None: + try: + data_dict = json.loads(last_event.data) + except json.JSONDecodeError: + data_dict = last_event.data + if getattr(last_event, "name", "") == "CodeAgent": + data_dict = data_dict.get("code", data_dict) + yield { + "event": "on_tool_end", + "run_id": tool_run_id, + "name": last_event.name, + "data": {"output": data_dict}, + }
469-479: Schema ‘multiple’ normalization is good; consider normalizing unknown keys.Optional: strip unknown keys to avoid pydantic schema surprises.
No code change required now.
487-505: Naive JSON extraction; prefer robust repair before regex.Use json_repair to salvage near‑JSON and reduce false matches from greedy
{.*}.- json_pattern = r"\{.*\}" + json_pattern = r"\{.*\}" schema_error_msg = "Try setting an output schema" @@ - try: - json_data = json.loads(content) - except json.JSONDecodeError: - json_match = re.search(json_pattern, content, re.DOTALL) + try: + json_data = json.loads(content) + except json.JSONDecodeError: + from json_repair import repair_json + try: + json_data = json.loads(repair_json(content)) + except Exception: + json_match = re.search(json_pattern, content, re.DOTALL) if json_match: try: json_data = json.loads(json_match.group()) except json.JSONDecodeError: logger.warning("[CUGA] Could not parse content as JSON even with regex match.") return {"content": content, "error": schema_error_msg} else: logger.warning("[CUGA] No JSON pattern found in the content.") return {"content": content, "error": schema_error_msg}
411-415: Tool metadata log may raise AttributeError.Not all Tools expose
.metadata. Usegetattr.- logger.info(f"[CUGA] metadata: {[tool.metadata for tool in self.tools]}") + logger.info(f"[CUGA] metadata: {[getattr(tool, 'metadata', None) for tool in self.tools]}")
689-694: Prefix may be None; default to empty string.Avoid
"Nonename"attribute lookups.- prefix = provider_info.get("prefix") + prefix = provider_info.get("prefix", "") model_kwargs = {} for input_ in inputs: if hasattr(self, f"{prefix}{input_.name}"): model_kwargs[input_.name] = getattr(self, f"{prefix}{input_.name}")
57-59: Icon mapping case consistency.Class
icon = "bot"vs message property"icon": "Bot". Align with the frontend’s exact mapping to avoid missing icons.- icon = "bot" + icon = "Bot"If the frontend expects lowercase, invert the change and update the message property instead.
245-256: Unused variables and dead code.
lc_messages,tools_used, andfinal_responseare unused. Trim to reduce noise.No diff provided to keep this PR focused.
src/backend/tests/unit/components/agents/test_cuga_agent.py (3)
47-84: Add brief docstring to explain intent.A one‑liner docstring improves test clarity and meets repo guidelines.
- async def test_build_config_update(self, component_class, default_kwargs): + async def test_build_config_update(self, component_class, default_kwargs): + """Validate provider switching and model_name field propagation for OpenAI/Custom."""
329-333: TODO left unimplemented.Add an assertion for memory inputs advanced flag or mark with xfail until implemented.
I can add a focused test once we settle expected placement of
memory_inputs.
427-430: Unimplemented structured response test.Mark xfail with reason or implement. Currently it always passes without coverage.
- async def test_cuga_structured_response_with_schema(self): - # TODO: Add test for structured response with schema - pass + @pytest.mark.xfail(reason="Structured response with schema not implemented yet") + async def test_cuga_structured_response_with_schema(self): + """Pending: end-to-end structured output with real agent events.""" + ...
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (4)
pyproject.toml(1 hunks)src/backend/tests/unit/components/agents/test_cuga_agent.py(1 hunks)src/lfx/src/lfx/components/agents/__init__.py(1 hunks)src/lfx/src/lfx/components/agents/cuga_agent.py(1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
src/backend/tests/unit/components/**/*.py
📄 CodeRabbit inference engine (.cursor/rules/backend_development.mdc)
src/backend/tests/unit/components/**/*.py: Mirror the component directory structure for unit tests in src/backend/tests/unit/components/
Use ComponentTestBaseWithClient or ComponentTestBaseWithoutClient as base classes for component unit tests
Provide file_names_mapping for backward compatibility in component tests
Create comprehensive unit tests for all new components
Files:
src/backend/tests/unit/components/agents/test_cuga_agent.py
{src/backend/**/*.py,tests/**/*.py,Makefile}
📄 CodeRabbit inference engine (.cursor/rules/backend_development.mdc)
{src/backend/**/*.py,tests/**/*.py,Makefile}: Run make format_backend to format Python code before linting or committing changes
Run make lint to perform linting checks on backend Python code
Files:
src/backend/tests/unit/components/agents/test_cuga_agent.py
src/backend/tests/unit/**/*.py
📄 CodeRabbit inference engine (.cursor/rules/backend_development.mdc)
Test component integration within flows using create_flow, build_flow, and get_build_events utilities
Files:
src/backend/tests/unit/components/agents/test_cuga_agent.py
src/backend/tests/**/*.py
📄 CodeRabbit inference engine (.cursor/rules/testing.mdc)
src/backend/tests/**/*.py: Unit tests for backend code must be located in the 'src/backend/tests/' directory, with component tests organized by component subdirectory under 'src/backend/tests/unit/components/'.
Test files should use the same filename as the component under test, with an appropriate test prefix or suffix (e.g., 'my_component.py' → 'test_my_component.py').
Use the 'client' fixture (an async httpx.AsyncClient) for API tests in backend Python tests, as defined in 'src/backend/tests/conftest.py'.
When writing component tests, inherit from the appropriate base class in 'src/backend/tests/base.py' (ComponentTestBase, ComponentTestBaseWithClient, or ComponentTestBaseWithoutClient) and provide the required fixtures: 'component_class', 'default_kwargs', and 'file_names_mapping'.
Each test in backend Python test files should have a clear docstring explaining its purpose, and complex setups or mocks should be well-commented.
Test both sync and async code paths in backend Python tests, using '@pytest.mark.asyncio' for async tests.
Mock external dependencies appropriately in backend Python tests to isolate unit tests from external services.
Test error handling and edge cases in backend Python tests, including using 'pytest.raises' and asserting error messages.
Validate input/output behavior and test component initialization and configuration in backend Python tests.
Use the 'no_blockbuster' pytest marker to skip the blockbuster plugin in tests when necessary.
Be aware of ContextVar propagation in async tests; test both direct event loop execution and 'asyncio.to_thread' scenarios to ensure proper context isolation.
Test error handling by mocking internal functions using monkeypatch in backend Python tests.
Test resource cleanup in backend Python tests by using fixtures that ensure proper initialization and cleanup of resources.
Test timeout and performance constraints in backend Python tests using 'asyncio.wait_for' and timing assertions.
Test Langflow's Messag...
Files:
src/backend/tests/unit/components/agents/test_cuga_agent.py
src/backend/**/components/**/*.py
📄 CodeRabbit inference engine (.cursor/rules/icons.mdc)
In your Python component class, set the
iconattribute to a string matching the frontend icon mapping exactly (case-sensitive).
Files:
src/backend/tests/unit/components/agents/test_cuga_agent.py
🧠 Learnings (1)
📚 Learning: 2025-07-18T18:25:54.486Z
Learnt from: CR
PR: langflow-ai/langflow#0
File: .cursor/rules/backend_development.mdc:0-0
Timestamp: 2025-07-18T18:25:54.486Z
Learning: Applies to src/backend/tests/unit/components/**/*.py : Create comprehensive unit tests for all new components
Applied to files:
src/backend/tests/unit/components/agents/test_cuga_agent.py
🧬 Code graph analysis (3)
src/lfx/src/lfx/components/agents/cuga_agent.py (4)
src/backend/base/langflow/schema/table.py (1)
EditMode(27-30)src/lfx/src/lfx/base/agents/agent.py (3)
LCToolsAgentComponent(219-268)_build_tools_names(251-255)get_tool_description(248-249)src/lfx/src/lfx/custom/custom_component/component.py (4)
_get_component_toolkit(1806-1809)log(1475-1492)get_base_args(268-281)to_toolkit(1314-1335)src/backend/base/langflow/schema/log.py (1)
SendMessageFunctionType(16-29)
src/lfx/src/lfx/components/agents/__init__.py (1)
src/lfx/src/lfx/components/agents/cuga_agent.py (1)
CugaComponent(53-819)
src/backend/tests/unit/components/agents/test_cuga_agent.py (1)
src/lfx/src/lfx/components/agents/cuga_agent.py (8)
CugaComponent(53-819)get_agent_requirements(386-417)call_agent(196-335)json_response(542-635)set_component_params(684-695)_preprocess_schema(465-479)build_structured_output_base(481-540)message_response(338-384)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
- GitHub Check: Lint Backend / Run Mypy (3.11)
- GitHub Check: Lint Backend / Run Mypy (3.12)
- GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 4
- GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 1
- GitHub Check: Lint Backend / Run Mypy (3.10)
- GitHub Check: Lint Backend / Run Mypy (3.13)
- GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 5
- GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 3
- GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 2
- GitHub Check: Run Backend Tests / Integration Tests - Python 3.10
- GitHub Check: Test Starter Templates
- GitHub Check: Update Starter Projects
- GitHub Check: Run Ruff Check and Format
- GitHub Check: Ruff Style Check (3.13)
🔇 Additional comments (2)
src/lfx/src/lfx/components/agents/__init__.py (1)
12-19: LGTM: clean lazy export wiring for CugaComponent.Import map, TYPE_CHECKING, and all are consistent. No action needed.
src/backend/tests/unit/components/agents/test_cuga_agent.py (1)
437-447: Policies may be treated as file path by CUGA.
InstructionsManager.set_instructions_from_one_file(self.policies)suggests a path; herepoliciesis free text. Confirm API accepts raw text, or adapt to a text setter.If a file path is required, we should write temp content in the test and pass the path.
| "aiosqlite==0.21.0", | ||
| "fastparquet>=2024.11.0", | ||
| "traceloop-sdk>=0.43.1", | ||
| "cuga==0.1.1.dev1", |
There was a problem hiding this comment.
🛠️ Refactor suggestion
🧩 Analysis chain
Avoid pinning to a dev pre-release for a core install; gate or constrain safely.
cuga==0.1.1.dev1 is a dev pre-release. Pulling this by default increases supply‑chain risk and build flakiness. Prefer:
- Make it optional (extras) or feature‑gated, or
- Constrain with an upper bound and allow patch updates, and
- Document the extra in CI and dev docs.
Example options:
- "cuga==0.1.1.dev1",
+ # CUGA integration (optional)
+ # Move to an extra to avoid pulling a dev pre-release for all users.Or move to an extra:
[project.optional-dependencies]
+cuga = [
+ "cuga>=0.1.1.dev1,<0.2.0",
+]Verification script (checks availability and latest on PyPI):
🏁 Script executed:
#!/bin/bash
curl -s https://pypi.org/pypi/cuga/json | jq '.info.version, .releases | keys | .[-10:]'Length of output: 54
🏁 Script executed:
#!/bin/bash
set -euo pipefail
curl -sSf https://pypi.org/pypi/cuga/json | jq -r '.info.version as $v | {latest: $v, has_requested_dev: (.releases | has("0.1.1.dev1")), recent_releases: (.releases | keys | sort | .[-20:]) }'Length of output: 128
Remove pinned dev pre-release cuga==0.1.1.dev1 from core install
PyPI shows latest stable 0.1.0 and 0.1.1.dev1 is a dev pre-release; do not pull it by default.
Location: pyproject.toml — line 132.
- "cuga==0.1.1.dev1",
+ # CUGA integration (optional)
+ # Move to an extra to avoid pulling a dev pre-release for all users. [project.optional-dependencies]
+cuga = [
+ "cuga>=0.1.1.dev1,<0.2.0",
+]📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "cuga==0.1.1.dev1", | |
| # CUGA integration (optional) | |
| # Move to an extra to avoid pulling a dev pre-release for all users. | |
| [project.optional-dependencies] | |
| cuga = [ | |
| "cuga>=0.1.1.dev1,<0.2.0", | |
| ] |
🤖 Prompt for AI Agents
In pyproject.toml around line 132, remove the pinned development pre-release
dependency "cuga==0.1.1.dev1" from the core install and replace it with either
the stable release specifier (for example "cuga==0.1.0") or a permissive range
(for example "cuga>=0.1.0,<0.2.0") as appropriate for the project policy; update
any corresponding lock or dependency files and run dependency tooling
(poetry/pip) to verify the change.
| component.get_agent_requirements = AsyncMock(return_value=(MockLanguageModel(), [], [])) | ||
| component.call_agent = AsyncMock(return_value='{"name": "test", "value": 123}') | ||
|
|
||
| result = await component.json_response() |
There was a problem hiding this comment.
Fix mock to match async-iterator contract of call_agent.
Tests stub call_agent to return a string, masking a real bug. Provide an async generator yielding an on_chain_end with AgentFinish.
- component.get_agent_requirements = AsyncMock(return_value=(MockLanguageModel(), [], []))
- component.call_agent = AsyncMock(return_value='{"name": "test", "value": 123}')
+ component.get_agent_requirements = AsyncMock(return_value=(MockLanguageModel(), [], []))
+ from langchain_core.agents import AgentFinish
+ async def fake_events():
+ yield {
+ "event": "on_chain_end",
+ "run_id": "r1",
+ "name": "CugaAgent",
+ "data": {"output": AgentFinish(return_values={"output": '{"name":"test","value":123}'}, log="")}
+ }
+ component.call_agent = fake_events📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| component.get_agent_requirements = AsyncMock(return_value=(MockLanguageModel(), [], [])) | |
| component.call_agent = AsyncMock(return_value='{"name": "test", "value": 123}') | |
| result = await component.json_response() | |
| component.get_agent_requirements = AsyncMock(return_value=(MockLanguageModel(), [], [])) | |
| from langchain_core.agents import AgentFinish | |
| async def fake_events(): | |
| yield { | |
| "event": "on_chain_end", | |
| "run_id": "r1", | |
| "name": "CugaAgent", | |
| "data": {"output": AgentFinish(return_values={"output": '{"name":"test","value":123}'}, log="")} | |
| } | |
| component.call_agent = fake_events | |
| result = await component.json_response() |
🤖 Prompt for AI Agents
In src/backend/tests/unit/components/agents/test_cuga_agent.py around lines 118
to 121, the test currently stubs call_agent to return a plain string which hides
a bug; replace that stub with an async generator that yields a single
on_chain_end event carrying an AgentFinish instance (constructed with the
appropriate return_values/output used by json_response), and assign that
generator as the AsyncMock side effect (or directly set component.call_agent to
the async generator). Ensure AgentFinish is imported in the test and that
json_response iterates/consumes the async iterator as in real code.
| component.get_agent_requirements = AsyncMock(return_value=(MockLanguageModel(), [], [])) | ||
| component.call_agent = AsyncMock(return_value='Here is the result: {"status": "success"} - done!') | ||
|
|
There was a problem hiding this comment.
Same: align mock with async event stream.
- component.get_agent_requirements = AsyncMock(return_value=(MockLanguageModel(), [], []))
- component.call_agent = AsyncMock(return_value='Here is the result: {"status": "success"} - done!')
+ component.get_agent_requirements = AsyncMock(return_value=(MockLanguageModel(), [], []))
+ from langchain_core.agents import AgentFinish
+ async def fake_events():
+ yield {
+ "event": "on_chain_end",
+ "run_id": "r2",
+ "name": "CugaAgent",
+ "data": {"output": AgentFinish(return_values={"output": 'Here is the result: {"status": "success"} - done!'}, log="")}
+ }
+ component.call_agent = fake_eventsCommittable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/backend/tests/unit/components/agents/test_cuga_agent.py around lines
134-136, the component.call_agent mock currently returns a plain string but the
real implementation yields an async event stream; change the mock to return an
async generator (or AsyncMock side_effect that is an async generator function)
that yields the same pieces of output as stream events (e.g., yield the result
payload and a final done event) so the test consumes it with async for/await
like production code expects.
| component.get_agent_requirements = AsyncMock(return_value=(MockLanguageModel(), [], [])) | ||
| component.call_agent = AsyncMock(return_value="This is just plain text with no JSON") | ||
|
|
There was a problem hiding this comment.
Same: align mock with async event stream.
- component.get_agent_requirements = AsyncMock(return_value=(MockLanguageModel(), [], []))
- component.call_agent = AsyncMock(return_value="This is just plain text with no JSON")
+ component.get_agent_requirements = AsyncMock(return_value=(MockLanguageModel(), [], []))
+ from langchain_core.agents import AgentFinish
+ async def fake_events():
+ yield {
+ "event": "on_chain_end",
+ "run_id": "r3",
+ "name": "CugaAgent",
+ "data": {"output": AgentFinish(return_values={"output": "This is just plain text with no JSON"}, log="")}
+ }
+ component.call_agent = fake_events📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| component.get_agent_requirements = AsyncMock(return_value=(MockLanguageModel(), [], [])) | |
| component.call_agent = AsyncMock(return_value="This is just plain text with no JSON") | |
| component.get_agent_requirements = AsyncMock(return_value=(MockLanguageModel(), [], [])) | |
| from langchain_core.agents import AgentFinish | |
| async def fake_events(): | |
| yield { | |
| "event": "on_chain_end", | |
| "run_id": "r3", | |
| "name": "CugaAgent", | |
| "data": {"output": AgentFinish(return_values={"output": "This is just plain text with no JSON"}, log="")} | |
| } | |
| component.call_agent = fake_events |
🤖 Prompt for AI Agents
In src/backend/tests/unit/components/agents/test_cuga_agent.py around lines 150
to 152, the mocked component.call_agent currently returns a plain string but the
production code expects an async event stream; change the mock to return an
async iterator/async generator that yields the same sequence of events the real
agent would (e.g., events with type/data or content chunks), or use a provided
helper to convert a list of event dicts to an async stream; update the AsyncMock
return_value to that async generator so the test consumes the stream as the real
code does.
| if current_input: | ||
| os.environ["DYNA_ADVANCED_FEATURES__REGISRY"] = "false" | ||
| from cuga.backend.activity_tracker.tracker import ActivityTracker | ||
| from cuga.backend.cuga_graph.utils.agent_loop import StreamEvent | ||
| from cuga.backend.cuga_graph.utils.controller import ( | ||
| AgentRunner as CugaAgent, | ||
| ) | ||
| from cuga.backend.cuga_graph.utils.controller import ( | ||
| ExperimentResult as AgentResult, | ||
| ) | ||
| from cuga.backend.llm.models import LLMManager | ||
| from cuga.configurations.instructions_manager import InstructionsManager | ||
|
|
||
| llm_manager = LLMManager() | ||
| llm_manager.set_llm(llm) | ||
| instructions_manager = InstructionsManager() | ||
| if self.policies: | ||
| logger.info(f"policies are: {self.policies}") | ||
| instructions_manager.set_instructions_from_one_file(self.policies) | ||
| tracker = ActivityTracker() | ||
| tracker.set_tools(tools) | ||
| cuga_agent = CugaAgent(browser_enabled=self.browser_enabled) | ||
| if self.browser_enabled: | ||
| await cuga_agent.initialize_freemode_env(start_url=self.web_apps.strip(), interface_mode="browser_only") | ||
| else: | ||
| await cuga_agent.initialize_appworld_env() | ||
| yield { |
There was a problem hiding this comment.
Potential UnboundLocalError when input is empty.
If current_input is falsy, cuga_agent isn’t initialized but later used. Guard or early‑return.
- if current_input:
+ if current_input:
...
- yield {
+ else:
+ yield {
+ "event": "on_chain_end",
+ "run_id": str(uuid.uuid4()),
+ "name": "CugaAgent",
+ "data": {"output": AgentFinish(return_values={"output": ""}, log="")},
+ }
+ return
+ yield {Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/lfx/src/lfx/components/agents/cuga_agent.py around lines 210 to 236,
there is a potential UnboundLocalError because cuga_agent (and related locals
like tracker, instructions_manager, llm_manager) are only created inside the if
current_input block but later code/yield assumes they exist; fix by either
returning/raising early when current_input is falsy (so downstream code won't
run) or by initializing the required variables to safe defaults (e.g.,
cuga_agent = None, tracker = None, instructions_manager = None, llm_manager =
None) before the if and then guard any later use with explicit checks that raise
a clear error if they remain uninitialized; choose the early-return approach if
no further processing should occur without input, otherwise initialize and add
guards where those variables are consumed.
| # Simulate browser tool usage | ||
| if getattr(self, "BROWSER", False) and any( | ||
| word in current_input.lower() for word in ["search", "web", "browse"] | ||
| ): | ||
| tool_run_id = str(uuid.uuid4()) |
There was a problem hiding this comment.
Wrong flag checked for browser path.
The code checks self.BROWSER but the input is browser_enabled. Browser tool never triggers.
- if getattr(self, "BROWSER", False) and any(
+ if bool(getattr(self, "browser_enabled", False)) and any(
word in current_input.lower() for word in ["search", "web", "browse"]
):📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # Simulate browser tool usage | |
| if getattr(self, "BROWSER", False) and any( | |
| word in current_input.lower() for word in ["search", "web", "browse"] | |
| ): | |
| tool_run_id = str(uuid.uuid4()) | |
| # Simulate browser tool usage | |
| if bool(getattr(self, "browser_enabled", False)) and any( | |
| word in current_input.lower() for word in ["search", "web", "browse"] | |
| ): | |
| tool_run_id = str(uuid.uuid4()) |
🤖 Prompt for AI Agents
In src/lfx/src/lfx/components/agents/cuga_agent.py around lines 257 to 261, the
browser path incorrectly checks self.BROWSER so the browser tool never triggers;
change the check to use getattr(self, "browser_enabled", False) (or check both
keys for backward compatibility like getattr(self, "browser_enabled", False) or
getattr(self, "BROWSER", False)) so the condition uses the actual flag, keeping
the rest of the logic the same.
| logger.info(f"[CUGA] Combined system prompt for structured agent: {combined_instructions}") | ||
|
|
||
| content = await self.call_agent( | ||
| current_input=structured_input, | ||
| tools=self.tools or [], | ||
| history_messages=self.chat_history, | ||
| llm=llm_model, | ||
| ) | ||
|
|
||
| logger.info(f"[CUGA] Structured agent result: {content}") | ||
|
|
There was a problem hiding this comment.
Bug: awaiting an async generator causes a TypeError; json_response never collects content.
call_agent returns an async iterator of events, but here it’s awaited as if it returned a string. This will raise at runtime and tests only pass because they mock call_agent to return a string. Process the iterator (like in message_response) and extract final text before schema validation.
Apply:
- content = await self.call_agent(
- current_input=structured_input,
- tools=self.tools or [],
- history_messages=self.chat_history,
- llm=llm_model,
- )
-
- logger.info(f"[CUGA] Structured agent result: {content}")
+ from lfx.schema.content_block import ContentBlock
+ from lfx.schema.message import MESSAGE_SENDER_AI
+ from lfx.base.agents.events import process_agent_events
+
+ agent_message = Message(
+ sender=MESSAGE_SENDER_AI,
+ sender_name="Cuga",
+ properties={"icon": "Bot", "state": "partial"},
+ content_blocks=[ContentBlock(title="Agent Steps", contents=[])],
+ session_id=self.graph.session_id,
+ )
+ event_iterator = self.call_agent(
+ current_input=structured_input,
+ tools=self.tools or [],
+ history_messages=self.chat_history,
+ llm=llm_model,
+ )
+ result_msg = await process_agent_events(
+ event_iterator, agent_message, cast("SendMessageFunctionType", self.send_message)
+ )
+ content = (result_msg.data or {}).get("text") or ""
+ logger.info(f"[CUGA] Structured agent result collected.")Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/lfx/src/lfx/components/agents/cuga_agent.py around lines 596 to 606, the
code awaits call_agent as if it returns a final string but call_agent actually
yields an async iterator; awaiting it will raise a TypeError and json_response
never receives aggregated content. Replace the direct await with the same
pattern used in message_response: iterate the async generator from call_agent,
collect/concatenate text events (and capture the final content payload), handle
tool/stream events appropriately, and set content to the assembled final text
before continuing with schema validation and logging so that tests and runtime
use the real streamed output.
|
Please run the following command locally and commit the changes: make build_component_indexOr alternatively: LFX_DEV=1 uv run python scripts/build_component_index.pyThen commit and push the updated |
|
Please run the following command locally and commit the changes: make build_component_indexOr alternatively: LFX_DEV=1 uv run python scripts/build_component_index.pyThen commit and push the updated |
|
Please run the following command locally and commit the changes: make build_component_indexOr alternatively: LFX_DEV=1 uv run python scripts/build_component_index.pyThen commit and push the updated |
|
Please run the following command locally and commit the changes: make build_component_indexOr alternatively: LFX_DEV=1 uv run python scripts/build_component_index.pyThen commit and push the updated |
|
Please run the following command locally and commit the changes: make build_component_indexOr alternatively: LFX_DEV=1 uv run python scripts/build_component_index.pyThen commit and push the updated |
…low into feat/cuga-integration
|
Please run the following command locally and commit the changes: make build_component_indexOr alternatively: LFX_DEV=1 uv run python scripts/build_component_index.pyThen commit and push the updated |
|
Please run the following command locally and commit the changes: make build_component_indexOr alternatively: LFX_DEV=1 uv run python scripts/build_component_index.pyThen commit and push the updated |
|



Implementation of https://cuga.dev as custom component
Summary by CodeRabbit
New Features
Tests
Chores