fix(wechat-bot): 修复 AGNES 媒体生成失败——优化步骤相对导入路径错误(从未生效)+ 国内模型直连 + 内容策略重试
This commit is contained in:
1 parent
fd2011b93e
commit
7739f17594
2 files changed
+52
-9
No files matched your search
@@ -156,6 +156,7 @@ def _proxy_for_url(url: str) -> str | None:
|
|||||||
注意:httpx 默认 trust_env=True 会忽略 proxy 参数而改用环境变量 HTTP_PROXY,
|
注意:httpx 默认 trust_env=True 会忽略 proxy 参数而改用环境变量 HTTP_PROXY,
|
||||||
故调用方必须同时传 trust_env=False 才能真正走此处指定的代理。
|
故调用方必须同时传 trust_env=False 才能真正走此处指定的代理。
|
||||||
"""
|
"""
|
||||||
|
import os
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
host = (urlparse(url).hostname or "").lower()
|
host = (urlparse(url).hostname or "").lower()
|
||||||
if "agnes-ai.com" in host or "agnes-ai.space" in host:
|
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 等)必须直连,不经出网代理。
|
# 平台自有域名(含自建 Dify/n8n 等)必须直连,不经出网代理。
|
||||||
if "qihuyun.com" in host:
|
if "qihuyun.com" in host:
|
||||||
return None
|
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()
|
return _outbound_proxy()
|
||||||
|
|
||||||
|
|
||||||
@@ -186,7 +193,7 @@ async def _optimize_media_prompt(raw_prompt: str, media_type: str, db=None) -> s
|
|||||||
return stripped
|
return stripped
|
||||||
# 用文本 LLM 做翻译增强
|
# 用文本 LLM 做翻译增强
|
||||||
try:
|
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 ..models import ModelProvider as MP
|
||||||
from ..database import SessionLocal as SL
|
from ..database import SessionLocal as SL
|
||||||
import httpx as _httpx
|
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": "system", "content": "You are a professional prompt engineer. Output ONLY the final prompt text, no explanations."},
|
||||||
{"role": "user", "content": prompt_zh},
|
{"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(
|
async with _httpx.AsyncClient(
|
||||||
timeout=30.0, proxy=_proxy_for_url(api_url), trust_env=False
|
timeout=30.0, proxy=_proxy_for_url(api_url), trust_env=False
|
||||||
) as client:
|
) as client:
|
||||||
@@ -309,7 +320,11 @@ async def _generate_media(db, ai_model, prompt: str, user_id: str = None) -> str
|
|||||||
if not api_url:
|
if not api_url:
|
||||||
return "⚠️ 该图像模型未配置 Endpoint。"
|
return "⚠️ 该图像模型未配置 Endpoint。"
|
||||||
# 先优化 prompt(用户原始输入含多轮对话噪音 → 高质量图像生成提示词)
|
# 先优化 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 = {
|
payload = {
|
||||||
"model": ai_model.name,
|
"model": ai_model.name,
|
||||||
"prompt": optimized_prompt,
|
"prompt": optimized_prompt,
|
||||||
@@ -320,7 +335,16 @@ async def _generate_media(db, ai_model, prompt: str, user_id: str = None) -> str
|
|||||||
# 3 次重试后仍失败 → AGNES 侧持续抖动,提示稍后重试而非甩原始错误
|
# 3 次重试后仍失败 → AGNES 侧持续抖动,提示稍后重试而非甩原始错误
|
||||||
return _friendly_media_err("图像", err)
|
return _friendly_media_err("图像", err)
|
||||||
if resp.status_code != 200:
|
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:
|
try:
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
except Exception:
|
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 ""
|
api_url = f"{provider.api_base_url.rstrip('/')}/v1/videos" if provider else ""
|
||||||
if not api_url:
|
if not api_url:
|
||||||
return "⚠️ 该视频模型未配置 Endpoint。"
|
return "⚠️ 该视频模型未配置 Endpoint。"
|
||||||
# 优化 prompt(用户输入含多轮对话噪音 → 高质量视频提示词)
|
# 优化 prompt(用户输入含多轮对话噪音 → 高质量视频提示词);失败退化为原始提示词
|
||||||
optimized_prompt = await _optimize_media_prompt(prompt, "video", db)
|
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
|
base = api_url.split("/v1")[0] if "/v1" in api_url else api_url
|
||||||
payload = {
|
payload = {
|
||||||
"model": ai_model.name,
|
"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:
|
if resp is None:
|
||||||
return _friendly_media_err("视频", err)
|
return _friendly_media_err("视频", err)
|
||||||
if resp.status_code != 200:
|
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:
|
try:
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ def _proxy_for_url(url: str) -> str | None:
|
|||||||
"""按目标域名选择是否走代理(与 chat.py 同逻辑)。
|
"""按目标域名选择是否走代理(与 chat.py 同逻辑)。
|
||||||
|
|
||||||
2026-07-15 调整:AGNES 改走香港代理(实测延迟更低),不再直连。
|
2026-07-15 调整:AGNES 改走香港代理(实测延迟更低),不再直连。
|
||||||
|
国内域名(尊重 NO_PROXY)直连,更稳定。
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
host = (_urlparse(url).hostname or "").lower()
|
host = (_urlparse(url).hostname or "").lower()
|
||||||
@@ -40,6 +41,10 @@ def _proxy_for_url(url: str) -> str | None:
|
|||||||
return _HK_PROXY
|
return _HK_PROXY
|
||||||
if "qihuyun.com" in host:
|
if "qihuyun.com" in host:
|
||||||
return None
|
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"
|
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:
|
if ai_model:
|
||||||
try:
|
try:
|
||||||
result = _run_async(_generate_media(db, ai_model, prompt, user_id))
|
result = _run_async(_generate_media(db, ai_model, prompt, user_id))
|
||||||
except Exception as e:
|
except Exception:
|
||||||
result = f"(媒体生成失败:{e})"
|
# 任何未预期异常(如 AGNES 连接重置)统一兜底为友好提示,
|
||||||
|
# 绝不直接把原始异常(如 "Server disconnected")甩给用户
|
||||||
|
result = "⚠️ 媒体生成暂时失败(连接或服务波动),请稍后重试。"
|
||||||
if result:
|
if result:
|
||||||
# IM 不渲染 HTML,把 <img>/<video> 还原为纯文本链接返回
|
# IM 不渲染 HTML,把 <img>/<video> 还原为纯文本链接返回
|
||||||
s = result.strip()
|
s = result.strip()
|
||||||
|
|||||||
Reference in new issue
Block a user