From 23be60852107044e30504a9ce2b90466ec242f05 Mon Sep 17 00:00:00 2001 From: dekun Date: Sun, 28 Jun 2026 15:42:28 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B8=85=E7=90=86=20Ollama/API=20URL=20?= =?UTF-8?q?=E4=B8=AD=E7=9A=84=E7=BB=88=E7=AB=AF=E6=8E=A7=E5=88=B6=E5=AD=97?= =?UTF-8?q?=E7=AC=A6=EF=BC=8C=E4=BF=AE=E5=A4=8D=20AI=20=E8=B0=83=E7=94=A8?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/routers/admin.py | 9 +++++---- backend/app/services/llm.py | 17 +++++++++-------- backend/app/services/url_sanitize.py | 24 ++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 12 deletions(-) create mode 100644 backend/app/services/url_sanitize.py diff --git a/backend/app/routers/admin.py b/backend/app/routers/admin.py index f77eec2..990bdb5 100644 --- a/backend/app/routers/admin.py +++ b/backend/app/routers/admin.py @@ -18,6 +18,7 @@ from app.schemas import ( SystemSettingsOut, SystemSettingsUpdate, ) +from app.services.url_sanitize import sanitize_http_url, sanitize_model_name router = APIRouter(prefix="/admin", tags=["admin"]) @@ -66,13 +67,13 @@ def update_settings( if data.ai_provider is not None: row.ai_provider = data.ai_provider.value if data.ollama_base_url is not None: - row.ollama_base_url = data.ollama_base_url or None + row.ollama_base_url = sanitize_http_url(data.ollama_base_url) or None if data.ollama_model is not None: - row.ollama_model = data.ollama_model or None + row.ollama_model = sanitize_model_name(data.ollama_model) or None if data.openai_base_url is not None: - row.openai_base_url = data.openai_base_url or None + row.openai_base_url = sanitize_http_url(data.openai_base_url) or None if data.openai_model is not None: - row.openai_model = data.openai_model or None + row.openai_model = sanitize_model_name(data.openai_model) or None if data.openai_api_key is not None and data.openai_api_key.strip(): row.openai_api_key = data.openai_api_key.strip() if data.ocr_service_url is not None: diff --git a/backend/app/services/llm.py b/backend/app/services/llm.py index 723d5d5..10c2434 100644 --- a/backend/app/services/llm.py +++ b/backend/app/services/llm.py @@ -4,6 +4,7 @@ from sqlalchemy.orm import Session from app.core.config import settings as app_settings from app.models.user import SchoolLevel, SystemSettings from app.services.school_level import school_level_label +from app.services.url_sanitize import sanitize_http_url, sanitize_model_name CURRICULUM_JUNIOR = """初中课程标准:代数、几何(全等/相似/勾股)、一次函数与简单二次函数、基础概率统计。 严禁使用:高中导数、向量、解析几何、排列组合进阶、复数、微积分、大学线性代数等。""" @@ -112,18 +113,18 @@ def load_ai_config(db: Session) -> AIConfig: if row is None: return AIConfig( provider="ollama", - ollama_base_url=app_settings.OLLAMA_BASE_URL, - ollama_model=app_settings.OLLAMA_MODEL, - openai_base_url=app_settings.OPENAI_BASE_URL, - openai_model=app_settings.OPENAI_MODEL, + ollama_base_url=sanitize_http_url(app_settings.OLLAMA_BASE_URL), + ollama_model=sanitize_model_name(app_settings.OLLAMA_MODEL), + openai_base_url=sanitize_http_url(app_settings.OPENAI_BASE_URL), + openai_model=sanitize_model_name(app_settings.OPENAI_MODEL), openai_api_key=None, ) return AIConfig( provider=row.ai_provider or "ollama", - ollama_base_url=row.ollama_base_url or app_settings.OLLAMA_BASE_URL, - ollama_model=row.ollama_model or app_settings.OLLAMA_MODEL, - openai_base_url=row.openai_base_url or app_settings.OPENAI_BASE_URL, - openai_model=row.openai_model or app_settings.OPENAI_MODEL, + ollama_base_url=sanitize_http_url(row.ollama_base_url or app_settings.OLLAMA_BASE_URL), + ollama_model=sanitize_model_name(row.ollama_model or app_settings.OLLAMA_MODEL), + openai_base_url=sanitize_http_url(row.openai_base_url or app_settings.OPENAI_BASE_URL), + openai_model=sanitize_model_name(row.openai_model or app_settings.OPENAI_MODEL), openai_api_key=row.openai_api_key, ) diff --git a/backend/app/services/url_sanitize.py b/backend/app/services/url_sanitize.py new file mode 100644 index 0000000..b27e029 --- /dev/null +++ b/backend/app/services/url_sanitize.py @@ -0,0 +1,24 @@ +import re + +# ANSI 颜色/光标控制序列(粘贴终端输出时常见) +_ANSI_ESCAPE = re.compile(r"\x1b\[[0-9;?]*[ -/]*[@-~]|\x1b\][^\x07]*\x07|\x1b[@-Z\\-_]") + +# 其它不可见控制字符(保留普通 URL 字符) +_CTRL_CHARS = re.compile(r"[\x00-\x1f\x7f]") + + +def sanitize_http_url(url: str | None) -> str: + """去掉 URL 中的 ANSI/控制字符,避免 httpx Invalid non-printable ASCII character。""" + if not url: + return "" + cleaned = _ANSI_ESCAPE.sub("", url) + cleaned = _CTRL_CHARS.sub("", cleaned) + return cleaned.strip() + + +def sanitize_model_name(name: str | None) -> str: + if not name: + return "" + cleaned = _ANSI_ESCAPE.sub("", name) + cleaned = _CTRL_CHARS.sub("", cleaned) + return cleaned.strip()