Conversation
Replace Betalgo.Ranul.OpenAI (C#) and async-openai (Rust) with custom DTOs matching the OpenAI JSON wire format. C# SDK: - Add custom OpenAI types: ChatMessage, ChatCompletionResponse, ToolCallingTypes, AudioTypes, LiveAudioTranscriptionTypes - Add type-safe enums: ChatMessageRole, ToolType, FinishReason, ResponseErrorType with JsonStringEnumConverter for AOT serialization - Wire Stop and MaxCompletionTokens through ChatSettings - Simplify ToolChoiceConverter (remove HashSet, inline deserialization) - Remove ~60 redundant per-property JsonIgnore attributes (covered by JsonSourceGenerationOptions.DefaultIgnoreCondition) - Remove unused RichardSzalay.MockHttp test dependency - Fix Stop type from IList<string> to string (matching Core contract) - Remove dead Seed property (SDK uses metadata[random_seed] path) Rust SDK: - Add custom chat_types module replacing async-openai types - Remove 3 dead types (ChatCompletionToolChoiceOption and related) - Add #[non_exhaustive] to public enums - Tighten module visibility to pub(crate) - Clean up Cargo.toml (move tokio-stream to dev-deps, remove unused serde build-dep) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace all Betalgo references in samples with SDK-native types: - Remove Betalgo.Ranul.OpenAI package references from 4 .csproj files and Directory.Packages.props - Replace string-based Role with ChatMessageRole enum - Replace string-based Type with ToolType.Function enum - Replace string-based FinishReason with FinishReason enum - Replace ChatCompletionCreateResponse with ChatCompletionResponse - Update API docs to remove Betalgo inheritance references Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Align with JS, Python, and Rust SDKs which all use plain strings (or string literal unions) for these fields. Removes ChatMessageRole, ToolType, FinishReason, and ResponseErrorType enums. Using strings is simpler for users and consistent across all SDK languages. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
This PR removes external OpenAI SDK dependencies from the Rust and C# SDKs by introducing/using first-party OpenAI-compatible DTOs and updating tests, docs, and samples accordingly.
Changes:
- Rust: replace
async-openaichat DTOs with in-repoopenai::chat_typesand update crate exports/dependencies. - C#: remove
Betalgo.Ranul.OpenAIusage by adding custom OpenAI-compatible request/response/tool DTOs and updating clients/tests/docs. - Samples: remove Betalgo package references and update sample code to use the new DTOs.
Reviewed changes
Copilot reviewed 36 out of 36 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| sdk/rust/src/openai/mod.rs | Exposes new internal chat_types module and keeps OpenAI client module exports. |
| sdk/rust/src/openai/chat_types.rs | Adds first-party Rust DTOs for OpenAI-compatible chat request/response + streaming types. |
| sdk/rust/src/openai/chat_client.rs | Switches chat client to use local DTOs instead of async-openai. |
| sdk/rust/src/lib.rs | Adjusts module visibility and re-exports to point to local chat DTOs. |
| sdk/rust/Cargo.toml | Drops async-openai; moves tokio-stream to dev-dependencies. |
| sdk/cs/test/FoundryLocal.Tests/Microsoft.AI.Foundry.Local.Tests.csproj | Removes RichardSzalay.MockHttp test dependency. |
| sdk/cs/test/FoundryLocal.Tests/LiveAudioTranscriptionTests.cs | Removes an unused System.Text.Json import. |
| sdk/cs/test/FoundryLocal.Tests/ChatCompletionsTests.cs | Updates tests to use in-SDK OpenAI-compatible types. |
| sdk/cs/src/OpenAI/ToolCallingTypes.cs | Adds custom DTOs for tools/tool-choice/response-format + JSON converter. |
| sdk/cs/src/OpenAI/ToolCallingExtensions.cs | Updates extended response format type docs and removes Betalgo dependency. |
| sdk/cs/src/OpenAI/LiveAudioTranscriptionTypes.cs | Replaces Betalgo realtime inheritance with local DTOs for streaming transcription results. |
| sdk/cs/src/OpenAI/ChatMessage.cs | Adds custom chat message + tool call DTOs. |
| sdk/cs/src/OpenAI/ChatCompletionResponse.cs | Adds custom chat completion response DTOs (incl. error shape). |
| sdk/cs/src/OpenAI/ChatCompletionRequestResponseTypes.cs | Replaces Betalgo request/response types with local request/response DTOs and serialization. |
| sdk/cs/src/OpenAI/ChatClient.cs | Updates client to use new request/response DTOs and adds settings fields. |
| sdk/cs/src/OpenAI/AudioTypes.cs | Adds custom audio transcription request/response DTOs. |
| sdk/cs/src/OpenAI/AudioTranscriptionRequestResponseTypes.cs | Replaces Betalgo transcription types with local request/response types and serialization. |
| sdk/cs/src/OpenAI/AudioClient.cs | Updates audio client to use new audio request/response DTOs. |
| sdk/cs/src/Microsoft.AI.Foundry.Local.csproj | Removes Betalgo.Ranul.OpenAI package reference from the SDK. |
| sdk/cs/src/Detail/JsonSerializationContext.cs | Updates source-gen JSON registrations to the new DTOs. |
| sdk/cs/README.md | Updates documentation samples to use Microsoft.AI.Foundry.Local.OpenAI types. |
| sdk/cs/docs/api/microsoft.ai.foundry.local.openaichatclient.md | Updates docs to reflect custom OpenAI-compatible types. |
| sdk/cs/docs/api/microsoft.ai.foundry.local.openaiaudioclient.md | Updates docs to reflect custom OpenAI-compatible types. |
| samples/cs/tutorial-voice-to-text/TutorialVoiceToText.csproj | Removes Betalgo package dependency from sample. |
| samples/cs/tutorial-voice-to-text/Program.cs | Updates imports to use SDK OpenAI-compatible types. |
| samples/cs/tutorial-tool-calling/TutorialToolCalling.csproj | Removes Betalgo package dependency from sample. |
| samples/cs/tutorial-tool-calling/Program.cs | Updates imports to use SDK OpenAI-compatible types. |
| samples/cs/tutorial-document-summarizer/TutorialDocumentSummarizer.csproj | Removes Betalgo package dependency from sample. |
| samples/cs/tutorial-document-summarizer/Program.cs | Updates imports to use SDK OpenAI-compatible types. |
| samples/cs/tutorial-chat-assistant/TutorialChatAssistant.csproj | Removes Betalgo package dependency from sample. |
| samples/cs/tutorial-chat-assistant/Program.cs | Updates imports to use SDK OpenAI-compatible types. |
| samples/cs/tool-calling-foundry-local-web-server/Program.cs | Updates header; contains a modified finish-reason check in tool-calling loop. |
| samples/cs/tool-calling-foundry-local-sdk/Program.cs | Updates imports and response type names for tool-calling sample. |
| samples/cs/native-chat-completions/Program.cs | Updates imports to use SDK OpenAI-compatible types. |
| samples/cs/model-management-example/Program.cs | Updates imports to use SDK OpenAI-compatible types. |
| samples/cs/Directory.Packages.props | Removes Betalgo package version pin from samples. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
C# SDK: - Fix AOT/trimming errors in ToolChoiceConverter: use source-generated JsonSerializationContext instead of generic JsonSerializer overloads - Register ToolChoice.FunctionTool in JsonSerializationContext - Fix AudioTranscriptionRequestResponseTypes.ToJson() to serialize AudioTranscriptionRequestExtended with correct type info so metadata is not dropped (Copilot review comment) Rust SDK: - Restore pub visibility for openai and chat_types modules (samples and external consumers depend on these paths) - Move tokio-stream back to regular dependencies (dev-deps are not available during cargo doc, breaking rustdoc links) Samples: - Fix corrupted FinishReason check in tool-calling-foundry-local-web-server Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…to Python DeviceType - Re-add ChatMessageRole, ToolType, FinishReason enums to C# SDK - Use JsonStringEnumConverter with JsonStringEnumMemberName for AOT compat - Register enums in JsonSerializationContext - Update all tests and samples to use enum values - Add Invalid member to Python DeviceType for cross-SDK consistency Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Run dotnet format on C# SDK (19 files reformatted per .editorconfig) - Run cargo fmt on Rust SDK (import ordering per .rustfmt.toml) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix README.md to use ChatMessageRole enum in example - Fix IModel.cs doc comments: OpenAI.ChatClient -> OpenAIChatClient - Fix IModel API docs: remove stale OpenAI.* return type references - Fix Rust GENERATE-DOCS.md and api.md: remove async-openai references - Merge AudioTranscriptionRequestExtended into AudioTranscriptionRequest - Move Metadata property into base class - Eliminate runtime type-check in ToJson() - Remove AudioTranscriptionRequestExtended from JsonSerializationContext Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ption - ChatClient docs: ChatCompletionCreateResponse -> ChatCompletionResponse - AudioClient docs: AudioCreateTranscriptionResponse -> AudioTranscriptionResponse - LiveAudioTranscriptionResponse docs: remove stale properties (Id, Type, Status, Role, CallId, Name, Arguments, Output), add Content with TranscriptionContentPart, update property accessors to init Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep formatting only in files modified by this branch. Revert dotnet format changes to CoreInterop, FoundryLocalManager, Utils, and other files that were not part of the third-party library removal. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… README Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- AudioTranscriptionRequestResponseExtensions -> AudioTranscriptionExtensions - ChatCompletionsRequestResponseExtensions -> ChatCompletionExtensions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tensions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 53 out of 53 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Revert completion_tokens_details, prompt_tokens_details, and logprobs from C# and Rust SDKs. Core's OpenAI layer doesn't emit these yet — add them when it does instead of shipping always-null fields. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Core's OpenAI ChatMessage doesn't have a refusal field. Adding it to SDKs would create an always-null property. Will add when Core supports it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 61 out of 61 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Rust ChatResponseFormat: use snake_case keys (json_schema, lark_grammar)
instead of camelCase to match OpenAI wire format
- Rust ChatToolChoice: serialize None/Auto/Required as plain strings and
Function as {type: function, function: {name: ...}} per OpenAI spec
- C# ToolChoiceConverter.Write(): add null check for Type to throw a clear
JsonException instead of a runtime NullReferenceException
- README: add missing using Microsoft.AI.Foundry.Local.OpenAI directive
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The readonly modifier on auto-properties inside a readonly struct is redundant — the struct and get-only accessors already guarantee immutability. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 61 out of 61 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
sdk/js/src/openai/chatClient.ts:42
response_formatandtool_choiceare being serialized by passing the objects through as-is. With the current JS types (ResponseFormatusesjsonSchema/larkGrammarandToolChoiceusesname), this produces JSON that doesn’t match the OpenAI wire format used by the other SDKs in this PR (e.g.,json_schema/lark_grammar, and tool choice{ type: "function", function: { name } }or plain strings fornone|auto|required). Consider normalizing these objects in_serialize()so the emitted JSON matches the OpenAI field names/shapes.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| public class ToolDefinition | ||
| { | ||
| /// <summary>The type of tool.</summary> | ||
| [JsonPropertyName("type")] | ||
| public ToolType? Type { get; set; } |
There was a problem hiding this comment.
ToolDefinition.Type is nullable, but OpenAI tool definitions require type. Because the source-gen context ignores nulls (JsonIgnoreCondition.WhenWritingNull), a missing Type will silently omit the field and produce an invalid request. Consider making Type non-nullable (and similarly requiring Function) or adding validation before sending the request.
There was a problem hiding this comment.
I am following OpenAI SDK's convention - can revert if it seems too onerous
| public class FunctionDefinition | ||
| { | ||
| /// <summary>The name of the function.</summary> | ||
| [JsonPropertyName("name")] | ||
| public string? Name { get; set; } |
There was a problem hiding this comment.
FunctionDefinition.Name is required by the OpenAI tool spec, but it’s nullable here; when null it will be omitted during serialization (due to WhenWritingNull) and the request will fail at runtime. Consider making it non-nullable (or validating in ChatCompletionRequest.FromUserInput / before executing the command).
There was a problem hiding this comment.
I am following OpenAI SDK's convention - can revert if it seems too onerous
- ToolDefinition.Type: non-nullable with default ToolType.Function - FunctionDefinition: Function and Name marked as required - ToolChoice: factory methods (CreateAutoChoice, CreateNoneChoice, CreateRequiredChoice, CreateFunctionChoice) instead of static properties - Update all callers in samples and tests - Update sample Directory.Packages.props WinML version Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
af0c117 to
78a964f
Compare
Align enum names with the official OpenAI .NET SDK: - FinishReason renamed to ChatFinishReason - ToolType renamed to ChatToolKind (matching ChatToolKind in openai-dotnet) - Updated all references in SDK, tests, and samples - Updated sample Directory.Packages.props WinML version pin Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…nction Align property names with OpenAI wire format JSON field names: - ChatCompletionResponse.CreatedAtUnix -> Created (wire: "created") - ToolCall.FunctionCall -> ToolCall.Function (wire: "function") - Updated all references in tests and samples Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…lType The Chat-prefixed names matched the official OpenAI SDK but were inconsistent with our wire-format-aligned naming convention for all other types. Reverting to the original names for consistency. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- C# SDK: Fix PropertyDefinition.Type from string? to object? with JsonSchemaTypeConverter to support both single types and type arrays (e.g. ["string", "null"]) per JSON Schema specification - Add type array tool calling test to all 4 SDKs (C#, Python, JS/TS, Rust) - Each test sends a tool with 'type': ['string', 'null'] parameter and verifies successful tool call response without 500 errors - Add get_type_array_tool() helper to shared test utilities in each SDK Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove third-party OpenAI libraries; replace with custom DTOs
Removes
Betalgo.Ranul.OpenAI(C#) andasync-openai(Rust) dependencies, replacing them with custom data transfer objects that map directly to the OpenAI JSON wire format. This gives us full control over serialization, AOT compatibility, and spec alignment without waiting on third-party release cycles.Why remove Betalgo?
Betalgo is a community-maintained library that lags behind the official OpenAI spec:
refusalon ChatMessage (added mid-2024), nomax_completion_tokens(used by VS Code / newer clients)System.Text.Jsonsource generators — our SDK needs[JsonSerializable]registration for trimming/AOTSimilarly,
async-openaifor Rust usedCow<'a, str>patterns that caused clippy warnings after removal, and brought in unnecessary complexity.What replaced them
Custom DTOs in each SDK that map 1:1 to the OpenAI chat completion wire format. Design principle: our types match the JSON field names exactly (not the official SDK's abstracted class hierarchy).
Enums introduced (C#)
We added typed enums with
[JsonStringEnumConverter]and[JsonStringEnumMemberName]for AOT-safe serialization, matching the wire format string values:ChatMessageRolesystem,user,assistant,tool,developerdeveloperadded for reasoning model support (o1/o3)FinishReasonstop,length,tool_calls,content_filter,function_callfunction_callincluded for backward compat even though deprecatedToolTypefunctionRust uses a
FinishReasonenum with#[serde(rename_all = "snake_case")]and#[non_exhaustive].Python and JS use plain strings — no enums needed (dynamic typing).
Fields added beyond Betalgo
These fields exist in the OpenAI spec and were missing from Betalgo:
max_completion_tokensdeveloperroleChatMessageRole(C#, Rust)function_callFinishReasonenum (C#)Fields intentionally omitted
These exist in the OpenAI spec but are either cloud-only or not yet emitted by Core's OpenAI layer:
service_tier— cloud routing concept (auto/default/flex/scale/priority)annotations— web search URL citationsaudioon ChatCompletionMessage — audio output modalitystream_options,parallel_tool_calls— cloud features not supported by local modelscompletion_tokens_details,prompt_tokens_details— Core has these internally but doesn't emit them through the OpenAI layer yet; will add when Core supports themlogprobs— same; Core has internal support but doesn't expose through OpenAI layerrefusal— same; Core's OpenAIChatMessagedoesn't include refusal; will add when it doesNullability fixes
Audited all value types across the C# SDK against the OpenAI spec and streaming behavior:
ChatMessage.RoleChatMessageRole?(nullable)ChatMessageRole?ToolCall.Indexint?(nullable)AudioTranscriptionResponse.Durationfloat?(nullable)Rust SDK changes
async-openaidependency fromCargo.tomlchat_types.rs— all custom DTOs: request messages (System/User/Assistant/Tool/Developer), response types, streaming types, tool call typesChatClientSettingsserialization — replaced 80-line manual JSON builder with#[derive(Serialize)]+#[serde(skip_serializing_if)]annotationsSerializeimpls forChatResponseFormatandChatToolChoiceintypes.rs— these serialize as either plain strings or objects depending on variantChatResponseFormatusesjson_schema/lark_grammar(snake_case),ChatToolChoiceserializes None/Auto/Required as plain strings ("none","auto","required") and Function as{"type":"function","function":{"name":"..."}}Fromimpls —From<&str>delegates toFrom<String>instead of duplicating logic.into()calls (were needed forCow<'a, str>but not forString)pub(crate) mod detail— restored visibility restriction accidentally changed topub.expect()over.unwrap()— better panic messages in serialize()C# SDK — Official OpenAI .NET SDK alignment
Following the patterns from openai/openai-dotnet:
ToolChoicefactory methods —CreateAutoChoice(),CreateNoneChoice(),CreateRequiredChoice(),CreateFunctionChoice(string)instead of static properties (matchesChatToolChoicein official SDK)ToolDefinition.Type— non-nullable with defaultToolType.Function(matches official SDK'sChatTool.Kind)FunctionDefinition—FunctionandNamemarkedrequired(matches official SDK requiring function name at construction time)ToolChoiceConverter.Write()— null safety check addedFile renames (C#)
AudioTranscriptionRequestResponseTypes.csAudioTranscriptionExtensions.csChatCompletionRequestResponseTypes.csChatCompletionExtensions.csNew file organization (C#)
Public types and internal plumbing are cleanly separated:
ChatMessage.csChatMessageRole,ToolType,ChatMessage,ToolCall,FunctionCallChatCompletionResponse.csChatCompletionResponse,ChatChoice,FinishReason,CompletionUsage, token detailsToolCallingTypes.csToolDefinition,FunctionDefinition,PropertyDefinition,ResponseFormat,ToolChoiceAudioTypes.csAudioTranscriptionResponse,AudioTranscriptionRequest(internal)ChatCompletionExtensions.csChatCompletionRequest, serialization/deserialization helpersAudioTranscriptionExtensions.csCore compatibility
UsageResponse,ChatChoiceResponse,ChatCompletionCreateResponse) are covered by our SDK typesdeveloperrole is already supported by Core'sResponseEnums.cscompletion_tokens_details,prompt_tokens_details,logprobs) are intentionally omitted — will add when Core exposes themCross-SDK field alignment
max_completion_tokensfunction_call(deprecated)FinishReason.FunctionCalldeveloperrolePython and JS use dynamic types (dicts/objects), so new fields are automatically captured without SDK changes.
Testing
cargo check/clippy pass in CI (can't test locally due to ORT-Nightly registry)npx tsc --noEmitcompiles cleanRelated issues
max_completion_tokensfield used in the issue's example payload is now supported by all SDKs. The root cause (arraytypein tool parameter schemas) requires a Core-side fix (separate PR in neutron-server).