diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..78d6ca0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# Local VLM configuration (may contain secrets) — copy vlm_config.example.json +vlm_config.json + +# Environment / secrets +.env + +# Python +__pycache__/ +*.py[cod] +*$py.class +.venv/ +venv/ +env/ +.ipynb_checkpoints/ + +# Caches +.cache/ +cache/ + +# OS noise +.DS_Store diff --git a/22_vlm_robot_test.py b/22_vlm_robot_test.py index c2f31ec..1405961 100644 --- a/22_vlm_robot_test.py +++ b/22_vlm_robot_test.py @@ -8,9 +8,13 @@ 使用: python3 22_vlm_robot_test.py + python3 22_vlm_robot_test.py --model gpt5.1 -配置: - 修改API_BASE和API_KEY +配置 (优先级: 环境变量 > vlm_config.json > 默认值): + export VLM_API_KEY=你的密钥 + export VLM_API_BASE=http://localhost:8317/v1 # 可选 + export VLM_MODEL=gpt-5.1 # 可选 + 或复制 vlm_config.example.json 为 vlm_config.json 并填写 作者: wzy 日期: 2025-12-15 @@ -33,14 +37,57 @@ sys.exit(1) # ==================== 配置 ==================== +# +# 配置优先级: 环境变量 > vlm_config.json > 默认值 +# 切勿在源码中硬编码 API Key(本仓库为公开仓库)。 +# - 环境变量: VLM_API_BASE / VLM_API_KEY / VLM_MODEL +# - 配置文件: vlm_config.json (参见 vlm_config.example.json) -# ==================== 配置 ==================== -# API配置 - 使用cli-proxy-api -API_BASE = "http://localhost:8317/v1" -API_KEY = "cliproxy-ag-b9cd9ab23f51968c1afdf8fd2b7a6e26" +def load_config() -> Dict[str, str]: + """加载配置:环境变量 > 配置文件 > 默认值""" + config = { + "api_base": "http://localhost:8317/v1", + "api_key": "", + "model": "gpt-5.1", + } + + # 1. 尝试读取配置文件 + config_paths = [ + Path("vlm_config.json"), + Path.home() / ".config" / "vlm" / "config.json", + Path(__file__).parent / "vlm_config.json", + ] + for p in config_paths: + if p.exists(): + try: + file_config = json.loads(p.read_text()) + config.update(file_config) + print(f"[配置] 已加载配置文件: {p}") + break + except Exception as e: + print(f"[警告] 配置文件加载失败 {p}: {e}") + + # 2. 环境变量覆盖 + if os.environ.get("VLM_API_BASE"): + config["api_base"] = os.environ["VLM_API_BASE"] + if os.environ.get("VLM_API_KEY"): + config["api_key"] = os.environ["VLM_API_KEY"] + if os.environ.get("VLM_MODEL"): + config["model"] = os.environ["VLM_MODEL"] + + return config + + +CONFIG = load_config() +API_BASE = CONFIG["api_base"] +API_KEY = CONFIG["api_key"] + +if not API_KEY: + print("[警告] 未设置 API Key(环境变量 VLM_API_KEY 或 vlm_config.json)") + API_KEY = "test-key-placeholder" -# 可用模型列表 (用户自定义API) +# 可用模型别名 -> 实际模型 ID (用户自定义API) MODELS = { "gpt5": "gpt-5", "gpt5.1": "gpt-5.1", @@ -49,7 +96,12 @@ "gpt4o-search": "gpt-4o-search-preview", } +# 默认模型别名:优先使用配置中的 model(按其 ID 反查别名),否则回退到 gpt5.1 DEFAULT_MODEL = "gpt5.1" +for _alias, _model_id in MODELS.items(): + if _model_id == CONFIG.get("model"): + DEFAULT_MODEL = _alias + break # ==================== VLM客户端 ==================== @@ -259,14 +311,16 @@ def process_command(self, user_input: str) -> str: # ==================== 主程序 ==================== -def test_vlm_api(): +def test_vlm_api(model: str = None): """测试VLM API连接""" print("="*60) print("VLM API连接测试") print("="*60) - + vlm = VLMClient() - + if model: + vlm.set_model(model) + print("\n1. 测试基本连接...") if vlm.test_connection(): print(" ✓ API连接成功") @@ -284,17 +338,19 @@ def test_vlm_api(): return True -def interactive_mode(): +def interactive_mode(model: str = None): """交互模式""" print("="*60) print("VLM机械臂控制 - 交互模式") print("="*60) print("输入 'quit' 退出") print("输入 'model <名称>' 切换模型") - print(" 可用: flash, thinking, vision, search, code") + print(" 可用: " + ", ".join(MODELS.keys())) print() - + vlm = VLMClient() + if model: + vlm.set_model(model) controller = VLMRobotController(vlm) while True: @@ -327,13 +383,17 @@ def interactive_mode(): def main(): parser = argparse.ArgumentParser(description="VLM机械臂控制测试") parser.add_argument("--test", action="store_true", help="仅测试API连接") - parser.add_argument("--model", default="flash", help="选择模型") + parser.add_argument( + "--model", + default=DEFAULT_MODEL, + help="选择模型 (可用: " + ", ".join(MODELS.keys()) + ")", + ) args = parser.parse_args() - + if args.test: - test_vlm_api() + test_vlm_api(args.model) else: - interactive_mode() + interactive_mode(args.model) if __name__ == "__main__": diff --git a/23_vlm_full_test.py b/23_vlm_full_test.py index f121c67..0e26655 100644 --- a/23_vlm_full_test.py +++ b/23_vlm_full_test.py @@ -23,8 +23,9 @@ import json from pathlib import Path -# 添加路径 -sys.path.insert(0, '/l2k/home/wzy/21-L2Karm/12-unified-control/src') +# 添加路径(相对于本仓库的上层项目目录,避免硬编码绝对路径) +PROJECT_ROOT = Path(__file__).parent.parent +sys.path.insert(0, str(PROJECT_ROOT / "12-unified-control" / "src")) # 尝试导入 try: @@ -43,10 +44,51 @@ # ==================== 配置 ==================== +# +# 配置优先级: 环境变量 > vlm_config.json > 默认值 +# 切勿在源码中硬编码 API Key(本仓库为公开仓库)。 -API_BASE = "http://localhost:8317/v1" -API_KEY = "cliproxy-ag-b9cd9ab23f51968c1afdf8fd2b7a6e26" -MODEL = "gpt-5.1" + +def load_config() -> dict: + """加载配置:环境变量 > 配置文件 > 默认值""" + config = { + "api_base": "http://localhost:8317/v1", + "api_key": "", + "model": "gpt-5.1", + } + + config_paths = [ + Path("vlm_config.json"), + Path.home() / ".config" / "vlm" / "config.json", + Path(__file__).parent / "vlm_config.json", + ] + for p in config_paths: + if p.exists(): + try: + config.update(json.loads(p.read_text())) + print(f"[配置] 已加载配置文件: {p}") + break + except Exception as e: + print(f"[警告] 配置文件加载失败 {p}: {e}") + + if os.environ.get("VLM_API_BASE"): + config["api_base"] = os.environ["VLM_API_BASE"] + if os.environ.get("VLM_API_KEY"): + config["api_key"] = os.environ["VLM_API_KEY"] + if os.environ.get("VLM_MODEL"): + config["model"] = os.environ["VLM_MODEL"] + + return config + + +_CONFIG = load_config() +API_BASE = _CONFIG["api_base"] +API_KEY = _CONFIG["api_key"] +MODEL = _CONFIG["model"] + +if not API_KEY: + print("[警告] 未设置 API Key(环境变量 VLM_API_KEY 或 vlm_config.json)") + API_KEY = "test-key-placeholder" # 测试位置 STANDBY_POS = [0, -90, 180, 0, 0, 90] @@ -184,8 +226,8 @@ def teardown(self): try: self.robot.disconnect() print("✓ 机器人断开") - except: - pass + except Exception as e: + print(f"⚠️ 机器人断开异常: {e}") def record(self, name, success, detail=""): """记录结果""" diff --git a/25_vlm_mujoco_control.py b/25_vlm_mujoco_control.py index 591c5c7..10f9375 100644 --- a/25_vlm_mujoco_control.py +++ b/25_vlm_mujoco_control.py @@ -48,19 +48,56 @@ sys.exit(1) # ==================== VLM 配置 ==================== +# +# 配置优先级: 环境变量 > vlm_config.json > 默认值 +# 切勿在源码中硬编码 API Key(本仓库为公开仓库)。 + + +def load_config() -> Dict[str, str]: + """加载配置:环境变量 > 配置文件 > 默认值""" + config = { + "api_base": "http://localhost:8317/v1", + "api_key": "", + "model": "gpt-4o", + } + + config_paths = [ + Path("vlm_config.json"), + Path.home() / ".config" / "vlm" / "config.json", + Path(__file__).parent / "vlm_config.json", + ] + for p in config_paths: + if p.exists(): + try: + config.update(json.loads(p.read_text())) + print(f"[配置] 已加载配置文件: {p}") + break + except Exception as e: + print(f"[警告] 配置文件加载失败 {p}: {e}") + + if os.environ.get("VLM_API_BASE"): + config["api_base"] = os.environ["VLM_API_BASE"] + if os.environ.get("VLM_API_KEY"): + config["api_key"] = os.environ["VLM_API_KEY"] + if os.environ.get("VLM_MODEL"): + config["model"] = os.environ["VLM_MODEL"] + + return config + -# 优先从环境变量读取,否则使用默认值 -API_BASE = os.environ.get("VLM_API_BASE", "http://localhost:8317/v1") -API_KEY = os.environ.get("VLM_API_KEY", "") # 必须通过环境变量设置 -MODEL = os.environ.get("VLM_MODEL", "gpt-4o") +_CONFIG = load_config() +API_BASE = _CONFIG["api_base"] +API_KEY = _CONFIG["api_key"] +MODEL = _CONFIG["model"] # 检查 API 密钥 if not API_KEY: - config_file = Path.home() / ".config" / "vlm" / "api_key" - if config_file.exists(): - API_KEY = config_file.read_text().strip() + # 兼容旧的密钥文件 + old_key_file = Path.home() / ".config" / "vlm" / "api_key" + if old_key_file.exists(): + API_KEY = old_key_file.read_text().strip() else: - print("[警告] 未设置 VLM_API_KEY 环境变量") + print("[警告] 未设置 API Key(环境变量 VLM_API_KEY 或 vlm_config.json)") API_KEY = "test-key-placeholder" # ==================== VLM 客户端 ==================== @@ -120,7 +157,7 @@ def parse_command(self, user_input: str) -> Dict[str, Any]: if response.startswith("json"): response = response[4:] return json.loads(response) - except: + except (json.JSONDecodeError, ValueError, IndexError): return {"command": "chat", "params": {"response": response}} diff --git a/26_vlm_mujoco_grasp.py b/26_vlm_mujoco_grasp.py index 2a0ea18..8a62d07 100644 --- a/26_vlm_mujoco_grasp.py +++ b/26_vlm_mujoco_grasp.py @@ -253,7 +253,7 @@ def parse_command(self, user_input: str) -> Dict[str, Any]: if response.startswith("json"): response = response[4:] return json.loads(response) - except: + except (json.JSONDecodeError, ValueError, IndexError): return {"command": "chat", "params": {"response": response}} @@ -542,8 +542,9 @@ def place(self, location: str = "left") -> bool: "front": [0, -0.6, 0.6, 0, 0, 0], # 前方 } - joints = location_joints.get(location, location_joints["center"]) - + # 复制一份,避免就地修改 location_joints 中共享的列表 + joints = list(location_joints.get(location, location_joints["center"])) + # 1. 移动到放置位置 print(f" 阶段1: 移动到 {location}") self.set_arm_joints(joints) diff --git a/README.md b/README.md index 0103063..0811fa1 100644 --- a/README.md +++ b/README.md @@ -167,6 +167,29 @@ GEMINI_API_KEY=你的_Gemini_API_密钥 获取 API 密钥: https://makersuite.google.com/app/apikey +#### VLM 驱动脚本配置(22_/23_/25_/26_) + +顶层的 `22_vlm_robot_test.py`、`23_vlm_full_test.py`、`25_vlm_mujoco_control.py`、 +`26_vlm_mujoco_grasp.py` 使用 OpenAI 兼容接口,配置优先级统一为 +**环境变量 > `vlm_config.json` > 默认值**。请勿在源码中硬编码密钥。 + +方式一:环境变量 +```bash +export VLM_API_KEY=你的密钥 +export VLM_API_BASE=http://localhost:8317/v1 # 可选,默认即此值 +export VLM_MODEL=gpt-5.1 # 可选 +``` + +方式二:配置文件(复制示例并填写,`vlm_config.json` 已被 `.gitignore` 忽略) +```bash +cp vlm_config.example.json vlm_config.json +# 然后编辑 vlm_config.json 填入 api_base / api_key / model +``` + +> 注意:本仓库历史曾包含一个硬编码密钥,请务必在提供方处轮换该密钥。 + +获取 OpenAI 兼容接口/密钥取决于你所使用的代理或服务商。 + --- ## 📖 使用方法 diff --git a/README_en.md b/README_en.md index c43d8a0..acad01c 100644 --- a/README_en.md +++ b/README_en.md @@ -82,6 +82,36 @@ The platform is designed for continuous evolution. Potential areas for expansion This project demonstrates the transformation of a hobbyist robotic platform into a research-grade system through systematic engineering and community collaboration. By addressing real needs and responding to user feedback, the system has grown beyond its original scope to become a valuable tool for the robotics community. The combination of robust engineering, practical problem-solving, and collaborative development exemplifies the approach needed for advancing robotics research. +## VLM Driver Scripts Configuration + +The top-level driver scripts (`22_vlm_robot_test.py`, `23_vlm_full_test.py`, +`25_vlm_mujoco_control.py`, `26_vlm_mujoco_grasp.py`) talk to an OpenAI-compatible +endpoint. Configuration is resolved with a single precedence: +**environment variables > `vlm_config.json` > built-in defaults**. Never hardcode a +key in source — this is a public repository. + +Option A — environment variables: +```bash +export VLM_API_KEY=your_key +export VLM_API_BASE=http://localhost:8317/v1 # optional (this is the default) +export VLM_MODEL=gpt-5.1 # optional +``` + +Option B — config file (copy the example; `vlm_config.json` is gitignored): +```bash +cp vlm_config.example.json vlm_config.json +# then edit vlm_config.json with your api_base / api_key / model +``` + +Install the runtime dependencies via the packaging metadata: +```bash +pip install . # installs openai (required by all driver scripts) +pip install ".[sim]" # adds mujoco + numpy for the simulation scripts (25_/26_) +``` + +> Note: this repository's history previously contained a hardcoded API key. Rotate +> that key with your provider. + --- *For technical documentation, installation instructions, and API reference, please refer to the comprehensive documentation files included in the repository.* diff --git a/pyproject.toml b/pyproject.toml index 06255ae..2743b6e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,21 @@ [project] name = "my_project" version = "0.1.0" +description = "PAROL6 Python API + Gemini Vision, with VLM-driven control/sim driver scripts" +requires-python = ">=3.9" + +# Runtime dependencies used by the VLM driver scripts (22_/23_/25_/26_). +# The OpenAI client is required by all four scripts. +dependencies = [ + "openai", +] + +[project.optional-dependencies] +# MuJoCo simulation driver scripts (25_/26_). +sim = [ + "mujoco", + "numpy", +] [tool.setuptools] packages = ["Headless"] \ No newline at end of file diff --git a/vlm_config.example.json b/vlm_config.example.json new file mode 100644 index 0000000..bb2c55e --- /dev/null +++ b/vlm_config.example.json @@ -0,0 +1,5 @@ +{ + "api_base": "http://localhost:8317/v1", + "api_key": "YOUR_API_KEY_HERE", + "model": "gpt-5.1" +} diff --git a/vlm_config.json b/vlm_config.json deleted file mode 100644 index e670f29..0000000 --- a/vlm_config.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "api_base": "http://192.168.1.102:8000/v1", - "api_key": "sk-ai-proxy-key", - "model": "gemini-3-pro-preview" -}