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 @@ -734,14 +734,14 @@ def _eval_custom_function(self, formula: str) -> Any | None:
evaluated_args: list[str] = []
for arg in args:
arg = arg.strip()
if arg.startswith('"') and arg.endswith('"'):
if re.fullmatch(r'"(?:[^"]|"")*"', arg):
# String literal
evaluated_args.append(arg[1:-1])
evaluated_args.append(arg[1:-1].replace('""', '"'))
elif arg.startswith("'") and arg.endswith("'"):
# Single-quoted string literal
evaluated_args.append(arg[1:-1])
else:
# Variable reference - evaluate it
# Variable reference or expression - evaluate it
result = self.eval(f"={arg}")
evaluated_args.append(str(result) if result is not None else "")
return "".join(evaluated_args)
Expand Down
23 changes: 23 additions & 0 deletions python/packages/declarative/tests/test_graph_executors.py
Original file line number Diff line number Diff line change
Expand Up @@ -1810,6 +1810,29 @@ def test_recovered_outer_array_wins_over_nested_object(self):
class TestPowerFxConditionalImport:
"""The _declarative_base module should be importable without dotnet/powerfx."""

@pytest.mark.parametrize("function", ["Concat", "Concatenate"])
@pytest.mark.parametrize(
("arguments", "expected"),
[
('"hello", " world"', "hello world"),
('"", ""', ""),
('"a""b", "c"', 'a"bc'),
('"a,b(c)", "d"', "a,b(c)d"),
("'legacy', ' strings'", "legacy strings"),
],
)
def test_concatenation_literals_without_engine(
self, monkeypatch: pytest.MonkeyPatch, function: str, arguments: str, expected: str
) -> None:
"""String-only concatenation retains its engine-independent fast path."""
import agent_framework_declarative._workflows._declarative_base as base_mod

state = DeclarativeWorkflowState(State())
state.initialize()
monkeypatch.setattr(base_mod, "Engine", None)

assert state.eval(f"={function}({arguments})") == expected

def test_import_guard_exists(self):
"""The powerfx import must be wrapped in try/except."""
import agent_framework_declarative._workflows._declarative_base as base_mod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,47 @@ async def test_workflow_with_condition_group(self):
class TestWorkflowFactory:
"""Tests for WorkflowFactory."""

@pytest.mark.parametrize("function", ["Concat", "Concatenate"])
@pytest.mark.parametrize(
("arguments", "expected"),
[
('"hello", " world"', "hello world"),
('"", ""', ""),
('"a""b", "c"', 'a"bc'),
('"""", """"', '""'),
('"a" & "b", "c"', "abc"),
('"a" & Local.word & "b", "c"', "aWORDbc"),
('"a,b(c)", "d"', "a,b(c)d"),
('"a"",b(c)", "d"', 'a",b(c)d'),
('"路径", "与文本"', "路径与文本"),
('"a\\b", "c"', "a\\bc"),
('Local.word, "!"', "WORD!"),
('Concatenate("a""b", "c"), "d"', 'a"bcd'),
('MessageText(Local.messages), "!"', "hello!"),
('Local.missing, "!"', "!"),
],
)
async def test_concatenation_arguments(self, function: str, arguments: str, expected: str) -> None:
"""Render literals and expressions through factory-created workflow state."""
workflow = WorkflowFactory().create_workflow_from_definition({
"name": "concatenation_arguments",
"actions": [
{"kind": "SetValue", "id": "set_word", "path": "Local.word", "value": "WORD"},
{
"kind": "SetValue",
"id": "set_messages",
"path": "Local.messages",
"value": [{"role": "user", "text": "hello"}],
},
{"kind": "SendActivity", "id": "send", "activity": {"text": f"={function}({arguments})"}},
],
})

events = await workflow.run({})

# SendActivity omits empty rendered activities.
assert events.get_outputs() == ([expected] if expected else [])

def test_factory_creates_workflow(self):
"""Test creating workflow."""
factory = WorkflowFactory()
Expand Down
Loading