初始发布: 21 个 skills (Claude Code / Codex / DSH)
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
---
|
||||
name: minimax-vision
|
||||
description: 用 MiniMax-M3(火山方舟 Coding Plan)做图像识别与理解。触发关键词:「看图」「识图」「这张图」「图片里有什么」「读一下截图」「OCR」「识别文字」「看看这个截图」「分析这张图」「图表数据提取」「图像识别」「minimax 看图」。当用户直接发送图片、没有附带其他明确指令时,也自动调用本技能识图。能力:物体/颜色/计数识别、OCR 文字提取(含数字符号)、图表数据读取、空间关系判断、UI 截图分析、多图对比。不用于:生成图片(那是 glm-image / seedream)。
|
||||
---
|
||||
|
||||
# MiniMax-M3 图像识别
|
||||
|
||||
调用火山方舟 Coding Plan 的 `minimax-m3` 视觉模型识别图片。实测 4/4 满分(计数/OCR/图表/空间关系),平均响应约 3 秒。
|
||||
|
||||
## 脚本
|
||||
|
||||
`scripts/vision.py`(Python 3 标准库,无第三方依赖)
|
||||
|
||||
## API Key
|
||||
|
||||
优先读环境变量 `ARK_CP_API_KEY`,否则读 `~/.config/ark_cp_api_key`。不要把 key 写进代码或提交到仓库。
|
||||
|
||||
用的是火山**Coding Plan** 端点 `https://ark.cn-beijing.volces.com/api/coding/v3`(和 Agent Plan 的 key 不通用)。
|
||||
|
||||
## 用法
|
||||
|
||||
```bash
|
||||
V=~/.codex/skills/minimax-vision/scripts/vision.py
|
||||
|
||||
# 默认: 描述图片
|
||||
python3 "$V" screenshot.png
|
||||
|
||||
# 指定问题
|
||||
python3 "$V" invoice.png -p "提取发票号和总金额"
|
||||
|
||||
# OCR
|
||||
python3 "$V" doc.jpg -p "把图中所有文字原样读出来,只输出文字"
|
||||
|
||||
# 图表取数
|
||||
python3 "$V" chart.png -p "这个柱状图每根柱子的数值分别是多少?"
|
||||
|
||||
# 结构化输出(自动追加"只输出JSON"约束)
|
||||
python3 "$V" form.png -p "提取表单字段" --json
|
||||
|
||||
# 多图对比
|
||||
python3 "$V" before.png after.png -p "这两张图有什么不同?"
|
||||
|
||||
# 网络图片
|
||||
python3 "$V" https://example.com/pic.jpg -p "图里是什么?"
|
||||
|
||||
# 看耗时/token/request_id(输出到 stderr,不污染正文)
|
||||
python3 "$V" a.png --detail
|
||||
|
||||
# 长提问从 stdin 读
|
||||
cat question.txt | python3 "$V" a.png -p -
|
||||
```
|
||||
|
||||
## 参数
|
||||
|
||||
- `-p/--prompt` 提问,默认"详细描述这张图片的内容";传 `-` 从 stdin 读
|
||||
- `--json` 追加"只输出 JSON"约束,便于程序解析
|
||||
- `--max-tokens` 默认 2048;**返回空内容时优先调大这个值**(推理可能吃光额度)
|
||||
- `--detail` 在 stderr 打印耗时 / token 用量 / request_id
|
||||
|
||||
## 注意
|
||||
|
||||
- 支持 png/jpg/webp/gif 等常见格式,本地文件自动转 base64,http(s) 链接直接透传
|
||||
- 图片越大越慢,超大图建议先压到 2000px 以内
|
||||
- 报错 `模型返回空内容` → 加大 `--max-tokens`
|
||||
- 报错 401 → 检查 key 是不是拿成了 Agent Plan 的(两个套餐 key 不通用)
|
||||
Executable
+82
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python3
|
||||
"""MiniMax-M3 图像识别 (火山方舟 Coding Plan)
|
||||
|
||||
用法:
|
||||
vision.py <图片路径或URL> [-p 提问] [--json] [--max-tokens N] [--detail]
|
||||
vision.py a.png b.jpg -p "这两张图有什么区别?" # 多图对比
|
||||
cat prompt.txt | vision.py a.png -p - # 从 stdin 读提问
|
||||
|
||||
Key 优先级: 环境变量 ARK_CP_API_KEY > ~/.config/ark_cp_api_key
|
||||
"""
|
||||
import argparse, base64, json, mimetypes, os, sys, time, urllib.request, urllib.error
|
||||
|
||||
ENDPOINT = "https://ark.cn-beijing.volces.com/api/coding/v3/chat/completions"
|
||||
MODEL = "minimax-m3"
|
||||
|
||||
|
||||
def load_key():
|
||||
k = os.environ.get("ARK_CP_API_KEY")
|
||||
if k:
|
||||
return k.strip()
|
||||
p = os.path.expanduser("~/.config/ark_cp_api_key")
|
||||
if os.path.exists(p):
|
||||
return open(p, encoding="utf-8").read().strip()
|
||||
sys.exit("错误: 未找到 API key。请设置环境变量 ARK_CP_API_KEY 或写入 ~/.config/ark_cp_api_key")
|
||||
|
||||
|
||||
def to_part(src):
|
||||
"""本地文件转 base64 data URI; http(s) 链接直接透传"""
|
||||
if src.startswith(("http://", "https://")):
|
||||
return {"type": "image_url", "image_url": {"url": src}}
|
||||
if not os.path.exists(src):
|
||||
sys.exit(f"错误: 文件不存在 {src}")
|
||||
mime = mimetypes.guess_type(src)[0] or "image/png"
|
||||
if not mime.startswith("image/"):
|
||||
sys.exit(f"错误: 不是图片文件 {src} ({mime})")
|
||||
b64 = base64.b64encode(open(src, "rb").read()).decode()
|
||||
return {"type": "image_url", "image_url": {"url": f"data:{mime};base64,{b64}"}}
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description="MiniMax-M3 图像识别")
|
||||
ap.add_argument("images", nargs="+", help="图片路径或 URL, 可多张")
|
||||
ap.add_argument("-p", "--prompt", default="详细描述这张图片的内容。",
|
||||
help="提问; 传 - 表示从 stdin 读")
|
||||
ap.add_argument("--json", action="store_true", help="要求模型只输出 JSON")
|
||||
ap.add_argument("--max-tokens", type=int, default=2048)
|
||||
ap.add_argument("--detail", action="store_true", help="输出耗时/token/request_id")
|
||||
a = ap.parse_args()
|
||||
|
||||
prompt = sys.stdin.read().strip() if a.prompt == "-" else a.prompt
|
||||
if a.json:
|
||||
prompt += "\n\n只输出一个 JSON 对象,不要任何解释、不要代码块标记。"
|
||||
|
||||
content = [{"type": "text", "text": prompt}] + [to_part(s) for s in a.images]
|
||||
body = json.dumps({"model": MODEL, "max_tokens": a.max_tokens,
|
||||
"messages": [{"role": "user", "content": content}]}).encode()
|
||||
req = urllib.request.Request(ENDPOINT, data=body, headers={
|
||||
"Authorization": f"Bearer {load_key()}", "x-api-key": load_key(),
|
||||
"Content-Type": "application/json"})
|
||||
|
||||
t0 = time.time()
|
||||
try:
|
||||
d = json.load(urllib.request.urlopen(req, timeout=600))
|
||||
except urllib.error.HTTPError as e:
|
||||
sys.exit(f"API 错误 {e.code}: {e.read().decode('utf-8', 'replace')[:400]}")
|
||||
except Exception as e:
|
||||
sys.exit(f"请求失败: {e}")
|
||||
dt = time.time() - t0
|
||||
|
||||
text = (d["choices"][0]["message"].get("content") or "").strip()
|
||||
if not text:
|
||||
sys.exit("模型返回空内容 (可能 max_tokens 太小被推理耗尽, 试试调大 --max-tokens)")
|
||||
print(text)
|
||||
|
||||
if a.detail:
|
||||
u = d.get("usage", {})
|
||||
print(f"\n--- {dt:.1f}s | in {u.get('prompt_tokens')} / out {u.get('completion_tokens')} tok "
|
||||
f"| {d.get('id')}", file=sys.stderr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user