Skip to content

Conversation

Copilot
Copy link
Contributor

@Copilot Copilot AI commented Oct 2, 2025

  • Understand the issue - LLMs sometimes use Unix epoch as "null" timestamp
  • Add failing test case to verify the issue
  • Fix ProcessUpdate method to only update timestamp if later than current
  • Verify fix with tests
  • Check for similar issue in response-level CreatedAt
  • Run all AI tests to ensure no regressions
  • Enhance test with multiple timestamp scenarios per reviewer feedback
  • Replace DateTimeOffset.UnixEpoch with manual construction for .NET Framework compatibility
  • Add theory test for timestamp folding with pairs of timestamps
  • Refactor to use MemberData instead of duplicated InlineData attributes

Summary

Fixed the issue where ChatMessage.CreatedAt and ChatResponse.CreatedAt were being overwritten by Unix epoch timestamps from LLM responses.

The fix changes the timestamp update logic to only update if:

  1. The current timestamp is null, OR
  2. The new timestamp is not null AND later than the current timestamp

This prevents "null" timestamps (represented as Unix epoch) from overwriting valid timestamps while still allowing proper chronological updates.

Changes

  • Modified ChatResponseExtensions.ProcessUpdate() to use comparison logic similar to SpeechToTextResponseUpdateExtensions
  • Added comprehensive test case ToChatResponse_AlternativeTimestamps that validates multiple timestamp scenarios
  • Added focused theory test ToChatResponse_TimestampFolding that tests pairs of timestamps being folded together, using MemberData to avoid duplication
  • Replaced DateTimeOffset.UnixEpoch with manual construction for .NET Framework compatibility
  • All existing tests pass without modification

Fixes #6884

Original prompt

This section details on the original issue you should resolve

<issue_title>[AI Abstractions]: ChatMessage.CreatedAt overwritten by "null" timestamps in chat response updates</issue_title>
<issue_description>### Description

In Microsoft.Extensions.AI.ChatResponseExtensions.ToChatResponse(), a ChatMessage is built from a series of ChatResponseUpdate items. Each ChatResponseUpdate may have a CreatedAt timestamp and the last non-null timestamp of the updates becomes the timestamp for the resulting ChatMessage.

However, it appears that some LLM calls stream updates with timestamps set to the Unix epoch (i.e. 1/1/1970 12AM) as an equivalent for a "null" value. In those cases, because the deserialized value is a real DateTimeOffset, the resulting ChatMessage will end up with CreatedAt timestamp set to that "null" value.

It may be better to update the ChatMessage timestamp upon the next update only if that update's timestamp is later than the current timestamp. (Or, alternatively, treat a timestamp set to the UNIX epoch as equivalent to null.)

Reproduction Steps

Adding this test to the ChatResponseUpdateExtensionsTests.cs in the Microsoft.Extensions.AI.Abstractions.Tests exposes the issue.

    [Theory]
    [InlineData(false)]
    [InlineData(true)]
    public async Task ToChatResponse_AlternativeTimestamps(bool useAsync)
    {
        DateTimeOffset now = DateTimeOffset.UtcNow;

        ChatResponseUpdate[] updates =
        [
            new(ChatRole.Tool, "f") { MessageId = "4", CreatedAt = now },
            new(null, "g") { CreatedAt = DateTimeOffset.UnixEpoch },
        ];

        ChatResponse response = useAsync ?
            updates.ToChatResponse() :
            await YieldAsync(updates).ToChatResponseAsync();
        Assert.Single(response.Messages);

        Assert.Equal("fg", response.Messages[0].Text);
        Assert.Equal(ChatRole.Tool, response.Messages[0].Role);
        Assert.Equal(now, response.Messages[0].CreatedAt);
    }

Expected behavior

The first update's timestamp becomes the timestamp (i.e. CreatedAt) for the ChatMessage.

Actual behavior

The CreatedAt property of the ChatMessage is set to DateTimeOffset.UnixEpoc.

Regression?

Unknown.

Known Workarounds

None.

Configuration

.NET 9 with Microsoft.Extensions.AI.Abstractions version 9.9.0

Other information

No response</issue_description>

Comments on the Issue (you are @copilot in this section)

@stephentoub > It may be better to update the ChatMessage timestamp upon the next update only if that update's timestamp is later than the current timestamp.

This sounds reasonable. </comment_new>

Fixes #6884

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Microsoft Reviewers: Open in CodeFlow

Copilot AI and others added 2 commits October 2, 2025 22:51
Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
@Copilot Copilot AI changed the title [WIP] [AI Abstractions]: ChatMessage.CreatedAt overwritten by "null" timestamps in chat response updates Fix ChatMessage.CreatedAt overwritten by Unix epoch timestamps in streaming responses Oct 2, 2025
@Copilot Copilot AI requested a review from stephentoub October 2, 2025 23:02
Copilot finished work on behalf of stephentoub October 2, 2025 23:02
Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Copilot finished work on behalf of stephentoub October 2, 2025 23:21
@Copilot Copilot AI requested a review from stephentoub October 2, 2025 23:21
…bility

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
@Copilot Copilot AI requested a review from stephentoub October 2, 2025 23:53
Copilot finished work on behalf of stephentoub October 2, 2025 23:53
@stephentoub stephentoub marked this pull request as ready for review October 3, 2025 01:15
@stephentoub stephentoub requested a review from a team as a code owner October 3, 2025 01:15
@Copilot Copilot AI review requested due to automatic review settings October 3, 2025 01:15
@stephentoub stephentoub enabled auto-merge (squash) October 3, 2025 01:15
Copy link
Contributor

@Copilot 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 fixes an issue where ChatMessage.CreatedAt and ChatResponse.CreatedAt timestamps were being overwritten by Unix epoch timestamps (1/1/1970) from LLM streaming responses, which some providers use to represent "null" timestamps.

Key changes:

  • Updated timestamp comparison logic to only update when the new timestamp is later than the current one
  • Added comprehensive test coverage for various timestamp scenarios including Unix epoch handling
  • Applied the fix consistently to both message-level and response-level timestamp updates

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/Libraries/Microsoft.Extensions.AI.Abstractions/ChatCompletion/ChatResponseExtensions.cs Updated ProcessUpdate method to use timestamp comparison logic that prevents Unix epoch overwrites
test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/ChatCompletion/ChatResponseUpdateExtensionsTests.cs Added comprehensive test case validating timestamp update behavior across multiple scenarios

This was referenced Oct 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-ai Microsoft.Extensions.AI libraries

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[AI Abstractions]: ChatMessage.CreatedAt overwritten by "null" timestamps in chat response updates

3 participants