diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Functions/AIFunctionFactory.cs b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Functions/AIFunctionFactory.cs
index 4625b168aa6..addaaba7b58 100644
--- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Functions/AIFunctionFactory.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Functions/AIFunctionFactory.cs
@@ -135,7 +135,8 @@ public static AIFunction Create(Delegate method, AIFunctionFactoryOptions? optio
/// The method to be represented via the created .
///
/// The name to use for the . If , the name will be derived from
- /// any on , if available, or else from the name of .
+ /// any on , then from any
+ /// on , if available, or else from the name of .
///
///
/// The description to use for the . If , a description will be derived from
@@ -313,7 +314,8 @@ public static AIFunction Create(MethodInfo method, object? target, AIFunctionFac
///
///
/// The name to use for the . If , the name will be derived from
- /// any on , if available, or else from the name of .
+ /// any on , then from any
+ /// on , if available, or else from the name of .
///
///
/// The description to use for the . If , a description will be derived from
@@ -806,7 +808,7 @@ private ReflectionAIFunctionDescriptor(DescriptorKey key, JsonSerializerOptions
pType != typeof(IServiceProvider) &&
!string.IsNullOrEmpty(parameters[i].Name))
{
- _ = expectedArgumentNames.Add(parameters[i].Name!);
+ _ = expectedArgumentNames.Add(AIJsonUtilities.GetParameterSchemaName(parameters[i]));
}
}
@@ -815,7 +817,7 @@ private ReflectionAIFunctionDescriptor(DescriptorKey key, JsonSerializerOptions
ReturnParameterMarshaller = GetReturnParameterMarshaller(key, serializerOptions, out Type? returnType);
Method = key.Method;
- Name = key.Name ?? key.Method.GetCustomAttribute(inherit: true)?.DisplayName ?? GetFunctionName(key.Method);
+ Name = key.Name ?? key.Method.GetCustomAttribute(inherit: true)?.Name ?? key.Method.GetCustomAttribute(inherit: true)?.DisplayName ?? GetFunctionName(key.Method);
Description = key.Description ?? key.Method.GetCustomAttribute(inherit: true)?.Description ?? string.Empty;
JsonSerializerOptions = serializerOptions;
ReturnJsonSchema = returnType is null || key.ExcludeResultSchema ? null : AIJsonUtilities.CreateJsonSchema(
@@ -949,10 +951,12 @@ static bool IsAsyncMethod(MethodInfo method)
// Resolve the contract used to marshal the value from JSON -- can throw if not supported or not found.
JsonTypeInfo? typeInfo = serializerOptions.GetTypeInfo(parameterType);
bool hasDefaultValue = AIJsonUtilities.TryGetEffectiveDefaultValue(parameter, out object? effectiveDefaultValue);
+
+ string argumentName = AIJsonUtilities.GetParameterSchemaName(parameter);
return (arguments, _) =>
{
// If the parameter has an argument specified in the dictionary, return that argument.
- if (arguments.TryGetValue(parameter.Name, out object? value))
+ if (arguments.TryGetValue(argumentName, out object? value))
{
return value switch
{
@@ -999,7 +1003,7 @@ static bool IsAsyncMethod(MethodInfo method)
// If the parameter is required and there's no argument specified for it, throw.
if (!hasDefaultValue)
{
- Throw.ArgumentException(nameof(arguments), $"The arguments dictionary is missing a value for the required parameter '{parameter.Name}'.");
+ Throw.ArgumentException(nameof(arguments), $"The arguments dictionary is missing a value for the required parameter '{argumentName}'.");
}
// Otherwise, use the optional parameter's default value.
@@ -1217,9 +1221,11 @@ private static bool IsAIContentRelatedType(Type type) =>
{
return method.ReturnParameter.GetCustomAttribute(inherit: true)?.Description;
}
- catch (Exception e) when (e is ArgumentNullException or NullReferenceException)
+ catch (Exception e) when (e is ArgumentNullException or NullReferenceException or IndexOutOfRangeException)
{
- // DynamicMethod return parameters don't support GetCustomAttribute.
+ // DynamicMethod return parameters don't support GetCustomAttribute. Additionally, on .NET Framework,
+ // querying inherited attributes on the return parameter of an overriding method can throw
+ // IndexOutOfRangeException. In either case, treat the description as absent.
return null;
}
}
diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Functions/AIFunctionFactoryOptions.cs b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Functions/AIFunctionFactoryOptions.cs
index 094bb09337a..6d3d98d1633 100644
--- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Functions/AIFunctionFactoryOptions.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Functions/AIFunctionFactoryOptions.cs
@@ -50,7 +50,7 @@ public AIFunctionFactoryOptions()
/// Gets or sets the name to use for the function.
///
- /// The name to use for the function. The default value is a name derived from the passed or (for example, via a on the method).
+ /// The name to use for the function. The default value is a name derived from the passed or (for example, via an or on the method).
///
public string? Name { get; set; }
diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Functions/AINameAttribute.cs b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Functions/AINameAttribute.cs
new file mode 100644
index 00000000000..fa259dbe449
--- /dev/null
+++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Functions/AINameAttribute.cs
@@ -0,0 +1,34 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.Shared.DiagnosticIds;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Extensions.AI;
+
+///
+/// Specifies the name to use for an or one of its parameters.
+///
+///
+/// The name is the identifier a model sees and uses to invoke a function or to supply an argument.
+/// By default these names are inferred from .NET metadata: a function's name comes from the method name, and a
+/// parameter's name comes from the .NET parameter name. Apply this attribute to use a different identifier.
+///
+[AttributeUsage(AttributeTargets.Method | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
+[Experimental(DiagnosticIds.Experiments.AIName, UrlFormat = DiagnosticIds.UrlFormat)]
+public sealed class AINameAttribute : Attribute
+{
+ /// Initializes a new instance of the class.
+ /// The name to use for the function or parameter.
+ /// is .
+ /// is empty or composed entirely of whitespace.
+ public AINameAttribute(string name)
+ {
+ Name = Throw.IfNullOrWhitespace(name);
+ }
+
+ /// Gets the name to use for the function or parameter.
+ public string Name { get; }
+}
diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.json b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.json
index 2d105bc2a32..70f70619b16 100644
--- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.json
+++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.json
@@ -681,6 +681,22 @@
}
]
},
+ {
+ "Type": "sealed class Microsoft.Extensions.AI.AINameAttribute : System.Attribute",
+ "Stage": "Experimental",
+ "Methods": [
+ {
+ "Member": "Microsoft.Extensions.AI.AINameAttribute.AINameAttribute(string name);",
+ "Stage": "Experimental"
+ }
+ ],
+ "Properties": [
+ {
+ "Member": "string Microsoft.Extensions.AI.AINameAttribute.Name { get; }",
+ "Stage": "Experimental"
+ }
+ ]
+ },
{
"Type": "abstract class Microsoft.Extensions.AI.AITool",
"Stage": "Stable",
diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Utilities/AIJsonUtilities.Schema.Create.cs b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Utilities/AIJsonUtilities.Schema.Create.cs
index c7d2a34f005..6ed7261651b 100644
--- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Utilities/AIJsonUtilities.Schema.Create.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Utilities/AIJsonUtilities.Schema.Create.cs
@@ -10,6 +10,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
+using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Schema;
@@ -128,14 +129,20 @@ public static JsonElement CreateFunctionJsonSchema(
serializerOptions,
inferenceOptions);
- parameterSchemas.Add(parameter.Name, parameterSchema);
+ string parameterSchemaName = GetParameterSchemaName(parameter);
+ if (parameterSchemas.ContainsKey(parameterSchemaName))
+ {
+ Throw.ArgumentException(nameof(method), $"Multiple parameters are mapped to the same name '{parameterSchemaName}'. Ensure that any {nameof(AINameAttribute)} values do not collide with each other or with other parameter names.");
+ }
+
+ parameterSchemas.Add(parameterSchemaName, parameterSchema);
bool isRequired = !parameter.IsOptional && !hasDefaultValue;
#if NET || NETFRAMEWORK
isRequired = isRequired || parameter.GetCustomAttribute(inherit: true) is not null;
#endif
if (isRequired)
{
- (requiredProperties ??= []).Add((JsonNode)parameter.Name);
+ (requiredProperties ??= []).Add((JsonNode)parameterSchemaName);
}
}
@@ -282,12 +289,14 @@ JsonNode TransformSchemaNode(JsonSchemaExporterContext schemaExporterContext, Js
// to accommodate the fact that they're being nested inside of a higher-level schema.
if (parameter?.Name is not null && objSchema.TryGetPropertyValue(RefPropertyName, out JsonNode? paramName))
{
- // Fix up any $ref URIs to match the path from the root document.
+ // Fix up any $ref URIs to match the path from the root document. The schema name becomes a
+ // JSON Pointer segment, so escape it per RFC 6901 ('~' => "~0", '/' => "~1").
+ string parameterSchemaName = EscapeJsonPointerSegment(GetParameterSchemaName(parameter));
string refUri = paramName!.GetValue();
Debug.Assert(refUri is "#" || refUri.StartsWith("#/", StringComparison.Ordinal), $"Expected {nameof(refUri)} to be either # or start with #/, got {refUri}");
refUri = refUri == "#"
- ? $"#/{PropertiesPropertyName}/{parameter.Name}"
- : $"#/{PropertiesPropertyName}/{parameter.Name}/{refUri.AsMemory("#/".Length)}";
+ ? $"#/{PropertiesPropertyName}/{parameterSchemaName}"
+ : $"#/{PropertiesPropertyName}/{parameterSchemaName}/{refUri.AsMemory("#/".Length)}";
objSchema[RefPropertyName] = (JsonNode)refUri;
}
@@ -861,6 +870,31 @@ internal static bool TryGetEffectiveDefaultValue(ParameterInfo parameterInfo, ou
return false;
}
+ internal static string GetParameterSchemaName(ParameterInfo parameter) =>
+ parameter.GetCustomAttribute(inherit: true)?.Name ?? parameter.Name!;
+
+ // Escapes a string for use as a single JSON Pointer reference token per RFC 6901 ('~' => "~0", '/' => "~1").
+ private static string EscapeJsonPointerSegment(string segment)
+ {
+ if (segment.IndexOfAny(['~', '/']) < 0)
+ {
+ return segment;
+ }
+
+ StringBuilder sb = new(segment.Length + 2);
+ foreach (char c in segment)
+ {
+ _ = c switch
+ {
+ '~' => sb.Append("~0"),
+ '/' => sb.Append("~1"),
+ _ => sb.Append(c),
+ };
+ }
+
+ return sb.ToString();
+ }
+
///
/// Checks whether a parameter is an F# optional parameter declared with the ?param syntax.
/// F# optional parameters are annotated with Microsoft.FSharp.Core.OptionalArgumentAttribute
diff --git a/src/Shared/DiagnosticIds/DiagnosticIds.cs b/src/Shared/DiagnosticIds/DiagnosticIds.cs
index 4b4925850a4..691e2ae67ce 100644
--- a/src/Shared/DiagnosticIds/DiagnosticIds.cs
+++ b/src/Shared/DiagnosticIds/DiagnosticIds.cs
@@ -51,9 +51,8 @@ internal static class Experiments
internal const string AIImageGeneration = AIExperiments;
internal const string AISpeechToText = AIExperiments;
internal const string AITextToSpeech = AIExperiments;
- internal const string AIMcpServers = AIExperiments;
- internal const string AIFunctionApprovals = AIExperiments;
internal const string AIApprovalsInvocationRequired = AIExperiments;
+ internal const string AIName = AIExperiments;
internal const string AIChatReduction = AIExperiments;
internal const string AIToolSearch = AIExperiments;
diff --git a/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/Functions/AINameAttributeTest.cs b/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/Functions/AINameAttributeTest.cs
new file mode 100644
index 00000000000..d30825fa4ef
--- /dev/null
+++ b/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/Functions/AINameAttributeTest.cs
@@ -0,0 +1,216 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.ComponentModel;
+using System.Reflection;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Text.Json.Serialization.Metadata;
+using System.Threading.Tasks;
+using Xunit;
+
+#pragma warning disable S1144 // Unused private types or members should be removed (methods are invoked via reflection)
+
+namespace Microsoft.Extensions.AI;
+
+public class AINameAttributeTest
+{
+ [Fact]
+ public void OverridesSchemaPropertyName()
+ {
+ AIFunction func = AIFunctionFactory.Create(
+ ([AIName("my_param")] string myParam, int top) => myParam + top);
+
+ JsonElement expectedSchema = JsonDocument.Parse("""
+ {
+ "type": "object",
+ "properties": {
+ "my_param": { "type": "string" },
+ "top": { "type": "integer" }
+ },
+ "required": ["my_param", "top"]
+ }
+ """).RootElement;
+
+ AssertExtensions.EqualJsonValues(expectedSchema, func.JsonSchema);
+ }
+
+ [Fact]
+ public void HonoredByCreateFunctionJsonSchema()
+ {
+ Func query = ([AIName("my_param")] string myParam) => myParam;
+
+ JsonElement schema = AIJsonUtilities.CreateFunctionJsonSchema(query.Method);
+
+ Assert.Contains("my_param", schema.ToString());
+ }
+
+ [Fact]
+ public async Task BindsArgumentByOverriddenName_Async()
+ {
+ AIFunction func = AIFunctionFactory.Create(
+ ([AIName("$select")] string select,
+ [AIName("$expand")] string expand,
+ string filter) =>
+ $"select='{select}', expand='{expand}', filter='{filter}'");
+
+ object? result = await func.InvokeAsync(new()
+ {
+ ["$select"] = "Name,Id",
+ ["$expand"] = "Orders",
+ ["filter"] = "Active",
+ });
+
+ AssertExtensions.EqualFunctionCallResults("select='Name,Id', expand='Orders', filter='Active'", result);
+ }
+
+ [Fact]
+ public async Task MissingRequiredArgument_ReportsSchemaName_Async()
+ {
+ AIFunction func = AIFunctionFactory.Create(
+ ([AIName("my_param")] string myParam) => myParam);
+
+ ArgumentException ex = await Assert.ThrowsAsync(() => func.InvokeAsync().AsTask());
+
+ Assert.Contains("my_param", ex.Message);
+ }
+
+ [Fact]
+ public async Task HonoredByStrictUnmappedMemberHandling_Async()
+ {
+ JsonSerializerOptions strictOptions = new(AIJsonUtilities.DefaultOptions)
+ {
+ UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow,
+ };
+
+ AIFunction func = AIFunctionFactory.Create(
+ ([AIName("my_param")] string myParam) => myParam,
+ new AIFunctionFactoryOptions { SerializerOptions = strictOptions });
+
+ // The overridden name is "expected", so it passes strict validation.
+ AssertExtensions.EqualFunctionCallResults("Name", await func.InvokeAsync(new() { ["my_param"] = "Name" }));
+
+ // The underlying C# name is now an unexpected argument.
+ ArgumentException ex = await Assert.ThrowsAsync("arguments", async () =>
+ await func.InvokeAsync(new() { ["myParam"] = "Name" }));
+ Assert.Contains("myParam", ex.Message);
+ }
+
+ [Fact]
+ public async Task InheritedByOverride_Async()
+ {
+ MethodInfo overrideMethod = typeof(MyDerivedType).GetMethod(nameof(MyDerivedType.Method))!;
+ AIFunction func = AIFunctionFactory.Create(overrideMethod, new MyDerivedType());
+
+ Assert.Contains("my_param", func.JsonSchema.ToString());
+ Assert.DoesNotContain("\"myParam\"", func.JsonSchema.ToString());
+
+ AssertExtensions.EqualFunctionCallResults("param='Name'", await func.InvokeAsync(new() { ["my_param"] = "Name" }));
+ }
+
+ [Fact]
+ public void OverridesFunctionName()
+ {
+ AIFunction func = AIFunctionFactory.Create([AIName("my_tool")] () => "result");
+
+ Assert.Equal("my_tool", func.Name);
+ }
+
+ [Fact]
+ public void OverridesFunctionName_TakesPrecedenceOverDisplayName()
+ {
+ AIFunction func = AIFunctionFactory.Create([AIName("my_tool")][DisplayName("from-display-name")] () => "result");
+
+ Assert.Equal("my_tool", func.Name);
+ }
+
+ [Fact]
+ public void DisplayNameAttribute_StillHonoredWhenNoAINameAttribute()
+ {
+ AIFunction func = AIFunctionFactory.Create([DisplayName("from-display-name")] () => "result");
+
+ Assert.Equal("from-display-name", func.Name);
+ }
+
+ [Fact]
+ public void OptionsName_TakesPrecedenceOverAttribute()
+ {
+ AIFunction func = AIFunctionFactory.Create([AIName("my_tool")] () => "result", new AIFunctionFactoryOptions { Name = "explicit" });
+
+ Assert.Equal("explicit", func.Name);
+ }
+
+ [Fact]
+ public async Task FunctionAndParameterNames_BothHonored_Async()
+ {
+ AIFunction func = AIFunctionFactory.Create([AIName("my_tool")] ([AIName("my_param")] string myParam) => myParam);
+
+ Assert.Equal("my_tool", func.Name);
+ Assert.Contains("my_param", func.JsonSchema.ToString());
+
+ AssertExtensions.EqualFunctionCallResults("Name", await func.InvokeAsync(new() { ["my_param"] = "Name" }));
+ }
+
+ [Fact]
+ public void NameAndParameterSchema_PreservedByAsDeclarationOnly()
+ {
+ AIFunction func = AIFunctionFactory.Create([AIName("my_tool")] ([AIName("my_param")] string myParam) => myParam);
+
+ AIFunctionDeclaration declaration = func.AsDeclarationOnly();
+
+ Assert.Equal("my_tool", declaration.Name);
+ Assert.Equal(func.JsonSchema.ToString(), declaration.JsonSchema.ToString());
+ Assert.Contains("my_param", declaration.JsonSchema.ToString());
+ Assert.IsNotAssignableFrom(declaration);
+ }
+
+ [Fact]
+ public void InvalidArguments_Throw()
+ {
+ Assert.Throws("name", () => new AINameAttribute(null!));
+ Assert.Throws("name", () => new AINameAttribute(" "));
+ }
+
+ [Fact]
+ public void EscapesNameInJsonPointerRef()
+ {
+ JsonSerializerOptions options = new() { TypeInfoResolver = new DefaultJsonTypeInfoResolver() };
+
+ AIFunction func = AIFunctionFactory.Create(
+ ([AIName("a/b~c")] RecursiveNode node) => node.ToString(),
+ new AIFunctionFactoryOptions { SerializerOptions = options });
+
+ string schema = func.JsonSchema.ToString();
+
+ Assert.Contains("#/properties/a~1b~0c", schema);
+ Assert.DoesNotContain("#/properties/a/b~c", schema);
+ }
+
+ [Fact]
+ public void DuplicateNames_Throw()
+ {
+ ArgumentException ex = Assert.Throws(() => AIFunctionFactory.Create(
+ ([AIName("dup")] string first, [AIName("dup")] string second) => first + second));
+ Assert.Contains("dup", ex.Message);
+
+ ArgumentException ex2 = Assert.Throws(() => AIFunctionFactory.Create(
+ ([AIName("filter")] string select, string filter) => select + filter));
+ Assert.Contains("filter", ex2.Message);
+ }
+
+ private abstract class MyBaseType
+ {
+ public abstract string Method([AIName("my_param")] string myParam);
+ }
+
+ private sealed class MyDerivedType : MyBaseType
+ {
+ public override string Method(string myParam) => $"param='{myParam}'";
+ }
+
+ private sealed class RecursiveNode
+ {
+ public RecursiveNode? Next { get; set; }
+ }
+}
diff --git a/test/Libraries/Microsoft.Extensions.AI.Tests/Functions/AIFunctionFactoryTest.cs b/test/Libraries/Microsoft.Extensions.AI.Tests/Functions/AIFunctionFactoryTest.cs
index 1bd803aded3..179bb88b973 100644
--- a/test/Libraries/Microsoft.Extensions.AI.Tests/Functions/AIFunctionFactoryTest.cs
+++ b/test/Libraries/Microsoft.Extensions.AI.Tests/Functions/AIFunctionFactoryTest.cs
@@ -18,6 +18,7 @@
#pragma warning disable IDE0004 // Remove Unnecessary Cast
#pragma warning disable S103 // Lines should not be too long
#pragma warning disable S107 // Methods should not have too many parameters
+#pragma warning disable S1144 // Unused private types or members should be removed (override methods are invoked via reflection)
#pragma warning disable S2760 // Sequential tests should not check the same condition
#pragma warning disable S3358 // Ternary operators should not be nested
#pragma warning disable S5034 // "ValueTask" should be consumed correctly
@@ -1068,6 +1069,40 @@ public void AIFunctionFactory_ReturnTypeWithDescriptionAttribute()
static int Add(int a, int b) => a + b;
}
+ [Fact]
+ public void AIFunctionFactory_InheritedDescriptionAttributes_OnOverride()
+ {
+ MethodInfo overrideMethod = typeof(DerivedDescribed).GetMethod(nameof(DerivedDescribed.Compute))!;
+ AIFunction f = AIFunctionFactory.Create(overrideMethod, new DerivedDescribed());
+
+ Assert.Equal("The compute method", f.Description);
+
+ JsonElement valueParam = f.JsonSchema.GetProperty("properties").GetProperty("value");
+ Assert.Equal("The input value", valueParam.GetProperty("description").GetString());
+
+ Assert.NotNull(f.ReturnJsonSchema);
+ Assert.Equal("integer", f.ReturnJsonSchema!.Value.GetProperty("type").GetString());
+#if NET
+ // On modern .NET the return-parameter inheritance walk is fixed, so the inherited description is read.
+ Assert.Equal("The computed result", f.ReturnJsonSchema!.Value.GetProperty("description").GetString());
+#else
+ // On .NET Framework the inherited return-parameter description cannot be read and is silently dropped.
+ Assert.False(f.ReturnJsonSchema!.Value.TryGetProperty("description", out _));
+#endif
+ }
+
+ private abstract class BaseDescribed
+ {
+ [Description("The compute method")]
+ [return: Description("The computed result")]
+ public abstract int Compute([Description("The input value")] int value);
+ }
+
+ private sealed class DerivedDescribed : BaseDescribed
+ {
+ public override int Compute(int value) => value;
+ }
+
[Fact]
public void CreateDeclaration_Roundtrips()
{