Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update example to SK 1.1, fix TS generator and marshaller bugs #191

Merged
merged 7 commits into from
Jan 19, 2024
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
32 changes: 20 additions & 12 deletions examples/semantic-kernel/example.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
// @ts-check

import dotnet from 'node-api-dotnet';
import './bin/System.Text.Encodings.Web.js';
import './bin/Microsoft.Extensions.DependencyInjection.js';
import './bin/Microsoft.Extensions.Logging.Abstractions.js';
import './bin/Microsoft.SemanticKernel.Abstractions.js';
import './bin/Microsoft.SemanticKernel.Core.js';
import './bin/Microsoft.SemanticKernel.Connectors.AI.OpenAI.js';
import './bin/Microsoft.SemanticKernel.TemplateEngine.Basic.js';
import './bin/Microsoft.SemanticKernel.Connectors.OpenAI.js';

const Logging = dotnet.Microsoft.Extensions.Logging;
const SK = dotnet.Microsoft.SemanticKernel;
Expand All @@ -28,18 +29,22 @@ const loggerFactory = {
dispose() {}
};

let kernelBuilder = new SK.KernelBuilder();
kernelBuilder.WithLoggerFactory(loggerFactory);
const kernelBuilder = SK.Kernel.CreateBuilder();
//kernelBuilder.WithLoggerFactory(loggerFactory);

// The JS marshaller does not yet support extension methods.
SK.OpenAIKernelBuilderExtensions.WithAzureOpenAIChatCompletionService(
SK.OpenAIServiceCollectionExtensions.AddAzureOpenAIChatCompletion(
kernelBuilder,
process.env['OPENAI_DEPLOYMENT'] || '',
process.env['OPENAI_ENDPOINT'] || '',
process.env['OPENAI_KEY'] || '',
// Include optional parameters to disambiguate the overload.
undefined,
undefined,
undefined,
);

const kernel = kernelBuilder.Build();
const kernel = SK.KernelExtensions.Build(kernelBuilder);

const prompt = `{{$input}}

Expand All @@ -57,15 +62,18 @@ such orders would conflict with the First Law.
does not conflict with the First or Second Law.
`;

const requestSettings = new SK.Connectors.AI.OpenAI.OpenAIRequestSettings();
requestSettings.MaxTokens = 100;
const executionSettings = new SK.Connectors.OpenAI.OpenAIPromptExecutionSettings();
executionSettings.MaxTokens = 100;

// The JS marshaller does not yet support extension methods.
const summaryFunction = SK.OpenAIKernelExtensions
.CreateSemanticFunction(kernel, prompt, requestSettings);
const summaryFunction = SK.KernelExtensions.CreateFunctionFromPrompt(
kernel, prompt, executionSettings);

const summary = await SK.SKFunctionExtensions.InvokeAsync(
summaryFunction, textToSummarize, kernel);
const summarizeArguments = new Map();
summarizeArguments.set('input', textToSummarize);

const summary = await kernel.InvokeAsync(
summaryFunction, new SK.KernelArguments(summarizeArguments, undefined));

console.log();
console.log(summary.toString());
2 changes: 1 addition & 1 deletion examples/semantic-kernel/semantic-kernel.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.SemanticKernel" Version="1.0.0-beta8" />
<PackageReference Include="Microsoft.SemanticKernel" Version="1.1.0" />
<PackageReference Include="Microsoft.JavaScript.NodeApi.Generator" Version="0.4.*-*" />
</ItemGroup>

Expand Down
8 changes: 4 additions & 4 deletions src/NodeApi.DotNetHost/JSMarshaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ public Expression<JSCallback> BuildFromJSConstructorExpression(ConstructorInfo c

for (int i = 0; i < parameters.Length; i++)
{
argVariables[i] = Expression.Variable(parameters[i].ParameterType, parameters[i].Name);
argVariables[i] = Variable(parameters[i]);
statements.Add(Expression.Assign(argVariables[i],
BuildArgumentExpression(i, parameters[i])));
}
Expand Down Expand Up @@ -1859,10 +1859,10 @@ private LambdaExpression BuildConvertFromJSValueExpression(Type toType)
// public type is passed to JS and then passed back to .NET as `object` type.

/*
* (T)(value.TryUnwrap() ?? value.TryGetValueExternal());
* (T)(value.TryUnwrap() ?? value.GetValueExternalOrPrimitive());
*/
MethodInfo getExternalMethod =
typeof(JSNativeApi).GetStaticMethod(nameof(JSNativeApi.TryGetValueExternal));
MethodInfo getExternalMethod = typeof(JSNativeApi).GetStaticMethod(
nameof(JSNativeApi.GetValueExternalOrPrimitive));
statements = new[]
{
Expression.Convert(
Expand Down
5 changes: 4 additions & 1 deletion src/NodeApi.DotNetHost/ManagedHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -175,12 +175,14 @@ JSValue removeListener(JSCallbackArgs args)
}

public static bool IsTracingEnabled { get; } =
Debugger.IsAttached ||
Environment.GetEnvironmentVariable("TRACE_NODE_API_HOST") == "1";

public static void Trace(string msg)
{
if (IsTracingEnabled)
{
Debug.WriteLine(msg);
Console.WriteLine(msg);
Console.Out.Flush();
}
Expand Down Expand Up @@ -213,7 +215,8 @@ public static napi_value InitializeModule(napi_env env, napi_value exports)

JSRuntime runtime = new NodejsRuntime();

if (Environment.GetEnvironmentVariable("TRACE_NODE_API_RUNTIME") != null)
if (Debugger.IsAttached ||
Environment.GetEnvironmentVariable("TRACE_NODE_API_RUNTIME") != null)
{
TraceSource trace = new(typeof(JSValue).Namespace!);
trace.Switch.Level = SourceLevels.All;
Expand Down
96 changes: 56 additions & 40 deletions src/NodeApi.DotNetHost/TypeExporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,62 +94,78 @@ private JSReference ExportType(Type type)

private JSReference ExportClass(Type type)
{
string typeName = type.Name;
Trace($"### ExportClass({typeName}");

if (_exportedTypes.TryGetValue(type, out JSReference? classObjectReference))
{
return classObjectReference;
}

Trace($"> {nameof(TypeExporter)}.ExportClass({type.FormatName()})");

bool isStatic = type.IsAbstract && type.IsSealed;
Type classBuilderType =
(type.IsValueType ? typeof(JSStructBuilder<>) : typeof(JSClassBuilder<>))
.MakeGenericType(isStatic ? typeof(object) : type);

object classBuilder;
if (type.IsInterface || isStatic || type.IsValueType)
{
classBuilder = classBuilderType.CreateInstance(
new[] { typeof(string) }, new[] { type.Name });
}
else
// Add a temporary null entry to the dictionary while exporting this type, in case the
// type is encountered while exporting members. It will be non-null by the time this method returns
// (or removed if an exception is thrown).
_exportedTypes.Add(type, null!);
try
{
ConstructorInfo[] constructors =
type.GetConstructors(BindingFlags.Public | BindingFlags.Instance)
.Where(IsSupportedConstructor)
.ToArray();
JSCallbackDescriptor constructorDescriptor;
if (constructors.Length == 1 &&
!constructors[0].GetParameters().Any((p) => p.IsOptional))
bool isStatic = type.IsAbstract && type.IsSealed;
Type classBuilderType =
(type.IsValueType ? typeof(JSStructBuilder<>) : typeof(JSClassBuilder<>))
.MakeGenericType(isStatic ? typeof(object) : type);

object classBuilder;
if (type.IsInterface || isStatic || type.IsValueType)
{
constructorDescriptor =
_marshaller.BuildFromJSConstructorExpression(constructors[0]).Compile();
classBuilder = classBuilderType.CreateInstance(
new[] { typeof(string) }, new[] { type.Name });
}
else
{
// Multiple constructors or optional parameters require overload resolution.
constructorDescriptor =
_marshaller.BuildConstructorOverloadDescriptor(constructors);
}
ConstructorInfo[] constructors =
type.GetConstructors(BindingFlags.Public | BindingFlags.Instance)
.Where(IsSupportedConstructor)
.ToArray();
JSCallbackDescriptor constructorDescriptor;
if (constructors.Length == 1 &&
!constructors[0].GetParameters().Any((p) => p.IsOptional))
{
constructorDescriptor =
_marshaller.BuildFromJSConstructorExpression(constructors[0]).Compile();
}
else
{
// Multiple constructors or optional parameters require overload resolution.
constructorDescriptor =
_marshaller.BuildConstructorOverloadDescriptor(constructors);
}

classBuilder = classBuilderType.CreateInstance(
new[] { typeof(string), typeof(JSCallbackDescriptor) },
new object[] { type.Name, constructorDescriptor });
}
classBuilder = classBuilderType.CreateInstance(
new[] { typeof(string), typeof(JSCallbackDescriptor) },
new object[] { type.Name, constructorDescriptor });
}

ExportProperties(type, classBuilder);
ExportMethods(type, classBuilder);
ExportNestedTypes(type, classBuilder);
ExportProperties(type, classBuilder);
ExportMethods(type, classBuilder);
ExportNestedTypes(type, classBuilder);

string defineMethodName = type.IsInterface ? "DefineInterface" :
isStatic ? "DefineStaticClass" : type.IsValueType ? "DefineStruct" : "DefineClass";
MethodInfo defineClassMethod = classBuilderType.GetInstanceMethod(defineMethodName);
JSValue classObject = (JSValue)defineClassMethod.Invoke(
classBuilder,
defineClassMethod.GetParameters().Select((_) => (object?)null).ToArray())!;
string defineMethodName = type.IsInterface ? "DefineInterface" :
isStatic ? "DefineStaticClass" : type.IsValueType ? "DefineStruct" : "DefineClass";
MethodInfo defineClassMethod = classBuilderType.GetInstanceMethod(defineMethodName);
JSValue classObject = (JSValue)defineClassMethod.Invoke(
classBuilder,
defineClassMethod.GetParameters().Select((_) => (object?)null).ToArray())!;

classObjectReference = new JSReference(classObject);
_exportedTypes.Add(type, classObjectReference);
classObjectReference = new JSReference(classObject);
_exportedTypes[type] = classObjectReference;
}
catch
{
// Clean up the temporary null entry.
_exportedTypes.Remove(type);
throw;
}

// Also export any types returned by properties or methods of this type, because
// they might otherwise not be referenced by JS before they are used.
Expand Down
6 changes: 4 additions & 2 deletions src/NodeApi.Generator/SourceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ public abstract class SourceGenerator

private static readonly Regex s_paragraphBreakRegex = new(@" *\<para */\> *");

protected const char NonBreakingSpace = (char)0xA0;

public enum DiagnosticId
{
NoExports = 1000,
Expand Down Expand Up @@ -193,11 +195,11 @@ protected static IEnumerable<string> WrapComment(string comment, int wrapColumn)
}
}

yield return comment.Substring(0, i).TrimEnd();
yield return comment.Substring(0, i).TrimEnd().Replace(NonBreakingSpace, ' ');
comment = comment.Substring(i + 1);
}

yield return comment.TrimEnd();
yield return comment.TrimEnd().Replace(NonBreakingSpace, ' ');
}
}
}
Loading