[.Net] Fix #2660 and add tests for AutoGen.DotnetInteractive (#2676)

* update

* fix 2660

* remove unnecessary feed
This commit is contained in:
Xiaoyun Zhang 2024-05-13 20:40:26 -07:00 committed by GitHub
parent 5f7c34ac72
commit ba82c1d566
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 204 additions and 107 deletions

View File

@ -33,7 +33,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.Mistral", "src\Auto
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.Mistral.Tests", "test\AutoGen.Mistral.Tests\AutoGen.Mistral.Tests.csproj", "{15441693-3659-4868-B6C1-B106F52FF3BA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoGen.SemanticKernel.Tests", "test\AutoGen.SemanticKernel.Tests\AutoGen.SemanticKernel.Tests.csproj", "{1DFABC4A-8458-4875-8DCB-59F3802DAC65}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.SemanticKernel.Tests", "test\AutoGen.SemanticKernel.Tests\AutoGen.SemanticKernel.Tests.csproj", "{1DFABC4A-8458-4875-8DCB-59F3802DAC65}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoGen.DotnetInteractive.Tests", "test\AutoGen.DotnetInteractive.Tests\AutoGen.DotnetInteractive.Tests.csproj", "{B61388CA-DC73-4B7F-A7B2-7B9A86C9229E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -93,6 +95,10 @@ Global
{1DFABC4A-8458-4875-8DCB-59F3802DAC65}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1DFABC4A-8458-4875-8DCB-59F3802DAC65}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1DFABC4A-8458-4875-8DCB-59F3802DAC65}.Release|Any CPU.Build.0 = Release|Any CPU
{B61388CA-DC73-4B7F-A7B2-7B9A86C9229E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B61388CA-DC73-4B7F-A7B2-7B9A86C9229E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B61388CA-DC73-4B7F-A7B2-7B9A86C9229E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B61388CA-DC73-4B7F-A7B2-7B9A86C9229E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -111,6 +117,7 @@ Global
{6585D1A4-3D97-4D76-A688-1933B61AEB19} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
{15441693-3659-4868-B6C1-B106F52FF3BA} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
{1DFABC4A-8458-4875-8DCB-59F3802DAC65} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
{B61388CA-DC73-4B7F-A7B2-7B9A86C9229E} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {93384647-528D-46C8-922C-8DB36A382F0B}

View File

@ -2,8 +2,6 @@
<configuration>
<packageSources>
<clear />
<add key="dotnet-public" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public/nuget/v3/index.json" />
<add key="dotnet-tools" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json" />
<add key="nuget" value="https://api.nuget.org/v3/index.json" />
</packageSources>
<disabledPackageSources />

View File

@ -10,7 +10,7 @@
<FluentAssertionVersion>6.8.0</FluentAssertionVersion>
<XUnitVersion>2.4.2</XUnitVersion>
<MicrosoftNETTestSdkVersion>17.7.0</MicrosoftNETTestSdkVersion>
<MicrosoftDotnetInteractive>1.0.0-beta.23523.2</MicrosoftDotnetInteractive>
<MicrosoftDotnetInteractive>1.0.0-beta.24229.4</MicrosoftDotnetInteractive>
<MicrosoftSourceLinkGitHubVersion>8.0.0</MicrosoftSourceLinkGitHubVersion>
<JsonSchemaVersion>4.0.0</JsonSchemaVersion>
</PropertyGroup>

View File

@ -19,7 +19,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.DotNet.Interactive.VisualStudio" Version="$(MicrosoftDotnetInteractive)" />
<PackageReference Include="Microsoft.DotNet.Interactive" Version="$(MicrosoftDotnetInteractive)" />
</ItemGroup>
<ItemGroup>
@ -27,14 +27,12 @@
<EmbeddedResource Include="RestoreInteractive.config" />
</ItemGroup>
<ItemGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" Version="$(AzureOpenAIVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AutoGen\AutoGen.csproj" />
<ProjectReference Include="..\AutoGen.Core\AutoGen.Core.csproj" />
</ItemGroup>
</Project>

View File

@ -12,57 +12,58 @@ namespace AutoGen.DotnetInteractive;
public class DotnetInteractiveFunction : IDisposable
{
private readonly InteractiveService? _interactiveService = null;
private string? _notebookPath;
private string _notebookPath;
private readonly KernelInfoCollection _kernelInfoCollection = new KernelInfoCollection();
/// <summary>
/// Create an instance of <see cref="DotnetInteractiveFunction"/>"
/// </summary>
/// <param name="interactiveService">interactive service to use.</param>
/// <param name="notebookPath">notebook path if provided.</param>
public DotnetInteractiveFunction(InteractiveService interactiveService, string? notebookPath = null, bool continueFromExistingNotebook = false)
{
this._interactiveService = interactiveService;
this._notebookPath = notebookPath;
this._notebookPath = notebookPath ?? Path.GetTempPath() + "notebook.ipynb";
this._kernelInfoCollection.Add(new KernelInfo("csharp"));
this._kernelInfoCollection.Add(new KernelInfo("markdown"));
if (this._notebookPath != null)
if (continueFromExistingNotebook == false)
{
if (continueFromExistingNotebook == false)
// remove existing notebook
if (File.Exists(this._notebookPath))
{
// remove existing notebook
if (File.Exists(this._notebookPath))
{
File.Delete(this._notebookPath);
}
var document = new InteractiveDocument();
using var stream = File.OpenWrite(_notebookPath);
Notebook.Write(document, stream, this._kernelInfoCollection);
stream.Flush();
stream.Dispose();
File.Delete(this._notebookPath);
}
else if (continueFromExistingNotebook == true && File.Exists(this._notebookPath))
var document = new InteractiveDocument();
using var stream = File.OpenWrite(_notebookPath);
Notebook.Write(document, stream, this._kernelInfoCollection);
stream.Flush();
stream.Dispose();
}
else if (continueFromExistingNotebook == true && File.Exists(this._notebookPath))
{
// load existing notebook
using var readStream = File.OpenRead(this._notebookPath);
var document = Notebook.Read(readStream, this._kernelInfoCollection);
foreach (var cell in document.Elements)
{
// load existing notebook
using var readStream = File.OpenRead(this._notebookPath);
var document = Notebook.Read(readStream, this._kernelInfoCollection);
foreach (var cell in document.Elements)
if (cell.KernelName == "csharp")
{
if (cell.KernelName == "csharp")
{
var code = cell.Contents;
this._interactiveService.SubmitCSharpCodeAsync(code, default).Wait();
}
var code = cell.Contents;
this._interactiveService.SubmitCSharpCodeAsync(code, default).Wait();
}
}
else
{
// create an empty notebook
var document = new InteractiveDocument();
}
else
{
// create an empty notebook
var document = new InteractiveDocument();
using var stream = File.OpenWrite(_notebookPath);
Notebook.Write(document, stream, this._kernelInfoCollection);
stream.Flush();
stream.Dispose();
}
using var stream = File.OpenWrite(_notebookPath);
Notebook.Write(document, stream, this._kernelInfoCollection);
stream.Flush();
stream.Dispose();
}
}

View File

@ -5,7 +5,6 @@ using System.Diagnostics;
using System.Reactive.Linq;
using System.Reflection;
using Microsoft.DotNet.Interactive;
using Microsoft.DotNet.Interactive.App.Connection;
using Microsoft.DotNet.Interactive.Commands;
using Microsoft.DotNet.Interactive.Connection;
using Microsoft.DotNet.Interactive.Events;
@ -41,7 +40,7 @@ public class InteractiveService : IDisposable
public async Task<bool> StartAsync(string workingDirectory, CancellationToken ct = default)
{
this.kernel = await this.CreateKernelAsync(workingDirectory, ct);
this.kernel = await this.CreateKernelAsync(workingDirectory, true, ct);
return true;
}
@ -84,7 +83,51 @@ public class InteractiveService : IDisposable
return await this.SubmitCommandAsync(command, ct);
}
private async Task<Kernel> CreateKernelAsync(string workingDirectory, CancellationToken ct = default)
public bool RestoreDotnetInteractive()
{
this.WriteLine("Restore dotnet interactive tool");
// write RestoreInteractive.config from embedded resource to this.workingDirectory
var assembly = Assembly.GetAssembly(typeof(InteractiveService))!;
var resourceName = "AutoGen.DotnetInteractive.RestoreInteractive.config";
using (var stream = assembly.GetManifestResourceStream(resourceName)!)
using (var fileStream = File.Create(Path.Combine(this.installingDirectory, "RestoreInteractive.config")))
{
stream.CopyTo(fileStream);
}
// write dotnet-tool.json from embedded resource to this.workingDirectory
resourceName = "AutoGen.DotnetInteractive.dotnet-tools.json";
using (var stream2 = assembly.GetManifestResourceStream(resourceName)!)
using (var fileStream2 = File.Create(Path.Combine(this.installingDirectory, "dotnet-tools.json")))
{
stream2.CopyTo(fileStream2);
}
var psi = new ProcessStartInfo
{
FileName = "dotnet",
Arguments = $"tool restore --configfile RestoreInteractive.config",
WorkingDirectory = this.installingDirectory,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true,
};
using var process = new Process { StartInfo = psi };
process.OutputDataReceived += this.PrintProcessOutput;
process.ErrorDataReceived += this.PrintProcessOutput;
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
process.WaitForExit();
return process.ExitCode == 0;
}
private async Task<Kernel> CreateKernelAsync(string workingDirectory, bool restoreWhenFail = true, CancellationToken ct = default)
{
try
{
@ -139,13 +182,13 @@ public class InteractiveService : IDisposable
return compositeKernel;
}
catch (CommandLineInvocationException ex) when (ex.Message.Contains("Cannot find a tool in the manifest file that has a command named 'dotnet-interactive'"))
catch (CommandLineInvocationException) when (restoreWhenFail)
{
var success = this.RestoreDotnetInteractive();
if (success)
{
return await this.CreateKernelAsync(workingDirectory, ct);
return await this.CreateKernelAsync(workingDirectory, false, ct);
}
throw;
@ -176,50 +219,6 @@ public class InteractiveService : IDisposable
this.Output?.Invoke(this, data);
}
private bool RestoreDotnetInteractive()
{
this.WriteLine("Restore dotnet interactive tool");
// write RestoreInteractive.config from embedded resource to this.workingDirectory
var assembly = Assembly.GetAssembly(typeof(InteractiveService))!;
var resourceName = "AutoGen.DotnetInteractive.RestoreInteractive.config";
using (var stream = assembly.GetManifestResourceStream(resourceName)!)
using (var fileStream = File.Create(Path.Combine(this.installingDirectory, "RestoreInteractive.config")))
{
stream.CopyTo(fileStream);
}
// write dotnet-tool.json from embedded resource to this.workingDirectory
resourceName = "AutoGen.DotnetInteractive.dotnet-tools.json";
using (var stream2 = assembly.GetManifestResourceStream(resourceName)!)
using (var fileStream2 = File.Create(Path.Combine(this.installingDirectory, "dotnet-tools.json")))
{
stream2.CopyTo(fileStream2);
}
var psi = new ProcessStartInfo
{
FileName = "dotnet",
Arguments = $"tool restore --configfile RestoreInteractive.config",
WorkingDirectory = this.installingDirectory,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true,
};
using var process = new Process { StartInfo = psi };
process.OutputDataReceived += this.PrintProcessOutput;
process.ErrorDataReceived += this.PrintProcessOutput;
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
process.WaitForExit();
return process.ExitCode == 0;
}
private void PrintProcessOutput(object sender, DataReceivedEventArgs e)
{
if (!string.IsNullOrEmpty(e.Data))

View File

@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"Microsoft.dotnet-interactive": {
"version": "1.0.431302",
"version": "1.0.522904",
"commands": [
"dotnet-interactive"
]

View File

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(TestTargetFramework)</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<IsPackable>false</IsPackable>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ApprovalTests" Version="$(ApprovalTestVersion)" />
<PackageReference Include="FluentAssertions" Version="$(FluentAssertionVersion)" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkVersion)" />
<PackageReference Include="xunit" Version="$(XUnitVersion)" />
<PackageReference Include="xunit.runner.console" Version="$(XUnitVersion)" />
<PackageReference Include="xunit.runner.visualstudio" Version="$(XUnitVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\AutoGen.SourceGenerator\AutoGen.SourceGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\AutoGen.Tests\AutoGen.Tests.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,82 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// DotnetInteractiveServiceTest.cs
using FluentAssertions;
using Xunit;
using Xunit.Abstractions;
namespace AutoGen.DotnetInteractive.Tests;
public class DotnetInteractiveServiceTest : IDisposable
{
private ITestOutputHelper _output;
private InteractiveService _interactiveService;
private string _workingDir;
public DotnetInteractiveServiceTest(ITestOutputHelper output)
{
_output = output;
_workingDir = Path.Combine(Path.GetTempPath(), "test", Path.GetRandomFileName());
if (!Directory.Exists(_workingDir))
{
Directory.CreateDirectory(_workingDir);
}
_interactiveService = new InteractiveService(_workingDir);
_interactiveService.StartAsync(_workingDir, default).Wait();
}
public void Dispose()
{
_interactiveService.Dispose();
}
[Fact]
public async Task ItRunCSharpCodeSnippetTestsAsync()
{
var cts = new CancellationTokenSource();
var isRunning = await _interactiveService.StartAsync(_workingDir, cts.Token);
isRunning.Should().BeTrue();
_interactiveService.IsRunning().Should().BeTrue();
// test code snippet
var hello_world = @"
Console.WriteLine(""hello world"");
";
await this.TestCSharpCodeSnippet(_interactiveService, hello_world, "hello world");
await this.TestCSharpCodeSnippet(
_interactiveService,
code: @"
Console.WriteLine(""hello world""
",
expectedOutput: "Error: (2,32): error CS1026: ) expected");
await this.TestCSharpCodeSnippet(
service: _interactiveService,
code: "throw new Exception();",
expectedOutput: "Error: System.Exception: Exception of type 'System.Exception' was thrown");
}
[Fact]
public async Task ItRunPowershellScriptTestsAsync()
{
// test power shell
var ps = @"Write-Output ""hello world""";
await this.TestPowershellCodeSnippet(_interactiveService, ps, "hello world");
}
private async Task TestPowershellCodeSnippet(InteractiveService service, string code, string expectedOutput)
{
var result = await service.SubmitPowershellCodeAsync(code, CancellationToken.None);
result.Should().StartWith(expectedOutput);
}
private async Task TestCSharpCodeSnippet(InteractiveService service, string code, string expectedOutput)
{
var result = await service.SubmitCSharpCodeAsync(code, CancellationToken.None);
result.Should().StartWith(expectedOutput);
}
}

View File

@ -4,12 +4,6 @@
AutoGen.Net provides the following packages, you can choose to install one or more of them based on your needs:
> [!Note]
> The `AutoGen.DotnetInteractive` has a dependency on `Microsoft.DotNet.Interactive.VisualStudio` which is not available on nuget.org. To restore the dependency, you need to add the following package source to your project:
> ```bash
> https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json
> ```
- `AutoGen`: The one-in-all package. This package has dependencies over `AutoGen.Core`, `AutoGen.OpenAI`, `AutoGen.LMStudio`, `AutoGen.SemanticKernel` and `AutoGen.SourceGenerator`.
- `AutoGen.Core`: The core package, this package provides the abstraction for message type, agent and group chat.
- `AutoGen.OpenAI`: This package provides the integration agents over openai models.

View File

@ -19,12 +19,6 @@ For example, in data analysis scenario, agent can resolve tasks like "What is th
## How to run dotnet code snippet?
The built-in feature of running dotnet code snippet is provided by [dotnet-interactive](https://github.com/dotnet/interactive). To run dotnet code snippet, you need to install the following package to your project, which provides the intergraion with dotnet-interactive:
> [!Note]
> The `AutoGen.DotnetInteractive` has a dependency on `Microsoft.DotNet.Interactive.VisualStudio` which is not available on nuget.org. To restore the dependency, you need to add the following package source to your project:
> ```bash
> https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json
> ```
```xml
<PackageReference Include="AutoGen.DotnetInteractive" />
```