mirror of
https://github.com/vegu-ai/talemate.git
synced 2026-09-01 19:48:52 +02:00
Enhance HuggingFace integration with timeout handling and improved template fetching logic
This commit is contained in:
@@ -49,6 +49,7 @@
|
||||
- "Scene Forking: Fixed forks and rollbacks created from the changelog UI sometimes failing to load with a 'message Field required' error. Affected history entries are now repaired during reconstruction so the scene loads."
|
||||
- "GPU VRAM: Switching scenes now releases the GPU memory that local CUDA work (embeddings, TTS) leaves reserved, instead of holding it for the rest of the session where it could leave too little VRAM to load another scene. Can be turned off under Settings → Game → General."
|
||||
- "Autocomplete: Fixed contextual autocomplete (character attributes, world entries, scene intros, and similar fields) sometimes repeating the text you had already typed instead of only continuing from where you left off."
|
||||
- "HuggingFace Prompt Template Lookup: Fixed automatic prompt-template detection hanging indefinitely when HuggingFace can't be reached (e.g. network or IPv6 routing issues). The lookup now times out, logs a warning, and lets startup continue instead of stalling silently."
|
||||
0.37.1:
|
||||
fixes:
|
||||
- "Scene History: Fixed a crash when generating dialogue in long scenes that combine layered history with pre-established (static) history entries. The affected scene could become unplayable until its layered history was manually edited."
|
||||
|
||||
@@ -7,11 +7,30 @@ import pydantic
|
||||
from typing import Any
|
||||
|
||||
import huggingface_hub
|
||||
import requests
|
||||
import structlog
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
|
||||
__all__ = ["model_prompt", "PromptSpec"]
|
||||
|
||||
# (connect, read) timeout in seconds applied to every huggingface_hub request.
|
||||
# Some HF calls (notably HfApi.list_models) issue requests without any timeout,
|
||||
# so a network issue reaching huggingface.co would otherwise hang indefinitely.
|
||||
HF_REQUEST_TIMEOUT = (10, 30)
|
||||
|
||||
|
||||
class _TimeoutHTTPSession(requests.Session):
|
||||
"""requests.Session that applies a default timeout to every request."""
|
||||
|
||||
def request(self, *args, **kwargs):
|
||||
kwargs.setdefault("timeout", HF_REQUEST_TIMEOUT)
|
||||
return super().request(*args, **kwargs)
|
||||
|
||||
|
||||
# Ensure all huggingface_hub HTTP requests fail fast on connection issues
|
||||
# instead of hanging forever. Calls that pass their own timeout are unaffected.
|
||||
huggingface_hub.configure_http_backend(backend_factory=_TimeoutHTTPSession)
|
||||
|
||||
BASE_TEMPLATE_PATH = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
"..",
|
||||
@@ -298,7 +317,42 @@ class ModelPrompt:
|
||||
return True
|
||||
return False
|
||||
|
||||
def query_hf_for_prompt_template_suggestion(self, model_name: str):
|
||||
def _download_hf_file(
|
||||
self, repo_id: str, filename: str, revision: str
|
||||
) -> str | None:
|
||||
"""
|
||||
Download a single file from a HF repo into a temporary directory and
|
||||
return its contents. Returns None if the file is missing or cannot be
|
||||
retrieved (e.g. 404 or a connection issue).
|
||||
"""
|
||||
try:
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
path = huggingface_hub.hf_hub_download(
|
||||
repo_id=repo_id,
|
||||
filename=filename,
|
||||
cache_dir=tmpdir,
|
||||
revision=revision,
|
||||
)
|
||||
with open(path) as f:
|
||||
return f.read()
|
||||
except Exception as e:
|
||||
if not str(e).startswith("404"):
|
||||
log.error(
|
||||
"query_hf_for_prompt_template_suggestion",
|
||||
error=str(e),
|
||||
filename=filename,
|
||||
)
|
||||
return None
|
||||
|
||||
def _match_template_identifier(self, content: str) -> str | None:
|
||||
"""Return the template file name whose identifier matches content."""
|
||||
for identifier_cls in TEMPLATE_IDENTIFIERS:
|
||||
identifier = identifier_cls()
|
||||
if identifier(content):
|
||||
return f"{identifier.template_str}.jinja2"
|
||||
return None
|
||||
|
||||
def query_hf_for_prompt_template_suggestion(self, model_name: str) -> str | None:
|
||||
api = huggingface_hub.HfApi()
|
||||
|
||||
log.debug("query_hf_for_prompt_template_suggestion", model_name=model_name)
|
||||
@@ -314,81 +368,46 @@ class ModelPrompt:
|
||||
|
||||
branch_name = "main"
|
||||
|
||||
models = list(api.list_models(model_name=model_name))
|
||||
try:
|
||||
models = list(api.list_models(model_name=model_name))
|
||||
if not models and model_name_alt:
|
||||
models = list(api.list_models(model_name=model_name_alt))
|
||||
except Exception as e:
|
||||
log.warning(
|
||||
"query_hf_for_prompt_template_suggestion: "
|
||||
"could not reach HuggingFace to determine prompt template",
|
||||
model_name=model_name,
|
||||
error=str(e),
|
||||
)
|
||||
return None
|
||||
|
||||
if not models:
|
||||
if model_name_alt:
|
||||
models = list(api.list_models(model_name=model_name_alt))
|
||||
if not models:
|
||||
return None
|
||||
return None
|
||||
|
||||
model = models[0]
|
||||
|
||||
repo_id = f"{model.id}"
|
||||
|
||||
# check chat_template.jinja2
|
||||
try:
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
chat_template_path = huggingface_hub.hf_hub_download(
|
||||
repo_id=repo_id,
|
||||
filename="chat_template.jinja2",
|
||||
cache_dir=tmpdir,
|
||||
revision=branch_name,
|
||||
)
|
||||
if not chat_template_path:
|
||||
return None
|
||||
with open(chat_template_path) as f:
|
||||
chat_template = f.read()
|
||||
for identifer_cls in TEMPLATE_IDENTIFIERS:
|
||||
identifier = identifer_cls()
|
||||
if identifier(chat_template):
|
||||
return f"{identifier.template_str}.jinja2"
|
||||
except Exception as e:
|
||||
if not str(e).startswith("404"):
|
||||
log.error("query_hf_for_prompt_template_suggestion", error=str(e))
|
||||
# Files are checked in priority order; the first matching identifier wins.
|
||||
for filename in ("chat_template.jinja2", "README.md"):
|
||||
content = self._download_hf_file(repo_id, filename, branch_name)
|
||||
if content:
|
||||
match = self._match_template_identifier(content)
|
||||
if match:
|
||||
return match
|
||||
|
||||
# Check README.md
|
||||
try:
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
readme_path = huggingface_hub.hf_hub_download(
|
||||
repo_id=repo_id,
|
||||
filename="README.md",
|
||||
cache_dir=tmpdir,
|
||||
revision=branch_name,
|
||||
)
|
||||
if not readme_path:
|
||||
return None
|
||||
with open(readme_path) as f:
|
||||
readme = f.read()
|
||||
for identifer_cls in TEMPLATE_IDENTIFIERS:
|
||||
identifier = identifer_cls()
|
||||
if identifier(readme):
|
||||
return f"{identifier.template_str}.jinja2"
|
||||
except Exception as e:
|
||||
if not str(e).startswith("404"):
|
||||
log.error("query_hf_for_prompt_template_suggestion", error=str(e))
|
||||
|
||||
try:
|
||||
# Check tokenizer_config.json
|
||||
# "chat_template" key
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
config_path = huggingface_hub.hf_hub_download(
|
||||
repo_id=repo_id,
|
||||
filename="tokenizer_config.json",
|
||||
cache_dir=tmpdir,
|
||||
revision=branch_name,
|
||||
)
|
||||
if not config_path:
|
||||
return None
|
||||
with open(config_path) as f:
|
||||
config = json.load(f)
|
||||
for identifer_cls in TEMPLATE_IDENTIFIERS:
|
||||
identifier = identifer_cls()
|
||||
if identifier(config.get("chat_template", "")):
|
||||
return f"{identifier.template_str}.jinja2"
|
||||
except Exception as e:
|
||||
if not str(e).startswith("404"):
|
||||
log.error("query_hf_for_prompt_template_suggestion", error=str(e))
|
||||
# tokenizer_config.json stores the chat template under a "chat_template" key
|
||||
config_content = self._download_hf_file(
|
||||
repo_id, "tokenizer_config.json", branch_name
|
||||
)
|
||||
if config_content:
|
||||
try:
|
||||
config = json.loads(config_content)
|
||||
except json.JSONDecodeError:
|
||||
config = {}
|
||||
match = self._match_template_identifier(config.get("chat_template", ""))
|
||||
if match:
|
||||
return match
|
||||
|
||||
|
||||
model_prompt = ModelPrompt()
|
||||
|
||||
Reference in New Issue
Block a user