-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Description
Initial Checks
- I confirm that I'm using the latest version of Pydantic AI
- I confirm that I searched for my issue in https://github.com/pydantic/pydantic-ai/issues before opening this issue
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