From 070371800dc49cd987fca5a4fc2dfd3333addcef Mon Sep 17 00:00:00 2001 From: Michael Sitarzewski Date: Tue, 25 Aug 2026 19:10:58 -0500 Subject: [PATCH 1/2] Fix empty output on hard batches: raise max_completion_tokens (16384, tunable) GPT-5.x reasoning models spend max_completion_tokens on hidden reasoning BEFORE emitting output. At 4096 a hard 50-post batch consumes the entire budget reasoning (finish_reason=length, content_len=0) -> the client raises 'empty content' -> the whole batch errors. Easy batches fit and pass, so the failure looks intermittent. Raise the default to 16384 (observed usage ~5-7k, ample headroom) and make it tunable via FINTICK_LLM_MAX_TOKENS so no model-specific ceiling needs another code change. Co-Authored-By: Claude Opus 4.8 --- fintick/aggregate.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) 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}, From cf45b70dc402bdbfa57648bb33f2bf8fd4e71533 Mon Sep 17 00:00:00 2001 From: Michael Sitarzewski Date: Tue, 25 Aug 2026 19:11:29 -0500 Subject: [PATCH 2/2] test: assert the 16384 max_completion_tokens default Co-Authored-By: Claude Opus 4.8 --- tests/test_aggregate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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: