diff --git a/src/cmos/formatter.py b/src/cmos/formatter.py index fd2cfee..f7c435c 100644 --- a/src/cmos/formatter.py +++ b/src/cmos/formatter.py @@ -88,6 +88,17 @@ def build_user_message(messy_entry: str) -> str: ) +def _is_reasoning_model(model: str) -> bool: + """Reasoning-model families (GPT-5, o-series) don't accept temperature overrides. + + OpenAI rejects ``temperature=0`` on these with a 400 BadRequestError; + only the default (1) is supported. Determinism on reasoning models is a + property of the architecture, not a parameter. + """ + prefixes = ("gpt-5", "o1", "o3", "o4") + return any(model.startswith(p) for p in prefixes) + + def _openai_caller(system: str, user: str) -> str: """Default caller — hits the real OpenAI API. @@ -99,14 +110,18 @@ def _openai_caller(system: str, user: str) -> str: from openai import OpenAI # imported lazily so unit tests do not need the network client = OpenAI() - response = client.chat.completions.create( - model=MODEL, - temperature=0, - messages=[ + kwargs: dict = { + "model": MODEL, + "messages": [ {"role": "system", "content": system}, {"role": "user", "content": user}, ], - ) + } + # Non-reasoning models still benefit from temperature=0; reasoning models + # reject it. + if not _is_reasoning_model(MODEL): + kwargs["temperature"] = 0 + response = client.chat.completions.create(**kwargs) return response.choices[0].message.content or ""