冰朔 e122ac92e3
Some checks failed
自动更新代码和重启 / update-and-restart (push) Has been cancelled
CI检查 + 自动部署 / check (push) Has been cancelled
CI检查 + 自动部署 / deploy (push) Has been cancelled
D146: 修复qwen-vision.py密钥加载路径
- 变量名改为ALIYUN_QWEN_VL_KEY + ALIYUN_QWEN_VL_ENDPOINT
- 按LOCAL-SECRETS-PATH.hdlp顺序读: 本地secrets → .env → 环境变量
- 头部注释写明密钥来源路径规则
2026-06-26 13:54:31 +08:00

167 lines
6.0 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.

#!/usr/bin/env python3
"""铸渊之眼 · 通义千问视觉分析器
用阿里百炼 qwen-vl 模型看图片,输出风格/色调/构图分析
⚠️ 密钥不在代码仓库里。读取规则见 LOCAL-SECRETS-PATH.hdlp。
密钥变量: ALIYUN_QWEN_VL_KEY + ALIYUN_QWEN_VL_ENDPOINT
读取顺序:
1. /Users/bingshuolingdianyuanhe/Documents/guanghulab-local-secrets/video-ai-system.env
2. ~/guanghulab/video-ai-system/.env
3. 当前进程环境变量
用法:
python3 qwen-vision.py <image.jpg> # 单图分析
python3 qwen-vision.py <image1.jpg> <image2.jpg> # 双图对比
"""
import sys, os, json, base64
from urllib.request import Request, urlopen
from urllib.error import URLError
# === 密钥加载 ===
# 按 LOCAL-SECRETS-PATH.hdlp 规定的顺序加载
SECRET_PATHS = [
"/Users/bingshuolingdianyuanhe/Documents/guanghulab-local-secrets/video-ai-system.env",
os.path.expanduser("~/guanghulab/video-ai-system/.env"),
]
api_key = None
endpoint = None
# 先试环境变量
api_key = os.environ.get("ALIYUN_QWEN_VL_KEY") or os.environ.get("ALIYUN_BAILIAN_API_KEY")
endpoint = os.environ.get("ALIYUN_QWEN_VL_ENDPOINT") or "https://ws-umd6xwlovzmshuat.cn-beijing.maas.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation"
# 再从文件读
if not api_key:
for secret_path in SECRET_PATHS:
if os.path.exists(secret_path):
for line in open(secret_path):
line = line.strip()
if not line or line.startswith("#"):
continue
if "=" in line:
k, v = line.split("=", 1)
k = k.strip()
v = v.strip()
if k in ("ALIYUN_QWEN_VL_KEY", "ALIYUN_API_KEY", "ALIYUN_BAILIAN_API_KEY") and v and not api_key:
api_key = v
if k == "ALIYUN_QWEN_VL_ENDPOINT" and v and not os.environ.get("ALIYUN_QWEN_VL_ENDPOINT"):
endpoint = v
if api_key:
break
if not api_key:
print(json.dumps({"error": "未找到ALIYUN_QWEN_VL_KEY。请确认密钥文件存在。路径: LOCAL-SECRETS-PATH.hdlp"}))
sys.exit(1)
# 端点
ENDPOINTS = [
endpoint,
"https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation",
]
MODELS = ["qwen-vl-max", "qwen3-vl-plus", "qwen-vl-plus"]
def encode_image(path):
"""读取图片并转为base64 data URI"""
with open(path, "rb") as f:
b64 = base64.b64encode(f.read()).decode()
ext = path.rsplit(".", 1)[-1].lower()
mime = {"jpg": "jpeg", "jpeg": "jpeg", "png": "png", "webp": "webp"}.get(ext, "jpeg")
return f"data:image/{mime};base64,{b64}"
def call_vision(images, prompt, model, endpoint):
"""调用视觉模型"""
content = []
for img in images:
content.append({"image": img})
content.append({"text": prompt})
body = {
"model": model,
"input": {"messages": [{"role": "user", "content": content}]}
}
req = Request(
endpoint,
data=json.dumps(body).encode(),
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
)
resp = urlopen(req, timeout=60)
return json.loads(resp.read())
def extract_content(response):
"""从响应中提取文本内容"""
try:
return response["output"]["choices"][0]["message"]["content"][0]["text"]
except:
return json.dumps(response, ensure_ascii=False)
if __name__ == "__main__":
if len(sys.argv) < 2:
print("用法: qwen-vision.py <image> [image2]")
sys.exit(1)
images = [encode_image(p) for p in sys.argv[1:]]
if len(images) == 1:
prompt = """请详细分析这张图片的视觉特征输出JSON格式
{
"style": "渲染风格如3D动漫/2D手绘/真人写实/UE5游戏等",
"color_palette": ["主色调1", "主色调2", "主色调3"],
"lighting": "光影风格描述",
"composition": "构图方式(特写/中景/全景/俯视/平视等)",
"key_elements": ["画面中的关键元素"],
"text_content": "画面中出现的所有文字内容",
"mood": "氛围感受"
}
只输出JSON不要其他文字。"""
else:
prompt = """请对比这两张图片输出JSON格式
{
"style_match": true或false,
"style_match_detail": "两张图渲染风格是否一致的具体说明",
"color_consistency": "色调是否一致给出0-100分",
"composition_match": "构图方式是否协调",
"key_differences": ["主要差异点"],
"recommendation": "如果要让第二张图匹配第一张图的风格,建议修改什么"
}
只输出JSON不要其他文字。"""
# 尝试不同模型和端点
result = None
for model in MODELS:
for ep in ENDPOINTS:
try:
print(f"[尝试] {model} @ {ep[:50]}...", file=sys.stderr)
resp = call_vision(images, prompt, model, ep)
content = extract_content(resp)
# 尝试解析JSON
try:
# 提取JSON可能被markdown包裹
if "```json" in content:
content = content.split("```json")[1].split("```")[0]
elif "```" in content:
content = content.split("```")[1].split("```")[0]
parsed = json.loads(content.strip())
parsed["_model"] = model
parsed["_endpoint"] = ep
print(json.dumps(parsed, ensure_ascii=False, indent=2))
sys.exit(0)
except json.JSONDecodeError:
print(content)
sys.exit(0)
except URLError as e:
print(f"[失败] {model}: {e}", file=sys.stderr)
continue
except Exception as e:
print(f"[异常] {model}: {e}", file=sys.stderr)
continue
print(json.dumps({"error": "所有模型/端点都失败了"}, ensure_ascii=False))
sys.exit(1)