Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -319,8 +319,9 @@ def _extract_llm_schema(
json_mode = config.get("json_mode", get_default("json_mode"))
if not json_mode:
output.is_schemaless = True
output_field = config.get("output_field", get_default("output_field"))
output.schema_fields.add(output_field)
output.schema_fields.add("content")
output.schema_fields.add("raw_response")
return

output.is_schemaless = True
Expand Down
29 changes: 29 additions & 0 deletions tests/validation/static_analyzer/test_schema_extractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,35 @@ def test_nested_schema_properties(self):
# Should NOT have nested fields at top level
assert "name" not in schema.available_fields

def test_output_field_produces_named_field(self):
"""Test that output_field declares the named field in schema_fields."""
config = {
"name": "classify",
"json_mode": False,
"output_field": "issue_type",
"prompt": "Classify this issue",
}
schema = self.extractor.extract_schema(config)

assert "issue_type" in schema.schema_fields
assert "issue_type" in schema.available_fields
assert "content" in schema.available_fields
# raw_response should NOT appear — output_field replaces it
assert "raw_response" not in schema.available_fields

def test_non_json_without_output_field_defaults_to_raw_response(self):
"""Regression guard: non-JSON mode without output_field still produces raw_response."""
config = {
"name": "agent",
"json_mode": False,
"prompt": "Generate something",
}
schema = self.extractor.extract_schema(config)

assert "raw_response" in schema.available_fields
assert "content" in schema.available_fields
assert schema.is_schemaless

def test_hitl_agent_uses_canonical_hitl_schema(self):
"""Test HITL action uses the canonical HITL output schema."""
config = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -891,6 +891,34 @@ def test_context_scope_reports_error_when_schema_load_fails(self):
"not found" in e.message or "could not be loaded" in e.message for e in context_errors
)

def test_output_field_observable_by_downstream(self):
"""Downstream action can observe an output_field-produced field."""
workflow_config = {
"actions": [
{
"name": "classify",
"json_mode": False,
"output_field": "issue_type",
"prompt": "Classify",
},
{
"name": "route",
"depends_on": ["classify"],
"context_scope": {"observe": ["classify.issue_type"]},
"prompt": "Route based on {{ action.classify.issue_type }}",
"schema": {
"type": "object",
"properties": {"team": {"type": "string"}},
},
},
],
}

result = analyze_workflow(workflow_config)

errors = [e for e in result.errors if "issue_type" in e.message]
assert not errors, f"False positive on output_field: {errors}"


class TestPrimaryDependencyValidation:
"""Tests for primary_dependency validation."""
Expand Down
Loading