// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using System.Reflection; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; using Json.Schema; using Json.Schema.Generation; using Microsoft.Teams.AI.Annotations; using Microsoft.Teams.AI.Messages; using Microsoft.Teams.Common.Extensions; using Microsoft.Teams.Common.Json; namespace Microsoft.Teams.AI; /// /// defines a block of code that /// can be called by a model /// [JsonConverter(typeof(TrueTypeJsonConverter))] [Obsolete("Microsoft.Teams.AI is deprecated and will be removed by end of summer 2026.")] public interface IFunction { /// /// the unique name /// public string Name { get; } /// /// a description of what the function /// should be used for /// public string? Description { get; } /// /// the Json Schema representing what /// parameters the function accepts /// public JsonSchema? Parameters { get; } } /// /// defines a block of code that /// can be called by a model /// [Obsolete("Microsoft.Teams.AI is deprecated and will be removed by end of summer 2026.")] public class Function : IFunction { [JsonPropertyName("name")] [JsonPropertyOrder(0)] public string Name { get; set; } [JsonPropertyName("description")] [JsonPropertyOrder(1)] public string? Description { get; set; } [JsonPropertyName("parameters")] [JsonPropertyOrder(2)] public JsonSchema? Parameters { get; set; } [JsonIgnore] public Delegate Handler { get; set; } public Function(string name, string? description, Delegate handler) { Name = name; Description = description; Handler = handler; Parameters = GenerateParametersSchema(handler); } public Function(string name, string? description, JsonSchema parameters, Delegate handler) { Name = name; Description = description; Parameters = parameters; Handler = handler; } internal Task Invoke(FunctionCall call) { if (call.Arguments is not null && Parameters is not null) { var valid = Parameters.Evaluate(JsonNode.Parse(call.Arguments), new() { EvaluateAs = SpecVersion.DraftNext }); if (!valid.IsValid) { Console.WriteLine(JsonSerializer.Serialize(valid)); throw new ArgumentException( string.Join("\n", valid.Errors?.Select(e => $"{e.Key} => {e.Value}") ?? []) ); } } var args = call.Parse() ?? new Dictionary(); var method = Handler.GetMethodInfo(); var parameters = method.GetParameters().Select(param => { var name = param.GetCustomAttribute()?.Name ?? param.Name ?? param.Position.ToString(); args.TryGetValue(name, out var value); if (value is JsonElement element) { return element.Deserialize(param.ParameterType); } // Special param type to get the arguments dictionary (IDictionary args) if (value is null && name == "args" && param.ParameterType == typeof(IDictionary)) { value = args; } return value; }).ToArray(); return method.InvokeAsync(Handler.Target, parameters); } public override string ToString() { return JsonSerializer.Serialize(this, new JsonSerializerOptions() { WriteIndented = true }); } /// /// Generates a JsonSchema for the parameters of a delegate handler using reflection /// private static JsonSchema? GenerateParametersSchema(Delegate handler) { var method = handler.GetMethodInfo(); var methodParams = method.GetParameters(); if (methodParams.Length == 0) { return null; } var parameters = methodParams.Select(p => { var paramName = p.GetCustomAttribute()?.Name ?? p.Name ?? p.Position.ToString(); var schema = new JsonSchemaBuilder().FromType(p.ParameterType).Build(); var required = !p.IsOptional; return (paramName, schema, required); }); return new JsonSchemaBuilder() .Type(SchemaValueType.Object) .Properties(parameters.Select(item => (item.paramName, item.schema)).ToArray()) .Required(parameters.Where(item => item.required).Select(item => item.paramName)) .Build(); } }