fix: 全面修复6项问题——Auto模型路由/媒体提示词优化/微信Bot/Token驾驶舱/技能市场清空

1. resolve_auto_model: image/video/audio无显式默认时自动发现启用模型(原返回None→文本模型回退→AI说无法生成)
2. 新增_optimize_media_prompt: 调用文本LLM精炼用户原始输入为AGNES高质量提示词(去除对话噪音+英文优化)
3. detect_media_intent: 扩展关键词覆盖多轮对话模式(做图片/生成个图/画一下/制作视频等)
4. chat.py+im_llm.py: 所有httpx调用补trust_env=False+_proxy_for_url, 规避容器坏代理
5. token_usage.py: 模型名回退链(model表→日志自带的model_name→auto标签), 不再显示unknown
6. skills_admin.py: 新增POST /batch-delete批量删除API(ids空则删全部)
7. 技能市场数据已清空, 用户可重新导入技能
This commit is contained in:
deploy committed 2026-07-15 17:05:00 +08:00
1 parent 4d57987aa5
commit 148e4a21c2
5 files changed
+210 -16

No files matched your search

+100 -8
View File
@@ -48,14 +48,21 @@ def detect_media_intent(text: str) -> str | None:
if t.endswith(("?", "?")) or any(q in t for q in ("吗", "呢", "怎么", "如何", "能否", "是否可以", "可以吗", "会不会", "why", "how")):
return None
video_kw = ("视频", "短片", "影片", "vide", "动画视频", "一段视频", "做个视频", "生成视频",
"视频生成", "来段视频", "拍个视频", "做段视频", "视频剪辑")
"视频生成", "来段视频", "拍个视频", "做段视频", "视频剪辑", "做视频",
"生成一个视频", "制作一个视频", "帮我做视频", "制作视频",
"生成短片", "做短片", "制作短片", "一段短片")
image_kw = ("图片", "图像", "插画", "海报", "照片", "头像", "壁纸", "漫画", "表情包", "logo", "图标",
"imag", "画一张", "画张", "画一只", "画只", "画个", "画幅",
"来张", "来一张", "来幅", "发张图", "发个图",
"生成图片", "生成一张", "生成一幅", "生成图", "图像生成",
"做一张", "做张图", "做张", "出张图", "出一张图",
"帮我画", "给我画", "请画", "绘制", "作画", "ai绘画", "ai画图",
"画个图", "画张图", "画一幅图", "一张图", "一幅画", "生成一幅")
"画个图", "画张图", "画一幅图", "一张图", "一幅画", "生成一幅",
"画画", "画一下", "画出来", "生成个图", "生成一张图", "生成个图片",
"做图片", "做个图", "制图", "整张图", "整张图出来", "搞张图",
"帮我生成图片", "帮我生成图像", "给我生成图片", "给我生成图像",
"生成一张图片", "生成一个图片", "制作一张图", "制作一张图片",
"来一张图片", "画一张图片", "画一幅", "画一副")
audio_kw = ("语音", "配音", "朗读", "播报", "念一段", "有声", "tts", "音频", "生成语音",
"读一下", "念一下", "语音合成")
if any(k in t for k in video_kw):
@@ -152,6 +159,86 @@ def _proxy_for_url(url: str) -> str | None:
return _outbound_proxy()
async def _optimize_media_prompt(raw_prompt: str, media_type: str, db=None) -> str:
"""使用平台文本模型对用户原始输入做「媒体提示词增强优化」。
AGNES 图像/视频模型对 prompt 质量高度敏感:
- 用户在多轮对话中说"帮我生成一张刚才讨论的产品图"包含大量会话噪音
- AGNES 需要的是**详细的、描述性的、英文优先或中英混合**的视觉提示词
- 缺乏构图/风格/光影/质量等指导词时,生成结果与预期差异巨大
本函数把用户原文交给文本 LLM 精炼为符合 AGNES 预期的优质 prompt。
失败时回退到原始输入(不做额外引导,保证必达)。
"""
if not raw_prompt or len(raw_prompt.strip()) < 2:
return raw_prompt
# 如果原始输入已经很长(>200 字)且英文为主,可能本身就是一个不错的 prompt,直接返回
stripped = raw_prompt.strip()
ascii_chars = sum(1 for c in stripped if ord(c) < 128)
if len(stripped) > 200 and ascii_chars / max(len(stripped), 1) > 0.6:
return stripped
# 用文本 LLM 做翻译增强
try:
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
# 独立数据库会话,避免与主流程竞争
_db = db
_should_close = False
if _db is None:
_db = SL()
_should_close = True
try:
text_model = _resolve_auto("text", _db)
if not text_model:
return stripped
provider = _db.query(MP).filter(MP.id == text_model.provider_id).first()
api_key = text_model.api_key or (provider.api_key if provider else None)
api_url = text_model.endpoint
if not api_url and provider and provider.api_base_url:
api_url = f"{provider.api_base_url.rstrip('/')}/v1/chat/completions"
if not (api_key and api_url):
return stripped
prompt_zh = (
"你是一个专业的AI图像/视频提示词工程师。请将以下用户需求提炼成"
f"一段高质量的{'图像' if media_type == 'image' else '视频'}生成"
"提示词。要求:\n"
"1. 用英文书写(AGNES模型对英文响应最佳),核心关键词可保留中文;\n"
"2. 添加构图/风格/光影/色彩/质量描述(如 highly detailed, 8K, "
"professional lighting, masterpiece, cinematic等);\n"
"3. 去掉与视觉无关的对话噪音;\n"
"4. 只输出最终prompt文本,不要任何解释、不要引号包裹、不要\"Prompt:\"前缀。\n\n"
f"用户需求:{stripped}"
)
messages = [
{"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}
async with _httpx.AsyncClient(
timeout=30.0, proxy=_proxy_for_url(api_url), trust_env=False
) as client:
resp = await client.post(
api_url,
headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
json=body,
)
if resp.status_code == 200:
data = resp.json()
opt = data.get("choices", [{}])[0].get("message", {}).get("content", "").strip()
if opt and len(opt) > 5:
return opt
finally:
if _should_close:
_db.close()
except Exception:
pass
return stripped
async def _generate_media(db, ai_model, prompt: str, user_id: str = None) -> str:
"""图像/视频生成模型统一入口:按 capabilities 调用 AGNES 等媒体生成 API。
返回可直接在 AI 聊天中渲染的富文本(含 <img>/<video> 标签)。"""
@@ -171,12 +258,11 @@ async def _generate_media(db, ai_model, prompt: str, user_id: str = None) -> str
api_url = f"{provider.api_base_url.rstrip('/')}/v1/images/generations" if provider else ""
if not api_url:
return "⚠️ 该图像模型未配置 Endpoint。"
# 注意:extra_body 是 OpenAI SDK 专属参数,不能直接作为原始 JSON 字段下发,
# AGNES 收到未知顶层字段会直接断开连接(表现为 "Server disconnected without
# sending a response")。保持与 OpenAI 兼容的极简结构即可。
# 先优化 prompt(用户原始输入含多轮对话噪音 → 高质量图像生成提示词)
optimized_prompt = await _optimize_media_prompt(prompt, "image", db)
payload = {
"model": ai_model.name,
"prompt": prompt,
"prompt": optimized_prompt,
"size": "1024x768",
}
try:
@@ -206,10 +292,12 @@ 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)
base = api_url.split("/v1")[0] if "/v1" in api_url else api_url
payload = {
"model": ai_model.name,
"prompt": prompt,
"prompt": optimized_prompt,
"height": 768,
"width": 1152,
"num_frames": 121,
@@ -1762,7 +1850,11 @@ async def send_message(
]
request_body["tool_choice"] = "auto"
async with httpx.AsyncClient(timeout=ai_model.timeout or 120) as client:
async with httpx.AsyncClient(
timeout=ai_model.timeout or 120,
proxy=_proxy_for_url(api_url),
trust_env=False,
) as client:
resp = await client.post(
api_url,
headers={