Skip to content

Remove third party libraries#587

Open
bmehta001 wants to merge 37 commits intomainfrom
bhamehta/remove-third-party-libs
Open

Remove third party libraries#587
bmehta001 wants to merge 37 commits intomainfrom
bhamehta/remove-third-party-libs

Conversation

@bmehta001
Copy link
Copy Markdown
Contributor

@bmehta001 bmehta001 commented Apr 3, 2026

Remove third-party OpenAI libraries; replace with custom DTOs

Removes Betalgo.Ranul.OpenAI (C#) and async-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:

  • Missing fields: no refusal on ChatMessage (added mid-2024), no max_completion_tokens (used by VS Code / newer clients)
  • Heavyweight: pulls in types/abstractions we don't use (images, embeddings, fine-tuning, assistants)
  • AOT friction: not designed for System.Text.Json source generators — our SDK needs [JsonSerializable] registration for trimming/AOT
  • Spec drift: when OpenAI adds fields, we're blocked until Betalgo ships an update

Similarly, async-openai for Rust used Cow<'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:

Enum Values Why
ChatMessageRole system, user, assistant, tool, developer Type safety for role field; developer added for reasoning model support (o1/o3)
FinishReason stop, length, tool_calls, content_filter, function_call Wire-format-aligned name; function_call included for backward compat even though deprecated
ToolType function Wire-format-aligned name; single value today, forward-compatible

Rust uses a FinishReason enum 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:

Field Where Why
max_completion_tokens Request settings (all 4 SDKs) Newer OpenAI field used by VS Code and other clients
developer role ChatMessageRole (C#, Rust) Required for reasoning models; Core already supports it
function_call FinishReason enum (C#) Was in Rust but missing from C#; needed for backward compat

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 citations
  • audio on ChatCompletionMessage — audio output modality
  • stream_options, parallel_tool_calls — cloud features not supported by local models
  • completion_tokens_details, prompt_tokens_details — Core has these internally but doesn't emit them through the OpenAI layer yet; will add when Core supports them
  • logprobs — same; Core has internal support but doesn't expose through OpenAI layer
  • refusal — same; Core's OpenAI ChatMessage doesn't include refusal; will add when it does

Nullability fixes

Audited all value types across the C# SDK against the OpenAI spec and streaming behavior:

Field Change Reason
ChatMessage.Role ChatMessageRole? (nullable) Streaming deltas may omit role — matches official SDK's ChatMessageRole?
ToolCall.Index int? (nullable) Only present in streaming delta chunks, absent in non-streaming
AudioTranscriptionResponse.Duration float? (nullable) May be absent depending on response format

Rust SDK changes

  • Removed async-openai dependency from Cargo.toml
  • New chat_types.rs — all custom DTOs: request messages (System/User/Assistant/Tool/Developer), response types, streaming types, tool call types
  • Simplified ChatClientSettings serialization — replaced 80-line manual JSON builder with #[derive(Serialize)] + #[serde(skip_serializing_if)] annotations
  • Custom Serialize impls for ChatResponseFormat and ChatToolChoice in types.rs — these serialize as either plain strings or objects depending on variant
  • Wire format alignment: ChatResponseFormat uses json_schema/lark_grammar (snake_case), ChatToolChoice serializes None/Auto/Required as plain strings ("none", "auto", "required") and Function as {"type":"function","function":{"name":"..."}}
  • Deduplicated From implsFrom<&str> delegates to From<String> instead of duplicating logic
  • Fixed clippy errors — removed 6 useless .into() calls (were needed for Cow<'a, str> but not for String)
  • pub(crate) mod detail — restored visibility restriction accidentally changed to pub
  • .expect() over .unwrap() — better panic messages in serialize()

C# SDK — Official OpenAI .NET SDK alignment

Following the patterns from openai/openai-dotnet:

  • ToolChoice factory methodsCreateAutoChoice(), CreateNoneChoice(), CreateRequiredChoice(), CreateFunctionChoice(string) instead of static properties (matches ChatToolChoice in official SDK)
  • ToolDefinition.Type — non-nullable with default ToolType.Function (matches official SDK's ChatTool.Kind)
  • FunctionDefinitionFunction and Name marked required (matches official SDK requiring function name at construction time)
  • ToolChoiceConverter.Write() — null safety check added

File renames (C#)

Old New Reason
AudioTranscriptionRequestResponseTypes.cs AudioTranscriptionExtensions.cs Better describes contents (extension methods + internal request DTO)
ChatCompletionRequestResponseTypes.cs ChatCompletionExtensions.cs Same — internal plumbing, not public types

New file organization (C#)

Public types and internal plumbing are cleanly separated:

File Visibility Contents
ChatMessage.cs Public ChatMessageRole, ToolType, ChatMessage, ToolCall, FunctionCall
ChatCompletionResponse.cs Public ChatCompletionResponse, ChatChoice, FinishReason, CompletionUsage, token details
ToolCallingTypes.cs Public ToolDefinition, FunctionDefinition, PropertyDefinition, ResponseFormat, ToolChoice
AudioTypes.cs Public AudioTranscriptionResponse, AudioTranscriptionRequest (internal)
ChatCompletionExtensions.cs Internal ChatCompletionRequest, serialization/deserialization helpers
AudioTranscriptionExtensions.cs Internal Serialization/deserialization helpers

Core compatibility

  • All fields emitted by Core's OpenAI layer (UsageResponse, ChatChoiceResponse, ChatCompletionCreateResponse) are covered by our SDK types
  • developer role is already supported by Core's ResponseEnums.cs
  • Fields not yet emitted by Core's OpenAI layer (completion_tokens_details, prompt_tokens_details, logprobs) are intentionally omitted — will add when Core exposes them

Cross-SDK field alignment

Field C# Rust Python JS
max_completion_tokens
function_call (deprecated) N/A (dicts)
FinishReason.FunctionCall strings strings
developer role strings strings

Python and JS use dynamic types (dicts/objects), so new fields are automatically captured without SDK changes.

Testing

  • C# SDK: builds clean, 15/27 tests pass (12 failures are pre-existing model path issues unrelated to this PR)
  • Rust SDK: cargo check/clippy pass in CI (can't test locally due to ORT-Nightly registry)
  • JS/TS: npx tsc --noEmit compiles clean
  • Python: serialization verified manually

Related issues

bmehta001 and others added 3 commits April 3, 2026 16:48
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>
Copilot AI review requested due to automatic review settings April 3, 2026 22:59
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 3, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
foundry-local Ready Ready Preview, Comment Apr 6, 2026 0:26am

Request Review

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-openai chat DTOs with in-repo openai::chat_types and update crate exports/dependencies.
  • C#: remove Betalgo.Ranul.OpenAI usage 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>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_format and tool_choice are being serialized by passing the objects through as-is. With the current JS types (ResponseFormat uses jsonSchema/larkGrammar and ToolChoice uses name), 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 for none|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.

Comment on lines +19 to +23
public class ToolDefinition
{
/// <summary>The type of tool.</summary>
[JsonPropertyName("type")]
public ToolType? Type { get; set; }
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am following OpenAI SDK's convention - can revert if it seems too onerous

Comment on lines +33 to +37
public class FunctionDefinition
{
/// <summary>The name of the function.</summary>
[JsonPropertyName("name")]
public string? Name { get; set; }
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants