Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

[Microsoft.Extensions.AI.OpenAI] AsOpenAIStreamingChatCompletionUpdatesAsync writes finish_reason "stop" on every chunk with a null FinishReason

Aperta Adatta ai principianti
#7,863 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

@nurullahturkoglu ci sta già lavorando.

Dal 20/10/2026.

  • #7866 di @nurullahturkoglu — aperta

Valutazione

Difficoltà
2/5
Tempo stimato
1-3 ore
Idoneità per principianti
76/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
csharp
Ambito
api, backend

Direzione di ricerca

Inizia da src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIChatExtensions.cs, in particolare da ToChatFinishReason e AsOpenAIStreamingChatCompletionUpdatesAsync; controlla il percorso di conversione dello streaming e i test esistenti per questi metodi. Riproduci il problema con i due aggiornamenti dell’issue, quindi verifica che il chunk di contenuto non abbia un motivo di fine e che il chunk terminale riporti length.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

area-ai untriaged
Description

MicrosoftExtensionsAIChatExtensions.AsOpenAIStreamingChatCompletionUpdatesAsync sets finish_reason to "stop" on every chunk whose ChatResponseUpdate.FinishReason is null. In a stream, every chunk before the last one has no finish reason, so each content chunk is serialized as if the response had already finished.

A client that reads the stream with the OpenAI SDK, or any client that ends the turn on the first non-null finish_reason, stops after the first chunk. A response cut off by the token limit also reports "stop" on its content chunks before the terminal "length" chunk.

The cause is ToChatFinishReason, which maps null to ChatFinishReason.Stop through its default arm:

https://github.com/dotnet/extensions/blob/431c048e2810e217bca1aadb637d0b79bdbad256/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIChatExtensions.cs#L293-L302

Reproduction Steps
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <OutputType>Exe</OutputType>
    <ImplicitUsings>enable</ImplicitUsings>
    <NoWarn>OPENAI001</NoWarn>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.AI.OpenAI" Version="10.11.0" />
  </ItemGroup>
</Project>
using System.ClientModel.Primitives;
using Microsoft.Extensions.AI;
using OpenAI.Chat;

static async IAsyncEnumerable<ChatResponseUpdate> Updates()
{
    // A content chunk: no finish reason yet.
    yield return new ChatResponseUpdate(ChatRole.Assistant, "Hello") { ResponseId = "r1", ModelId = "m" };
    // The terminal chunk: the model stopped at the token limit.
    yield return new ChatResponseUpdate { ResponseId = "r1", ModelId = "m", FinishReason = Microsoft.Extensions.AI.ChatFinishReason.Length };
    await Task.CompletedTask;
}

await foreach (var chunk in Updates().AsOpenAIStreamingChatCompletionUpdatesAsync())
{
    Console.WriteLine(ModelReaderWriter.Write(chunk).ToString());
}
Expected behavior

The first chunk has no finish reason ("finish_reason": null, or the property is absent). Only the terminal chunk has "finish_reason": "length".

Actual behavior
{"id":"r1","choices":[{"delta":{"content":"Hello","tool_calls":[],"role":"assistant"},"logprobs":{"content":[],"refusal":[]},"finish_reason":"stop","index":0}],"created":-62135596800,"model":"m","object":"chat.completion.chunk"}
{"id":"r1","choices":[{"delta":{"content":"","tool_calls":[],"role":"assistant"},"logprobs":{"content":[],"refusal":[]},"finish_reason":"length","index":0}],"created":-62135596800,"model":"m","object":"chat.completion.chunk"}

The content chunk reports "finish_reason":"stop".

Regression?

Not known. The default arm has this behavior on main at the commit linked above.

Known Workarounds

Write the chunks by hand instead of using the converter. Alternatively, keep the StreamingChatCompletionUpdate as RawRepresentation, which the converter passes through unchanged. That works only when the updates come from OpenAIChatClient.

Configuration
  • Microsoft.Extensions.AI.OpenAI 10.11.0, OpenAI 2.x (resolved transitively)
  • .NET SDK 10.0.402, macOS 27.0.1
  • The behavior does not depend on the platform. It comes from the mapping code.
Other information

A possible fix: let ToChatFinishReason return ChatFinishReason? and map null to null on the streaming path. OpenAIChatModelFactory.StreamingChatCompletionUpdate already accepts a nullable finishReason; with null, the chunk is written without a finish_reason. The non-streaming AsOpenAIChatCompletion can keep its Stop default, because a complete response always has a finish reason.

A related detail on the same line: createdAt: update.CreatedAt ?? default serializes as "created":-62135596800 when CreatedAt is null.

Lingua principale
C#
Stelle
3.3k
Fork
916
Merge medio
1g 13h
PR unite (30g)
40

Preparare l'ambiente

Apri in Codespaces

Avvia il container di sviluppo del progetto nel browser, con il tuo account GitHub.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di dotnet/extensions

Tutte le issue di dotnet/extensions

Issue simili

Altre issue su C#

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.