diff --git a/backend/python/app/api/im_llm.py b/backend/python/app/api/im_llm.py
index 5a0d1cd..f528dcc 100644
--- a/backend/python/app/api/im_llm.py
+++ b/backend/python/app/api/im_llm.py
@@ -1,234 +1,283 @@
-"""
-IM Bot 真实 LLM 调用。
-
-原实现位于 channels.py(通用频道管理模块),该模块已移除。
-为保持 IM Bot(微信机器人等)正常生成回复,将 _call_real_llm 迁移至此独立模块。
-
-行为(对齐 AI 聊天):
-- Issue 7:图像/视频/语音意图优先走平台媒体模型(与 AI 聊天同一套 Auto 路由),
- 不再对"制作图片"这类请求只回文本。
-- Issue 8:默认 Auto 模型;每轮先自动发现本账号已安装且平台已部署的技能,
- 多个适用时由模型自行判断选 1 个最优,通过 function calling 执行。
-- 2026-07-15:修复 trust_env=False + proxy 处理,避免容器坏代理导致调用失败;
- 媒体生成前先经 _optimize_media_prompt 精炼提示词,提升 AGNES 生成质量。
-"""
-import asyncio
-import json
-import logging
-import re as _re
-from datetime import datetime, timezone, timedelta
-from typing import Optional
-from urllib.parse import urlparse as _urlparse
-
-import httpx
-from sqlalchemy.orm import Session
-
-from ..capability_registry import resolve_auto_model
-from ..models import AIModel, ModelProvider, TokenUsageLog, gen_uuid
-from app.core.observability import get_trace_id
-
-logger = logging.getLogger(__name__)
-
-
-# 香港出网代理(实测对 AGNES 延迟更低,2026-07-15 用户实测),与 chat.py 同值。
-_HK_PROXY = "http://qyyfw:XHR521yhf@103.242.14.208:11495"
-
-
-def _proxy_for_url(url: str) -> str | None:
- """按目标域名选择是否走代理(与 chat.py 同逻辑)。
-
- 2026-07-15 调整:AGNES 改走香港代理(实测延迟更低),不再直连。
- 国内域名(尊重 NO_PROXY)直连,更稳定。
- """
- import os
- host = (_urlparse(url).hostname or "").lower()
- if "agnes-ai.com" in host or "agnes-ai.space" in host:
- 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"
-
-
-def _run_async(coro, timeout=None):
- """在同步上下文中运行协程,兼容「无事件循环」与「已有事件循环」两种场景。
-
- IM 个人微信在独立线程中调用(无事件循环)→ asyncio.run;
- IM 多平台 Webhook 可能在 FastAPI 协程内调用(已有事件循环)→ 丢到线程池执行。
-
- timeout: 可选,秒数;超时抛出 asyncio.TimeoutError(由调用方捕获)。
- """
- try:
- loop = asyncio.get_running_loop()
- except RuntimeError:
- loop = None
-
- if timeout is not None:
- async def _timed():
- return await asyncio.wait_for(coro, timeout=timeout)
- target = _timed()
- else:
- target = coro
-
- if loop is not None and loop.is_running():
- import concurrent.futures
- with concurrent.futures.ThreadPoolExecutor(1) as ex:
- return ex.submit(lambda: asyncio.run(target)).result(timeout=(timeout + 10) if timeout else None)
- return asyncio.run(target)
-
-
-def _log_media_usage(db: Session, user_id: str, ai_model, media_type: str, prompt: str):
- """IM Bot 媒体生成记账:把图片/视频消耗写入 TokenUsageLog,供驾驶舱统计。
-
- 此前 IM Bot 路径完全不写 TokenUsageLog,导致微信生成的图片/视频在驾驶舱里“查无此项”。
- 媒体 API 不返回 token 用量,这里用与 chat.py _generate_media_task 一致的估算。
- """
- if not user_id or not ai_model:
- return
- try:
- base = 1200 if media_type == "video" else 400
- est = base + (len(prompt or "") // 4)
- db.add(TokenUsageLog(
- id=gen_uuid(),
- user_id=user_id,
- model_id=ai_model.id,
- model_name=ai_model.name,
- trace_id=get_trace_id(),
- prompt_tokens=est,
- completion_tokens=0,
- total_tokens=est,
- created_at=datetime.now(timezone(timedelta(hours=8))).replace(tzinfo=None),
- ))
- db.commit()
- except Exception as e:
- try:
- db.rollback()
- except Exception:
- pass
- logger.warning(f"[im_llm] 媒体 Token 记账失败(忽略): {e}")
-
-
-def _call_real_llm(db: Session, user=None, prompt: str = "", platform: str = "im", user_id: str = None) -> "str | dict":
- """调用七虎AI 生成回复,供 IM Bot 复用。支持媒体意图路由与已安装技能自动发现。"""
- try:
- from app.api.chat import (
- _generate_media,
- detect_media_intent,
- _discover_installed_skills,
- _build_system_prompt,
- _execute_skill_tool,
- )
-
- # ===== 1) 媒体意图路由(Issue 7)=====
- intent = detect_media_intent(prompt)
- if intent:
- ai_model = resolve_auto_model(intent, db)
- if ai_model:
- try:
- result = _run_async(_generate_media(db, ai_model, prompt, user_id), timeout=300)
- except Exception:
- # 任何未预期异常(如 AGNES 连接重置)统一兜底为友好提示,
- # 绝不直接把原始异常(如 "Server disconnected")甩给用户
- result = "⚠️ 媒体生成暂时失败(连接或服务波动),请稍后重试。"
- if result:
- # 媒体生成成功:返回结构化对象,交由 im_bot_user 在微信内联发送图片/视频
- s = result.strip()
- if s.startswith("
str | None:
+ """按目标域名选择是否走代理(与 chat.py 同逻辑)。
+
+ 2026-07-15 调整:AGNES 改走香港代理(实测延迟更低),不再直连。
+ 国内域名(尊重 NO_PROXY)直连,更稳定。
+ """
+ import os
+ host = (_urlparse(url).hostname or "").lower()
+ if "agnes-ai.com" in host or "agnes-ai.space" in host:
+ 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"
+
+
+def _run_async(coro, timeout=None):
+ """在同步上下文中运行协程,兼容「无事件循环」与「已有事件循环」两种场景。
+
+ IM 个人微信在独立线程中调用(无事件循环)→ asyncio.run;
+ IM 多平台 Webhook 可能在 FastAPI 协程内调用(已有事件循环)→ 丢到线程池执行。
+
+ timeout: 可选,秒数;超时抛出 asyncio.TimeoutError(由调用方捕获)。
+ """
+ try:
+ loop = asyncio.get_running_loop()
+ except RuntimeError:
+ loop = None
+
+ if timeout is not None:
+ async def _timed():
+ return await asyncio.wait_for(coro, timeout=timeout)
+ target = _timed()
+ else:
+ target = coro
+
+ if loop is not None and loop.is_running():
+ import concurrent.futures
+ with concurrent.futures.ThreadPoolExecutor(1) as ex:
+ return ex.submit(lambda: asyncio.run(target)).result(timeout=(timeout + 10) if timeout else None)
+ return asyncio.run(target)
+
+
+def _log_media_usage(db: Session, user_id: str, ai_model, media_type: str, prompt: str):
+ """IM Bot 媒体生成记账:把图片/视频消耗写入 TokenUsageLog,供驾驶舱统计。
+
+ 此前 IM Bot 路径完全不写 TokenUsageLog,导致微信生成的图片/视频在驾驶舱里"查无此项"。
+ 媒体 API 不返回 token 用量,这里用与 chat.py _generate_media_task 一致的估算。
+ """
+ if not user_id or not ai_model:
+ return
+ try:
+ base = 1200 if media_type == "video" else 400
+ est = base + (len(prompt or "") // 4)
+ db.add(TokenUsageLog(
+ id=gen_uuid(),
+ user_id=user_id,
+ model_id=ai_model.id,
+ model_name=ai_model.name,
+ trace_id=get_trace_id(),
+ prompt_tokens=est,
+ completion_tokens=0,
+ total_tokens=est,
+ created_at=datetime.now(timezone(timedelta(hours=8))).replace(tzinfo=None),
+ ))
+ db.commit()
+ except Exception as e:
+ try:
+ db.rollback()
+ except Exception:
+ pass
+ logger.warning(f"[im_llm] 媒体 Token 记账失败(忽略): {e}")
+
+
+def _call_real_llm(db: Session, user=None, prompt: str = "", platform: str = "im", user_id: str = None) -> "str | dict":
+ """调用七虎AI 生成回复,供 IM Bot 复用。支持媒体意图路由、已安装技能自动发现、模型 failover。"""
+ try:
+ from app.api.chat import (
+ _generate_media,
+ detect_media_intent,
+ _discover_installed_skills,
+ _build_system_prompt,
+ _execute_skill_tool,
+ )
+
+ # ===== 1) 媒体意图路由(Issue 7)=====
+ intent = detect_media_intent(prompt)
+ if intent:
+ ai_model = resolve_auto_model(intent, db)
+ if ai_model:
+ try:
+ result = _run_async(_generate_media(db, ai_model, prompt, user_id), timeout=300)
+ except Exception:
+ # 任何未预期异常(如 AGNES 连接重置)统一兜底为友好提示,
+ # 绝不直接把原始异常(如 "Server disconnected")甩给用户
+ result = "⚠️ 媒体生成暂时失败(连接或服务波动),请稍后重试。"
+ if result:
+ # 媒体生成成功:返回结构化对象,交由 im_bot_user 在微信内联发送图片/视频
+ s = result.strip()
+ if s.startswith("![]()