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 @@ -3,6 +3,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.Json;
using Microsoft.Agents.AI.Workflows.Declarative.Kit;
Expand Down Expand Up @@ -152,7 +153,7 @@ _ when typeof(TimeSpan).IsAssignableFrom(targetType.Type) => ConvertToTimeSpan()
sourceValue switch
{
null => null,
string s => int.Parse(s),
string s => int.Parse(s, CultureInfo.InvariantCulture),
int i => i,
long l => Convert.ToInt32(l),
decimal c => Convert.ToInt32(c),
Expand All @@ -166,7 +167,7 @@ _ when typeof(TimeSpan).IsAssignableFrom(targetType.Type) => ConvertToTimeSpan()
sourceValue switch
{
null => null,
string s => long.Parse(s),
string s => long.Parse(s, CultureInfo.InvariantCulture),
int i => i,
long l => l,
decimal c => Convert.ToInt64(c),
Expand All @@ -180,7 +181,7 @@ _ when typeof(TimeSpan).IsAssignableFrom(targetType.Type) => ConvertToTimeSpan()
sourceValue switch
{
null => null,
string s => decimal.Parse(s),
string s => decimal.Parse(s, CultureInfo.InvariantCulture),
int i => i,
long l => l,
decimal c => c,
Expand All @@ -194,7 +195,7 @@ _ when typeof(TimeSpan).IsAssignableFrom(targetType.Type) => ConvertToTimeSpan()
sourceValue switch
{
null => null,
string s => double.Parse(s),
string s => double.Parse(s, CultureInfo.InvariantCulture),
int i => i,
long l => l,
decimal c => Convert.ToDouble(c),
Expand All @@ -208,7 +209,7 @@ _ when typeof(TimeSpan).IsAssignableFrom(targetType.Type) => ConvertToTimeSpan()
sourceValue switch
{
null => null,
string s => DateTime.Parse(s),
string s => DateTime.Parse(s, CultureInfo.InvariantCulture),
int i => new DateTime(i),
long l => new DateTime(l),
decimal c => new DateTime(Convert.ToInt64(c)),
Expand All @@ -222,7 +223,7 @@ _ when typeof(TimeSpan).IsAssignableFrom(targetType.Type) => ConvertToTimeSpan()
sourceValue switch
{
null => null,
string s => TimeSpan.Parse(s),
string s => TimeSpan.Parse(s, CultureInfo.InvariantCulture),
int i => TimeSpan.FromTicks(i),
long l => TimeSpan.FromTicks(l),
decimal c => TimeSpan.FromTicks(Convert.ToInt64(c)),
Expand Down Expand Up @@ -253,8 +254,9 @@ _ when typeof(TimeSpan).IsAssignableFrom(targetType.Type) => ConvertToTimeSpan()
{
null => null,
string sourceText => sourceText,
DateTime dateTime => dateTime.ToString("o"), // ISO 8601
TimeSpan timeSpan => timeSpan.ToString("c"), // Constant ("c") format
DateTime dateTime => dateTime.ToString("o", CultureInfo.InvariantCulture), // ISO 8601
TimeSpan timeSpan => timeSpan.ToString("c", CultureInfo.InvariantCulture), // Constant ("c") format
IFormattable formattable => formattable.ToString(null, CultureInfo.InvariantCulture),
_ => $"{sourceValue}",
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.

using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Microsoft.Agents.ObjectModel;
using Microsoft.PowerFx;
Expand All @@ -26,14 +27,15 @@ public static string Format(this RecalcEngine engine, TemplateSegment segment)

if (segment is ExpressionSegment { Expression: not null } expressionSegment)
{
ParserOptions options = new() { Culture = CultureInfo.InvariantCulture };
if (expressionSegment.Expression.ExpressionText is not null)
{
return engine.Eval(expressionSegment.Expression.ExpressionText).Format();
return engine.Eval(expressionSegment.Expression.ExpressionText, options: options).Format();
Comment thread
baywet marked this conversation as resolved.
Resolved
}

if (expressionSegment.Expression.VariableReference is not null)
{
return engine.Eval(expressionSegment.Expression.VariableReference.ToString()).Format();
return engine.Eval(expressionSegment.Expression.VariableReference.ToString(), options: options).Format();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
using Microsoft.Agents.ObjectModel;
Expand All @@ -24,7 +25,11 @@ internal sealed class WorkflowExpressionEngine
public WorkflowExpressionEngine(WorkflowFormulaState state)
{
this._state = state;
this._parserOptions = state.AllowsSideEffects ? new ParserOptions { AllowsSideEffects = true } : null;
this._parserOptions = new ParserOptions
{
Culture = CultureInfo.InvariantCulture,
AllowsSideEffects = state.AllowsSideEffects,
};
}

public EvaluationResult<bool> GetValue(BoolExpression boolean) => this.Evaluate(boolean);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
Expand Down Expand Up @@ -57,6 +58,57 @@ public async Task ResumeAsync_LegacyCheckpointPreservesNextAssignmentAsync(bool
Assert.Equal(clear ? null : "updated", state.Get("TestValue").ToObject());
}

[Theory]
[InlineData("de-DE")]
[InlineData("fr-FR")]
public async Task DeclarativeWorkflowExecution_UsesInvariantCulture_ForFormulasAndNumberParsingAsync(string cultureName)
{
CultureInfo originalCulture = CultureInfo.CurrentCulture;
try
{
CultureInfo.CurrentCulture = new CultureInfo(cultureName);

const string Yaml = """
kind: Workflow
trigger:
kind: OnConversationStart
id: repro
actions:
- kind: ParseValue
id: parse_price
variable: Local.Price
value: ="3.14"
valueType: Number
- kind: SetVariable
id: set_max
variable: Local.Max
value: =Max(1, 2)
- kind: SendActivity
id: send_max
activity: "Max is {Local.Max}"
""";

using StringReader yamlReader = new(Yaml);
DeclarativeWorkflowOptions options = new(new MockAgentProvider().Object);
Workflow workflow = DeclarativeWorkflowBuilder.Build<string>(yamlReader, options);

await using StreamingRun run = await InProcessExecution.RunStreamingAsync(workflow, "start");
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
{
Assert.False(evt is WorkflowErrorEvent, (evt as WorkflowErrorEvent)?.Exception?.ToString());
Assert.False(evt is ExecutorFailedEvent, (evt as ExecutorFailedEvent)?.Data?.ToString());
}

WorkflowFormulaState state = GetRootState(workflow);
Assert.Equal(3.14D, Convert.ToDouble(state.Get("Price").ToObject(), CultureInfo.InvariantCulture));
Assert.Equal(2.0D, Convert.ToDouble(state.Get("Max").ToObject(), CultureInfo.InvariantCulture));
}
finally
{
CultureInfo.CurrentCulture = originalCulture;
}
}

[Theory]
[InlineData(false)]
[InlineData(true)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

using System;
using System.Collections.Generic;
using System.Globalization;
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
using Microsoft.Agents.AI.Workflows.Declarative.Kit;

Expand Down Expand Up @@ -85,6 +86,25 @@ public void ConvertFromString()
VerifyConversion($"{now.TimeOfDay:c}", typeof(TimeSpan), now.TimeOfDay);
}

[Theory]
[InlineData("de-DE")]
[InlineData("fr-FR")]
public void ConvertNumberFromStringUsesInvariantCulture(string cultureName)
{
CultureInfo originalCulture = CultureInfo.CurrentCulture;
try
{
CultureInfo.CurrentCulture = new CultureInfo(cultureName);
VerifyConversion("3.14", typeof(double), 3.14D);
VerifyConversion("3.14", typeof(decimal), 3.14M);
VerifyConversion(3.14D, typeof(string), "3.14");
}
finally
{
CultureInfo.CurrentCulture = originalCulture;
}
}

[Fact]
public void ConvertJson()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.

using System.Collections.Generic;
using System.Globalization;
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
using Microsoft.Agents.ObjectModel;
using Microsoft.PowerFx;
Expand Down Expand Up @@ -138,4 +139,26 @@ public void FormatTextSegmentWithValue()
// Assert
Assert.Equal("Hello World", result);
}

[Theory]
[InlineData("de-DE")]
[InlineData("fr-FR")]
public void FormatExpressionSegmentWithFormulaUsesInvariantCulture(string cultureName)
{
CultureInfo originalCulture = CultureInfo.CurrentCulture;
try
{
CultureInfo.CurrentCulture = new CultureInfo(cultureName);
RecalcEngine engine = new();
ExpressionSegment segment = new(ValueExpression.Expression("Max(1, 2)"));

string result = engine.Format(segment);

Assert.Equal("2", result);
}
finally
{
CultureInfo.CurrentCulture = originalCulture;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.

using System.Collections.Generic;
using System.Globalization;
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
using Microsoft.Agents.ObjectModel;
using Microsoft.PowerFx.Types;
Expand Down Expand Up @@ -94,6 +95,28 @@ public void FormatExpressionSegment()
Assert.Equal("2", result);
}

[Theory]
[InlineData("de-DE")]
[InlineData("fr-FR")]
public void FormatExpressionSegmentUsesInvariantCulture(string cultureName)
{
CultureInfo originalCulture = CultureInfo.CurrentCulture;
try
{
CultureInfo.CurrentCulture = new CultureInfo(cultureName);
ExpressionSegment expressionSegment = new(ValueExpression.Expression("Max(1, 2)"));
TemplateLine line = new([expressionSegment]);

string? result = this.Engine.Format(line);

Assert.Equal("2", result);
}
finally
{
CultureInfo.CurrentCulture = originalCulture;
}
}

[Fact]
public void FormatVariableSegment()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
Expand Down Expand Up @@ -455,6 +456,28 @@ public void NumberExpressionGetValueForFormula() =>
NumberExpression.Expression("31.1 + 2.2"),
expectedValue: 33.3);

[Theory]
[InlineData("de-DE")]
[InlineData("fr-FR")]
public void NumberExpressionGetValueFormulaUsesInvariantCulture(string cultureName)
{
CultureInfo originalCulture = CultureInfo.CurrentCulture;
try
{
CultureInfo.CurrentCulture = new CultureInfo(cultureName);
this.EvaluateExpression(
NumberExpression.Expression("Max(1, 2)"),
expectedValue: 2);
this.EvaluateExpression(
NumberExpression.Expression("31.1 + 2.2"),
expectedValue: 33.3);
}
finally
{
CultureInfo.CurrentCulture = originalCulture;
}
}

#endregion

#region DataValueExpression Tests
Expand Down
Loading