Skip to content
Open
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 @@ -1067,9 +1067,17 @@ def is_single_string_output(expected_outputs: List[AgentSpecProperty]) -> bool:
adapter takes it directly from the agent's final message rather than forcing
structured generation. Mirrors ``LlmNodeExecutor``'s single-string handling
and lets a string output work on models without structured-output support.

An enum is excluded: its schema names the only values the output may take, so
free text cannot satisfy it. Treating it as free text left the allowed set
unenforced — the agent replied in prose and a branch keyed on the value
matched nothing.
"""
outputs = expected_outputs or []
return len(outputs) == 1 and outputs[0].type == "string"
if len(outputs) != 1 or outputs[0].type != "string":
return False
schema = outputs[0].json_schema or {}
return not schema.get("enum")


def extract_outputs_from_invoke_result(
Expand Down
20 changes: 20 additions & 0 deletions pyagentspec/tests/adapters/langgraph/flows/test_agentnode.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,26 @@ def test_is_single_string_output() -> None:
assert is_single_string_output([StringProperty(title="a"), StringProperty(title="b")]) is False


def test_a_single_enum_output_is_not_free_text() -> None:
"""An enum names the only values its output may take, so it must go through
structured generation rather than being filled from the final message.

Treated as free text, the allowed set was never enforced: the agent replied in
prose and a BranchingNode keyed on the value matched none of its paths.
"""
from pyagentspec.adapters.langgraph._node_execution import is_single_string_output
from pyagentspec.property import Property, StringProperty

enum_output = Property(
title="sentiment",
json_schema={"type": "string", "enum": ["positive", "negative", "neutral"]},
)

assert is_single_string_output([enum_output]) is False
# A plain string output keeps the free-text shortcut.
assert is_single_string_output([StringProperty(title="answer")]) is True


def test_single_string_output_taken_from_final_message_without_structured_generation() -> None:
"""An AgentNode whose agent declares a single string output should resolve
that output from the agent's final message — no structured generation, so
Expand Down
Loading