Skip to content

feat: add CUGA integration with agent component and tests#9907

Merged
ogabrielluiz merged 14 commits into
langflow-ai:mainfrom
sami-marreed:feat/cuga-integration
Oct 15, 2025
Merged

feat: add CUGA integration with agent component and tests#9907
ogabrielluiz merged 14 commits into
langflow-ai:mainfrom
sami-marreed:feat/cuga-integration

Conversation

@sami-marreed
Copy link
Copy Markdown
Contributor

@sami-marreed sami-marreed commented Sep 17, 2025

Implementation of https://cuga.dev as custom component

Summary by CodeRabbit

  • New Features

    • Introduced the Cuga agent component with configurable provider support, memory integration, optional browser/API sub-agents, and policy/format controls.
    • Provides dual outputs (Response and Structured Response) with JSON parsing and optional schema validation.
    • Now available via standard agent component exports.
  • Tests

    • Added comprehensive unit tests covering configuration, outputs, JSON handling, schema validation, browser/API toggles, tool usage, and client-assisted scenarios.
  • Chores

    • Added new dependency: cuga (v0.1.1.dev1).

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Sep 17, 2025

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Introduces 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

Cohort / File(s) Summary
Dependency Update
pyproject.toml
Adds dependency cuga==0.1.1.dev1.
New Agent Component
src/lfx/src/lfx/components/agents/cuga_agent.py
Adds CugaComponent with provider mapping, LLM setup, memory/tools integration, event-streaming agent execution, structured JSON responses with schema validation, and helper utilities.
Agents Package Export
src/lfx/src/lfx/components/agents/__init__.py
Publishes CugaComponent via __all__, dynamic import map, and TYPE_CHECKING import.
Unit Tests for Cuga
src/backend/tests/unit/components/agents/test_cuga_agent.py
Adds extensive tests for configuration updates, outputs, OpenAI input filtering, JSON parsing/validation, schema preprocessing, structured outputs, browser/API toggles, memory inputs, and client-enabled tool/model scenarios.

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
Loading
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60–90 minutes

Possibly related PRs

Suggested labels

enhancement, size:XXL, lgtm

Suggested reviewers

  • ogabrielluiz
  • edwinjosechittilappilly
  • mfortman11
  • Cristhianzl

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 73.91% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "feat: add CUGA integration with agent component and tests" is a concise, single-sentence summary that accurately describes the main change (adding a Cuga agent component and accompanying tests) and follows conventional-commit style, so it is clear and relevant for teammates scanning history.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Sep 17, 2025
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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__REGISRY looks 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 = None is 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. Use getattr.

-        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, and final_response are 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

📥 Commits

Reviewing files that changed from the base of the PR and between 1377c48 and 7453ee1.

⛔ Files ignored due to path filters (1)
  • uv.lock is 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 icon attribute 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; here policies is 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.

Comment thread pyproject.toml Outdated
"aiosqlite==0.21.0",
"fastparquet>=2024.11.0",
"traceloop-sdk>=0.43.1",
"cuga==0.1.1.dev1",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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.

Suggested change
"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.

Comment on lines +118 to +121
component.get_agent_requirements = AsyncMock(return_value=(MockLanguageModel(), [], []))
component.call_agent = AsyncMock(return_value='{"name": "test", "value": 123}')

result = await component.json_response()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
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.

Comment on lines +134 to +136
component.get_agent_requirements = AsyncMock(return_value=(MockLanguageModel(), [], []))
component.call_agent = AsyncMock(return_value='Here is the result: {"status": "success"} - done!')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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_events

Committable 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.

Comment on lines +150 to +152
component.get_agent_requirements = AsyncMock(return_value=(MockLanguageModel(), [], []))
component.call_agent = AsyncMock(return_value="This is just plain text with no JSON")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
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.

Comment on lines +210 to +236
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 {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Comment on lines +257 to +261
# 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())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
# 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.

Comment on lines +596 to +606
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}")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Sep 18, 2025
@github-actions
Copy link
Copy Markdown
Contributor

⚠️ Component index needs to be updated

Please run the following command locally and commit the changes:

make build_component_index

Or alternatively:

LFX_DEV=1 uv run python scripts/build_component_index.py

Then commit and push the updated src/lfx/src/lfx/_assets/component_index.json file.

@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Oct 15, 2025
@github-actions
Copy link
Copy Markdown
Contributor

⚠️ Component index needs to be updated

Please run the following command locally and commit the changes:

make build_component_index

Or alternatively:

LFX_DEV=1 uv run python scripts/build_component_index.py

Then commit and push the updated src/lfx/src/lfx/_assets/component_index.json file.

@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Oct 15, 2025
@github-actions
Copy link
Copy Markdown
Contributor

⚠️ Component index needs to be updated

Please run the following command locally and commit the changes:

make build_component_index

Or alternatively:

LFX_DEV=1 uv run python scripts/build_component_index.py

Then commit and push the updated src/lfx/src/lfx/_assets/component_index.json file.

@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Oct 15, 2025
@github-actions
Copy link
Copy Markdown
Contributor

⚠️ Component index needs to be updated

Please run the following command locally and commit the changes:

make build_component_index

Or alternatively:

LFX_DEV=1 uv run python scripts/build_component_index.py

Then commit and push the updated src/lfx/src/lfx/_assets/component_index.json file.

@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Oct 15, 2025
@github-actions
Copy link
Copy Markdown
Contributor

⚠️ Component index needs to be updated

Please run the following command locally and commit the changes:

make build_component_index

Or alternatively:

LFX_DEV=1 uv run python scripts/build_component_index.py

Then commit and push the updated src/lfx/src/lfx/_assets/component_index.json file.

@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Oct 15, 2025
@github-actions
Copy link
Copy Markdown
Contributor

⚠️ Component index needs to be updated

Please run the following command locally and commit the changes:

make build_component_index

Or alternatively:

LFX_DEV=1 uv run python scripts/build_component_index.py

Then commit and push the updated src/lfx/src/lfx/_assets/component_index.json file.

@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Oct 15, 2025
@ogabrielluiz ogabrielluiz added the lgtm This PR has been approved by a maintainer label Oct 15, 2025
@github-actions
Copy link
Copy Markdown
Contributor

⚠️ Component index needs to be updated

Please run the following command locally and commit the changes:

make build_component_index

Or alternatively:

LFX_DEV=1 uv run python scripts/build_component_index.py

Then commit and push the updated src/lfx/src/lfx/_assets/component_index.json file.

@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Oct 15, 2025
@sonarqubecloud
Copy link
Copy Markdown

@ogabrielluiz ogabrielluiz merged commit 4d4ca8c into langflow-ai:main Oct 15, 2025
71 of 72 checks passed
korenLazar pushed a commit to kiran-kate/langflow that referenced this pull request Nov 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request lgtm This PR has been approved by a maintainer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants