智能体
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
DB_Agent/install_help.py

720 lines
29 KiB

#!/usr/bin/env python
# /// script
# dependencies = [
# "tomli",
# "click",
# "inquirer",
# ]
# [tool.uv]
# exclude-newer = "2025-03-07T00:00:00Z"
# ///
import os
import tomli
import glob
import click
import inquirer
from pathlib import Path
from typing import Dict, Any
# For I18N support, we use a simple class to store translations and a global instance
# to access it.
class I18N:
# Define supported languages in current install help script
SUPPORTED_LANGUAGES = ["en", "zh"]
# The translation dictionary contains a mapping from language code to a dictionary
TRANSLATIONS = {
"en": {
# Common
"workspace_not_found": "Workspace root not found.",
"cannot_parse": "Cannot parse {}: {}",
"no_extras_defined": "No extras defined",
"no_extras_found": "No workspace or extras found.",
"operation_canceled": "Operation canceled.",
"available_packages": "Available packages: {}",
"copy_command": "Please copy the above command to execute in terminal. For more help, run:",
"finished": "Finished!",
# Description of the CLI command
"cli_description": "UV Workspace Extras Helper - Manage optional dependencies in UV workspace",
"list_cmd_description": "List all extras in the workspace",
"install_cmd_description": "Generate installation commands for extras",
"deploy_cmd_description": "Use predefined deployment templates",
# Option descriptions
"verbose_option": "Show detailed dependency information",
"interactive_option": "Interactive guide to generate installation commands",
"all_option": "Generate command to install all extras",
"china_option": "Use Tsinghua PyPI mirror for faster installation in China",
"preset_option": "Use predefined deployment template",
"list_presets_option": "List all predefined deployment templates",
"language_option": "Specify language (en/zh)",
# List command
"extras_in_workspace": "Extras in workspace:\n",
"available_extras": "Available extras:",
"dependencies": "dependencies",
# Installation command
"install_all_extras": "# Install all optional features:",
"install_extras_for": "# Install {} feature for {}:",
"package_not_in_workspace": "Error: Package '{}' not in workspace or has no extras defined.",
"package_no_extras": "Package '{}' has no extras defined.",
"extra_not_in_package": "Error: Extra '{}' not found in package '{}'.",
"available_extras_in_package": "Available extras: {}",
# Interactive installation
"welcome": "Welcome to DB-GPT Installation Assistant!",
"help_message": "This tool will help you generate the correct installation commands.\n",
"select_mode": "Please select installation mode",
"select_extras": "Please select extras to install (space to select/deselect, enter to confirm)",
"installation_info": "📋 Installation Information",
"selected_mode": "📦 Selected mode: {}",
"description": "📝 Description: {}",
"note": " Note: {}",
"will_install": "🧩 Will install the following extras: {}",
"config_file": " Configuration file: {}",
"generate_command": "Generate installation command?",
"installation_command": "🚀 Installation Command",
"startup_command": "🏃 Startup Command",
"further_configuration": " Further Configuration",
"set_api_key": "Please make sure you set the correct API Key in the configuration file {}",
"set_model_path": "Please make sure you set the correct model path in the configuration file {}",
# Deployment command
"available_presets": "Available deployment presets:",
"specify_preset": "Please specify a deployment preset name, or use --list to view all presets",
"preset_not_found": "Error: Preset '{}' not found",
"available_presets_list": "Available presets: {}",
"using_preset": "Using preset '{}' to generate deployment command",
# Preset descriptions
"openai_preset": "OpenAI Proxy Mode",
"openai_desc": "Using OpenAI API as proxy, suitable for environments without GPU",
"openai_note": "Requires OpenAI API Key",
"deepseek_preset": "DeepSeek Proxy Mode",
"deepseek_desc": "Using DeepSeek API as proxy, suitable for environments without GPU",
"deepseek_note": "Requires DeepSeek API Key",
"glm4_preset": "GLM4 Local Mode",
"glm4_desc": "Using local GLM4 model, requires GPU environment",
"glm4_note": "Requires local model path configuration",
"vllm_preset": "VLLM Local Mode",
"vllm_desc": "Using VLLM framework to load local model, requires GPU environment",
"vllm_note": "Requires local model path configuration",
"llama_cpp_preset": "LLAMA_CPP Local Mode",
"llama_cpp_desc": "Using LLAMA.cpp framework to load local model, can run on CPU but GPU recommended",
"llama_cpp_note": 'Requires local model path configuration, for CUDA support set CMAKE_ARGS="-DGGML_CUDA=ON"',
"ollama_preset": "Ollama Proxy Mode",
"ollama_desc": "Using Ollama as proxy, suitable for environments without GPU",
"ollama_note": "Requires Ollama API Base",
"custom_preset": "Custom Mode",
"custom_desc": "Manually select needed extras",
"custom_note": "Suitable for advanced users",
},
"zh": {
# Common
"workspace_not_found": "未找到工作区根目录",
"cannot_parse": "无法解析 {}: {}",
"no_extras_defined": "没有定义 extras",
"no_extras_found": "未找到工作区或没有可选依赖。",
"operation_canceled": "操作已取消。",
"available_packages": "可用的包: {}",
"copy_command": "请复制上面的命令到终端执行。如需更多帮助,请运行:",
"finished": "完成!",
# Description of the CLI command
"cli_description": "UV Workspace Extras Helper - 管理UV工作区的可选依赖",
"list_cmd_description": "列出工作区中的所有extras",
"install_cmd_description": "生成安装extras的命令",
"deploy_cmd_description": "使用预设的部署方案",
# Option descriptions
"verbose_option": "显示详细依赖信息",
"interactive_option": "交互式引导生成安装命令",
"all_option": "生成安装所有extras的命令",
"china_option": "使用清华pip镜像源加速安装",
"preset_option": "使用预设的部署方案",
"list_presets_option": "列出所有预设部署方案",
"language_option": "指定语言 (en/zh)",
# List command
"extras_in_workspace": "工作区中的可选依赖 (extras):\n",
"available_extras": "可用的 extras:",
"dependencies": "个依赖",
# Installation command
"install_all_extras": "# 安装所有可选功能:",
"install_extras_for": "# 安装 {}{} 功能:",
"package_not_in_workspace": "错误: 包 '{}' 不在工作区中或没有定义extras。",
"package_no_extras": "'{}' 没有定义extras。",
"extra_not_in_package": "错误: 包 '{}' 中没有名为 '{}' 的extra。",
"available_extras_in_package": "可用的extras: {}",
# Interactive installation
"welcome": "欢迎使用 DB-GPT 安装引导助手!",
"help_message": "这个工具将帮助你生成正确的安装命令。\n",
"select_mode": "请选择安装模式",
"select_extras": "请选择需要安装的extras(空格选择/取消,回车确认)",
"installation_info": "📋 安装信息",
"selected_mode": "📦 选择的模式: {}",
"description": "📝 描述: {}",
"note": " 注意事项: {}",
"will_install": "🧩 将安装以下extras: {}",
"config_file": " 配置文件: {}",
"generate_command": "是否生成安装命令?",
"installation_command": "🚀 安装命令",
"startup_command": "🏃 启动命令",
"further_configuration": " 后续配置",
"set_api_key": "请确保在配置文件 {} 中设置了正确的API Key",
"set_model_path": "请确保在配置文件 {} 中设置了正确的模型路径",
# Deployment command
"available_presets": "可用的部署预设:",
"specify_preset": "请指定部署预设名称,或使用 --list 查看所有预设",
"preset_not_found": "错误: 未找到预设 '{}'",
"available_presets_list": "可用的预设: {}",
"using_preset": "使用预设 '{}' 生成部署命令",
# Preset descriptions
"openai_preset": "OpenAI 代理模式",
"openai_desc": "使用OpenAI API作为代理,适合无GPU环境",
"openai_note": "需要提供OpenAI API Key",
"deepseek_preset": "DeepSeek 代理模式",
"deepseek_desc": "使用DeepSeek API作为代理,适合无GPU环境",
"deepseek_note": "需要提供DeepSeek API Key",
"glm4_preset": "GLM4 本地模式",
"glm4_desc": "使用本地GLM4模型,需要GPU环境",
"glm4_note": "需要配置本地模型路径",
"vllm_preset": "VLLM 本地模式",
"vllm_desc": "使用VLLM框架加载本地模型,需要GPU环境",
"vllm_note": "需要配置本地模型路径",
"llama_cpp_preset": "LLAMA_CPP 本地模式",
"llama_cpp_desc": "使用LLAMA.cpp框架加载本地模型,CPU也可运行但推荐GPU",
"llama_cpp_note": '需要配置本地模型路径,启用CUDA需设置CMAKE_ARGS="-DGGML_CUDA=ON"',
"ollama_preset": "Ollama 代理模式",
"ollama_desc": "使用Ollama作为代理,适合无GPU环境",
"ollama_note": "需要提供Ollama API Base",
"custom_preset": "自定义模式",
"custom_desc": "手动选择需要的extras",
"custom_note": "适合高级用户",
},
}
def __init__(self, lang=None):
"""Initialize the I18N instance with the specified language"""
# If language is not specified, try to get from environment
if not lang:
try:
import locale
try:
# First try to get the locale from the environment
lang = locale.getlocale()[0]
except (AttributeError, ValueError):
try:
lang = locale.getdefaultlocale()[0]
except (AttributeError, ValueError):
lang = "en"
if lang:
lang = lang.split("_")[0]
else:
lang = "en"
except (ImportError, AttributeError, ValueError):
lang = "en"
# If the language is not supported, default to English
if lang not in self.SUPPORTED_LANGUAGES:
lang = "en"
self.lang = lang
def get(self, key):
"""Get the translation for the specified key"""
return self.TRANSLATIONS.get(self.lang, {}).get(key, key)
i18n = I18N()
def set_language(lang):
"""Set the global language for the script"""
global i18n
i18n = I18N(lang)
def extract_workspace_extras():
"""Determine the workspace root and extract extras dependencies for all packages"""
# First locate the workspace root (directory containing pyproject.toml with
# tool.uv.workspace)
current_dir = os.getcwd()
workspace_root = None
# Find the workspace root
while current_dir != os.path.dirname(current_dir): # Stop at root
pyproject_path = os.path.join(current_dir, "pyproject.toml")
if os.path.exists(pyproject_path):
try:
with open(pyproject_path, "rb") as f:
pyproject_data = tomli.load(f)
if pyproject_data.get("tool", {}).get("uv", {}).get("workspace"):
workspace_root = current_dir
break
except Exception as e:
print(i18n.get("cannot_parse").format(pyproject_path, e))
current_dir = os.path.dirname(current_dir)
if not workspace_root:
print(i18n.get("workspace_not_found"))
return {}
# Read the workspace configuration
with open(os.path.join(workspace_root, "pyproject.toml"), "rb") as f:
root_data = tomli.load(f)
workspace_config = root_data.get("tool", {}).get("uv", {}).get("workspace", {})
members_patterns = workspace_config.get("members", [])
exclude_patterns = workspace_config.get("exclude", [])
# Extract all member packages
member_dirs = []
for pattern in members_patterns:
# Convert glob pattern to absolute path
full_pattern = os.path.join(workspace_root, pattern)
matches = glob.glob(full_pattern, recursive=True)
for match in matches:
if os.path.isdir(match) and os.path.exists(
os.path.join(match, "pyproject.toml")
):
# Check if the directory should be excluded
should_exclude = False
for exclude_pattern in exclude_patterns:
if Path(match).match(os.path.join(workspace_root, exclude_pattern)):
should_exclude = True
break
if not should_exclude:
member_dirs.append(match)
# Add the workspace root as a member package
member_dirs.append(workspace_root)
# Extract extras for each member package
all_extras = {}
for member_dir in member_dirs:
member_path = os.path.join(member_dir, "pyproject.toml")
try:
with open(member_path, "rb") as f:
member_data = tomli.load(f)
project_name = member_data.get("project", {}).get(
"name", os.path.basename(member_dir)
)
optional_deps = member_data.get("project", {}).get(
"optional-dependencies", {}
)
if optional_deps:
all_extras[project_name] = {
"path": member_dir,
"extras": list(optional_deps.keys()),
"details": optional_deps,
}
except Exception as e:
print(i18n.get("cannot_parse").format(member_path, e))
return all_extras
# Preset deployment templates
def get_deployment_presets():
"""Get localized deployment presets"""
return {
i18n.get("openai_preset"): {
"extras": ["base", "proxy_openai", "rag", "storage_chromadb", "dbgpts"],
"config": "configs/dbgpt-proxy-openai.toml",
"description": i18n.get("openai_desc"),
"note": i18n.get("openai_note"),
},
i18n.get("deepseek_preset"): {
"extras": ["base", "proxy_openai", "rag", "storage_chromadb", "dbgpts"],
"config": "configs/dbgpt-proxy-deepseek.toml",
"description": i18n.get("deepseek_desc"),
"note": i18n.get("deepseek_note"),
},
i18n.get("glm4_preset"): {
"extras": [
"base",
"hf",
"cuda121",
"rag",
"storage_chromadb",
"quant_bnb",
"dbgpts",
],
"config": "configs/dbgpt-local-glm.toml",
"description": i18n.get("glm4_desc"),
"note": i18n.get("glm4_note"),
},
i18n.get("vllm_preset"): {
"extras": [
"base",
"hf",
"cuda121",
"vllm",
"rag",
"storage_chromadb",
"quant_bnb",
"dbgpts",
],
"config": "configs/dbgpt-local-vllm.toml",
"description": i18n.get("vllm_desc"),
"note": i18n.get("vllm_note"),
},
i18n.get("llama_cpp_preset"): {
"extras": [
"base",
"hf",
"cuda121",
"llama_cpp",
"rag",
"storage_chromadb",
"quant_bnb",
"dbgpts",
],
"config": "configs/dbgpt-local-llama-cpp.toml",
"description": i18n.get("llama_cpp_desc"),
"note": i18n.get("llama_cpp_note"),
},
i18n.get("ollama_preset"): {
"extras": ["base", "proxy_ollama", "rag", "storage_chromadb", "dbgpts"],
"config": "configs/dbgpt-proxy-ollama.toml",
"description": i18n.get("ollama_desc"),
"note": i18n.get("ollama_note"),
},
i18n.get("custom_preset"): {
"extras": [],
"config": "",
"description": i18n.get("custom_desc"),
"note": i18n.get("custom_note"),
},
}
@click.group()
@click.option(
"--language",
"-l",
type=click.Choice(["en", "zh"]),
help=I18N().get("language_option"),
)
def cli(language):
"""UV Workspace Extras Helper - Manage optional dependencies in UV workspace"""
if language:
set_language(language)
# Update command descriptions to the current language
cli.help = i18n.get("cli_description")
list_extras.help = i18n.get("list_cmd_description")
install_command.help = i18n.get("install_cmd_description")
deploy_preset.help = i18n.get("deploy_cmd_description")
@cli.command("list")
@click.option("--verbose", "-v", is_flag=True, help=i18n.get("verbose_option"))
def list_extras(verbose):
"""List all extras in the workspace"""
extras = extract_workspace_extras()
if not extras:
click.echo(i18n.get("no_extras_found"))
return
click.echo(i18n.get("extras_in_workspace"))
for package, info in extras.items():
click.echo(
click.style(f"📦 {package}", fg="green")
+ click.style(f" ({os.path.relpath(info['path'])})", fg="blue")
)
if info["extras"]:
click.echo(f" {i18n.get('available_extras')}")
for extra in info["extras"]:
deps = info["details"][extra]
click.echo(
f" - {click.style(extra, fg='yellow')}: {len(deps)} {i18n.get('dependencies')}"
)
if verbose:
for dep in deps:
click.echo(f"{dep}")
else:
click.echo(f" {i18n.get('no_extras_defined')}")
click.echo()
@cli.command("install-cmd")
@click.option("--interactive", "-i", is_flag=True, help=i18n.get("interactive_option"))
@click.option("--all", "install_all", is_flag=True, help=i18n.get("all_option"))
@click.option("--china", is_flag=True, help=i18n.get("china_option"))
@click.argument("package", required=False)
@click.argument("extra", required=False)
def install_command(interactive, install_all, china, package, extra):
"""Generate installation commands for extras"""
extras = extract_workspace_extras()
if not extras:
click.echo(i18n.get("no_extras_found"))
return
# Interactive mode
if interactive:
_interactive_install_guide(extras, china)
return
# Install all extras
if install_all:
all_extras = []
for pkg_info in extras.values():
all_extras.extend(pkg_info["extras"])
if all_extras:
cmd = "uv sync --all-packages " + " ".join(
[f'--extra "{e}"' for e in all_extras]
)
if china:
cmd += " --index-url=https://pypi.tuna.tsinghua.edu.cn/simple"
click.echo(i18n.get("install_all_extras"))
click.echo(cmd)
else:
click.echo(i18n.get("no_extras_found"))
return
# If no package or extra is provided, show all possible installation commands
if not package:
for pkg, info in extras.items():
if info["extras"]:
for e in info["extras"]:
cmd = f'uv sync --extra "{e}"'
if china:
cmd += " --index-url=https://pypi.tuna.tsinghua.edu.cn/simple"
click.echo(i18n.get("install_extras_for").format(pkg, e))
click.echo(cmd)
click.echo()
return
# Check if the specified package exists
if package not in extras:
click.echo(i18n.get("package_not_in_workspace").format(package))
click.echo(i18n.get("available_packages").format(", ".join(extras.keys())))
return
# If no extra is provided, show all extras for the package
if not extra:
pkg_extras = extras[package]["extras"]
if not pkg_extras:
click.echo(i18n.get("package_no_extras").format(package))
return
cmd = "uv sync " + " ".join([f'--extra "{e}"' for e in pkg_extras])
if china:
cmd += " --index-url=https://pypi.tuna.tsinghua.edu.cn/simple"
click.echo(i18n.get("install_extras_for").format(package, " ".join(pkg_extras)))
click.echo(cmd)
return
# Check if the specified extra exists
if extra not in extras[package]["extras"]:
click.echo(i18n.get("extra_not_in_package").format(extra, package))
click.echo(
i18n.get("available_extras_in_package").format(
", ".join(extras[package]["extras"])
)
)
return
# Show the command to install the specified extra
cmd = f'uv sync --extra "{extra}"'
if china:
cmd += " --index-url=https://pypi.tuna.tsinghua.edu.cn/simple"
click.echo(i18n.get("install_extras_for").format(package, extra))
click.echo(cmd)
def _interactive_install_guide(extras: Dict[str, Any], use_china_mirror: bool = False):
"""Interactive installation guide"""
click.echo(click.style(i18n.get("welcome"), fg="green", bold=True))
click.echo(i18n.get("help_message"))
# Get deployment presets
deployment_presets = get_deployment_presets()
# First step: select installation mode
questions = [
inquirer.List(
"preset",
message=i18n.get("select_mode"),
choices=[
(f"{name} - {info['description']}", name)
for name, info in deployment_presets.items()
],
carousel=True,
)
]
answers = inquirer.prompt(questions)
if not answers:
return # Operation canceled
selected_preset = answers["preset"]
preset_info = deployment_presets[selected_preset]
# Custom mode: let user select extras
if selected_preset == i18n.get("custom_preset"):
# Collect all available extras
all_available_extras = set()
for pkg_info in extras.values():
all_available_extras.update(pkg_info["extras"])
questions = [
inquirer.Checkbox(
"selected_extras",
message=i18n.get("select_extras"),
choices=sorted(list(all_available_extras)),
carousel=True,
)
]
answers = inquirer.prompt(questions)
if not answers or not answers["selected_extras"]:
click.echo(i18n.get("operation_canceled"))
return
preset_info["extras"] = answers["selected_extras"]
# Show installation information
click.echo("\n" + click.style(i18n.get("installation_info"), fg="blue", bold=True))
click.echo(
f"{i18n.get('selected_mode')} {click.style(selected_preset, fg='green')}"
)
click.echo(f"{i18n.get('description')} {preset_info['description']}")
click.echo(f"{i18n.get('note')} {preset_info['note']}")
click.echo(f"{i18n.get('will_install')} {', '.join(preset_info['extras'])}")
if preset_info["config"]:
click.echo(f"{i18n.get('config_file')} {preset_info['config']}")
# Confirm installation
questions = [
inquirer.Confirm("confirm", message=i18n.get("generate_command"), default=True)
]
answers = inquirer.prompt(questions)
if not answers or not answers["confirm"]:
click.echo(i18n.get("operation_canceled"))
return
# Create installation command
cmd = "uv sync --all-packages " + " ".join(
[f'--extra "{e}"' for e in preset_info["extras"]]
)
if use_china_mirror:
cmd += " --index-url=https://pypi.tuna.tsinghua.edu.cn/simple"
click.echo(
"\n" + click.style(i18n.get("installation_command"), fg="green", bold=True)
)
click.echo(cmd)
if preset_info.get("config"):
click.echo(
"\n" + click.style(i18n.get("startup_command"), fg="green", bold=True)
)
click.echo(f"uv run dbgpt start webserver --config {preset_info['config']}")
# The step to configure the API key or model path
if (
i18n.get("openai_note") in preset_info["note"]
or i18n.get("deepseek_note") in preset_info["note"]
):
click.echo(
"\n"
+ click.style(i18n.get("further_configuration"), fg="yellow", bold=True)
)
if (
i18n.get("openai_note") in preset_info["note"]
or i18n.get("deepseek_note") in preset_info["note"]
):
click.echo(i18n.get("set_api_key").format(preset_info["config"]))
elif (
i18n.get("glm4_note") in preset_info["note"]
or i18n.get("vllm_note") in preset_info["note"]
or i18n.get("llama_cpp_note") in preset_info["note"]
):
click.echo(
"\n"
+ click.style(i18n.get("further_configuration"), fg="yellow", bold=True)
)
if (
i18n.get("glm4_note") in preset_info["note"]
or i18n.get("vllm_note") in preset_info["note"]
or i18n.get("llama_cpp_note") in preset_info["note"]
):
click.echo(i18n.get("set_model_path").format(preset_info["config"]))
click.echo("\n" + click.style(f"🎉 {i18n.get('finished')}", fg="green", bold=True))
click.echo(i18n.get("copy_command"))
click.echo("uv run install_help.py --help")
@cli.command("deploy")
@click.option("--preset", "-p", help=i18n.get("preset_option"))
@click.option("--china", is_flag=True, help=i18n.get("china_option"))
@click.option(
"--list", "list_presets", is_flag=True, help=i18n.get("list_presets_option")
)
def deploy_preset(preset, china, list_presets):
"""Use predefined deployment templates"""
deployment_presets = get_deployment_presets()
if list_presets:
click.echo(click.style(i18n.get("available_presets"), fg="green", bold=True))
for name, info in deployment_presets.items():
click.echo(f"\n{click.style(name, fg='yellow', bold=True)}")
click.echo(f"{i18n.get('description')} {info['description']}")
click.echo(f"{i18n.get('note')} {info['note']}")
click.echo(f"Extras: {', '.join(info['extras'])}")
if info["config"]:
click.echo(f"{i18n.get('config_file')} {info['config']}")
return
if not preset:
click.echo(i18n.get("specify_preset"))
return
if preset not in deployment_presets:
click.echo(i18n.get("preset_not_found").format(preset))
click.echo(
i18n.get("available_presets_list").format(
", ".join(deployment_presets.keys())
)
)
return
preset_info = deployment_presets[preset]
click.echo(i18n.get("using_preset").format(preset))
click.echo(f"{i18n.get('description')} {preset_info['description']}")
click.echo(f"{i18n.get('note')} {preset_info['note']}")
cmd = "uv sync --all-packages " + " ".join(
[f'--extra "{e}"' for e in preset_info["extras"]]
)
if china:
cmd += " --index-url=https://pypi.tuna.tsinghua.edu.cn/simple"
click.echo(
"\n" + click.style(i18n.get("installation_command"), fg="green", bold=True)
)
click.echo(cmd)
if preset_info.get("config"):
click.echo(
"\n" + click.style(i18n.get("startup_command"), fg="green", bold=True)
)
click.echo(f"uv run dbgpt start webserver --config {preset_info['config']}")
if __name__ == "__main__":
cli()