fix: 修正 Hermite 同步逻辑与模型优先级配置
This commit is contained in:
@@ -60,12 +60,15 @@ def sync_hermes_model_settings(
|
|||||||
target_path.parent.mkdir(parents=True, exist_ok=True)
|
target_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
config = _load_existing_config(target_path)
|
config = _load_existing_config(target_path)
|
||||||
config["model"] = _build_primary_model_config(primary_route)
|
config["model"] = _build_primary_model_config(primary_route, existing_model_config=config.get("model"))
|
||||||
|
|
||||||
if fallback_route is None:
|
if fallback_route is None:
|
||||||
config.pop("fallback_model", None)
|
config.pop("fallback_model", None)
|
||||||
else:
|
else:
|
||||||
config["fallback_model"] = _build_fallback_model_config(fallback_route)
|
config["fallback_model"] = _build_fallback_model_config(
|
||||||
|
fallback_route,
|
||||||
|
existing_fallback_config=config.get("fallback_model"),
|
||||||
|
)
|
||||||
|
|
||||||
_atomic_yaml_write(target_path, config)
|
_atomic_yaml_write(target_path, config)
|
||||||
return target_path
|
return target_path
|
||||||
@@ -87,7 +90,11 @@ def _load_existing_config(config_path: Path) -> dict[str, Any]:
|
|||||||
return dict(loaded)
|
return dict(loaded)
|
||||||
|
|
||||||
|
|
||||||
def _build_primary_model_config(route: HermesModelRoute) -> dict[str, Any]:
|
def _build_primary_model_config(
|
||||||
|
route: HermesModelRoute,
|
||||||
|
*,
|
||||||
|
existing_model_config: Any = None,
|
||||||
|
) -> dict[str, Any]:
|
||||||
normalized_model = route.model.strip()
|
normalized_model = route.model.strip()
|
||||||
normalized_endpoint = route.endpoint.strip().rstrip("/")
|
normalized_endpoint = route.endpoint.strip().rstrip("/")
|
||||||
if not normalized_model or not normalized_endpoint:
|
if not normalized_model or not normalized_endpoint:
|
||||||
@@ -99,10 +106,11 @@ def _build_primary_model_config(route: HermesModelRoute) -> dict[str, Any]:
|
|||||||
"default": normalized_model,
|
"default": normalized_model,
|
||||||
"base_url": normalized_endpoint,
|
"base_url": normalized_endpoint,
|
||||||
}
|
}
|
||||||
|
existing_api_key = _extract_existing_api_key(existing_model_config)
|
||||||
if route.api_key.strip():
|
if route.api_key.strip():
|
||||||
payload["api_key"] = route.api_key.strip()
|
payload["api_key"] = route.api_key.strip()
|
||||||
else:
|
elif existing_api_key:
|
||||||
payload.pop("api_key", None)
|
payload["api_key"] = existing_api_key
|
||||||
if api_mode != "chat_completions":
|
if api_mode != "chat_completions":
|
||||||
payload["api_mode"] = api_mode
|
payload["api_mode"] = api_mode
|
||||||
else:
|
else:
|
||||||
@@ -110,7 +118,11 @@ def _build_primary_model_config(route: HermesModelRoute) -> dict[str, Any]:
|
|||||||
return payload
|
return payload
|
||||||
|
|
||||||
|
|
||||||
def _build_fallback_model_config(route: HermesModelRoute) -> dict[str, Any]:
|
def _build_fallback_model_config(
|
||||||
|
route: HermesModelRoute,
|
||||||
|
*,
|
||||||
|
existing_fallback_config: Any = None,
|
||||||
|
) -> dict[str, Any]:
|
||||||
normalized_model = route.model.strip()
|
normalized_model = route.model.strip()
|
||||||
normalized_endpoint = route.endpoint.strip().rstrip("/")
|
normalized_endpoint = route.endpoint.strip().rstrip("/")
|
||||||
if not normalized_model or not normalized_endpoint:
|
if not normalized_model or not normalized_endpoint:
|
||||||
@@ -122,10 +134,11 @@ def _build_fallback_model_config(route: HermesModelRoute) -> dict[str, Any]:
|
|||||||
"model": normalized_model,
|
"model": normalized_model,
|
||||||
"base_url": normalized_endpoint,
|
"base_url": normalized_endpoint,
|
||||||
}
|
}
|
||||||
|
existing_api_key = _extract_existing_api_key(existing_fallback_config)
|
||||||
if route.api_key.strip():
|
if route.api_key.strip():
|
||||||
payload["api_key"] = route.api_key.strip()
|
payload["api_key"] = route.api_key.strip()
|
||||||
else:
|
elif existing_api_key:
|
||||||
payload.pop("api_key", None)
|
payload["api_key"] = existing_api_key
|
||||||
if api_mode != "chat_completions":
|
if api_mode != "chat_completions":
|
||||||
payload["api_mode"] = api_mode
|
payload["api_mode"] = api_mode
|
||||||
else:
|
else:
|
||||||
@@ -133,6 +146,15 @@ def _build_fallback_model_config(route: HermesModelRoute) -> dict[str, Any]:
|
|||||||
return payload
|
return payload
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_existing_api_key(config_section: Any) -> str:
|
||||||
|
if not isinstance(config_section, dict):
|
||||||
|
return ""
|
||||||
|
api_key = config_section.get("api_key")
|
||||||
|
if not isinstance(api_key, str):
|
||||||
|
return ""
|
||||||
|
return api_key.strip()
|
||||||
|
|
||||||
|
|
||||||
def _infer_api_mode(route: HermesModelRoute) -> str:
|
def _infer_api_mode(route: HermesModelRoute) -> str:
|
||||||
provider_label = route.provider_label.strip().casefold()
|
provider_label = route.provider_label.strip().casefold()
|
||||||
endpoint = route.endpoint.strip().lower().rstrip("/")
|
endpoint = route.endpoint.strip().lower().rstrip("/")
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
@@ -23,7 +24,9 @@ from app.services.hermes_sync import (
|
|||||||
restore_hermes_config_snapshot,
|
restore_hermes_config_snapshot,
|
||||||
sync_hermes_model_settings,
|
sync_hermes_model_settings,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, slots=True)
|
@dataclass(frozen=True, slots=True)
|
||||||
class ModelSlotConfig:
|
class ModelSlotConfig:
|
||||||
@@ -314,14 +317,14 @@ class SettingsService:
|
|||||||
def load_saved_model_api_key(self, slot: str | None) -> str:
|
def load_saved_model_api_key(self, slot: str | None) -> str:
|
||||||
if not slot or slot not in MODEL_SLOT_CONFIGS:
|
if not slot or slot not in MODEL_SLOT_CONFIGS:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
settings_row, secrets_row = self.ensure_settings_ready()
|
|
||||||
model_rows = self.ensure_model_settings_ready(settings_row, secrets_row)
|
|
||||||
encrypted_value = model_rows[slot].api_key_encrypted
|
|
||||||
if not encrypted_value:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
return decrypt_secret(encrypted_value)
|
settings_row, secrets_row = self.ensure_settings_ready()
|
||||||
|
model_rows = self.ensure_model_settings_ready(settings_row, secrets_row)
|
||||||
|
encrypted_value = model_rows[slot].api_key_encrypted
|
||||||
|
if not encrypted_value:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
return self._decrypt_model_api_key(encrypted_value, slot=slot)
|
||||||
|
|
||||||
def get_runtime_model_config(self, slot: str) -> dict[str, str]:
|
def get_runtime_model_config(self, slot: str) -> dict[str, str]:
|
||||||
if slot not in MODEL_SLOT_CONFIGS:
|
if slot not in MODEL_SLOT_CONFIGS:
|
||||||
@@ -489,9 +492,7 @@ class SettingsService:
|
|||||||
model_row.api_key_encrypted = encrypt_secret(normalized_api_key)
|
model_row.api_key_encrypted = encrypt_secret(normalized_api_key)
|
||||||
|
|
||||||
def _build_hermes_model_route(self, model_row: SystemModelSetting) -> HermesModelRoute:
|
def _build_hermes_model_route(self, model_row: SystemModelSetting) -> HermesModelRoute:
|
||||||
api_key = ""
|
api_key = self._decrypt_model_api_key(model_row.api_key_encrypted, slot=model_row.slot)
|
||||||
if model_row.api_key_encrypted:
|
|
||||||
api_key = decrypt_secret(model_row.api_key_encrypted)
|
|
||||||
|
|
||||||
return HermesModelRoute(
|
return HermesModelRoute(
|
||||||
provider_label=str(model_row.provider or "").strip(),
|
provider_label=str(model_row.provider or "").strip(),
|
||||||
@@ -500,6 +501,16 @@ class SettingsService:
|
|||||||
api_key=api_key,
|
api_key=api_key,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _decrypt_model_api_key(self, encrypted_value: str, *, slot: str) -> str:
|
||||||
|
normalized_value = str(encrypted_value or "").strip()
|
||||||
|
if not normalized_value:
|
||||||
|
return ""
|
||||||
|
try:
|
||||||
|
return decrypt_secret(normalized_value)
|
||||||
|
except ValueError:
|
||||||
|
logger.warning("Skipping undecryptable model API key for slot=%s", slot)
|
||||||
|
return ""
|
||||||
|
|
||||||
def _ensure_settings_schema(self) -> None:
|
def _ensure_settings_schema(self) -> None:
|
||||||
bind = self.db.get_bind()
|
bind = self.db.get_bind()
|
||||||
inspector = inspect(bind)
|
inspector = inspect(bind)
|
||||||
@@ -511,7 +522,7 @@ class SettingsService:
|
|||||||
settings_columns = {column["name"] for column in inspector.get_columns("system_settings")}
|
settings_columns = {column["name"] for column in inspector.get_columns("system_settings")}
|
||||||
if "onlyoffice_enabled" not in settings_columns:
|
if "onlyoffice_enabled" not in settings_columns:
|
||||||
migration_statements.append(
|
migration_statements.append(
|
||||||
"ALTER TABLE system_settings ADD COLUMN onlyoffice_enabled BOOLEAN DEFAULT 0"
|
"ALTER TABLE system_settings ADD COLUMN onlyoffice_enabled BOOLEAN DEFAULT FALSE"
|
||||||
)
|
)
|
||||||
if "onlyoffice_public_url" not in settings_columns:
|
if "onlyoffice_public_url" not in settings_columns:
|
||||||
migration_statements.append(
|
migration_statements.append(
|
||||||
|
|||||||
Reference in New Issue
Block a user