Skip to content

WIP Phi Silica Agent for Copilot+PCs #373

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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
38 changes: 36 additions & 2 deletions build.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
$metadata = Get-Content $PSScriptRoot/tools/metadata.json | ConvertFrom-Json
$dotnetSDKVersion = $(Get-Content $PSScriptRoot/global.json | ConvertFrom-Json).Sdk.Version
$dotnetLocalDir = if ($IsWindows) { "$env:LocalAppData\Microsoft\dotnet" } else { "$env:HOME/.dotnet" }
$windowsOnlyAgents = @('phisilica')

function Start-Build
{
Expand All @@ -20,7 +21,7 @@ function Start-Build
[string] $Runtime = [NullString]::Value,

[Parameter()]
[ValidateSet('openai-gpt', 'msaz', 'interpreter', 'ollama')]
[ValidateSet('openai-gpt', 'msaz', 'interpreter', 'ollama', 'phisilica')]
[string[]] $AgentToInclude,

[Parameter()]
Expand All @@ -43,13 +44,18 @@ function Start-Build
$MyInvocation.MyCommand.Parameters["AgentToInclude"].Attributes |
Where-Object { $_ -is [ValidateSet] } |
Select-Object -First 1 |
ForEach-Object ValidValues
ForEach-Object ValidValues |
Skip-Unapplicable
} else {
$agents.Split(",", [System.StringSplitOptions]::TrimEntries)
Write-Verbose "Include agents specified in Metadata.json"
}
}

if (HasUnapplicableAgent $AgentToInclude) {
throw "The following specified agent(s) cannot be built on the current platform: $($windowsOnlyAgents -join ', ')."
}

$RID = $Runtime ?? (dotnet --info |
Select-String '^\s*RID:\s+(\w+-\w+)$' |
Select-Object -First 1 |
Expand All @@ -69,6 +75,7 @@ function Start-Build
$msaz_dir = Join-Path $agent_dir "Microsoft.Azure.Agent"
$interpreter_agent_dir = Join-Path $agent_dir "AIShell.Interpreter.Agent"
$ollama_agent_dir = Join-Path $agent_dir "AIShell.Ollama.Agent"
$phisilica_agent_dir = Join-Path $agent_dir "AIShell.PhiSilica.Agent"

$config = $Configuration.ToLower()
$out_dir = Join-Path $PSScriptRoot "out"
Expand All @@ -79,6 +86,7 @@ function Start-Build
$msaz_out_dir = Join-Path $app_out_dir "agents" "Microsoft.Azure.Agent"
$interpreter_out_dir = Join-Path $app_out_dir "agents" "AIShell.Interpreter.Agent"
$ollama_out_dir = Join-Path $app_out_dir "agents" "AIShell.Ollama.Agent"
$phisilica_out_dir = Join-Path $app_out_dir "agents" "AIShell.PhiSilica.Agent"

if ($Clean) {
if (Test-Path $out_dir) {
Expand Down Expand Up @@ -152,6 +160,12 @@ function Start-Build
dotnet publish $ollama_csproj -c $Configuration -o $ollama_out_dir
}

if ($LASTEXITCODE -eq 0 -and $AgentToInclude -contains 'phisilica') {
Write-Host "`n[Build the PhiSilica agent ...]`n" -ForegroundColor Green
$phisilica_csproj = GetProjectFile $phisilica_agent_dir
dotnet publish $phisilica_csproj -c $Configuration -o $phisilica_out_dir
}

if ($LASTEXITCODE -eq 0 -and -not $NotIncludeModule) {
Write-Host "`n[Build the AIShell module ...]`n" -ForegroundColor Green
$aish_module_csproj = GetProjectFile $module_dir
Expand All @@ -175,6 +189,26 @@ function Start-Build
}
}

filter Skip-Unapplicable {
if ($IsWindows -or $windowsOnlyAgents -notcontains $_) {
$_
}
}

function HasUnapplicableAgent($specifiedAgents) {
if ($IsWindows) {
return $false
}

foreach ($agent in $windowsOnlyAgents) {
if ($specifiedAgents -contains $agent) {
return $true
}
}

return $false
}

function GetProjectFile($dir)
{
return Get-Item "$dir/*.csproj" | ForEach-Object FullName
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net8.0-windows10.0.26100.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<NoWarn>CS8305</NoWarn>
<Platforms>AnyCPU</Platforms>
<WindowsPackageType>None</WindowsPackageType>
<EnableDynamicLoading>true</EnableDynamicLoading>
<WindowsAppSdkBootstrapInitialize>true</WindowsAppSdkBootstrapInitialize>
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
<RuntimeIdentifier>win-arm64</RuntimeIdentifier>
<EnableMsixTooling>true</EnableMsixTooling>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\AIShell.Abstraction\AIShell.Abstraction.csproj">
<!-- Disable copying AIShell.Abstraction.dll to output folder -->
<Private>false</Private>
<!-- Disable copying the transitive dependencies to output folder -->
<ExcludeAssets>runtime</ExcludeAssets>
</ProjectReference>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.8.250410001-experimental1" />
</ItemGroup>

</Project>
97 changes: 97 additions & 0 deletions shell/agents/AIShell.PhiSilica.Agent/PhiSilicaAgent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
using AIShell.Abstraction;
using Microsoft.Windows.AI;
using Microsoft.Windows.AI.Generative;

namespace AIShell.PhiSilica.Agent;

public sealed partial class PhiSilicaAgent : ILLMAgent
{
private readonly Task _initTask;
private LanguageModel _model;

public string Name => "PhiSilica";
public string Description => "This is the Phi Silica agent, an offline local agent on Copilot+ PCs";
public string SettingFile => null;

public IEnumerable<CommandBase> GetCommands() => null;
public bool CanAcceptFeedback(UserAction action) => false;
public Task RefreshChatAsync(IShell shell, bool force) => Task.CompletedTask;
public void OnUserAction(UserActionPayload actionPayload) { }
public void Initialize(AgentConfig config) { }
public void Dispose() { }

public PhiSilicaAgent()
{
// Start the initialization for AI feature and model on a background thread.
_initTask = Task.Run(InitFeatureAndModelAsync);
}

private async Task InitFeatureAndModelAsync()
{
AIFeatureReadyState state = LanguageModel.GetReadyState();
if (state is AIFeatureReadyState.NotSupportedOnCurrentSystem)
{
throw new PlatformNotSupportedException("The Phi Silica feature is not supported on current system.");
}

if (state is AIFeatureReadyState.DisabledByUser)
{
throw new PlatformNotSupportedException("The Phi Silica feature is currently disabled.");
}

if (state is AIFeatureReadyState.EnsureNeeded)
{
// Initialize the WinRT runtime.
AIFeatureReadyResult result = await LanguageModel.EnsureReadyAsync();
// Do not proceed if it failed to get the feature ready.
if (result.Status is not AIFeatureReadyResultState.Success)
{
throw new InvalidOperationException(result.ErrorDisplayText, result.Error);
}
}

_model = await LanguageModel.CreateAsync();
}

public async Task<bool> ChatAsync(string input, IShell shell)
{
IHost host = shell.Host;

try
{
// Wait for the init task to finish. Once it's finished, calling this again is a non-op.
await _initTask;
}
catch (Exception e)
{
host.WriteErrorLine(e.Message);
if (e is InvalidOperationException && e.InnerException is not null)
{
host.WriteErrorLine(e.InnerException.StackTrace);
}
else if (e is not PlatformNotSupportedException)
{
// Show stack trace for non-PNS exception.
host.WriteErrorLine(e.StackTrace);
}

return false;
}

var result = await host.RunWithSpinnerAsync(
status: "Thinking ...",
func: async () => await _model.GenerateResponseAsync(input)
);

if (result is not null && !string.IsNullOrEmpty(result.Text))
{
host.RenderFullResponse(result.Text);
}
else
{
host.WriteErrorLine("No response received from the language model.");
}

return true;
}
}