mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-09-22 19:06:25 +00:00
[OSS-129] Map GPT-5.6 family to the official 1.05M context window (#7012)
* fix(core): map GPT-5.6 to the official 1.05M context window LiteLLM fallback treated Sol, Terra, Luna, and the gpt-5.6 alias as unknown and used the 8k default. * fix(openai): give GPT-5.6 its own 1.05M window Native lookup matched gpt-5 first, so Sol, Terra, and Luna inherited 1,047,576. Longest-prefix matching keeps gpt-5 and gpt-5.4-mini on their own sizes. * fix(azure): map GPT-5.6 deployments to the official 1.05M window Azure had no gpt-5 / gpt-5.6 entry, so Sol, Terra, and Luna fell back to 8k. * feat(cli): list GPT-5.6 Sol, Terra, and Luna in curated catalogs The family is generally available; keep gpt-5.5 as the offline default. * refactor: keep context-window tables in longest-prefix order Drop the runtime sort and document that new keys must be inserted longest-first so startswith matching stays correct. * fix(core): resolve prefixed LiteLLM models to the GPT-5.6 window openai/gpt-5.6-luna kept its provider prefix on self.model, so startswith matching missed the 1.05M mapping. Strip recognized prefixes for lookup and leave unknown ones intact.
This commit is contained in:
@@ -132,6 +132,10 @@ PROVIDERS: list[str] = [
|
||||
|
||||
MODELS: dict[str, list[str]] = {
|
||||
"openai": [
|
||||
"gpt-5.6-sol",
|
||||
"gpt-5.6-terra",
|
||||
"gpt-5.6-luna",
|
||||
"gpt-5.6",
|
||||
"gpt-5.5",
|
||||
"gpt-5.5-pro",
|
||||
"gpt-5.4",
|
||||
|
||||
@@ -49,11 +49,15 @@ _PROVIDERS: list[tuple[str, str]] = [
|
||||
# live from the vendor's own API via ``model_catalog.get_provider_models``;
|
||||
# this list is the hand-verified backstop used when no API key is available.
|
||||
# Keep entries to real, current model ids — last verified against each vendor's
|
||||
# official model docs on 2026-07-05.
|
||||
# official model docs on 2026-08-17.
|
||||
_PROVIDER_MODELS: dict[str, list[tuple[str, str]]] = {
|
||||
"openai": [
|
||||
("gpt-5.5", "GPT-5.5"),
|
||||
("gpt-5.5-pro", "GPT-5.5 Pro"),
|
||||
("gpt-5.6-sol", "GPT-5.6 Sol"),
|
||||
("gpt-5.6-terra", "GPT-5.6 Terra"),
|
||||
("gpt-5.6-luna", "GPT-5.6 Luna"),
|
||||
("gpt-5.6", "GPT-5.6"),
|
||||
("gpt-5.4", "GPT-5.4"),
|
||||
("gpt-5.4-mini", "GPT-5.4 Mini"),
|
||||
("gpt-5.2", "GPT-5.2"),
|
||||
|
||||
@@ -18,3 +18,10 @@ def test_huggingface_models():
|
||||
"""Test that Huggingface models are properly configured."""
|
||||
assert "huggingface" in MODELS
|
||||
assert len(MODELS["huggingface"]) > 0
|
||||
|
||||
|
||||
def test_openai_models_include_gpt56_family() -> None:
|
||||
"""Curated OpenAI list should include Sol, Terra, Luna, and the alias."""
|
||||
openai_models = MODELS["openai"]
|
||||
for model in ("gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gpt-5.6"):
|
||||
assert model in openai_models
|
||||
|
||||
@@ -170,6 +170,7 @@ LLM_CONTEXT_WINDOW_SIZES: Final[dict[str, int]] = {
|
||||
"gpt-4o": 128000,
|
||||
"gpt-4o-mini": 200000,
|
||||
"gpt-5.4-mini": 200000,
|
||||
"gpt-5.6": 1050000, # sol, terra, luna, and the gpt-5.6 alias
|
||||
"gpt-4-turbo": 128000,
|
||||
"gpt-4.1": 1047576, # Based on official docs
|
||||
"gpt-4.1-mini-2025-04-14": 1047576,
|
||||
@@ -2447,6 +2448,18 @@ class LLM(BaseLLM):
|
||||
logging.error(f"Failed to get supported params: {e!s}")
|
||||
return True # Default to True
|
||||
|
||||
def _context_window_model_name(self) -> str:
|
||||
"""Return the model id used for context-window lookup.
|
||||
|
||||
LiteLLM keeps provider-qualified names such as ``openai/gpt-5.6-luna``.
|
||||
Strip a recognized provider prefix so those resolve through the same
|
||||
mapping as the bare model id. Unrecognized prefixes are left intact.
|
||||
"""
|
||||
prefix, separator, remainder = self.model.partition("/")
|
||||
if separator and remainder and prefix.lower() in SUPPORTED_NATIVE_PROVIDERS:
|
||||
return remainder
|
||||
return self.model
|
||||
|
||||
def get_context_window_size(self) -> int:
|
||||
"""
|
||||
Returns the context window size, using 75% of the maximum to avoid
|
||||
@@ -2470,8 +2483,9 @@ class LLM(BaseLLM):
|
||||
self.context_window_size = int(
|
||||
DEFAULT_CONTEXT_WINDOW_SIZE * CONTEXT_WINDOW_USAGE_RATIO
|
||||
)
|
||||
model_name = self._context_window_model_name()
|
||||
for key, value in LLM_CONTEXT_WINDOW_SIZES.items():
|
||||
if self.model.startswith(key):
|
||||
if model_name.startswith(key) or self.model.startswith(key):
|
||||
self.context_window_size = int(value * CONTEXT_WINDOW_USAGE_RATIO)
|
||||
return self.context_window_size
|
||||
|
||||
|
||||
@@ -1302,20 +1302,21 @@ class AzureCompletion(BaseLLM):
|
||||
f"Context window for {key} must be between {min_context} and {max_context}"
|
||||
)
|
||||
|
||||
# Longest prefix first. Always insert new keys in that order so
|
||||
# startswith prefers gpt-5.6 over gpt-5, gpt-4o-mini over gpt-4o, etc.
|
||||
context_windows = {
|
||||
"gpt-4": 8192,
|
||||
"gpt-4o": 128000,
|
||||
"gpt-4o-mini": 200000,
|
||||
"gpt-5.4-mini": 200000,
|
||||
"gpt-4-turbo": 128000,
|
||||
"gpt-35-turbo": 16385,
|
||||
"gpt-3.5-turbo": 16385,
|
||||
"text-embedding": 8191,
|
||||
"gpt-3.5-turbo": 16385,
|
||||
"gpt-5.4-mini": 200000,
|
||||
"gpt-35-turbo": 16385,
|
||||
"gpt-4o-mini": 200000,
|
||||
"gpt-4-turbo": 128000,
|
||||
"gpt-5.6": 1050000,
|
||||
"gpt-4o": 128000,
|
||||
"gpt-4": 8192,
|
||||
}
|
||||
|
||||
for model_prefix, size in sorted(
|
||||
context_windows.items(), key=lambda x: len(x[0]), reverse=True
|
||||
):
|
||||
for model_prefix, size in context_windows.items():
|
||||
if self.model.startswith(model_prefix):
|
||||
return int(size * CONTEXT_WINDOW_USAGE_RATIO)
|
||||
|
||||
|
||||
@@ -2664,23 +2664,25 @@ class OpenAICompletion(BaseLLM):
|
||||
f"Context window for {key} must be between {min_context} and {max_context}"
|
||||
)
|
||||
|
||||
# Context window sizes for OpenAI models
|
||||
# Longest prefix first. Always insert new keys in that order so
|
||||
# startswith prefers gpt-5.6 over gpt-5, gpt-4o-mini over gpt-4o, etc.
|
||||
context_windows = {
|
||||
"gpt-4": 8192,
|
||||
"gpt-4o": 128000,
|
||||
"gpt-4o-mini": 200000,
|
||||
"gpt-5.4-mini": 200000,
|
||||
"gpt-4-turbo": 128000,
|
||||
"gpt-4.1": 1047576,
|
||||
"gpt-4.1-mini-2025-04-14": 1047576,
|
||||
"gpt-4.1-nano-2025-04-14": 1047576,
|
||||
"gpt-5": 1047576,
|
||||
"gpt-5.4-mini": 200000,
|
||||
"gpt-4-turbo": 128000,
|
||||
"gpt-4o-mini": 200000,
|
||||
"gpt-5-mini": 1047576,
|
||||
"gpt-5-nano": 1047576,
|
||||
"o1-preview": 128000,
|
||||
"gpt-5.6": 1050000,
|
||||
"o1-mini": 128000,
|
||||
"o3-mini": 200000,
|
||||
"o4-mini": 200000,
|
||||
"gpt-4.1": 1047576,
|
||||
"gpt-4o": 128000,
|
||||
"gpt-5": 1047576,
|
||||
"gpt-4": 8192,
|
||||
}
|
||||
|
||||
for model_prefix, size in context_windows.items():
|
||||
|
||||
@@ -5,7 +5,7 @@ from unittest.mock import patch, MagicMock, Mock
|
||||
from urllib.parse import urlparse
|
||||
import pytest
|
||||
|
||||
from crewai.llm import LLM
|
||||
from crewai.llm import CONTEXT_WINDOW_USAGE_RATIO, LLM
|
||||
from crewai.crew import Crew
|
||||
from crewai.agent import Agent
|
||||
from crewai.task import Task
|
||||
@@ -653,6 +653,26 @@ def test_azure_context_window_size():
|
||||
assert context_size_gpt4o > context_size_gpt4 # GPT-4o has larger context
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"model",
|
||||
[
|
||||
"azure/gpt-5.6",
|
||||
"azure/gpt-5.6-sol",
|
||||
"azure/gpt-5.6-terra",
|
||||
"azure/gpt-5.6-luna",
|
||||
],
|
||||
)
|
||||
def test_azure_gpt56_family_uses_official_context_window(model: str) -> None:
|
||||
"""Azure must not fall back to the 8k default for GPT-5.6 deployments."""
|
||||
llm = LLM(model=model)
|
||||
assert llm.get_context_window_size() == int(1_050_000 * CONTEXT_WINDOW_USAGE_RATIO)
|
||||
|
||||
|
||||
def test_azure_gpt54_mini_keeps_its_window() -> None:
|
||||
llm = LLM(model="azure/gpt-5.4-mini")
|
||||
assert llm.get_context_window_size() == int(200000 * CONTEXT_WINDOW_USAGE_RATIO)
|
||||
|
||||
|
||||
def test_azure_message_formatting():
|
||||
"""
|
||||
Test that messages are properly formatted for Azure API
|
||||
|
||||
@@ -6,7 +6,7 @@ from unittest.mock import patch, MagicMock
|
||||
import openai
|
||||
import pytest
|
||||
|
||||
from crewai.llm import LLM
|
||||
from crewai.llm import CONTEXT_WINDOW_USAGE_RATIO, LLM
|
||||
from crewai.llms.providers.openai.completion import OpenAICompletion, ResponsesAPIResult
|
||||
from crewai.crew import Crew
|
||||
from crewai.agent import Agent
|
||||
@@ -1841,6 +1841,30 @@ def test_openai_gpt5_still_applies_stop_words_client_side():
|
||||
assert "I need to search" in result
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"model",
|
||||
["gpt-5.6", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"],
|
||||
)
|
||||
def test_openai_gpt56_family_uses_official_context_window(model: str) -> None:
|
||||
"""Native OpenAI must not inherit the shorter gpt-5 window for GPT-5.6."""
|
||||
llm = OpenAICompletion(model=model)
|
||||
assert llm.get_context_window_size() == int(1_050_000 * CONTEXT_WINDOW_USAGE_RATIO)
|
||||
|
||||
|
||||
def test_openai_prefixed_gpt56_luna_uses_official_context_window() -> None:
|
||||
llm = LLM(model="openai/gpt-5.6-luna")
|
||||
assert isinstance(llm, OpenAICompletion)
|
||||
assert llm.model == "gpt-5.6-luna"
|
||||
assert llm.get_context_window_size() == int(1_050_000 * CONTEXT_WINDOW_USAGE_RATIO)
|
||||
|
||||
|
||||
def test_openai_gpt5_and_gpt54_mini_keep_their_windows() -> None:
|
||||
gpt5 = OpenAICompletion(model="gpt-5")
|
||||
gpt54_mini = OpenAICompletion(model="gpt-5.4-mini")
|
||||
assert gpt5.get_context_window_size() == int(1_047_576 * CONTEXT_WINDOW_USAGE_RATIO)
|
||||
assert gpt54_mini.get_context_window_size() == int(200000 * CONTEXT_WINDOW_USAGE_RATIO)
|
||||
|
||||
|
||||
def test_openai_stop_words_still_applied_to_regular_responses():
|
||||
"""
|
||||
Test that stop words ARE still applied for regular (non-structured) responses.
|
||||
|
||||
@@ -11,7 +11,7 @@ from crewai.events.event_types import (
|
||||
ToolUsageFinishedEvent,
|
||||
ToolUsageStartedEvent,
|
||||
)
|
||||
from crewai.llm import CONTEXT_WINDOW_USAGE_RATIO, LLM
|
||||
from crewai.llm import CONTEXT_WINDOW_USAGE_RATIO, DEFAULT_CONTEXT_WINDOW_SIZE, LLM
|
||||
from crewai.llms.providers.anthropic.completion import AnthropicCompletion
|
||||
from crewai.utilities.token_counter_callback import TokenCalcHandler
|
||||
from pydantic import BaseModel
|
||||
@@ -343,6 +343,49 @@ def test_context_window_validation():
|
||||
assert "must be between 1024 and 2097152" in str(excinfo.value)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"model",
|
||||
["gpt-5.6", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"],
|
||||
)
|
||||
def test_gpt56_family_uses_official_context_window(model: str) -> None:
|
||||
"""GPT-5.6 Sol, Terra, Luna, and the alias share a 1.05M window."""
|
||||
llm = LLM(model=model, is_litellm=True)
|
||||
assert llm.get_context_window_size() == int(1_050_000 * CONTEXT_WINDOW_USAGE_RATIO)
|
||||
|
||||
|
||||
def test_gpt56_does_not_override_gpt54_mini_window() -> None:
|
||||
"""A more specific older prefix must keep its own window."""
|
||||
llm = LLM(model="gpt-5.4-mini", is_litellm=True)
|
||||
assert llm.get_context_window_size() == int(200000 * CONTEXT_WINDOW_USAGE_RATIO)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"model",
|
||||
[
|
||||
"openai/gpt-5.6",
|
||||
"openai/gpt-5.6-sol",
|
||||
"openai/gpt-5.6-terra",
|
||||
"openai/gpt-5.6-luna",
|
||||
],
|
||||
)
|
||||
def test_gpt56_family_context_window_with_provider_prefix(model: str) -> None:
|
||||
"""LiteLLM keeps the provider prefix on self.model; lookup must still hit gpt-5.6."""
|
||||
llm = LLM(model=model, is_litellm=True)
|
||||
assert llm.model == model
|
||||
assert llm._context_window_model_name() == model.partition("/")[2]
|
||||
assert llm.get_context_window_size() == int(1_050_000 * CONTEXT_WINDOW_USAGE_RATIO)
|
||||
|
||||
|
||||
def test_unrecognized_provider_prefix_is_not_stripped() -> None:
|
||||
"""Unknown prefixes stay on the lookup name and do not inherit the gpt-5.6 window."""
|
||||
llm = LLM(model="acme/gpt-5.6-luna", is_litellm=True)
|
||||
assert llm.model == "acme/gpt-5.6-luna"
|
||||
assert llm._context_window_model_name() == "acme/gpt-5.6-luna"
|
||||
assert llm.get_context_window_size() == int(
|
||||
DEFAULT_CONTEXT_WINDOW_SIZE * CONTEXT_WINDOW_USAGE_RATIO
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def get_weather_tool_schema():
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user