Route Agent D.R.E chatbot through admin-ai (dedicated dre-chatbot virtual key, $5/day budget) instead of native DeepSeek key
This commit is contained in:
+16
-16
@@ -1,6 +1,6 @@
|
||||
"""Agent D.R.E chatbot — public FAQ assistant.
|
||||
|
||||
Backed by DeepSeek (deepseek-v4-flash, thinking disabled) via native API.
|
||||
Backed by deepseek-v4-flash via the admin-ai LiteLLM proxy (dedicated virtual key, budget capped).
|
||||
Stdlib-only (urllib + json) so no new venv dependencies.
|
||||
Public endpoint: POST /api/chat
|
||||
|
||||
@@ -31,10 +31,10 @@ logger = logging.getLogger("dre.chatbot")
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
DEEPSEEK_BASE_URL = os.environ.get("DEEPSEEK_BASE_URL", "https://api.deepseek.com").rstrip("/")
|
||||
DEEPSEEK_API_KEY = os.environ.get("DEEPSEEK_API_KEY", "")
|
||||
CHATBOT_BASE_URL = os.environ.get("DRE_CHATBOT_BASE_URL", "https://admin-ai.itpropartner.com/v1").rstrip("/")
|
||||
CHATBOT_API_KEY = os.environ.get("DRE_CHATBOT_API_KEY", "")
|
||||
CHATBOT_MODEL = os.environ.get("DRE_CHATBOT_MODEL", "deepseek-v4-flash")
|
||||
CHATBOT_MAX_TOKENS = int(os.environ.get("DRE_CHATBOT_MAX_TOKENS", "300"))
|
||||
CHATBOT_MAX_TOKENS = int(os.environ.get("DRE_CHATBOT_MAX_TOKENS", "400"))
|
||||
CHATBOT_TIMEOUT = float(os.environ.get("DRE_CHATBOT_TIMEOUT", "30"))
|
||||
|
||||
MAX_MESSAGE_CHARS = 1000
|
||||
@@ -97,9 +97,9 @@ def _is_private_topic(text: str) -> bool:
|
||||
return any(m in low for m in PRIVATE_TOPIC_MARKERS)
|
||||
|
||||
|
||||
def _call_deepseek(system_prompt: str, history: list[dict], message: str) -> str | None:
|
||||
if not DEEPSEEK_API_KEY:
|
||||
logger.error("DEEPSEEK_API_KEY not set; chatbot cannot call LLM")
|
||||
def _call_llm(system_prompt: str, history: list[dict], message: str) -> str | None:
|
||||
if not CHATBOT_API_KEY:
|
||||
logger.error("DRE_CHATBOT_API_KEY not set; chatbot cannot call LLM")
|
||||
return None
|
||||
|
||||
messages = [{"role": "system", "content": system_prompt}]
|
||||
@@ -115,15 +115,14 @@ def _call_deepseek(system_prompt: str, history: list[dict], message: str) -> str
|
||||
"messages": messages,
|
||||
"max_tokens": CHATBOT_MAX_TOKENS,
|
||||
"temperature": 0.3,
|
||||
"thinking": {"type": "disabled"},
|
||||
}
|
||||
url = f"{DEEPSEEK_BASE_URL}/v1/chat/completions"
|
||||
url = f"{CHATBOT_BASE_URL}/chat/completions"
|
||||
req = urllib.request.Request(
|
||||
url,
|
||||
data=json.dumps(payload).encode("utf-8"),
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {DEEPSEEK_API_KEY}",
|
||||
"Authorization": f"Bearer {CHATBOT_API_KEY}",
|
||||
},
|
||||
method="POST",
|
||||
)
|
||||
@@ -131,20 +130,21 @@ def _call_deepseek(system_prompt: str, history: list[dict], message: str) -> str
|
||||
with urllib.request.urlopen(req, timeout=CHATBOT_TIMEOUT) as resp:
|
||||
data = json.loads(resp.read().decode("utf-8"))
|
||||
except (urllib.error.URLError, urllib.error.HTTPError, TimeoutError, OSError, json.JSONDecodeError) as exc:
|
||||
logger.error("deepseek call failed: %s", exc)
|
||||
logger.error("LLM call failed: %s", exc)
|
||||
return None
|
||||
|
||||
try:
|
||||
content = data["choices"][0]["message"].get("content") or ""
|
||||
content = content.strip()
|
||||
except (KeyError, IndexError, TypeError):
|
||||
logger.error("unexpected deepseek response shape: %s", data)
|
||||
logger.error("unexpected LLM response shape: %s", data)
|
||||
return None
|
||||
|
||||
# If thinking was not actually disabled, content can come back empty. Do not
|
||||
# surface the model's reasoning; treat it as a failed call instead.
|
||||
# deepseek-v4-flash reasons even through LiteLLM; with max_tokens set high
|
||||
# enough, content is still populated. If content is empty, never surface the
|
||||
# model's reasoning_content (chain-of-thought); treat it as a failed call.
|
||||
if not content:
|
||||
logger.error("deepseek returned empty content (thinking may not be disabled)")
|
||||
logger.error("LLM returned empty content")
|
||||
return None
|
||||
return content
|
||||
|
||||
@@ -193,7 +193,7 @@ async def chat(req: ChatRequest, request: Request):
|
||||
return {"reply": FALLBACK_OUT_OF_SCOPE}
|
||||
|
||||
# 2. LLM answer from public FAQ only
|
||||
reply = _call_deepseek(_system_prompt(), req.history, message)
|
||||
reply = _call_llm(_system_prompt(), req.history, message)
|
||||
if reply is None:
|
||||
return {"reply": FALLBACK_ERROR}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user