-
Notifications
You must be signed in to change notification settings - Fork 2
Fix Microsoft AI adapters for latest abstractions #2
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
Fix Microsoft AI adapters for latest abstractions #2
Conversation
There was a problem hiding this 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 upgrades the Together.NET SDK to use Microsoft.Extensions.AI 9.5.0, enables central package management, and adds comprehensive support for new Together API resources. The changes expand API coverage to match reference Python/TypeScript SDKs while maintaining compatibility with the latest Microsoft.Extensions.AI abstractions.
Key changes:
- Upgraded Microsoft.Extensions.AI to version 9.5.0 and enabled centralized package version management
- Added support for 8 new Together API resources (Audio, Batch, Endpoints, Hardware, Evaluation, CodeInterpreter, Jobs, Videos)
- Updated Microsoft.Extensions.AI adapters (chat, embeddings, speech-to-text, image generation) to align with latest abstractions
Reviewed Changes
Copilot reviewed 37 out of 37 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| docs/api-coverage.md | Documents complete API coverage including new endpoints and Microsoft.Extensions.AI adapters |
| Together/TogetherClient.cs | Adds properties for 8 new client instances (Audio, Batch, Endpoints, etc.) |
| Together/Together.csproj | Enables central package management and suppresses MEAI001 warning |
| Together/Models/Videos/VideoModels.cs | Defines models for video generation API |
| Together/Models/Jobs/JobModels.cs | Defines models for background job status tracking |
| Together/Models/Evaluations/EvaluationModels.cs | Defines models for evaluation workflows |
| Together/Models/Endpoints/EndpointsModels.cs | Defines models for dedicated endpoint management |
| Together/Models/CodeInterpreter/CodeInterpreterModels.cs | Defines models for code execution API |
| Together/Models/ChatCompletions/FunctionTool.cs | Updates Parameters property to accept nullable values |
| Together/Models/ChatCompletions/ChatCompletionRequest.cs | Adds AdditionalParameters extension data |
| Together/Models/Batch/BatchJob.cs | Defines models for batch processing jobs |
| Together/Models/Audio/AudioModels.cs | Defines models for speech synthesis and transcription |
| Together/Extensions/MicrosoftAI/TogetherAISpeechToTextClient.cs | Implements ISpeechToTextClient for audio transcription |
| Together/Extensions/MicrosoftAI/TogetherAIImageClient.cs | Implements IChatClient for image generation |
| Together/Extensions/MicrosoftAI/TogetherAIEmbeddingGenerator.cs | Implements IEmbeddingGenerator for text embeddings |
| Together/Extensions/MicrosoftAI/TogetherAIClientExtensions.cs | Provides extension methods for creating Microsoft.AI adapters |
| Together/Extensions/MicrosoftAI/TogetherAIChatClient.cs | Implements IChatClient for chat completions |
| Together/Extensions/MicrosoftAI/ChatOptionConverters.cs | Converts between Microsoft.Extensions.AI and Together SDK types |
| Together/Clients/*.cs | Implements REST clients for Video, Job, Hardware, Evaluation, Endpoint, CodeInterpreter, Batch, and Audio APIs |
| Together.slnx | New XML-based solution file |
| Together.sln | Removed old solution file |
| Together.Tests/Together.Tests.csproj | Updates test project to use central package management |
| Together.Tests/MicrosoftAI/MicrosoftAIAdapterTests.cs | Tests for Microsoft.Extensions.AI adapter implementations |
| Together.Tests/Clients/NewResourceClientTests.cs | Tests for new REST client implementations |
| Together.SemanticKernel/Together.SemanticKernel.csproj | Removes hardcoded package version |
| Directory.Packages.props | Centralized package version definitions |
| Directory.Build.props | Enables central package management |
| .github/workflows/*.yml | Updates workflows to use Together.slnx |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <PropertyGroup> | ||
| <ImplicitUsings>enable</ImplicitUsings> | ||
| <IsPackable>false</IsPackable> | ||
| <IsTestProject>true</IsTestProject> |
Copilot
AI
Oct 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The MEAI001 warning suppression is not documented. Add a comment explaining why this warning is suppressed (e.g., '').
| <IsTestProject>true</IsTestProject> | |
| <IsTestProject>true</IsTestProject> | |
| <!-- MEAI001: Suppresses experimental API warnings for Microsoft.Extensions.AI 9.5.0 preview features --> |
| var request = new AudioFileRequest | ||
| { | ||
| Content = audio, | ||
| FileName = options?.AdditionalProperties?.TryGetValue("file_name", out var name) == true ? name?.ToString() ?? "audio.wav" : "audio.wav", |
Copilot
AI
Oct 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The nested ternary operation makes this line difficult to read. Consider extracting the file name resolution logic into a separate helper method or local variable.
| var request = new AudioFileRequest | |
| { | |
| Content = audio, | |
| FileName = options?.AdditionalProperties?.TryGetValue("file_name", out var name) == true ? name?.ToString() ?? "audio.wav" : "audio.wav", | |
| var fileName = "audio.wav"; | |
| if (options?.AdditionalProperties?.TryGetValue("file_name", out var name) == true) | |
| { | |
| fileName = name?.ToString() ?? "audio.wav"; | |
| } | |
| var request = new AudioFileRequest | |
| { | |
| Content = audio, | |
| FileName = fileName, |
| Height = options?.AdditionalProperties?.TryGetValue("height", out var height) == true && int.TryParse(height?.ToString(), out var h) ? h : (int?)null, | ||
| Width = options?.AdditionalProperties?.TryGetValue("width", out var width) == true && int.TryParse(width?.ToString(), out var w) ? w : (int?)null, | ||
| Steps = options?.AdditionalProperties?.TryGetValue("steps", out var steps) == true && int.TryParse(steps?.ToString(), out var s) ? s : (int?)null, |
Copilot
AI
Oct 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lines 86-88 contain duplicate parsing logic. Extract this pattern into a reusable helper method like TryGetIntProperty(Dictionary<string, object?> properties, string key).
| return await SendRequestAsync<CreateVideoRequest, CreateVideoResponse>("/../v2/videos", request, cancellationToken); | ||
| } | ||
|
|
||
| public async Task<VideoJob> RetrieveAsync(string id, CancellationToken cancellationToken = default) | ||
| { | ||
| return await SendRequestAsync<VideoJob>($"/../v2/videos/{id}", HttpMethod.Get, null, cancellationToken); |
Copilot
AI
Oct 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The path prefix /../v2/videos is unusual and potentially error-prone. Verify that the /../ prefix is intentional or if the path should be /v2/videos instead.
| return await SendRequestAsync<CreateVideoRequest, CreateVideoResponse>("/../v2/videos", request, cancellationToken); | |
| } | |
| public async Task<VideoJob> RetrieveAsync(string id, CancellationToken cancellationToken = default) | |
| { | |
| return await SendRequestAsync<VideoJob>($"/../v2/videos/{id}", HttpMethod.Get, null, cancellationToken); | |
| return await SendRequestAsync<CreateVideoRequest, CreateVideoResponse>("/v2/videos", request, cancellationToken); | |
| } | |
| public async Task<VideoJob> RetrieveAsync(string id, CancellationToken cancellationToken = default) | |
| { | |
| return await SendRequestAsync<VideoJob>($"/v2/videos/{id}", HttpMethod.Get, null, cancellationToken); |
| return await SendRequestAsync<CreateVideoRequest, CreateVideoResponse>("/../v2/videos", request, cancellationToken); | ||
| } | ||
|
|
||
| public async Task<VideoJob> RetrieveAsync(string id, CancellationToken cancellationToken = default) | ||
| { | ||
| return await SendRequestAsync<VideoJob>($"/../v2/videos/{id}", HttpMethod.Get, null, cancellationToken); |
Copilot
AI
Oct 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The path prefix /../v2/videos is unusual and potentially error-prone. Verify that the /../ prefix is intentional or if the path should be /v2/videos instead.
| return await SendRequestAsync<CreateVideoRequest, CreateVideoResponse>("/../v2/videos", request, cancellationToken); | |
| } | |
| public async Task<VideoJob> RetrieveAsync(string id, CancellationToken cancellationToken = default) | |
| { | |
| return await SendRequestAsync<VideoJob>($"/../v2/videos/{id}", HttpMethod.Get, null, cancellationToken); | |
| return await SendRequestAsync<CreateVideoRequest, CreateVideoResponse>("/v2/videos", request, cancellationToken); | |
| } | |
| public async Task<VideoJob> RetrieveAsync(string id, CancellationToken cancellationToken = default) | |
| { | |
| return await SendRequestAsync<VideoJob>($"/v2/videos/{id}", HttpMethod.Get, null, cancellationToken); |
| PresencePenalty = options?.PresencePenalty, | ||
| FrequencyPenalty = options?.FrequencyPenalty, | ||
| MaxTokens = options?.MaxOutputTokens, | ||
| Seed = options?.Seed is long seedValue ? (ulong?)seedValue : null, |
Copilot
AI
Oct 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Casting a signed long to ulong can produce incorrect results for negative values. Either add validation to reject negative seeds or document that negative values will be converted incorrectly.
| Seed = options?.Seed is long seedValue ? (ulong?)seedValue : null, | |
| Seed = options?.Seed is long seedValue && seedValue >= 0 ? (ulong?)seedValue : null, |
| continue; | ||
| } | ||
|
|
||
| var payload = line.Substring("data:".Length).Trim(); |
Copilot
AI
Oct 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using Substring can throw if the line is shorter than the prefix. Use line["data:".Length..] or add a length check before calling Substring to prevent potential exceptions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| private static AudioTranscriptionResult BuildTranscriptionResult(string responseFormat, string json) | ||
| { | ||
| var result = new AudioTranscriptionResult { RawJson = json }; | ||
| var format = responseFormat?.ToLowerInvariant(); | ||
| result.VerboseResponse = format == "verbose_json" | ||
| ? JsonSerializer.Deserialize<AudioTranscriptionVerboseResponse>(json) | ||
| : null; | ||
| result.Response = format == "verbose_json" | ||
| ? null | ||
| : JsonSerializer.Deserialize<AudioTranscriptionResponse>(json); | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| private static AudioTranslationResult BuildTranslationResult(string responseFormat, string json) | ||
| { | ||
| var result = new AudioTranslationResult { RawJson = json }; | ||
| var format = responseFormat?.ToLowerInvariant(); | ||
| result.VerboseResponse = format == "verbose_json" | ||
| ? JsonSerializer.Deserialize<AudioTranslationVerboseResponse>(json) | ||
| : null; | ||
| result.Response = format == "verbose_json" | ||
| ? null | ||
| : JsonSerializer.Deserialize<AudioTranslationResponse>(json); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handle non‑JSON transcription formats without deserializing
Both BuildTranscriptionResult and BuildTranslationResult always run the payload through JsonSerializer.Deserialize whenever the responseFormat isn’t verbose_json. The audio APIs also return plain text (e.g. "text", "srt", "vtt") when those formats are requested, so calling this code with any non‑JSON format will throw a JsonException even though the HTTP call succeeded. Consider returning the raw text (or leaving Response/VerboseResponse null) when the format isn’t JSON instead of deserializing unconditionally.
Useful? React with 👍 / 👎.
Summary
Testing
/root/.dotnet/dotnet build Together.slnx/root/.dotnet/dotnet test Together.slnx(fails: requires live API access / valid credentials)https://chatgpt.com/codex/tasks/task_e_68fc981b30188326bbe15abdb9fcb04d