Skip to content
Draft
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
2 changes: 1 addition & 1 deletion samples/cs/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.10" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="9.0.10" />
<PackageVersion Include="NAudio" Version="2.2.1" />
<PackageVersion Include="OpenAI" Version="2.5.0" />
<PackageVersion Include="OpenAI" Version="2.10.0" />
</ItemGroup>
</Project>
1 change: 1 addition & 0 deletions samples/cs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Both packages provide the same APIs, so the same source code works on all platfo
| [embeddings](embeddings/) | Generate single and batch text embeddings using the Foundry Local SDK. |
| [audio-transcription-example](audio-transcription-example/) | Transcribe audio files using the Foundry Local SDK. |
| [foundry-local-web-server](foundry-local-web-server/) | Set up a local OpenAI-compliant web server. |
| [responses-foundry-local-web-server](responses-foundry-local-web-server/) | Use the OpenAI Responses API (non-streaming, streaming, tool calling) against the local web server. |
| [tool-calling-foundry-local-sdk](tool-calling-foundry-local-sdk/) | Use tool calling with native chat completions. |
| [tool-calling-foundry-local-web-server](tool-calling-foundry-local-web-server/) | Use tool calling with the local web server. |
| [model-management-example](model-management-example/) | Manage models, variant selection, and updates. |
Expand Down
174 changes: 174 additions & 0 deletions samples/cs/responses-foundry-local-web-server/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// <complete_code>
// Demonstrates the OpenAI Responses API against the Foundry Local OpenAI-compatible web service.
//
// SDK responsibilities (Foundry Local):
// - SDK initialization
// - EP download/registration
// - model lookup, download, load
// - starting/stopping the local web service
//
// Responses API calls go through the official OpenAI .NET package's `ResponsesClient`
// pointed at the local web service, mirroring how `samples/cs/foundry-local-web-server`
// uses `OpenAIClient.GetChatClient(...)` for chat completions.

using System.ClientModel;

using Microsoft.AI.Foundry.Local;

using OpenAI;
using OpenAI.Responses;

var config = new Configuration
{
AppName = "foundry_local_samples",
LogLevel = Microsoft.AI.Foundry.Local.LogLevel.Information,
Web = new Configuration.WebService
{
Urls = "http://127.0.0.1:52495"
}
};

// Initialize the singleton instance.
await FoundryLocalManager.CreateAsync(config, Utils.GetAppLogger());
var mgr = FoundryLocalManager.Instance;

// Download and register all execution providers.
var currentEp = "";
await mgr.DownloadAndRegisterEpsAsync((epName, percent) =>
{
if (epName != currentEp)
{
if (currentEp != "") Console.WriteLine();
currentEp = epName;
}
Console.Write($"\r {epName.PadRight(30)} {percent,6:F1}%");
});
if (currentEp != "") Console.WriteLine();

// Get the model catalog
var catalog = await mgr.GetCatalogAsync();

// Get a model using an alias
var model = await catalog.GetModelAsync("qwen2.5-0.5b") ?? throw new Exception("Model not found");

// Download the model (the method skips download if already cached)
await model.DownloadAsync(progress =>
{
Console.Write($"\rDownloading model: {progress:F2}%");
if (progress >= 100f)
{
Console.WriteLine();
}
});

// Load the model
Console.Write($"Loading model {model.Id}...");
await model.LoadAsync();
Console.WriteLine("done.");

// Start the web service
Console.Write($"Starting web service on {config.Web.Urls}...");
await mgr.StartWebServiceAsync();
Console.WriteLine("done.");

try
{
// <<<<<< OPEN AI RESPONSES SDK USAGE >>>>>>
// Use the OpenAI Responses client to call the local Foundry web service.
ApiKeyCredential key = new("notneeded");
OpenAIClient openai = new(key, new OpenAIClientOptions
{
Endpoint = new Uri(config.Web.Urls + "/v1"),
});
ResponsesClient responses = openai.GetResponsesClient();

// 1) Non-streaming
Console.WriteLine("\n=== Non-streaming ===");
ResponseResult simple = await responses.CreateResponseAsync(model.Id, "Reply with one short sentence about local AI.");
Console.WriteLine($"[ASSISTANT]: {simple.GetOutputText()}");

// 2) Streaming
Console.WriteLine("\n=== Streaming ===");
Console.Write("[ASSISTANT]: ");
await foreach (StreamingResponseUpdate update in responses.CreateResponseStreamingAsync(model.Id, "Count from 1 to 3."))
{
if (update is StreamingResponseOutputTextDeltaUpdate delta && !string.IsNullOrEmpty(delta.Delta))
{
Console.Write(delta.Delta);
}
}
Console.WriteLine();

// 3) Function/tool calling — full round-trip via previous_response_id.
// The function takes no arguments, which matches the pattern small models handle reliably.
Console.WriteLine("\n=== Function calling ===");
var emptyParamsSchema = BinaryData.FromString("""
{
"type": "object",
"properties": {},
"additionalProperties": false
}
""");

ResponseTool getWeatherTool = ResponseTool.CreateFunctionTool(
functionName: "get_weather",
functionParameters: emptyParamsSchema,
strictModeEnabled: true,
functionDescription: "Get the current weather. This sample always returns Seattle weather.");

var toolCallOptions = new CreateResponseOptions(
model.Id,
new[] { ResponseItem.CreateUserMessageItem("Use the get_weather tool and then answer with the weather.") })
{
StoredOutputEnabled = true,
ToolChoice = ResponseToolChoice.CreateRequiredChoice(),
MaxOutputTokenCount = 64,
Temperature = 0.0f,
};
toolCallOptions.Tools.Add(getWeatherTool);

ResponseResult toolResponse = await responses.CreateResponseAsync(toolCallOptions);

FunctionCallResponseItem? functionCall = null;
foreach (var item in toolResponse.OutputItems)
{
if (item is FunctionCallResponseItem fc && fc.FunctionName == "get_weather")
{
functionCall = fc;
break;
}
}

if (functionCall is null)
{
Console.WriteLine("Model did not produce a function call; skipping tool round-trip.");
}
else
{
Console.WriteLine($"[TOOL CALL]: {functionCall.FunctionName}({functionCall.FunctionArguments})");

const string toolOutput = """{"location": "Seattle", "weather": "72 degrees F and sunny"}""";

var followUpOptions = new CreateResponseOptions(
model.Id,
new[] { ResponseItem.CreateFunctionCallOutputItem(functionCall.CallId, toolOutput) })
{
PreviousResponseId = toolResponse.Id,
StoredOutputEnabled = true,
MaxOutputTokenCount = 64,
Temperature = 0.0f,
};
followUpOptions.Tools.Add(getWeatherTool);

ResponseResult finalResponse = await responses.CreateResponseAsync(followUpOptions);
Console.WriteLine($"[ASSISTANT FINAL]: {finalResponse.GetOutputText()}");
}
// <<<<<< END OPEN AI RESPONSES SDK USAGE >>>>>>
}
finally
{
// Tidy up
await mgr.StopWebServiceAsync();
await model.UnloadAsync();
}
// </complete_code>
55 changes: 55 additions & 0 deletions samples/cs/responses-foundry-local-web-server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Foundry Local Responses web service sample (C#)

This sample starts the Foundry Local OpenAI-compatible web service, then uses the official OpenAI .NET SDK to call the Responses API.

The pattern is:

1. `FoundryLocalManager` handles Foundry Local setup, model download/load, web service startup, and cleanup.
1. `OpenAI.Responses.ResponsesClient` (from the official `OpenAI` NuGet package) handles the actual `/v1/responses` calls.

## Prerequisites

- .NET 9 SDK
- Internet access on first run to download the sample model

## What the sample does

1. Initializes `FoundryLocalManager`.
1. Downloads and registers execution providers.
1. Downloads and loads `qwen2.5-0.5b`.
1. Starts the local web service at `http://127.0.0.1:52495`.
1. Creates an `OpenAIClient` pointed at `http://127.0.0.1:52495/v1`.
1. Runs a non-streaming Responses call.
1. Runs a streaming Responses call (`StreamingResponseOutputTextDeltaUpdate` events).
1. Runs a Responses function-calling flow with a sample `get_weather` tool, then submits a tool result back via `previous_response_id`.
1. Stops the web service and unloads the model.

## Run the sample

```powershell
cd samples/cs/responses-foundry-local-web-server
dotnet run
```

## Expected output

```text
=== Non-streaming ===
[ASSISTANT]: 4

=== Streaming ===
[ASSISTANT]: 1, 2, 3.

=== Function calling ===
Tool call: get_weather()
Tool output: {"location": "Seattle", "weather": "72 degrees F and sunny"}
[ASSISTANT]: It's 72 degrees F and sunny in Seattle.
```

The exact model text varies.

## Troubleshooting

If the sample fails while creating `FoundryLocalManager` with a native symbol error such as `Failed to resolve 'execute_command_with_binary' symbol`, the installed Foundry Local Core runtime is older than the native bits expect. Try the latest stable `Microsoft.AI.Foundry.Local[.WinML]` package, or a recent ORT-Nightly package if needed.

If port `52495` is already in use, edit `Program.cs` and change `config.Web.Urls`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<!-- OpenAI Responses APIs are experimental in the official OpenAI .NET package. -->
<NoWarn>$(NoWarn);OPENAI001</NoWarn>
</PropertyGroup>

<!-- Windows: target Windows SDK for WinML hardware acceleration -->
<PropertyGroup Condition="$([MSBuild]::IsOSPlatform('Windows'))">
<TargetFramework>net9.0-windows10.0.26100</TargetFramework>
<WindowsAppSDKSelfContained>false</WindowsAppSDKSelfContained>
<Platforms>ARM64;x64</Platforms>
<WindowsPackageType>None</WindowsPackageType>
<EnableCoreMrtTooling>false</EnableCoreMrtTooling>
</PropertyGroup>

<!-- Non-Windows: standard .NET -->
<PropertyGroup Condition="!$([MSBuild]::IsOSPlatform('Windows'))">
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>

<PropertyGroup Condition="'$(RuntimeIdentifier)'==''">
<RuntimeIdentifier>$(NETCoreSdkRuntimeIdentifier)</RuntimeIdentifier>
</PropertyGroup>

<!-- Windows: WinML for hardware acceleration -->
<ItemGroup Condition="$([MSBuild]::IsOSPlatform('Windows'))">
<PackageReference Include="Microsoft.AI.Foundry.Local.WinML" />
</ItemGroup>

<!-- Non-Windows: standard SDK -->
<ItemGroup Condition="!$([MSBuild]::IsOSPlatform('Windows'))">
<PackageReference Include="Microsoft.AI.Foundry.Local" />
</ItemGroup>

<!-- Linux GPU support -->
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64'">
<PackageReference Include="Microsoft.ML.OnnxRuntime.Gpu" />
<PackageReference Include="Microsoft.ML.OnnxRuntimeGenAI.Cuda" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="OpenAI" />
</ItemGroup>

<!-- Shared utilities -->
<ItemGroup>
<Compile Include="../Shared/*.cs" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
<IsWindows Condition="$([MSBuild]::IsOSPlatform('Windows'))">true</IsWindows>
<UseWinML>false</UseWinML>
<UseAppHost>false</UseAppHost>
<!-- OpenAI Responses APIs are experimental in the official OpenAI .NET package. -->
<NoWarn>$(NoWarn);OPENAI001</NoWarn>
</PropertyGroup>

<ItemGroup>
Expand Down Expand Up @@ -47,6 +49,7 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.9" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="OpenAI" Version="2.10.0" />
<PackageReference Include="RichardSzalay.MockHttp" Version="7.0.0" />
<PackageReference Include="TUnit" Version="0.63.3" />
<PackageReference Include="TUnit.Core" Version="0.63.3" />
Expand Down
Loading
Loading