Skip to content

GitHubProvider streamed response lacks timestamp #2997

@mamazinho

Description

@mamazinho

Initial Checks

Description

I am trying to use the GitHubProvider with OpenAIChatModel in one of my applications and reproducing the documentation usage.
However, there is a difference between using the GitHubProvider and OpenAIProvider. GitHubProvider doesn't handle the usage information and the timestamp data so well.

I changed my app to use OpenAIProvider and the response was as expected (with timestamp and usage data).

I am printing the result.all_messages_json() and result.timestamp() at the end of the stream code and the print is very different using githubProvider and openAIProvider. The main point that makes a huge difference in my app is about the timestamp in the model response, I need this timestamp to control the order of messages.

As well, I will leave the code below with the option to switch between providers. Despite I am printing only all_messages_json() method, the problem also happens in new_messages() method

OpenAIProvider output
ALL MESSAGES:  b'[{"parts":[{"content":"Me diga apenas um nome brasileiro","timestamp":"2025-09-24T15:42:11.449728Z","part_kind":"user-prompt"}],"instructions":null,"kind":"request"},{"parts":[{"content":"Maria","id":null,"part_kind":"text"}],"usage":{"input_tokens":13,"cache_write_tokens":0,"cache_read_tokens":0,"output_tokens":1,"input_audio_tokens":0,"cache_audio_read_tokens":0,"output_audio_tokens":0,"details":{"accepted_prediction_tokens":0,"audio_tokens":0,"reasoning_tokens":0,"rejected_prediction_tokens":0}},"model_name":"gpt-4o-2024-08-06","timestamp":"2025-09-24T15:42:15Z","kind":"response","provider_name":"openai","provider_details":{"finish_reason":"stop"},"provider_response_id":"chatcmpl-CJLvDzQiCRHEEfWMjH8OKRJjtTggT","finish_reason":"stop"}]'
TIMESTAMP:  2025-09-24 15:42:15+00:00
GitHubProvider output
ALL MESSAGES:  b'[{"parts":[{"content":"Me diga apenas um nome brasileiro","timestamp":"2025-09-24T15:45:47.259848Z","part_kind":"user-prompt"}],"instructions":null,"kind":"request"},{"parts":[{"content":"Clarice Lispector","id":null,"part_kind":"text"}],"usage":{"input_tokens":13,"cache_write_tokens":0,"cache_read_tokens":0,"output_tokens":5,"input_audio_tokens":0,"cache_audio_read_tokens":0,"output_audio_tokens":0,"details":{"accepted_prediction_tokens":0,"audio_tokens":0,"reasoning_tokens":0,"rejected_prediction_tokens":0}},"model_name":"gpt-4o-2024-11-20","timestamp":"1970-01-01T00:00:00Z","kind":"response","provider_name":"github","provider_details":{"finish_reason":"stop"},"provider_response_id":"chatcmpl-CJLyeUMNKA83PjvLiTggPjBChHU3F","finish_reason":"stop"}]'
TIMESTAMP:  1970-01-01 00:00:00+00:00

Example Code

from datetime import datetime, timezone
from typing import Any, AsyncGenerator, Annotated

from fastapi import Form
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from pydantic_ai import Agent, UnexpectedModelBehavior
from pydantic_ai.models.openai import OpenAIChatModel
from pydantic_ai.providers.github import GitHubProvider
from pydantic_ai.providers.openai import OpenAIProvider
from pydantic_ai.messages import (
    ModelMessage,
    ModelRequest,
    ModelResponse,
    TextPart,
    UserPromptPart,
)

from datalab_api.apps.chats.choices import Sender
from datalab_api.config.settings import settings

model = OpenAIChatModel(model_name="gpt-4o", provider=OpenAIProvider(api_key=settings.openai_api_token_ai))
# model = OpenAIChatModel(model_name="gpt-4o", provider=GitHubProvider(api_key=settings.github_api_token_ai))
agent = Agent(model)


class ModelChatResponse(BaseModel):
    role: str
    timestamp: str
    content: str

    def formatted_json(self) -> str:
        return self.model_dump_json().encode("utf-8") + b"\n"
    
    @classmethod
    def from_model_message(cls, m: ModelMessage):
        first_part = m.parts[0]
        if isinstance(m, ModelRequest):
            if isinstance(first_part, UserPromptPart):
                assert isinstance(first_part.content, str)
                return ModelChatResponse(
                    role="user",
                    timestamp=first_part.timestamp.isoformat(),
                    content=first_part.content,
                )
        elif isinstance(m, ModelResponse):
            if isinstance(first_part, TextPart):
                return ModelChatResponse(
                    role="agent",
                    timestamp=m.timestamp.isoformat(),
                    content=first_part.content,
                )
        raise UnexpectedModelBehavior(f'Unexpected message type for chat app: {m}')

class ChatService:    
    async def stream_messages(self, chat_id: int, prompt: str) -> AsyncGenerator[Any, Any]:
        yield ModelChatResponse(
            role="user",
            timestamp=datetime.now(tz=timezone.utc).isoformat(),
            content=prompt,
        ).formatted_json()

        async with agent.run_stream(prompt) as result:
            async for text in result.stream_text():
                m = ModelResponse(parts=[TextPart(text)], timestamp=result.timestamp())
                yield ModelChatResponse.from_model_message(m).formatted_json()
        
        print("ALL MESSAGES: ", result.all_messages_json())
        print("TIMESTAMP: ", result.timestamp())


@chat_router.post("/{chat_id}/messages/")
async def post_chat(chat_id: int, prompt: Annotated[str, Form()]) -> StreamingResponse:
    return StreamingResponse(ChatService().stream_messages(chat_id, prompt), media_type="text/plain")

Python, Pydantic AI & LLM client version

Python: 3.13
Pydantic: 1.0.10
LLM Model: gpt-4o
Provider: GithubProvider
ChatModel: OpenAI

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions