@@ -15,6 +15,28 @@ def model_matches(model: str, patterns: list[str]) -> bool:
1515 return False
1616
1717
18+ def apply_ordered_model_rules (model : str , rules : list [str ]) -> bool :
19+ """Apply ordered include/exclude model rules to determine final support.
20+
21+ Rules semantics:
22+ - Each entry is a substring token. '!' prefix marks an exclude rule.
23+ - Case-insensitive substring matching against the raw model string.
24+ - Evaluated in order; the last matching rule wins.
25+ - If no rule matches, returns False.
26+ """
27+ raw = (model or "" ).strip ().lower ()
28+ decided : bool | None = None
29+ for rule in rules :
30+ token = rule .strip ().lower ()
31+ if not token :
32+ continue
33+ is_exclude = token .startswith ("!" )
34+ core = token [1 :] if is_exclude else token
35+ if core and core in raw :
36+ decided = not is_exclude
37+ return bool (decided )
38+
39+
1840@dataclass (frozen = True )
1941class ModelFeatures :
2042 supports_reasoning_effort : bool
@@ -27,9 +49,9 @@ class ModelFeatures:
2749 supports_prompt_cache_retention : bool
2850
2951
30- # Pattern tables capturing current behavior. Keep patterns lowercase.
52+ # Model lists capturing current behavior. Keep entries lowercase.
3153
32- REASONING_EFFORT_PATTERNS : list [str ] = [
54+ REASONING_EFFORT_MODELS : list [str ] = [
3355 # Mirror main behavior exactly (no unintended expansion)
3456 "o1-2024-12-17" ,
3557 "o1" ,
@@ -47,15 +69,15 @@ class ModelFeatures:
4769 "claude-opus-4-5" ,
4870]
4971
50- EXTENDED_THINKING_PATTERNS : list [str ] = [
72+ EXTENDED_THINKING_MODELS : list [str ] = [
5173 # Anthropic model family
5274 # We did not include sonnet 3.7 and 4 here as they don't brings
5375 # significant performance improvements for agents
5476 "claude-sonnet-4-5" ,
5577 "claude-haiku-4-5" ,
5678]
5779
58- PROMPT_CACHE_PATTERNS : list [str ] = [
80+ PROMPT_CACHE_MODELS : list [str ] = [
5981 "claude-3-7-sonnet" ,
6082 "claude-sonnet-3-7-latest" ,
6183 "claude-3-5-sonnet" ,
@@ -70,14 +92,27 @@ class ModelFeatures:
7092]
7193
7294# Models that support a top-level prompt_cache_retention parameter
73- PROMPT_CACHE_RETENTION_PATTERNS : list [str ] = [
74- # OpenAI GPT-5+ family
95+ # Source: OpenAI Prompt Caching docs (extended retention), which list:
96+ # - gpt-5.2
97+ # - gpt-5.1
98+ # - gpt-5.1-codex
99+ # - gpt-5.1-codex-mini
100+ # - gpt-5.1-chat-latest
101+ # - gpt-5
102+ # - gpt-5-codex
103+ # - gpt-4.1
104+ # Use ordered include/exclude rules (last wins) to naturally express exceptions.
105+ PROMPT_CACHE_RETENTION_MODELS : list [str ] = [
106+ # Broad allow for GPT-5 family and GPT-4.1 (covers gpt-5.2 and variants)
75107 "gpt-5" ,
76- # GPT-4.1 too
77108 "gpt-4.1" ,
109+ # Exclude all mini variants by default
110+ "!mini" ,
111+ # Re-allow the explicitly documented supported mini variant
112+ "gpt-5.1-codex-mini" ,
78113]
79114
80- SUPPORTS_STOP_WORDS_FALSE_PATTERNS : list [str ] = [
115+ SUPPORTS_STOP_WORDS_FALSE_MODELS : list [str ] = [
81116 # o-series families don't support stop words
82117 "o1" ,
83118 "o3" ,
@@ -89,7 +124,7 @@ class ModelFeatures:
89124]
90125
91126# Models that should use the OpenAI Responses API path by default
92- RESPONSES_API_PATTERNS : list [str ] = [
127+ RESPONSES_API_MODELS : list [str ] = [
93128 # OpenAI GPT-5 family (includes mini variants)
94129 "gpt-5" ,
95130 # OpenAI Codex (uses Responses API)
@@ -101,7 +136,7 @@ class ModelFeatures:
101136# and need plain strings instead
102137# NOTE: model_matches uses case-insensitive substring matching, not globbing.
103138# Keep these entries as bare substrings without wildcards.
104- FORCE_STRING_SERIALIZER_PATTERNS : list [str ] = [
139+ FORCE_STRING_SERIALIZER_MODELS : list [str ] = [
105140 "deepseek" , # e.g., DeepSeek-V3.2-Exp
106141 "glm" , # e.g., GLM-4.5 / GLM-4.6
107142 # Kimi K2-Instruct requires string serialization only on Groq
@@ -110,32 +145,31 @@ class ModelFeatures:
110145
111146# Models that we should send full reasoning content
112147# in the message input
113- SEND_REASONING_CONTENT_PATTERNS : list [str ] = [
148+ SEND_REASONING_CONTENT_MODELS : list [str ] = [
114149 "kimi-k2-thinking" ,
115150]
116151
117152
118153def get_features (model : str ) -> ModelFeatures :
119154 """Get model features."""
120155 return ModelFeatures (
121- supports_reasoning_effort = model_matches (model , REASONING_EFFORT_PATTERNS ),
122- supports_extended_thinking = model_matches (model , EXTENDED_THINKING_PATTERNS ),
123- supports_prompt_cache = model_matches (model , PROMPT_CACHE_PATTERNS ),
124- supports_stop_words = not model_matches (
125- model , SUPPORTS_STOP_WORDS_FALSE_PATTERNS
126- ),
127- supports_responses_api = model_matches (model , RESPONSES_API_PATTERNS ),
128- force_string_serializer = model_matches (model , FORCE_STRING_SERIALIZER_PATTERNS ),
129- send_reasoning_content = model_matches (model , SEND_REASONING_CONTENT_PATTERNS ),
130- supports_prompt_cache_retention = model_matches (
131- model , PROMPT_CACHE_RETENTION_PATTERNS
156+ supports_reasoning_effort = model_matches (model , REASONING_EFFORT_MODELS ),
157+ supports_extended_thinking = model_matches (model , EXTENDED_THINKING_MODELS ),
158+ supports_prompt_cache = model_matches (model , PROMPT_CACHE_MODELS ),
159+ supports_stop_words = not model_matches (model , SUPPORTS_STOP_WORDS_FALSE_MODELS ),
160+ supports_responses_api = model_matches (model , RESPONSES_API_MODELS ),
161+ force_string_serializer = model_matches (model , FORCE_STRING_SERIALIZER_MODELS ),
162+ send_reasoning_content = model_matches (model , SEND_REASONING_CONTENT_MODELS ),
163+ # Extended prompt_cache_retention support follows ordered include/exclude rules.
164+ supports_prompt_cache_retention = apply_ordered_model_rules (
165+ model , PROMPT_CACHE_RETENTION_MODELS
132166 ),
133167 )
134168
135169
136170# Default temperature mapping.
137171# Each entry: (pattern, default_temperature)
138- DEFAULT_TEMPERATURE_PATTERNS : list [tuple [str , float ]] = [
172+ DEFAULT_TEMPERATURE_MODELS : list [tuple [str , float ]] = [
139173 ("kimi-k2-thinking" , 1.0 ),
140174]
141175
@@ -145,7 +179,7 @@ def get_default_temperature(model: str) -> float:
145179
146180 Uses case-insensitive substring matching via model_matches.
147181 """
148- for pattern , value in DEFAULT_TEMPERATURE_PATTERNS :
182+ for pattern , value in DEFAULT_TEMPERATURE_MODELS :
149183 if model_matches (model , [pattern ]):
150184 return value
151185 return 0.0
0 commit comments