fix(im_llm): 代理绕过Docker内部hostname

This commit is contained in:
QihuAI Design committed 2026-07-31 08:51:15 +08:00
1 parent 96aabc31f6
commit 9a43ee244d
24 files changed
+898 -129

No files matched your search

+56 -25
View File
@@ -16,6 +16,7 @@ IM Bot 真实 LLM 调用。
* 平台知识库注入(get_platform_knowledge,技能清单实时同步)
"""
import asyncio
import os
import json
import logging
import re as _re
@@ -48,7 +49,8 @@ def _proxy_for_url(url: str) -> str | None:
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:
# 2026-07-30 修复 3+: 不代理 Docker 内部 hostname 和 localhost
if "qihuyun.com" in host or "localhost" in host or host.startswith("127.") or "qihu-aiflow" 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:
@@ -127,7 +129,12 @@ def _call_real_llm(db: Session, user=None, prompt: str = "", platform: str = "im
)
# ===== 1) 媒体意图路由(Issue 7)=====
intent = detect_media_intent(prompt)
# 2026-07-30 fix: 只对当前用户消息做媒体意图检测,避免历史中的图像请求污染
_cur_msg = prompt
_marker = "用户当前问题:"
if _marker in prompt:
_cur_msg = prompt[prompt.rfind(_marker) + len(_marker):].strip()
intent = detect_media_intent(_cur_msg)
if intent:
ai_model = resolve_auto_model(intent, db)
if ai_model:
@@ -196,6 +203,10 @@ def _call_real_llm(db: Session, user=None, prompt: str = "", platform: str = "im
provider = db.query(ModelProvider).filter(ModelProvider.id == ai_model.provider_id).first()
api_key = ai_model.api_key or (provider.api_key if provider else None)
api_url = ai_model.endpoint
# 2026-07-30: LiteLLM 端点统一用 master key(与 chat_service.py 对齐)
if api_url and "qihu-aiflow-litellm" in api_url:
_litellm_key = os.environ.get("LITELLM_MASTER_KEY") or "sk-litellm-master-key-aiflow-2026"
api_key = _litellm_key
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):
@@ -221,13 +232,15 @@ def _call_real_llm(db: Session, user=None, prompt: str = "", platform: str = "im
ai_content = ""
_http_ok = True
for round_num in range(max_rounds):
request_body = {"model": ai_model.name, "messages": messages, "max_tokens": 2048}
request_body = {"model": ai_model.name, "messages": messages, "max_tokens": 2048, "enable_thinking": False, "temperature": 0.7}
if function_definitions and skills:
request_body["tools"] = [
{"type": "function", "function": fd} for fd in function_definitions
]
request_body["tool_choice"] = "auto"
try:
import logging as _l5
_l5.warning(f'[im_llm REQ] {ai_model.name}: url={api_url[:60]}, proxy={_proxy_for_url(api_url)}, api_key={api_key[:20]}..., messages_count={len(request_body.get("messages", []))}, tools_count={len(request_body.get("tools", []))}')
data = chat_complete(
api_url,
api_key,
@@ -235,40 +248,58 @@ def _call_real_llm(db: Session, user=None, prompt: str = "", platform: str = "im
proxy=_proxy_for_url(api_url),
timeout=ai_model.timeout or 120,
)
_l5.warning(f'[im_llm RES] {ai_model.name}: data_keys={list(data.keys()) if data else None}, resp_type={type(data).__name__}')
except Exception as _exc:
_retry_log.append(f"{ai_model.name}: {str(_exc)[:60]}")
import traceback as _tb
logger.warning(f"[im_llm] 模型 {ai_model.name} 异常: {_exc}")
logger.warning(f"[im_llm] {ai_model.name} 异常详情: {_tb.format_exc()[:2000]}")
_http_ok = False
break
message = data.get("choices", [{}])[0].get("message", {})
tool_calls = message.get("tool_calls", [])
if tool_calls and skills:
message = data.get("choices", [{}])[0].get("message", {})
tool_calls = message.get("tool_calls", [])
# 2026-07-30 CRITICAL DEBUG
import logging as _lcr
_raw_content = message.get("content")
_raw_reasoning = message.get("reasoning_content")
_msg_keys = list(message.keys()) if message else []
_tool_calls = tool_calls
_lcr.warning(f'[CRITICAL] MODEL={ai_model.name} msg_keys={_msg_keys} content_len={len(_raw_content or "")} reasoning_len={len(_raw_reasoning or "")} has_tool_calls={bool(_tool_calls)}')
ai_content = message.get("content", "") or message.get("reasoning_content") or ""
if not ai_content and (message.get("reasoning_content") or ""):
ai_content = f"(AI 思考:{message.get('reasoning_content')[:500]}...)"
if tool_calls and skills:
messages.append({
"role": "assistant",
"content": message.get("content") or message.get("reasoning_content") or "",
"tool_calls": tool_calls,
})
for tc in tool_calls:
func_name = tc.get("function", {}).get("name", "")
try:
func_args = json.loads(tc.get("function", {}).get("arguments", "{}"))
except Exception:
func_args = {"input": tc.get("function", {}).get("arguments", "")}
tool_result = _execute_skill_tool(db, func_name, func_args, skills, prompt)
messages.append({
"role": "assistant",
"content": message.get("content") or "",
"tool_calls": tool_calls,
"role": "tool",
"tool_call_id": tc.get("id", "call_1"),
"content": tool_result,
})
for tc in tool_calls:
func_name = tc.get("function", {}).get("name", "")
try:
func_args = json.loads(tc.get("function", {}).get("arguments", "{}"))
except Exception:
func_args = {"input": tc.get("function", {}).get("arguments", "")}
tool_result = _execute_skill_tool(db, func_name, func_args, skills, prompt)
messages.append({
"role": "tool",
"tool_call_id": tc.get("id", "call_1"),
"content": tool_result,
})
continue
ai_content = message.get("content", "")
break
if round_num >= max_rounds - 1:
break
continue
break
# 检查本轮调用是否成功
import logging as _log2
_log2.warning(f'[im_llm trace] {ai_model.name}: http_ok={_http_ok}, ai_content_len={len(ai_content) if ai_content else 0}, ai_content_preview={repr(ai_content)[:200]}')
if _http_ok and ai_content:
return ai_content or "(AI 回复为空)"
# 本轮失败:记录日志,切换备选
logger.warning(f"[im_llm] 模型 {ai_model.name} 失败,切换备选...")
logger.warning('[im_llm] 模型 %s 失败 (http_ok=%s, content=%s), 切换备选...' % (
ai_model.name, _http_ok, repr(ai_content)[:100]
))
continue
except Exception as exc:
_retry_log.append(f"{ai_model.name}: {str(exc)[:60]}")