Skip to content
Merged
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
620e6b4
bufix(cuga_agent): fixed no output chat bug
offerakrabi Nov 17, 2025
bc1d7bb
fix: remove structured output feature
sami-marreed Nov 17, 2025
128022f
Merge branch 'feature/new-cuga-release' of github.com:sami-marreed/la…
sami-marreed Nov 17, 2025
2d8c2c6
fix: stablize component
sami-marreed Nov 17, 2025
d0f5e0c
fix: chat output component not working
sami-marreed Nov 17, 2025
7f7ee7b
fix: add strategy flag
sami-marreed Nov 18, 2025
95d5359
fix: update cuga version
sami-marreed Nov 18, 2025
be5bf6b
Merge branch 'main' of https://github.com/langflow-ai/langflow into f…
sami-marreed Nov 18, 2025
9a52902
feat: add component index
sami-marreed Nov 18, 2025
af34ace
[autofix.ci] apply automated fixes
autofix-ci[bot] Nov 18, 2025
7f19707
[autofix.ci] apply automated fixes (attempt 2/3)
autofix-ci[bot] Nov 18, 2025
3b26066
[autofix.ci] apply automated fixes (attempt 3/3)
autofix-ci[bot] Nov 18, 2025
25f9fcb
update some imports to use lfx
jordanrfrazier Nov 18, 2025
2ddab31
[autofix.ci] apply automated fixes
autofix-ci[bot] Nov 18, 2025
a4334d1
[autofix.ci] apply automated fixes (attempt 2/3)
autofix-ci[bot] Nov 18, 2025
280cadd
[autofix.ci] apply automated fixes (attempt 3/3)
autofix-ci[bot] Nov 18, 2025
16809a6
fix: update review
sami-marreed Nov 18, 2025
03e48a1
chore: build component index
sami-marreed Nov 18, 2025
f423a12
chore: build component
sami-marreed Nov 18, 2025
aa350ab
[autofix.ci] apply automated fixes
autofix-ci[bot] Nov 18, 2025
716ccd6
[autofix.ci] apply automated fixes (attempt 2/3)
autofix-ci[bot] Nov 18, 2025
26beab4
fix(cuga): ensure message id exists when not connected to output
sami-marreed Nov 19, 2025
caa9241
fix: component result id on chat output
sami-marreed Nov 19, 2025
4161d73
Merge branch 'feature/new-cuga-release' of github.com:sami-marreed/la…
sami-marreed Nov 19, 2025
725de68
fix: component result id on chat output
sami-marreed Nov 19, 2025
a4e33be
chore: fix component index
sami-marreed Nov 19, 2025
17d1f48
[autofix.ci] apply automated fixes
autofix-ci[bot] Nov 19, 2025
99b13aa
[autofix.ci] apply automated fixes (attempt 2/3)
autofix-ci[bot] Nov 19, 2025
5d3a440
[autofix.ci] apply automated fixes (attempt 3/3)
autofix-ci[bot] Nov 19, 2025
34c0bf8
Merge branch 'main' of https://github.com/langflow-ai/langflow into f…
sami-marreed Nov 19, 2025
cd789e1
Revert test_agent_component.py back to origin/main
sami-marreed Nov 19, 2025
24e968d
fix: remove unwanted tests
sami-marreed Nov 19, 2025
38fc66a
chore: new build index
sami-marreed Nov 19, 2025
6bea90e
[autofix.ci] apply automated fixes
autofix-ci[bot] Nov 19, 2025
dae85dd
fix: update test
sami-marreed Nov 19, 2025
0f08c6a
Merge branch 'feature/new-cuga-release' of github.com:sami-marreed/la…
sami-marreed Nov 19, 2025
3b17f8e
Merge branch 'main' of https://github.com/langflow-ai/langflow into f…
sami-marreed Nov 20, 2025
39280c6
chore: build index
sami-marreed Nov 20, 2025
236a0bc
[autofix.ci] apply automated fixes
autofix-ci[bot] Nov 20, 2025
3d77279
fix: update package of cuga
sami-marreed Nov 20, 2025
042d109
Merge branch 'feature/new-cuga-release' of github.com:sami-marreed/la…
sami-marreed Nov 20, 2025
400d4a5
chore: build component index
sami-marreed Nov 20, 2025
a63e0a2
[autofix.ci] apply automated fixes
autofix-ci[bot] Nov 20, 2025
79aaf8a
[autofix.ci] apply automated fixes (attempt 2/3)
autofix-ci[bot] Nov 20, 2025
fa94107
[autofix.ci] apply automated fixes (attempt 3/3)
autofix-ci[bot] Nov 20, 2025
0053bd6
update comp index
jordanrfrazier Nov 20, 2025
768d772
[autofix.ci] apply automated fixes
autofix-ci[bot] Nov 20, 2025
5745695
[autofix.ci] apply automated fixes (attempt 2/3)
autofix-ci[bot] Nov 20, 2025
c3b78d0
[autofix.ci] apply automated fixes (attempt 3/3)
autofix-ci[bot] Nov 20, 2025
d524a02
trying comp index again?
jordanrfrazier Nov 20, 2025
816bbe5
Merge branch 'main' into feature/new-cuga-release
jordanrfrazier Nov 21, 2025
9005e2b
[autofix.ci] apply automated fixes
autofix-ci[bot] Nov 21, 2025
135f043
[autofix.ci] apply automated fixes (attempt 2/3)
autofix-ci[bot] Nov 21, 2025
35a6887
[autofix.ci] apply automated fixes (attempt 3/3)
autofix-ci[bot] Nov 21, 2025
7b7188d
Merge branch 'main' into feature/new-cuga-release
jordanrfrazier Nov 21, 2025
93c80f7
[autofix.ci] apply automated fixes
autofix-ci[bot] Nov 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ dependencies = [
"fastparquet>=2024.11.0,<2025.0.0",
"traceloop-sdk>=0.43.1,<1.0.0",
"vlmrun[all]>=0.2.0",
"cuga==0.1.7",
"cuga==0.1.10",
"agent-lifecycle-toolkit~=0.4.1",
"astrapy>=2.1.0,<3.0.0",
]
Expand Down
288 changes: 27 additions & 261 deletions src/backend/tests/unit/components/models_and_agents/test_cuga_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,11 @@ def default_kwargs(self):
"policies": "You are a helpful assistant.",
"input_value": "",
"n_messages": 100,
"format_instructions": "You are an AI that extracts structured JSON objects from unstructured text.",
"output_schema": [],
"browser_enabled": False,
"web_apps": "",
"lite_mode": True,
"lite_mode_tool_threshold": 25,
"decomposition_strategy": "flexible",
}

async def test_build_config_update(self, component_class, default_kwargs):
Expand Down Expand Up @@ -118,149 +117,6 @@ async def test_build_config_update(self, component_class, default_kwargs):
# Verify model_name field is cleared for Custom
assert "model_name" not in updated_config

async def test_cuga_has_dual_outputs(self, component_class, default_kwargs):
"""Test that Cuga component has both Response and Structured Response outputs.

This test verifies that the CugaComponent has the correct output configuration
with both regular message response and structured JSON response capabilities.
"""
component = await self.component_setup(component_class, default_kwargs)

assert len(component.outputs) == 2
assert component.outputs[0].name == "response"
assert component.outputs[0].display_name == "Response"
assert component.outputs[0].method == "message_response"

assert component.outputs[1].name == "structured_response"
assert component.outputs[1].display_name == "Structured Response"
assert component.outputs[1].method == "json_response"
assert component.outputs[1].tool_mode is False

async def test_json_mode_filtered_from_openai_inputs(self, component_class, default_kwargs):
"""Test that json_mode is filtered out from OpenAI inputs.

This test ensures that the json_mode parameter is properly excluded from
the component's input fields since Cuga handles structured output differently.
"""
component = await self.component_setup(component_class, default_kwargs)

# Check that json_mode is not in the component's inputs
input_names = [inp.name for inp in component.inputs if hasattr(inp, "name")]
assert "json_mode" not in input_names

# Verify other OpenAI inputs are still present
assert "model_name" in input_names
assert "api_key" in input_names
assert "temperature" in input_names

async def test_json_response_parsing_valid_json(self, component_class, default_kwargs):
"""Test that json_response correctly parses JSON from agent response.

This test verifies that the json_response method can properly parse
valid JSON content from the agent's response.
"""
component = await self.component_setup(component_class, default_kwargs)
# Mock the get_agent_requirements method to avoid actual LLM calls
from unittest.mock import AsyncMock

component.get_agent_requirements = AsyncMock(return_value=(MockLanguageModel(), [], []))
component.call_agent = AsyncMock(return_value='{"name": "test", "value": 123}')

result = await component.json_response()

from lfx.schema.data import Data

assert isinstance(result, Data)
assert result.data == {"name": "test", "value": 123}

async def test_json_response_parsing_embedded_json(self, component_class, default_kwargs):
"""Test that json_response handles text containing JSON.

This test verifies that the json_response method can extract JSON
from text that contains other content alongside the JSON.
"""
component = await self.component_setup(component_class, default_kwargs)
# Mock the get_agent_requirements method to avoid actual LLM calls
from unittest.mock import AsyncMock

component.get_agent_requirements = AsyncMock(return_value=(MockLanguageModel(), [], []))
component.call_agent = AsyncMock(return_value='Here is the result: {"status": "success"} - done!')

result = await component.json_response()

from lfx.schema.data import Data

assert isinstance(result, Data)
assert result.data == {"status": "success"}

async def test_json_response_error_handling(self, component_class, default_kwargs):
"""Test that json_response handles completely non-JSON responses.

This test verifies that the json_response method gracefully handles
responses that don't contain any valid JSON content.
"""
component = await self.component_setup(component_class, default_kwargs)
# Mock the get_agent_requirements method to avoid actual LLM calls
from unittest.mock import AsyncMock

component.get_agent_requirements = AsyncMock(return_value=(MockLanguageModel(), [], []))
component.call_agent = AsyncMock(return_value="This is just plain text with no JSON")

result = await component.json_response()

from lfx.schema.data import Data

assert isinstance(result, Data)
assert "error" in result.data
assert result.data["content"] == "This is just plain text with no JSON"

async def test_model_building_without_json_mode(self, component_class, default_kwargs):
"""Test that model building works without json_mode attribute.

This test ensures that the component can build models without requiring
the json_mode attribute that has been filtered out.
"""
component = await self.component_setup(component_class, default_kwargs)
component.agent_llm = "OpenAI"

# Mock component for testing
from unittest.mock import Mock

mock_component = Mock()
mock_component.set.return_value = mock_component

# Should not raise AttributeError for missing json_mode
result = component.set_component_params(mock_component)

assert result is not None
# Verify set was called (meaning no AttributeError occurred)
mock_component.set.assert_called_once()

async def test_json_response_with_schema_validation(self, component_class, default_kwargs):
"""Test that json_response validates against provided schema.

This test verifies that the json_response method can validate JSON
content against a provided Pydantic schema.
"""
# Set up component with output schema
default_kwargs["output_schema"] = [
{"name": "name", "type": "str", "description": "Name field", "multiple": False},
{"name": "age", "type": "int", "description": "Age field", "multiple": False},
]
component = await self.component_setup(component_class, default_kwargs)
# Mock the get_agent_requirements method
from unittest.mock import AsyncMock

component.get_agent_requirements = AsyncMock(return_value=(MockLanguageModel(), [], []))
component.call_agent = AsyncMock(return_value='{"name": "John", "age": 25}')

result = await component.json_response()

from langflow.schema.data import Data

assert isinstance(result, Data)
assert result.data == {"name": "John", "age": 25}

async def test_cuga_component_initialization(self, component_class, default_kwargs):
"""Test that Cuga component initializes correctly with filtered inputs.

Expand All @@ -273,104 +129,26 @@ async def test_cuga_component_initialization(self, component_class, default_kwar
assert component.display_name == "Cuga"
assert component.name == "Cuga"
assert len(component.inputs) > 0
assert len(component.outputs) == 2
assert len(component.outputs) == 1

async def test_frontend_node_structure(self, component_class, default_kwargs):
"""Test that frontend node has correct structure with filtered inputs.

This test verifies that the frontend node representation has the correct
structure and excludes unwanted fields like json_mode.
structure and includes expected fields.
"""
component = await self.component_setup(component_class, default_kwargs)

frontend_node = component.to_frontend_node()
build_config = frontend_node["data"]["node"]["template"]

# Verify json_mode is not in build config
assert "json_mode" not in build_config

# Verify other expected fields are present
assert "agent_llm" in build_config
assert "policies" in build_config
assert "add_current_date_tool" in build_config
assert "browser_enabled" in build_config
assert "web_apps" in build_config

async def test_preprocess_schema(self, component_class, default_kwargs):
"""Test that _preprocess_schema correctly handles schema validation.

This test verifies that the schema preprocessing method correctly
converts string boolean values to actual booleans and validates field types.
"""
component = await self.component_setup(component_class, default_kwargs)

# Test schema preprocessing
raw_schema = [
{"name": "field1", "type": "str", "description": "Test field", "multiple": "true"},
{"name": "field2", "type": "int", "description": "Another field", "multiple": False},
]

processed = component._preprocess_schema(raw_schema)

assert len(processed) == 2
assert processed[0]["multiple"] is True # String "true" should be converted to bool
assert processed[1]["multiple"] is False

async def test_build_structured_output_base_with_validation(self, component_class, default_kwargs):
"""Test build_structured_output_base with schema validation.

This test verifies that the structured output building method can
validate JSON content against a provided schema.
"""
default_kwargs["output_schema"] = [
{"name": "name", "type": "str", "description": "Name field", "multiple": False},
{"name": "count", "type": "int", "description": "Count field", "multiple": False},
]
component = await self.component_setup(component_class, default_kwargs)

# Test valid JSON that matches schema
valid_content = '{"name": "test", "count": 42}'
result = await component.build_structured_output_base(valid_content)
assert result == [{"name": "test", "count": 42}]

async def test_build_structured_output_base_without_schema(self, component_class, default_kwargs):
"""Test build_structured_output_base without schema validation.

This test verifies that the structured output building method works
correctly when no schema validation is provided.
"""
component = await self.component_setup(component_class, default_kwargs)

# Test with no output_schema
content = '{"any": "data", "number": 123}'
result = await component.build_structured_output_base(content)
assert result == {"any": "data", "number": 123}

async def test_build_structured_output_base_embedded_json(self, component_class, default_kwargs):
"""Test extraction of JSON from embedded text.

This test verifies that the structured output building method can
extract JSON content from text that contains other content.
"""
component = await self.component_setup(component_class, default_kwargs)

content = 'Here is some text with {"embedded": "json"} inside it.'
result = await component.build_structured_output_base(content)
assert result == {"embedded": "json"}

async def test_build_structured_output_base_no_json(self, component_class, default_kwargs):
"""Test handling of content with no JSON.

This test verifies that the structured output building method handles
content that doesn't contain any JSON gracefully.
"""
component = await self.component_setup(component_class, default_kwargs)

content = "This is just plain text with no JSON at all."
result = await component.build_structured_output_base(content)
assert "error" in result
assert result["content"] == content

async def test_new_input_fields_present(self, component_class, default_kwargs):
"""Test that new input fields are present in the component.

Expand All @@ -383,50 +161,54 @@ async def test_new_input_fields_present(self, component_class, default_kwargs):

# Test for new fields specific to Cuga
assert "policies" in input_names
assert "format_instructions" in input_names
assert "output_schema" in input_names
assert "n_messages" in input_names
assert "browser_enabled" in input_names
assert "web_apps" in input_names
assert "lite_mode" in input_names
assert "lite_mode_tool_threshold" in input_names
assert "decomposition_strategy" in input_names

# Verify default values
assert hasattr(component, "policies")
assert hasattr(component, "format_instructions")
assert hasattr(component, "output_schema")
assert hasattr(component, "n_messages")
assert hasattr(component, "browser_enabled")
assert hasattr(component, "web_apps")
assert hasattr(component, "lite_mode")
assert hasattr(component, "lite_mode_tool_threshold")
assert hasattr(component, "decomposition_strategy")
assert component.n_messages == 100
assert component.browser_enabled is False
assert component.lite_mode is True
assert component.lite_mode_tool_threshold == 25
assert component.decomposition_strategy == "flexible"

async def test_cuga_has_correct_outputs(self, component_class, default_kwargs):
"""Test that Cuga component has the correct output configuration.
async def test_decomposition_strategy_field(self, component_class, default_kwargs):
"""Test that decomposition_strategy field is properly configured.

This test verifies that the CugaComponent has the expected output
configuration with both response and structured response outputs.
This test verifies that the decomposition_strategy field has the correct
options, default value, and advanced configuration.
"""
component = await self.component_setup(component_class, default_kwargs)

assert len(component.outputs) == 2
# Find the decomposition_strategy input
decomposition_input = None
for inp in component.inputs:
if hasattr(inp, "name") and inp.name == "decomposition_strategy":
decomposition_input = inp
break

# Test response output
response_output = component.outputs[0]
assert response_output.name == "response"
assert response_output.display_name == "Response"
assert response_output.method == "message_response"
assert decomposition_input is not None, "decomposition_strategy input not found"
assert decomposition_input.display_name == "Decomposition Strategy"
assert decomposition_input.value == "flexible"
assert decomposition_input.options == ["flexible", "exact"]
assert decomposition_input.advanced is True

# Test structured response output
structured_output = component.outputs[1]
assert structured_output.name == "structured_response"
assert structured_output.display_name == "Structured Response"
assert structured_output.method == "json_response"
assert structured_output.tool_mode is False
# Test setting different values
component.decomposition_strategy = "exact"
assert component.decomposition_strategy == "exact"

component.decomposition_strategy = "flexible"
assert component.decomposition_strategy == "flexible"

async def test_memory_inputs_advanced_setting(self, component_class, default_kwargs):
"""Test that memory inputs are properly set to advanced.
Expand Down Expand Up @@ -556,22 +338,6 @@ async def test_cuga_component_with_all_openai_models(self):

assert not failed_models, f"The following models failed the test: {failed_models}"

@pytest.mark.api_key_required
@pytest.mark.no_blockbuster
async def test_cuga_structured_response_with_schema(self):
"""Test CugaComponent structured response with schema validation.

This test verifies that the CugaComponent can generate structured
responses with schema validation using real API calls.

Note:
This test is currently a placeholder (TODO).

Requires:
OPENAI_API_KEY environment variable
"""
# TODO: Add test for structured response with schema

@pytest.mark.api_key_required
@pytest.mark.no_blockbuster
async def test_cuga_with_policies(self):
Expand Down
2 changes: 1 addition & 1 deletion src/lfx/src/lfx/_assets/component_index.json

Large diffs are not rendered by default.

Loading
Loading