diff --git a/fintick/aggregate.py b/fintick/aggregate.py index ee8c420..68e8cd7 100644 --- a/fintick/aggregate.py +++ b/fintick/aggregate.py @@ -32,6 +32,10 @@ LLM_API_KEY = os.environ.get("FINTICK_LLM_API_KEY", "ollama") LLM_MODEL = os.environ.get("FINTICK_LLM_MODEL", "qwen3.8:27b") DEFAULT_MODEL = LLM_MODEL # backward-compat alias +# Reasoning models (GPT-5.x) spend this budget on hidden reasoning BEFORE emitting output, so it +# must leave room for both. 4096 gets fully consumed by reasoning on hard batches -> empty content; +# 16384 leaves ample headroom (observed ~5-7k total). Tunable per-model via the env var. +LLM_MAX_TOKENS = int(os.environ.get("FINTICK_LLM_MAX_TOKENS", "16384")) WINDOW = timedelta(hours=6) MAX_POSTS = 200 DEFAULT_BATCH = 50 @@ -416,8 +420,8 @@ def call_inference( "model": model, "stream": False, # GPT-5.x requires max_completion_tokens (not max_tokens) and rejects any non-default - # temperature; response_format json_object constrains output regardless. - "max_completion_tokens": 4096, + # temperature; it also spends this budget on hidden reasoning first, so keep headroom. + "max_completion_tokens": LLM_MAX_TOKENS, "response_format": {"type": "json_object"}, "messages": [ {"role": "system", "content": SYSTEM_PROMPT}, diff --git a/tests/test_aggregate.py b/tests/test_aggregate.py index ee6091d..61086a4 100644 --- a/tests/test_aggregate.py +++ b/tests/test_aggregate.py @@ -72,7 +72,7 @@ def test_posts_openai_compatible_forced_json_request(self, urlopen: mock.Mock) - self.assertEqual(payload["model"], "m-1") self.assertIs(payload["stream"], False) self.assertEqual(payload["response_format"], {"type": "json_object"}) - self.assertLessEqual(payload["max_completion_tokens"], 4096) + self.assertEqual(payload["max_completion_tokens"], 16384) @mock.patch("fintick.aggregate.urllib.request.urlopen") def test_empty_content_raises(self, urlopen: mock.Mock) -> None: