Skip to content

Commit

Permalink
fix: Fixed some issues with generator.
Browse files Browse the repository at this point in the history
  • Loading branch information
HavenDV committed Mar 22, 2024
1 parent 5f6b861 commit 753a400
Show file tree
Hide file tree
Showing 19 changed files with 521 additions and 53 deletions.
7 changes: 7 additions & 0 deletions OpenAI.sln
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenAI.Constants", "src\lib
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeAsOpenApi", "src\libs\CodeAsOpenApi\CodeAsOpenApi.csproj", "{77577208-38DE-4F6E-B5E0-ECD500F7D94F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeAsOpenApi.Tests", "src\tests\CodeAsOpenApi.Tests\CodeAsOpenApi.Tests.csproj", "{58456295-DE0F-4D5A-97B8-BC9C466B0B63}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -68,6 +70,10 @@ Global
{77577208-38DE-4F6E-B5E0-ECD500F7D94F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{77577208-38DE-4F6E-B5E0-ECD500F7D94F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{77577208-38DE-4F6E-B5E0-ECD500F7D94F}.Release|Any CPU.Build.0 = Release|Any CPU
{58456295-DE0F-4D5A-97B8-BC9C466B0B63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{58456295-DE0F-4D5A-97B8-BC9C466B0B63}.Debug|Any CPU.Build.0 = Debug|Any CPU
{58456295-DE0F-4D5A-97B8-BC9C466B0B63}.Release|Any CPU.ActiveCfg = Release|Any CPU
{58456295-DE0F-4D5A-97B8-BC9C466B0B63}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -80,6 +86,7 @@ Global
{4F534836-40A3-4E75-8B71-C46CD4FBE137} = {AAA11B78-2764-4520-A97E-46AA7089A588}
{74A51E6E-2955-4909-A075-13D8E620FBFE} = {61E7E11E-4558-434C-ACE8-06316A3097B3}
{77577208-38DE-4F6E-B5E0-ECD500F7D94F} = {61E7E11E-4558-434C-ACE8-06316A3097B3}
{58456295-DE0F-4D5A-97B8-BC9C466B0B63} = {AAA11B78-2764-4520-A97E-46AA7089A588}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {CED9A020-DBA5-4BE6-8096-75E528648EC1}
Expand Down
20 changes: 20 additions & 0 deletions src/libs/CodeAsOpenApi/CodeAsOpenApi.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<IsPackable>false</IsPackable>
<GenerateDocumentationFile>false</GenerateDocumentationFile>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<NoWarn>$(NoWarn);CA1014;CA1031;CA1308</NoWarn>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="IsExternalInit" Version="1.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.1" PrivateAssets="all" />
</ItemGroup>

</Project>
6 changes: 6 additions & 0 deletions src/libs/CodeAsOpenApi/Models/InterfaceData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace H.Generators;

public readonly record struct InterfaceData(
string Namespace,
string Name,
IReadOnlyCollection<MethodData> Methods);
8 changes: 8 additions & 0 deletions src/libs/CodeAsOpenApi/Models/MethodData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace H.Generators;

public readonly record struct MethodData(
string Name,
string Description,
bool IsAsync,
bool IsVoid,
IReadOnlyCollection<ParameterData> Parameters);
14 changes: 14 additions & 0 deletions src/libs/CodeAsOpenApi/Models/ParameterData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace H.Generators;

public readonly record struct ParameterData(
string Name,
string Description,
string Type,
string SchemaType,
string? Format,
IReadOnlyCollection<string> EnumValues,
IReadOnlyCollection<ParameterData> Properties,
IReadOnlyCollection<ParameterData> ArrayItem,
bool IsRequired,
bool IsNullable,
string DefaultValue);
208 changes: 208 additions & 0 deletions src/libs/CodeAsOpenApi/OpenAiFunctionsGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
using System.ComponentModel;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace H.Generators;

public class OpenAiFunctionsGenerator
{
#region Methods

private static string GetDescription(ISymbol symbol)
{
return symbol.GetAttributes()
.FirstOrDefault(static x => x.AttributeClass?.Name == nameof(DescriptionAttribute))?
.ConstructorArguments.First().Value?.ToString() ?? string.Empty;
}

private static InterfaceData PrepareData(
(SemanticModel SemanticModel, AttributeData AttributeData, InterfaceDeclarationSyntax InterfaceSyntax, INamedTypeSymbol InterfaceSymbol) tuple)
{
var (_, _, _, interfaceSymbol) = tuple;

var methods = interfaceSymbol
.GetMembers()
.OfType<IMethodSymbol>()
.Where(static x => x.MethodKind == MethodKind.Ordinary)
.Select(static x => new MethodData(
Name: x.Name,
Description: GetDescription(x),
IsAsync: x.IsAsync || x.ReturnType.Name == "Task",
IsVoid: x.ReturnsVoid,
Parameters: x.Parameters
.Where(static x => x.Type.MetadataName != "CancellationToken")
.Select(static x => ToParameterData(
typeSymbol: x.Type,
name: x.Name,
description: GetDescription(x),
isRequired: !x.IsOptional))
.ToArray()))
.ToArray();

return new InterfaceData(
Namespace: interfaceSymbol.ContainingNamespace.ToDisplayString(),
Name: interfaceSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat),
Methods: methods);
}

private static ParameterData ToParameterData(ITypeSymbol typeSymbol, string? name = null, string? description = null, bool isRequired = true)
{
string schemaType;
string? format = null;
var properties = Array.Empty<ParameterData>();
ParameterData? arrayItem = null;
switch (typeSymbol.TypeKind)
{
case TypeKind.Enum:
schemaType = "string";
break;

case TypeKind.Structure:
switch (typeSymbol.SpecialType)
{
case SpecialType.System_Int32:
schemaType = "integer";
format = "int32";
break;

case SpecialType.System_Int64:
schemaType = "integer";
format = "int64";
break;

case SpecialType.System_Single:
schemaType = "number";
format = "double";
break;

case SpecialType.System_Double:
schemaType = "number";
format = "float";
break;

case SpecialType.System_DateTime:
schemaType = "string";
format = "date-time";
break;

case SpecialType.System_Boolean:
schemaType = "boolean";
break;

case SpecialType.None:
switch (typeSymbol.Name)
{
case "DateOnly":
schemaType = "string";
format = "date";
break;

default:
throw new NotImplementedException($"{typeSymbol.Name} is not implemented.");
}
break;

default:
throw new NotImplementedException($"{typeSymbol.SpecialType} is not implemented.");
}
break;

case TypeKind.Class:
switch (typeSymbol.SpecialType)
{
case SpecialType.System_String:
schemaType = "string";
break;


case SpecialType.None:
schemaType = "object";
properties = typeSymbol.GetMembers()
.OfType<IPropertySymbol>()
.Select(static y => ToParameterData(
typeSymbol: y.Type,
name: y.Name,
description: GetDescription(y),
isRequired: true))
.ToArray();
break;

default:
throw new NotImplementedException($"{typeSymbol.SpecialType} is not implemented.");
}
break;

case TypeKind.Interface when typeSymbol.MetadataName == "IReadOnlyCollection`1":
schemaType = "array";
arrayItem = (typeSymbol as INamedTypeSymbol)?.TypeArguments
.Select(static y => ToParameterData(y))
.FirstOrDefault();
break;

case TypeKind.Array:
schemaType = "array";
arrayItem = ToParameterData((typeSymbol as IArrayTypeSymbol)?.ElementType!);
break;

default:
throw new NotImplementedException($"{typeSymbol.TypeKind} is not implemented.");
}

return new ParameterData(
Name: !string.IsNullOrWhiteSpace(name)
? name!
: typeSymbol.Name,
Description: !string.IsNullOrWhiteSpace(description)
? description!
: GetDescription(typeSymbol),
Type: typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
DefaultValue: GetDefaultValue(typeSymbol),
SchemaType: schemaType,
Format: format,
Properties: properties,
ArrayItem: arrayItem != null
? new []{ arrayItem.Value }
: Array.Empty<ParameterData>(),
EnumValues: typeSymbol.TypeKind == TypeKind.Enum
? typeSymbol
.GetMembers()
.OfType<IFieldSymbol>()
.Select(static x => x.Name.ToLowerInvariant())
.ToArray()
: Array.Empty<string>(),
IsNullable: IsNullable(typeSymbol),
IsRequired: isRequired);
}

private static bool IsNullable(ITypeSymbol typeSymbol)
{
if (typeSymbol.TypeKind == TypeKind.Enum)
{
return false;
}
if (typeSymbol.TypeKind == TypeKind.Structure)
{
return false;
}

return typeSymbol.SpecialType switch
{
SpecialType.System_String => false,
_ => true,
};
}

private static string GetDefaultValue(ITypeSymbol typeSymbol)
{
switch (typeSymbol.SpecialType)
{
case SpecialType.System_String:
return "string.Empty";

default:
return string.Empty;
}
}

#endregion
}
56 changes: 56 additions & 0 deletions src/libs/CodeAsOpenApi/SourceGenerationHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
namespace H.Generators;

internal static class SourceGenerationHelper
{
/// <summary>
/// https://swagger.io/docs/specification/data-models/data-types/
/// </summary>
/// <param name="parameter"></param>
/// <param name="depth"></param>
/// <returns></returns>
public static string GenerateOpenApiSchema(ParameterData parameter, int depth = 0)
{
var indent = new string(' ', depth * 4);
if (parameter.ArrayItem.Count != 0)
{
return $@"new
{indent} {{
{indent} type = ""{parameter.SchemaType}"",
{indent} description = ""{parameter.Description}"",
{indent} items = {GenerateOpenApiSchema(parameter.ArrayItem.First(), depth: depth + 1)},
{indent} }}";
}
if (parameter.Properties.Count != 0)
{
return $@"new
{indent} {{
{indent} type = ""{parameter.SchemaType}"",
{indent} description = ""{parameter.Description}"",
{indent} properties = new Dictionary<string, object>
{indent} {{
{indent} {string.Join(",\n " + indent, parameter.Properties.Select(x => $@"[""{x.Name}""] = " + GenerateOpenApiSchema(x, depth: depth + 2)))}
{indent} }},
{indent} required = new string[] {{ {string.Join(", ", parameter.Properties
.Where(static x => x.IsRequired)
.Select(static x => $"\"{x.Name}\""))} }},
{indent} }}";
}

if (parameter.EnumValues.Count != 0)
{
return $@"new
{indent} {{
{indent} type = ""{parameter.SchemaType}"",
{indent} description = ""{parameter.Description}"",
{indent} @enum = new string[] {{ {string.Join(", ", parameter.EnumValues.Select(static x => $"\"{x}\""))} }},
{indent} }}";
}

return $@"new
{indent} {{
{indent} type = ""{parameter.SchemaType}"",{(parameter.Format != null ? $@"
{indent} format = ""{parameter.Format}""," : "")}
{indent} description = ""{parameter.Description}"",
{indent} }}";
}
}
16 changes: 16 additions & 0 deletions src/libs/OpenAI.Generators/SourceGenerationHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,22 @@ public static (string Name, string Description, Dictionary<string, object> Dicti
}};
}}
public static global::System.Collections.Generic.ICollection<global::OpenAI.Tool> AsTools(this {@interface.Name} functions)
{{
{@interface.Methods.Select((method, i) => $@"
var (name{i}, description{i}, jsonNode{i}) = functions.{method.Name}AsParametersJsonNode();").Inject()}
return new global::System.Collections.Generic.List<global::OpenAI.Tool>
{{
{@interface.Methods.Select((_, i) => $@"
new global::OpenAI.Tool(new global::OpenAI.Function(
name: name{i},
description: description{i},
parameters: jsonNode{i})),
").Inject()}
}};
}}
public static global::System.Collections.Generic.IReadOnlyDictionary<string, global::System.Func<string, global::System.Threading.CancellationToken, global::System.Threading.Tasks.Task<string>>> AsCalls(this {@interface.Name} service)
{{
return new global::System.Collections.Generic.Dictionary<string, global::System.Func<string, global::System.Threading.CancellationToken, global::System.Threading.Tasks.Task<string>>>
Expand Down
2 changes: 1 addition & 1 deletion src/libs/OpenAI/Client/Common/Tool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public Tool(Function function)

[JsonInclude]
[JsonPropertyName("id")]
public string Id { get; private set; }
public string Id { get; set; }

[JsonInclude]
[JsonPropertyName("index")]
Expand Down
7 changes: 5 additions & 2 deletions src/libs/OpenAI/Extensions/StringExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,11 @@ public static Message AsAssistantMessage(this string content)
/// <param name="json"></param>
/// <param name="name"></param>
/// <returns></returns>
public static Message AsFunctionMessage(this string json, string name)
public static Message AsFunctionMessage(this string json, string name, string toolCallId)
{
return new Message(new Tool(new Function(name)), json);
return new Message(new Tool(new Function(name))
{
Id = toolCallId,
}, json);
}
}

0 comments on commit 753a400

Please sign in to comment.