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:
1 parent
4d57987aa5
commit
148e4a21c2
5 files changed
+210
-16
No files matched your search
@@ -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={
|
||||
|
||||
Reference in new issue
Block a user