Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -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
94 changes: 77 additions & 17 deletions 22_vlm_robot_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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",
Expand All @@ -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客户端 ====================
Expand Down Expand Up @@ -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连接成功")
Expand All @@ -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:
Expand Down Expand Up @@ -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__":
Expand Down
56 changes: 49 additions & 7 deletions 23_vlm_full_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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]
Expand Down Expand Up @@ -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=""):
"""记录结果"""
Expand Down
55 changes: 46 additions & 9 deletions 25_vlm_mujoco_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 客户端 ====================
Expand Down Expand Up @@ -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):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): The narrowed exception list for JSON parsing might still miss some realistic failure modes

The previous bare except was too broad, but this new list is likely too narrow. For instance, if response is not a string (e.g., None or a dict), json.loads will raise TypeError, which now escapes and can break the command loop. Consider adding TypeError (and possibly KeyError if the upstream structure changes) or using a more generic Exception, since all parsing failures should trigger the same conservative fallback here.

Suggested change
except (json.JSONDecodeError, ValueError, IndexError):
except (json.JSONDecodeError, TypeError, ValueError, IndexError, KeyError):

return {"command": "chat", "params": {"response": response}}


Expand Down
7 changes: 4 additions & 3 deletions 26_vlm_mujoco_grasp.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}}


Expand Down Expand Up @@ -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)
Expand Down
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 兼容接口/密钥取决于你所使用的代理或服务商。

---

## 📖 使用方法
Expand Down
Loading