fix: recover character sheet requests from gateway 405
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
This commit is contained in:
parent
b7aade39db
commit
ff827a74bd
10
CHANGELOG.md
10
CHANGELOG.md
@ -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
|
## [1.26.1] — 2026-09-08
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@ -395,11 +395,23 @@ def _response_error_text(resp) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def _llm_chat_completion_urls(llm_url: str) -> list[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("/")
|
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] = []
|
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}/v1/chat/completions")
|
||||||
urls.append(f"{base}/chat/completions")
|
urls.append(f"{base}/chat/completions")
|
||||||
|
if versioned:
|
||||||
|
urls.append(f"{root}/chat/completions")
|
||||||
return list(dict.fromkeys(urls))
|
return list(dict.fromkeys(urls))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -28,6 +28,7 @@ from routes.conversation import (
|
|||||||
match_characters_voices,
|
match_characters_voices,
|
||||||
_request_timeout_seconds,
|
_request_timeout_seconds,
|
||||||
_response_error_text,
|
_response_error_text,
|
||||||
|
_llm_chat_completion_urls,
|
||||||
_persist_consistent_image_artifacts,
|
_persist_consistent_image_artifacts,
|
||||||
_data_image_digest,
|
_data_image_digest,
|
||||||
)
|
)
|
||||||
@ -112,6 +113,25 @@ class AttributionParserTests(unittest.TestCase):
|
|||||||
reason = "Bad Gateway"
|
reason = "Bad Gateway"
|
||||||
self.assertEqual(len(_response_error_text(ResponseStub())), 2000)
|
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):
|
def test_repairs_epigraph_split_before_mixed_guillemet_close(self):
|
||||||
raw = '{"segments":[' \
|
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"},' \
|
'{"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"},' \
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user