fix(im_llm): 代理绕过Docker内部hostname
This commit is contained in:
1 parent
96aabc31f6
commit
9a43ee244d
24 files changed
+898
-129
No files matched your search
@@ -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]}")
|
||||
|
||||
Reference in new issue
Block a user