From 7739f17594b20fc100dbbb0283be7b550cb0a2de Mon Sep 17 00:00:00 2001 From: QihuAI Design Date: Wed, 15 Jul 2026 22:04:22 +0800 Subject: [PATCH] =?UTF-8?q?fix(wechat-bot):=20=E4=BF=AE=E5=A4=8D=20AGNES?= =?UTF-8?q?=20=E5=AA=92=E4=BD=93=E7=94=9F=E6=88=90=E5=A4=B1=E8=B4=A5?= =?UTF-8?q?=E2=80=94=E2=80=94=E4=BC=98=E5=8C=96=E6=AD=A5=E9=AA=A4=E7=9B=B8?= =?UTF-8?q?=E5=AF=B9=E5=AF=BC=E5=85=A5=E8=B7=AF=E5=BE=84=E9=94=99=E8=AF=AF?= =?UTF-8?q?(=E4=BB=8E=E6=9C=AA=E7=94=9F=E6=95=88)+=20=E5=9B=BD=E5=86=85?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E7=9B=B4=E8=BF=9E=20+=20=E5=86=85=E5=AE=B9?= =?UTF-8?q?=E7=AD=96=E7=95=A5=E9=87=8D=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/python/app/api/chat.py | 50 +++++++++++++++++++++++++++----- backend/python/app/api/im_llm.py | 11 +++++-- 2 files changed, 52 insertions(+), 9 deletions(-) diff --git a/backend/python/app/api/chat.py b/backend/python/app/api/chat.py index 55cb612..9e23cd4 100644 --- a/backend/python/app/api/chat.py +++ b/backend/python/app/api/chat.py @@ -156,6 +156,7 @@ def _proxy_for_url(url: str) -> str | None: 注意:httpx 默认 trust_env=True 会忽略 proxy 参数而改用环境变量 HTTP_PROXY, 故调用方必须同时传 trust_env=False 才能真正走此处指定的代理。 """ + import os from urllib.parse import urlparse host = (urlparse(url).hostname or "").lower() if "agnes-ai.com" in host or "agnes-ai.space" in host: @@ -163,6 +164,12 @@ def _proxy_for_url(url: str) -> str | None: # 平台自有域名(含自建 Dify/n8n 等)必须直连,不经出网代理。 if "qihuyun.com" in host: return None + # 尊重 NO_PROXY:国内域名(硅基流动/DeepSeek/小米MoE 等)直连更稳定, + # 避免经易波动的智能路由代理 172.18.0.1:7890(对国内域名偶发连接重置)。 + _no_proxy = [h.strip().lower() for h in os.environ.get("NO_PROXY", "").split(",") if h.strip()] + for _np in _no_proxy: + if host == _np or host.endswith("." + _np) or _np in host: + return None return _outbound_proxy() @@ -186,7 +193,7 @@ async def _optimize_media_prompt(raw_prompt: str, media_type: str, db=None) -> s return stripped # 用文本 LLM 做翻译增强 try: - from .capability_registry import resolve_auto_model as _resolve_auto + from ..capability_registry import resolve_auto_model as _resolve_auto from ..models import ModelProvider as MP from ..database import SessionLocal as SL import httpx as _httpx @@ -224,7 +231,11 @@ async def _optimize_media_prompt(raw_prompt: str, media_type: str, db=None) -> s {"role": "system", "content": "You are a professional prompt engineer. Output ONLY the final prompt text, no explanations."}, {"role": "user", "content": prompt_zh}, ] - body = {"model": text_model.name, "messages": messages, "max_tokens": 512, "temperature": 0.7} + # max_tokens 调大到 1024:mimo 等推理模型会把 token 消耗在思考链(reasoning_content) + # 上,512 时 content 常被截断为空;并关闭推理(enable_thinking=False),让模型直接输出提示词。 + # 对不支持该参数的模型至多优化失败 → 下方 except 兜底退化为原始提示词,不阻断生成。 + body = {"model": text_model.name, "messages": messages, "max_tokens": 1024, + "temperature": 0.7, "enable_thinking": False} async with _httpx.AsyncClient( timeout=30.0, proxy=_proxy_for_url(api_url), trust_env=False ) as client: @@ -309,7 +320,11 @@ async def _generate_media(db, ai_model, prompt: str, user_id: str = None) -> str if not api_url: return "⚠️ 该图像模型未配置 Endpoint。" # 先优化 prompt(用户原始输入含多轮对话噪音 → 高质量图像生成提示词) - optimized_prompt = await _optimize_media_prompt(prompt, "image", db) + # 优化失败绝不中断生成,退化为原始提示词 + try: + optimized_prompt = await _optimize_media_prompt(prompt, "image", db) + except Exception: + optimized_prompt = prompt payload = { "model": ai_model.name, "prompt": optimized_prompt, @@ -320,7 +335,16 @@ async def _generate_media(db, ai_model, prompt: str, user_id: str = None) -> str # 3 次重试后仍失败 → AGNES 侧持续抖动,提示稍后重试而非甩原始错误 return _friendly_media_err("图像", err) if resp.status_code != 200: - return _friendly_media_err("图像", f"HTTP {resp.status_code}: {resp.text[:200]}") + # AGNES 内容策略拒绝:用原始(未优化)提示词重试一次,避免优化后提示词触发误杀 + if resp.status_code == 400 and "content_policy" in (resp.text or "").lower(): + payload["prompt"] = prompt + resp2, _ = await _agnes_post_json(api_url, headers, payload, timeout) + if resp2 is not None and resp2.status_code == 200: + resp = resp2 + else: + return "⚠️ 图像生成被内容策略拒绝,请换一个更中性的描述后重试。" + else: + return _friendly_media_err("图像", f"HTTP {resp.status_code}: {resp.text[:200]}") try: data = resp.json() except Exception: @@ -344,8 +368,11 @@ async def _generate_media(db, ai_model, prompt: str, user_id: str = None) -> str api_url = f"{provider.api_base_url.rstrip('/')}/v1/videos" if provider else "" if not api_url: return "⚠️ 该视频模型未配置 Endpoint。" - # 优化 prompt(用户输入含多轮对话噪音 → 高质量视频提示词) - optimized_prompt = await _optimize_media_prompt(prompt, "video", db) + # 优化 prompt(用户输入含多轮对话噪音 → 高质量视频提示词);失败退化为原始提示词 + try: + optimized_prompt = await _optimize_media_prompt(prompt, "video", db) + except Exception: + optimized_prompt = prompt base = api_url.split("/v1")[0] if "/v1" in api_url else api_url payload = { "model": ai_model.name, @@ -359,7 +386,16 @@ async def _generate_media(db, ai_model, prompt: str, user_id: str = None) -> str if resp is None: return _friendly_media_err("视频", err) if resp.status_code != 200: - return _friendly_media_err("视频", f"HTTP {resp.status_code}: {resp.text[:200]}") + # AGNES 内容策略拒绝:用原始(未优化)提示词重试一次,避免优化后提示词触发误杀 + if resp.status_code == 400 and "content_policy" in (resp.text or "").lower(): + payload["prompt"] = prompt + resp2, _ = await _agnes_post_json(api_url, headers, payload, timeout) + if resp2 is not None and resp2.status_code == 200: + resp = resp2 + else: + return "⚠️ 视频生成被内容策略拒绝,请换一个更中性的描述后重试。" + else: + return _friendly_media_err("视频", f"HTTP {resp.status_code}: {resp.text[:200]}") try: data = resp.json() except Exception: diff --git a/backend/python/app/api/im_llm.py b/backend/python/app/api/im_llm.py index 15b34f6..f4f6d3e 100644 --- a/backend/python/app/api/im_llm.py +++ b/backend/python/app/api/im_llm.py @@ -33,6 +33,7 @@ def _proxy_for_url(url: str) -> str | None: """按目标域名选择是否走代理(与 chat.py 同逻辑)。 2026-07-15 调整:AGNES 改走香港代理(实测延迟更低),不再直连。 + 国内域名(尊重 NO_PROXY)直连,更稳定。 """ import os host = (_urlparse(url).hostname or "").lower() @@ -40,6 +41,10 @@ def _proxy_for_url(url: str) -> str | None: return _HK_PROXY if "qihuyun.com" in host: return None + _no_proxy = [h.strip().lower() for h in os.environ.get("NO_PROXY", "").split(",") if h.strip()] + for _np in _no_proxy: + if host == _np or host.endswith("." + _np) or _np in host: + return None return os.environ.get("HTTPS_PROXY") or os.environ.get("HTTP_PROXY") or "http://172.18.0.1:7890" @@ -78,8 +83,10 @@ def _call_real_llm(db: Session, user=None, prompt: str = "", platform: str = "im if ai_model: try: result = _run_async(_generate_media(db, ai_model, prompt, user_id)) - except Exception as e: - result = f"(媒体生成失败:{e})" + except Exception: + # 任何未预期异常(如 AGNES 连接重置)统一兜底为友好提示, + # 绝不直接把原始异常(如 "Server disconnected")甩给用户 + result = "⚠️ 媒体生成暂时失败(连接或服务波动),请稍后重试。" if result: # IM 不渲染 HTML,把 /