mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-18 04:44:37 +00:00
K2 in test_logging_only_scope_chaos.py flaked on CircleCI with AttributeError: type object 'BaseModel' has no attribute '__pydantic_core_schema__' raised inside the OpenAI client while its 30-thread control burst parsed responses The SDKs define their response models with deferred schemas, so the first parse of each model rebuilds it, and pydantic's model_rebuild is not safe when several threads hit it at once. Every integration worker now builds those schemas in pytest_configure, before any test can start a thread pool Co-authored-by: yucheng-berri <293926326+yucheng-berri@users.noreply.github.com>
128 lines
3.6 KiB
Python
128 lines
3.6 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Final
|
|
|
|
_REPOSITORY: Final = Path(__file__).resolve().parents[3]
|
|
_CONCURRENT_FIRST_USE: Final = """
|
|
import sys
|
|
import threading
|
|
|
|
from anthropic.types import Message, RawContentBlockDeltaEvent
|
|
from openai.types.chat import ChatCompletion, ChatCompletionChunk
|
|
from openai.types.responses import Response, ResponseTextDeltaEvent
|
|
|
|
from tests.integration._support.sdk_models import build_sdk_models
|
|
|
|
USAGE = {"input_tokens": 1, "output_tokens": 1}
|
|
PAYLOADS = (
|
|
(
|
|
Response,
|
|
{
|
|
"id": "resp_1",
|
|
"object": "response",
|
|
"created_at": 1,
|
|
"model": "gpt-4.1-mini",
|
|
"status": "completed",
|
|
"output": [
|
|
{
|
|
"id": "msg_1",
|
|
"type": "message",
|
|
"role": "assistant",
|
|
"status": "completed",
|
|
"content": [{"type": "output_text", "text": "hi", "annotations": []}],
|
|
}
|
|
],
|
|
"usage": {**USAGE, "total_tokens": 2},
|
|
},
|
|
),
|
|
(
|
|
ChatCompletion,
|
|
{
|
|
"id": "chatcmpl_1",
|
|
"object": "chat.completion",
|
|
"created": 1,
|
|
"model": "gpt-4o-mini",
|
|
"choices": [{"index": 0, "finish_reason": "stop", "message": {"role": "assistant", "content": "hi"}}],
|
|
"usage": {"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2},
|
|
},
|
|
),
|
|
(
|
|
Message,
|
|
{
|
|
"id": "msg_1",
|
|
"type": "message",
|
|
"role": "assistant",
|
|
"model": "claude-sonnet-4-5",
|
|
"content": [{"type": "text", "text": "hi"}],
|
|
"stop_reason": "end_turn",
|
|
"usage": USAGE,
|
|
},
|
|
),
|
|
(
|
|
ResponseTextDeltaEvent,
|
|
{
|
|
"type": "response.output_text.delta",
|
|
"item_id": "msg_1",
|
|
"output_index": 0,
|
|
"content_index": 0,
|
|
"sequence_number": 1,
|
|
"delta": "hi",
|
|
"logprobs": [],
|
|
},
|
|
),
|
|
(
|
|
ChatCompletionChunk,
|
|
{
|
|
"id": "chatcmpl_1",
|
|
"object": "chat.completion.chunk",
|
|
"created": 1,
|
|
"model": "gpt-4o-mini",
|
|
"choices": [{"index": 0, "finish_reason": None, "delta": {"role": "assistant", "content": "hi"}}],
|
|
},
|
|
),
|
|
(
|
|
RawContentBlockDeltaEvent,
|
|
{"type": "content_block_delta", "index": 0, "delta": {"type": "text_delta", "text": "hi"}},
|
|
),
|
|
)
|
|
THREADS = 30
|
|
|
|
sys.setswitchinterval(1e-6)
|
|
build_sdk_models()
|
|
gate = threading.Barrier(THREADS)
|
|
failures = []
|
|
|
|
|
|
def first_use():
|
|
gate.wait()
|
|
for model, payload in PAYLOADS:
|
|
try:
|
|
model.model_construct(**payload)
|
|
except Exception as error:
|
|
failures.append(f"{model.__name__}: {type(error).__name__}: {error}")
|
|
|
|
|
|
threads = [threading.Thread(target=first_use) for _ in range(THREADS)]
|
|
for thread in threads:
|
|
thread.start()
|
|
for thread in threads:
|
|
thread.join()
|
|
print(sorted(set(failures)))
|
|
"""
|
|
|
|
|
|
def test_sdk_models_built_up_front_survive_concurrent_first_use_from_many_threads() -> None:
|
|
completed: Final = subprocess.run(
|
|
(sys.executable, "-P", "-c", _CONCURRENT_FIRST_USE),
|
|
env={**os.environ, "PYTHONPATH": str(_REPOSITORY)},
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=120,
|
|
check=False,
|
|
)
|
|
assert completed.returncode == 0, completed.stderr
|
|
assert completed.stdout.strip() == "[]", completed.stdout
|