Files
skill-template/llm-manager/scripts/providers.py
2026-04-04 10:35:02 +08:00

200 lines
7.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
平台配置中心7 个 LLM 平台的静态配置、别名解析、以及通过 account-manager 暴露的 CLI 查询已登录网页账号。
"""
import json
import os
import subprocess
import sys
# ---------------------------------------------------------------------------
# 平台静态配置
# ---------------------------------------------------------------------------
LLM_PROVIDERS = {
"doubao": {
"label": "豆包",
"aliases": ["豆包"],
"web_url": "https://www.doubao.com/chat/",
"api_base": "https://ark.volces.com/api/v3",
"api_model": None, # 豆包需要用户在火山引擎控制台建推理接入点model=ep-xxx
"has_api": True,
"api_note": "需先在火山引擎控制台创建推理接入点,--model 传 endpoint_id格式 ep-xxx",
},
"deepseek": {
"label": "DeepSeek",
"aliases": ["深度求索"],
"web_url": "https://chat.deepseek.com",
"api_base": "https://api.deepseek.com/v1",
"api_model": "deepseek-chat",
"has_api": True,
"api_note": "模型可选deepseek-chat / deepseek-reasoner",
},
"qianwen": {
"label": "通义千问",
"aliases": ["通义", "千问", "qwen", "tongyi"],
"web_url": "https://tongyi.aliyun.com/qianwen/",
"api_base": "https://dashscope.aliyuncs.com/compatible-mode/v1",
"api_model": "qwen-plus",
"has_api": True,
"api_note": "模型可选qwen-turbo / qwen-plus / qwen-max",
},
"kimi": {
"label": "Kimi",
"aliases": ["月之暗面", "moonshot"],
"web_url": "https://kimi.moonshot.cn",
"api_base": "https://api.moonshot.cn/v1",
"api_model": "moonshot-v1-8k",
"has_api": True,
"api_note": "模型可选moonshot-v1-8k / moonshot-v1-32k / moonshot-v1-128k",
},
"yiyan": {
"label": "文心一言",
"aliases": ["文心", "一言", "ernie", "wenxin"],
"web_url": "https://yiyan.baidu.com",
"api_base": "https://qianfan.baidubce.com/v2",
"api_model": "ernie-4.0-8k",
"has_api": True,
"api_note": "模型可选ernie-4.0-8k / ernie-3.5-8k",
},
"yuanbao": {
"label": "腾讯元宝",
"aliases": ["元宝"],
"web_url": "https://yuanbao.tencent.com/chat",
"api_base": None,
"api_model": None,
"has_api": False,
"api_note": "暂无公开 API仅支持网页模式",
},
"minimax": {
"label": "MiniMax",
"aliases": ["minimax", "MiniMax", "海螺", "海螺AI"],
"web_url": "https://chat.minimax.io/",
"api_base": "https://api.minimax.chat/v1",
"api_model": "MiniMax-Text-01",
"has_api": True,
"api_note": "模型可按 MiniMax 控制台可用模型调整,建议通过 --model 显式指定。",
},
}
# 构建别名查找表含中文、英文键、aliases
_ALIAS_TO_KEY: dict = {}
for _k, _spec in LLM_PROVIDERS.items():
_ALIAS_TO_KEY[_k] = _k
_ALIAS_TO_KEY[_k.lower()] = _k
_ALIAS_TO_KEY[_spec["label"]] = _k
for _a in (_spec.get("aliases") or []):
_ALIAS_TO_KEY[_a] = _k
_ALIAS_TO_KEY[_a.lower()] = _k
def resolve_provider_key(name: str):
"""将用户输入的平台名称/别名解析为内部 slug无法识别返回 None。"""
if not name:
return None
s = str(name).strip()
return _ALIAS_TO_KEY.get(s) or _ALIAS_TO_KEY.get(s.lower())
def provider_list_cn() -> str:
return "".join(s["label"] for s in LLM_PROVIDERS.values())
# ---------------------------------------------------------------------------
# 路径帮助(与 account-manager/scripts/main.py 完全一致:仅 JIANGCHANG_*
# ---------------------------------------------------------------------------
_OPENCLAW_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
def get_data_root() -> str:
env = (os.getenv("JIANGCHANG_DATA_ROOT") or "").strip()
if env:
return env
if sys.platform == "win32":
return r"D:\jiangchang-data"
return os.path.join(os.path.expanduser("~"), ".jiangchang-data")
def get_user_id() -> str:
uid = (os.getenv("JIANGCHANG_USER_ID") or "").strip()
return uid or "_anon"
# ---------------------------------------------------------------------------
# 跨技能:仅通过 account-manager 提供的 CLI 读取账号(不直接打开其数据库文件)
# ---------------------------------------------------------------------------
def _account_manager_script_path() -> str:
return os.path.join(_OPENCLAW_DIR, "account-manager", "scripts", "main.py")
def find_logged_in_account(provider_key: str) -> dict | None:
"""
调用 account-managerpick-logged-in <platform_key>取该平台已登录login_status=1的优先账号。
成功返回与 account.py get 一致的 dict失败或不可用时返回 None。
"""
script = _account_manager_script_path()
if not os.path.isfile(script):
return None
try:
proc = subprocess.run(
[sys.executable, script, "pick-logged-in", provider_key],
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
)
except OSError:
return None
raw = (proc.stdout or "").strip()
if not raw or raw.startswith("ERROR:"):
return None
try:
data = json.loads(raw.splitlines()[0])
except (json.JSONDecodeError, IndexError):
return None
if not isinstance(data, dict) or data.get("id") is None:
return None
plat = data.get("platform") or provider_key
if not (data.get("url") or "").strip():
data["url"] = LLM_PROVIDERS.get(provider_key, {}).get("web_url", "")
data["platform"] = plat
return data
# ---------------------------------------------------------------------------
# Chrome/Edge 检测(与 account-manager 逻辑保持一致)
# ---------------------------------------------------------------------------
def _win_find_exe(candidates):
for p in candidates:
if p and os.path.isfile(p):
return p
return None
def resolve_chromium_channel() -> str | None:
"""返回 'chrome' | 'msedge' | None。"""
if sys.platform != "win32":
return "chrome"
pf = os.environ.get("ProgramFiles", r"C:\Program Files")
pfx86 = os.environ.get("ProgramFiles(x86)", r"C:\Program Files (x86)")
local = os.environ.get("LocalAppData", "")
chrome = _win_find_exe([
os.path.join(pf, "Google", "Chrome", "Application", "chrome.exe"),
os.path.join(pfx86, "Google", "Chrome", "Application", "chrome.exe"),
os.path.join(local, "Google", "Chrome", "Application", "chrome.exe") if local else "",
])
if chrome:
return "chrome"
edge = _win_find_exe([
os.path.join(pfx86, "Microsoft", "Edge", "Application", "msedge.exe"),
os.path.join(pf, "Microsoft", "Edge", "Application", "msedge.exe"),
os.path.join(local, "Microsoft", "Edge", "Application", "msedge.exe") if local else "",
])
if edge:
return "msedge"
return None