fix: recover character sheet requests from gateway 405
Some checks are pending
Test and SonarQube / quality (push) Waiting to run

This commit is contained in:
mARTin-B78 2026-09-09 06:49:52 +02:00
parent b7aade39db
commit ff827a74bd
4 changed files with 44 additions and 2 deletions

View File

@ -9,6 +9,16 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
---
## [1.26.2] — 2026-09-09
### Fixed
- **Character-Sheet Generation Could Stop on HTTP 405.** If an OpenAI-compatible LLM gateway rejects the configured
version-prefixed chat route (`/v1/chat/completions`) with 405, character-sheet extraction now retries the gateway's
root chat route (`/chat/completions`). This applies to both the live stream and its non-streaming fallback, so an
otherwise healthy gateway no longer drops the current passage.
---
## [1.26.1] — 2026-09-08
### Added

View File

@ -1 +1 @@
1.26.1
1.26.2

View File

@ -395,11 +395,23 @@ def _response_error_text(resp) -> str:
def _llm_chat_completion_urls(llm_url: str) -> list[str]:
"""Return compatible chat-completions routes for an LLM base URL.
Most OpenAI-compatible servers use a ``/v1`` prefix, but a few routing
gateways expose the same API at their root. In particular, gateways can
reply 405 (rather than 404) when the version-prefixed route is disabled.
Trying the root route after a versioned route prevents a single passage
from being lost just because the configured gateway uses that variant.
"""
base = (llm_url or "http://localhost:11434/v1").rstrip("/")
versioned = bool(re.search(r"/v\d+(?:\.\d+)?$", base))
root = re.sub(r"/v\d+(?:\.\d+)?$", "", base) if versioned else base
urls: list[str] = []
if not re.search(r"/v\d+(?:\.\d+)?$", base):
if not versioned:
urls.append(f"{base}/v1/chat/completions")
urls.append(f"{base}/chat/completions")
if versioned:
urls.append(f"{root}/chat/completions")
return list(dict.fromkeys(urls))

View File

@ -28,6 +28,7 @@ from routes.conversation import (
match_characters_voices,
_request_timeout_seconds,
_response_error_text,
_llm_chat_completion_urls,
_persist_consistent_image_artifacts,
_data_image_digest,
)
@ -112,6 +113,25 @@ class AttributionParserTests(unittest.TestCase):
reason = "Bad Gateway"
self.assertEqual(len(_response_error_text(ResponseStub())), 2000)
def test_chat_completion_urls_fall_back_from_versioned_gateway_route(self):
# Some OpenAI-compatible routing gateways return 405 for /v1 but
# accept their chat endpoint at the root. Keep the configured route
# first, then try that compatible alternative.
self.assertEqual(
_llm_chat_completion_urls("http://gateway:28080/v1"),
[
"http://gateway:28080/v1/chat/completions",
"http://gateway:28080/chat/completions",
],
)
self.assertEqual(
_llm_chat_completion_urls("http://gateway:28080"),
[
"http://gateway:28080/v1/chat/completions",
"http://gateway:28080/chat/completions",
],
)
def test_repairs_epigraph_split_before_mixed_guillemet_close(self):
raw = '{"segments":[' \
'{"speaker":"Schwester Larissa","type":"dialogue","text":"»Von dem Windhager Felsgesteyn bis zur Kueste von Mendena ziehet es sich hin, von Ifirns Eisgebirg bis zu dem dampfenden Waldt der Mohas, und"},' \