add DeepSeek AI provider
This commit is contained in:
+35
-16
@@ -83,6 +83,11 @@ try:
|
||||
except (TypeError, ValueError):
|
||||
BACKUP_INTERVAL_HOURS = 24
|
||||
NOUS_BASE_URL = "https://inference-api.nousresearch.com/v1"
|
||||
DEEPSEEK_BASE_URL = "https://api.deepseek.com/v1"
|
||||
DEEPSEEK_MODELS = [
|
||||
{"id": "deepseek-chat", "label": "DeepSeek Chat", "free": False},
|
||||
{"id": "deepseek-reasoner", "label": "DeepSeek Reasoner", "free": False},
|
||||
]
|
||||
NOUS_FREE_MODELS = [
|
||||
{"id": "meituan/longcat-2.0:free", "label": "Meituan · LongCat 2.0", "free": True},
|
||||
{"id": "upstage/solar-pro-4:free", "label": "Upstage · Solar Pro 4", "free": True},
|
||||
@@ -1412,6 +1417,14 @@ def http_bytes(method: str, url: str, payload: Any = None, headers: dict[str, st
|
||||
raise RuntimeError(f"{error_prefix}_unreachable:{exc.reason}") from exc
|
||||
|
||||
|
||||
def ai_requires_api_key(provider: str) -> bool:
|
||||
return str(provider or "").strip().lower() in {"nous", "deepseek"}
|
||||
|
||||
|
||||
def ai_is_configured(runtime: dict[str, str]) -> bool:
|
||||
return bool(runtime.get("base_url") and runtime.get("model") and (not ai_requires_api_key(runtime.get("provider", "")) or runtime.get("api_key")))
|
||||
|
||||
|
||||
def ai_runtime_settings() -> dict[str, str]:
|
||||
saved: dict[str, str] = {}
|
||||
try:
|
||||
@@ -1422,10 +1435,14 @@ def ai_runtime_settings() -> dict[str, str]:
|
||||
pass
|
||||
provider = saved.get("AI_PROVIDER", AI_PROVIDER).strip().lower() or "custom"
|
||||
base = saved.get("AI_BASE_URL", AI_BASE_URL).strip().rstrip("/")
|
||||
model = saved.get("AI_MODEL", AI_MODEL).strip()
|
||||
if provider == "nous":
|
||||
base = NOUS_BASE_URL
|
||||
elif provider == "deepseek":
|
||||
base = DEEPSEEK_BASE_URL
|
||||
model = model or "deepseek-chat"
|
||||
mockup_timeout = _setting_int(saved, "AI_MOCKUP_TIMEOUT_SECONDS", "AI_MOCKUP_TIMEOUT_SECONDS", AI_MOCKUP_TIMEOUT_SECONDS, 20, 300)
|
||||
return {"provider": provider, "base_url": base, "api_key": saved.get("AI_API_KEY", AI_API_KEY).strip(), "model": saved.get("AI_MODEL", AI_MODEL).strip(), "mockup_timeout_seconds": str(mockup_timeout)}
|
||||
return {"provider": provider, "base_url": base, "api_key": saved.get("AI_API_KEY", AI_API_KEY).strip(), "model": model, "mockup_timeout_seconds": str(mockup_timeout)}
|
||||
|
||||
|
||||
def nous_models() -> list[dict[str, Any]]:
|
||||
@@ -1980,8 +1997,8 @@ def ai_generate_message(prospect: dict[str, Any], offer: str, tone: str = "warm"
|
||||
ai_base_url = runtime["base_url"]
|
||||
ai_api_key = runtime["api_key"]
|
||||
ai_model = runtime["model"]
|
||||
if runtime["provider"] == "nous" and not ai_api_key:
|
||||
raise RuntimeError("nous_api_key_required_for_standalone_app")
|
||||
if ai_requires_api_key(runtime["provider"]) and not ai_api_key:
|
||||
raise RuntimeError("ai_provider_api_key_required")
|
||||
if not ai_base_url or not ai_model:
|
||||
raise RuntimeError("ai_not_configured_set_AI_BASE_URL_and_AI_MODEL")
|
||||
evidence_ids = ["prospect_record"]
|
||||
@@ -2412,8 +2429,8 @@ def ai_revise_message(prospect: dict[str, Any], current_subject: str, current_me
|
||||
ai_base_url = runtime["base_url"]
|
||||
ai_api_key = runtime["api_key"]
|
||||
ai_model = runtime["model"]
|
||||
if runtime["provider"] == "nous" and not ai_api_key:
|
||||
raise RuntimeError("nous_api_key_required_for_standalone_app")
|
||||
if ai_requires_api_key(runtime["provider"]) and not ai_api_key:
|
||||
raise RuntimeError("ai_provider_api_key_required")
|
||||
if not ai_base_url or not ai_model:
|
||||
raise RuntimeError("ai_not_configured_set_AI_BASE_URL_and_AI_MODEL")
|
||||
current_subject = str(current_subject or "").strip()[:300]
|
||||
@@ -2551,8 +2568,8 @@ def _parse_ai_object(content: Any) -> dict[str, Any]:
|
||||
def ai_assess_prospect(prospect: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Assess whether a prospect is worth human review/contact."""
|
||||
runtime = ai_runtime_settings()
|
||||
if runtime["provider"] == "nous" and not runtime["api_key"]:
|
||||
raise RuntimeError("nous_api_key_required_for_standalone_app")
|
||||
if ai_requires_api_key(runtime["provider"]) and not runtime["api_key"]:
|
||||
raise RuntimeError("ai_provider_api_key_required")
|
||||
if not runtime["base_url"] or not runtime["model"]:
|
||||
raise RuntimeError("ai_not_configured_set_AI_BASE_URL_and_AI_MODEL")
|
||||
profile = company_profile_settings()
|
||||
@@ -2719,7 +2736,7 @@ def generate_mockup_html(prospect: dict[str, Any], snapshot: dict[str, Any] | No
|
||||
snapshot = snapshot or {}
|
||||
runtime = ai_runtime_settings()
|
||||
fallback = fallback_mockup_html(prospect, snapshot)
|
||||
if not runtime["base_url"] or not runtime["model"] or (runtime["provider"] == "nous" and not runtime["api_key"]):
|
||||
if not runtime["base_url"] or not runtime["model"] or (ai_requires_api_key(runtime["provider"]) and not runtime["api_key"]):
|
||||
return fallback, "fallback", ""
|
||||
source = {
|
||||
"business_name": _mockup_text(prospect.get("name"), 160),
|
||||
@@ -2779,7 +2796,7 @@ def generate_mockup_pages(prospect: dict[str, Any], snapshot: dict[str, Any] | N
|
||||
return {"home": html}, source, model
|
||||
fallback = fallback_mockup_pages(prospect, snapshot)
|
||||
runtime = ai_runtime_settings()
|
||||
if not runtime["base_url"] or not runtime["model"] or (runtime["provider"] == "nous" and not runtime["api_key"]):
|
||||
if not runtime["base_url"] or not runtime["model"] or (ai_requires_api_key(runtime["provider"]) and not runtime["api_key"]):
|
||||
return fallback, "fallback", ""
|
||||
source = {
|
||||
"business_name": _mockup_text(prospect.get("name"), 160),
|
||||
@@ -3033,7 +3050,7 @@ def generate_premium_mockup(prospect: dict[str, Any], snapshot: dict[str, Any] |
|
||||
requested_design = _premium_design_from_instructions(instructions)
|
||||
fallback = premium_render_pages(prospect, snapshot, mode=mode, design=requested_design)
|
||||
runtime = ai_runtime_settings()
|
||||
if not runtime["base_url"] or not runtime["model"] or (runtime["provider"] == "nous" and not runtime["api_key"]):
|
||||
if not runtime["base_url"] or not runtime["model"] or (ai_requires_api_key(runtime["provider"]) and not runtime["api_key"]):
|
||||
return fallback, "fallback", ""
|
||||
source = {
|
||||
"business_name": _mockup_text(prospect.get("name"), 160),
|
||||
@@ -3198,12 +3215,14 @@ class Handler(BaseHTTPRequestHandler):
|
||||
"email_extraction_always": True,
|
||||
"browser_use_enabled": browser_runtime["enabled"],
|
||||
"ai_provider": ai_runtime["provider"],
|
||||
"ai_configured": bool(ai_runtime["base_url"] and ai_runtime["model"] and (ai_runtime["provider"] != "nous" or ai_runtime["api_key"])),
|
||||
"ai_configured": ai_is_configured(ai_runtime),
|
||||
})
|
||||
if parsed.path == "/api/ai/models":
|
||||
provider = (parse_qs(parsed.query).get("provider", [AI_PROVIDER])[0] or AI_PROVIDER).strip().lower()
|
||||
if provider == "nous":
|
||||
return self.send_json(200, {"provider": "nous", "base_url": NOUS_BASE_URL, "models": nous_models()})
|
||||
if provider == "deepseek":
|
||||
return self.send_json(200, {"provider": "deepseek", "base_url": DEEPSEEK_BASE_URL, "models": DEEPSEEK_MODELS})
|
||||
return self.send_json(200, {"provider": provider or "custom", "models": []})
|
||||
if parsed.path == "/api/logs":
|
||||
params = parse_qs(parsed.query)
|
||||
@@ -3241,7 +3260,7 @@ class Handler(BaseHTTPRequestHandler):
|
||||
if parsed.path == "/api/settings":
|
||||
saved = _saved_settings()
|
||||
ai_runtime = ai_runtime_settings()
|
||||
models = nous_models() if ai_runtime["provider"] == "nous" else []
|
||||
models = nous_models() if ai_runtime["provider"] == "nous" else DEEPSEEK_MODELS if ai_runtime["provider"] == "deepseek" else []
|
||||
browser_runtime = browser_use_settings()
|
||||
maps_runtime = gmaps_runtime_settings()
|
||||
scan_timeout = website_scan_timeout_seconds()
|
||||
@@ -3251,7 +3270,7 @@ class Handler(BaseHTTPRequestHandler):
|
||||
"ready": backend_mode() == "browser" or bool(GMAPS_URL),
|
||||
**maps_runtime,
|
||||
},
|
||||
"ai": {"provider": ai_runtime["provider"], "base_url": ai_runtime["base_url"], "model": ai_runtime["model"], "configured": bool(ai_runtime["base_url"] and ai_runtime["model"] and (ai_runtime["provider"] != "nous" or ai_runtime["api_key"])), "models": models},
|
||||
"ai": {"provider": ai_runtime["provider"], "base_url": ai_runtime["base_url"], "model": ai_runtime["model"], "configured": ai_is_configured(ai_runtime), "models": models},
|
||||
"browser_use": browser_runtime,
|
||||
"email_collection": {"always_on": True, "public_only": True},
|
||||
"limits": {"max_results": MAX_RESULTS, "website_scan_timeout_seconds": scan_timeout, "ai_mockup_timeout_seconds": int(ai_runtime.get("mockup_timeout_seconds") or AI_MOCKUP_TIMEOUT_SECONDS)},
|
||||
@@ -3702,7 +3721,7 @@ class Handler(BaseHTTPRequestHandler):
|
||||
return self.send_json(200, {
|
||||
"saved": True,
|
||||
"saved_keys": saved_keys,
|
||||
"ai": {"provider": ai_runtime["provider"], "base_url": ai_runtime["base_url"], "model": ai_runtime["model"], "configured": bool(ai_runtime["base_url"] and ai_runtime["model"] and (ai_runtime["provider"] != "nous" or ai_runtime["api_key"]))},
|
||||
"ai": {"provider": ai_runtime["provider"], "base_url": ai_runtime["base_url"], "model": ai_runtime["model"], "configured": ai_is_configured(ai_runtime)},
|
||||
"browser_use": browser_use_settings(),
|
||||
"maps": gmaps_runtime_settings(),
|
||||
"limits": {"website_scan_timeout_seconds": website_scan_timeout_seconds(), "ai_mockup_timeout_seconds": int(ai_runtime.get("mockup_timeout_seconds") or AI_MOCKUP_TIMEOUT_SECONDS)},
|
||||
@@ -3854,8 +3873,8 @@ class Handler(BaseHTTPRequestHandler):
|
||||
runtime = ai_runtime_settings()
|
||||
if not runtime["base_url"] or not runtime["model"]:
|
||||
return self.send_json(400, {"error": "ai_not_configured_set_provider_and_model"})
|
||||
if runtime["provider"] == "nous" and not runtime["api_key"]:
|
||||
return self.send_json(400, {"error": "nous_api_key_required_for_standalone_app"})
|
||||
if ai_requires_api_key(runtime["provider"]) and not runtime["api_key"]:
|
||||
return self.send_json(400, {"error": "ai_provider_api_key_required"})
|
||||
headers = {"Authorization": "Bearer " + runtime["api_key"]} if runtime["api_key"] else {}
|
||||
result = http_json("POST", runtime["base_url"] + "/chat/completions", {"model": runtime["model"], "temperature": 0, "max_tokens": 8, "messages": [{"role": "user", "content": "Reply with exactly OK."}]}, headers=headers, timeout=30, error_prefix="ai")
|
||||
content = (((result.get("choices") or [{}])[0].get("message") or {}).get("content") or "") if isinstance(result, dict) else ""
|
||||
|
||||
Reference in New Issue
Block a user