Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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.15" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="9.0.15" />
<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>
Comment on lines +12 to +18

<!-- 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 @@
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<UseWinML>false</UseWinML>
<!-- OpenAI Responses APIs are experimental in the official OpenAI .NET package. -->
<NoWarn>$(NoWarn);OPENAI001</NoWarn>
</PropertyGroup>

<ItemGroup>
Expand Down Expand Up @@ -54,6 +56,7 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.15" />
<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