add agnes-image

This commit is contained in:
朱潮 2026-06-14 08:16:00 +08:00
parent 195bd49236
commit ec2e0acea2
4 changed files with 772 additions and 0 deletions

View File

@ -0,0 +1,254 @@
---
name: agnes-image
description: 使用 Agnes AIOpenAI 兼容网关)生成与理解图片、生成视频。支持文生图、图生图(保持角色一致性)、贴纸/卡通形象生成、白底转透明 PNG、多模态图片理解描述/分析/问答/OCR以及文生视频、图生视频、多图视频、关键帧动画。
category: Creative Generation
---
# Agnes Image
本 Skill 封装了 Agnes AI 的图像生成能力(模型 `agnes-image-2.1-flash`),接口兼容 OpenAI 风格,支持文生图、图生图,并内置「白底转透明」后处理,适合批量生成游戏/应用所需的角色、图标、贴纸等素材。
## 前置条件
设置 API Key二选一
```bash
export AGNES_API_KEY="sk-xxxxxxxx"
```
或在每次调用时通过 `--api-key` 传入。
## 使用方法
### 文生图
生成单张图片,输出可访问的图片链接:
```bash
python {baseDir}/scripts/generate_image.py --prompt "一只圆润萌系的小火龙,大眼睛,贴纸风格"
```
指定尺寸:
```bash
python {baseDir}/scripts/generate_image.py --prompt "壮丽的山川日出" --size "1024x768"
```
### 图生图(保持一致性)
提供参考图 URL 或 `data:image` Base64让新图沿用参考图的角色/构图:
```bash
python {baseDir}/scripts/generate_image.py \
--prompt "把这只小龙变成展翅的成年形态,保持配色和脸部不变" \
--image "https://example.com/baby-dragon.png"
```
可多次指定 `--image` 传入多张参考图。
### 下载保存到本地
`--save`,脚本会下载图片并保存,输出 `SAVED:<path>`
```bash
python {baseDir}/scripts/generate_image.py --prompt "一只小猫贴纸" --save ./outputs/cat.png
```
### 白底转透明 PNG
配合 `--save --transparent`,自动把纯白背景抠成透明(保留主体内部白色,如白描边、白肚皮):
```bash
python {baseDir}/scripts/generate_image.py \
--prompt "一只圆润萌系企鹅,白色背景,贴纸风格" \
--save ./outputs/penguin.png --transparent
```
边缘若有白色残留,可调高容差 `--thresh`(默认 60
## 参数说明
- `--prompt`: (必选) 图像生成的文本描述,支持中英文。
- `--model`: (可选) 模型 ID默认 `agnes-image-2.1-flash`
- `--size`: (可选) 图像尺寸,如 `1024x1024`、`1024x768`,默认 `1024x1024`
- `--image`: (可选) 参考图 URL 或 `data:image/...;base64,...`,用于图生图,可多次指定。
- `--api-key`: (可选) Agnes API Key未提供时读取 `AGNES_API_KEY` 环境变量。
- `--save`: (可选) 下载并保存到指定本地路径。
- `--transparent`: (可选) 配合 `--save`,把纯白背景转为透明 PNG需 Pillow
- `--thresh`: (可选) 透明化时的白色容差,默认 60值越大清除越彻底。
## 工作流
1. 调用 `generate_image.py` 脚本。
2. 脚本始终输出以 `MEDIA_URL: ` 开头的图片链接,用 Markdown 展示:`![image](URL)`。
3. 加了 `--save` 时,会额外下载到本地并输出以 `SAVED: ` 开头的文件路径(`MEDIA_URL` 仍会一并输出)。
4. 生成的图片链接来自 Agnes 平台输出存储,可直接公网访问。
> 注:`--save --transparent` 时,`MEDIA_URL` 指向 Agnes 返回的原始白底图,本地 `SAVED` 文件才是抠好的透明 PNG。
## 批量生成小贴士
生成系列素材(如角色的多个进化阶段)时,建议:
- 在每个 prompt 中固定统一的「风格描述」(如 `chibi kawaii sticker, big eyes, white background`),保证视觉一致。
- 需要严格保持同一角色时,用 `--image` 把上一阶段的图当参考做图生图。
- 透明素材直接用 `--save --transparent`,省去手动抠图。
## 注意事项
- 接口兼容 OpenAI 风格Base URL 为 `https://apihub.agnes-ai.com/v1`
- API Key 属于敏感信息,请勿提交到公开仓库或硬编码到代码中。
- 图生图时,确保参考图 URL 可公开访问。
- 透明化基于「边缘漫水填充」,仅对纯色(白)背景效果最佳;复杂背景请勿使用 `--transparent`
---
## 图片理解(多模态)
使用 `understand_image.py` 让模型(默认 `agnes-2.0-flash`)基于图片进行描述、分析、问答或信息提取。支持公网图片 URL也支持本地图片文件自动转 Base64
### 理解图片 URL
```bash
python {baseDir}/scripts/understand_image.py \
--prompt "用中文描述这张图片的内容和风格" \
--image "https://example.com/image.jpg"
```
### 理解本地图片
```bash
python {baseDir}/scripts/understand_image.py \
--prompt "这是什么动物?什么颜色?" \
--image-file ./outputs/fox.png
```
### 多图对比 / 信息提取
可多次指定 `--image``--image-file` 传入多张图片:
```bash
python {baseDir}/scripts/understand_image.py \
--prompt "比较这两张图的差异" \
--image-file ./a.png --image-file ./b.png
```
### 带系统提示
```bash
python {baseDir}/scripts/understand_image.py \
--prompt "提取图中所有文字" \
--image "https://example.com/poster.jpg" \
--system "你是专业的 OCR 助手,只输出图中文字"
```
### 图片理解参数说明
- `--prompt`: (必选) 对图片的问题或指令。
- `--image`: (可选) 图片公网 URL 或 `data:image` Base64可多次指定。
- `--image-file`: (可选) 本地图片文件路径,自动转 Base64可多次指定。
- `--system`: (可选) 系统提示system 消息)。
- `--model`: (可选) 模型 ID默认 `agnes-2.0-flash`
- `--temperature`: (可选) 采样温度,越低越确定。
- `--max-tokens`: (可选) 最多生成的 token 数。
- `--api-key`: (可选) Agnes API Key未提供时读取 `AGNES_API_KEY` 环境变量。
### 图片理解工作流
1. 调用 `understand_image.py` 脚本。
2. 脚本直接输出模型的文本回答(无前缀),可直接读取使用。
3. 不传任何图片时,会退化为普通文本对话。
---
## 视频生成(异步)
使用 `generate_video.py` 生成视频(模型 `agnes-video-v2.0`)。视频生成是异步任务,脚本会自动创建任务并轮询,完成后输出视频链接。生成通常需要 1-3 分钟。
> ⚠️ **重要:视频生成用到的所有参考图(图生视频 / 多图 / 关键帧)必须是「可公网访问的图片 URL」**`http://` 或 `https://`**不支持本地文件路径,也不支持 Base64**。
> 如果你手上只有本地图片,请先:
> 1. 用 `generate_image.py` 生成图片,它返回的 `MEDIA_URL` 就是可直接使用的公网 URL
> 2. 把本地图片上传到图床 / 对象存储(如 S3、R2使用其公网 URL。
>
> 脚本会校验 `--image` 是否为合法 URL传入本地路径会直接报错并给出提示。
### 文生视频
```bash
python {baseDir}/scripts/generate_video.py \
--prompt "A cinematic shot of a cat walking on the beach at sunset, warm golden lighting" \
--duration 5
```
### 图生视频(让单张图动起来)
```bash
python {baseDir}/scripts/generate_video.py \
--prompt "The character breathes gently, hair moving in the wind, keep face consistent" \
--image "https://example.com/character.png" --duration 5
```
### 多图视频(在多张图之间过渡)
```bash
python {baseDir}/scripts/generate_video.py \
--prompt "Smooth transformation between the two scenes, cinematic pacing" \
--image "https://example.com/a.png" --image "https://example.com/b.png"
```
### 关键帧动画
```bash
python {baseDir}/scripts/generate_video.py \
--prompt "Smooth transition between keyframes, maintain character identity" \
--image "https://example.com/k1.png" --image "https://example.com/k2.png" \
--keyframes
```
### 下载保存
```bash
python {baseDir}/scripts/generate_video.py --prompt "日出延时摄影" --duration 5 --save ./out.mp4
```
### 视频参数说明
- `--prompt`: (必选) 视频内容的文本描述。
- `--model`: (可选) 模型 ID默认 `agnes-video-v2.0`
- `--image`: (可选) 参考图 URL1 张=图生视频2 张及以上=多图视频,可多次指定。
- `--keyframes`: (可选) 关键帧动画模式(配合多张 `--image`)。
- `--duration`: (可选) 目标时长(秒),按帧率自动换算帧数(覆盖 `--num-frames`)。
- `--num-frames`: (可选) 帧数,必须 ≤441 且满足 8n+1默认 121约 5 秒);脚本会自动规整。
- `--frame-rate`: (可选) 帧率 1-60默认 24。
- `--width` / `--height`: (可选) 分辨率,默认 1152×768服务端会标准化到最接近的档位。
- `--negative-prompt`: (可选) 负向提示词。
- `--seed`: (可选) 随机种子,用于结果复现。
- `--save`: (可选) 下载视频并保存到本地。
- `--poll-interval`: (可选) 轮询间隔秒数,默认 5。
- `--max-wait`: (可选) 最大等待秒数,默认 600。
- `--api-key`: (可选) Agnes API Key未提供时读取 `AGNES_API_KEY` 环境变量。
### 常用时长参数
| 目标时长 | 推荐参数 |
|---------|---------|
| 约 3 秒 | `--num-frames 81 --frame-rate 24` |
| 约 5 秒 | `--num-frames 121 --frame-rate 24` |
| 约 10 秒 | `--num-frames 241 --frame-rate 24` |
| 约 18 秒 | `--num-frames 441 --frame-rate 24` |
> 也可以直接用 `--duration <秒数>` 让脚本自动换算(会规整到合法的 8n+1 帧数)。
### 视频生成工作流
1. 调用 `generate_video.py`,脚本自动创建任务(`POST /v1/videos`)。
2. 用返回的 `video_id` 轮询查询(`GET /agnesapi?video_id=...`),进度打到 stderr。
3. 任务 `completed`stdout 输出以 `MEDIA_URL: ` 开头的视频链接。
4. 加 `--save` 时额外下载并输出 `SAVED: ` 路径。
### 视频注意事项
- `num_frames` 必须 ≤441 且满足 8n+1如 81、121、241、441脚本会自动规整非法值。
- 实际输出尺寸/时长以接口返回的 `size`、`seconds` 字段为准(服务端会标准化分辨率)。
- **参考图必须是可公网访问的 URL`http`/`https`),不支持本地文件或 Base64**;本地图片请先用 `generate_image.py` 拿到公网 URL或上传图床后再传入。
- 轮询期间对瞬时网络抖动有容错,会自动重试。

View File

@ -0,0 +1,186 @@
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "requests>=2.31.0",
# "pillow>=10.0.0",
# ]
# ///
"""使用 Agnes AIOpenAI 兼容网关)生成图片。
支持文生图图生图参考图保持一致性URL/Base64 输出下载保存白底转透明
"""
import argparse
import io
import json
import os
import sys
import time
import urllib.request
import requests
API_URL = "https://apihub.agnes-ai.com/v1/images/generations"
DEFAULT_MODEL = "agnes-image-2.1-flash"
def call_api(prompt, model, size, api_key, images=None, want_b64=False, retries=3):
"""调用 Agnes 图像生成接口,返回解析后的 JSON。
对瞬时网络/SSL 抖动自动重试重试间隔递增
"""
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}",
}
extra = {"response_format": "b64_json" if want_b64 else "url"}
if images:
# 图生图:参考图放在 extra_body.image支持公网 URL 或 data:image Base64
extra["image"] = images
payload = {
"model": model,
"prompt": prompt,
"size": size,
"extra_body": extra,
}
# 文生图需要 Base64 输出时,按文档使用顶层 return_base64
if want_b64 and not images:
payload["return_base64"] = True
last_err = None
for attempt in range(1, retries + 1):
try:
resp = requests.post(API_URL, headers=headers, json=payload, timeout=180)
resp.raise_for_status()
return resp.json()
except (requests.exceptions.SSLError,
requests.exceptions.ConnectionError,
requests.exceptions.Timeout) as e:
# 瞬时网络抖动:重试
last_err = e
if attempt < retries:
time.sleep(attempt * 2)
continue
raise
raise last_err
def fetch_bytes(url):
"""下载 URL 内容为字节。"""
with urllib.request.urlopen(url, timeout=180) as r:
return r.read()
def make_transparent(png_bytes, thresh=60, white_min=225):
"""把纯白背景变透明(保留内部白色)。
从图像四边多个种子点做 flood fill只清除与边缘连通的白色区域
"""
from PIL import Image, ImageDraw
img = Image.open(io.BytesIO(png_bytes)).convert("RGBA")
w, h = img.size
seeds = []
steps = 12
for i in range(steps + 1):
t = int(i * (w - 1) / steps)
seeds += [(t, 0), (t, h - 1)]
s = int(i * (h - 1) / steps)
seeds += [(0, s), (w - 1, s)]
for sx, sy in seeds:
r, g, b, a = img.getpixel((sx, sy))
if a > 0 and r >= white_min and g >= white_min and b >= white_min:
ImageDraw.floodfill(img, (sx, sy), (255, 255, 255, 0), thresh=thresh)
out = io.BytesIO()
img.save(out, "PNG")
return out.getvalue()
def main():
parser = argparse.ArgumentParser(description="使用 Agnes AI 生成图片。")
parser.add_argument("--prompt", required=True, help="图像生成的文本描述(必选)")
parser.add_argument("--model", default=DEFAULT_MODEL, help=f"模型 ID默认 {DEFAULT_MODEL}")
parser.add_argument("--size", default="1024x1024", help="图像尺寸,如 1024x1024、1024x768")
parser.add_argument("--image", action="append", default=None,
help="参考图 URL 或 data:image Base64图生图可多次指定")
parser.add_argument("--api-key", help="Agnes API Key也可用 AGNES_API_KEY 环境变量)")
parser.add_argument("--save", help="下载并保存到本地路径,输出 SAVED:<path>")
parser.add_argument("--transparent", action="store_true",
help="配合 --save把纯白背景转为透明 PNG")
parser.add_argument("--thresh", type=int, default=60, help="透明化白色容差,默认 60")
args = parser.parse_args()
api_key = args.api_key or os.environ.get("AGNES_API_KEY")
if not api_key:
print("ERROR: 缺少 API Key请用 --api-key 或设置 AGNES_API_KEY 环境变量。")
sys.exit(1)
# 需要保存且要透明时,直接请求 URL 再下载处理Pillow 处理本地字节)
want_b64 = False
try:
result = call_api(
prompt=args.prompt,
model=args.model,
size=args.size,
api_key=api_key,
images=args.image,
want_b64=want_b64,
)
except requests.exceptions.RequestException as e:
print(f"ERROR: API 请求失败: {e}")
if getattr(e, "response", None) is not None:
print(f"Response body: {e.response.text[:500]}")
sys.exit(1)
data = result.get("data") or []
if not data:
print(f"ERROR: 返回中无图像数据。完整响应: {json.dumps(result)[:500]}")
sys.exit(1)
item = data[0]
url = item.get("url")
b64 = item.get("b64_json")
if not args.save:
# 仅输出链接(对齐 seedream 约定)
if url:
print(f"MEDIA_URL: {url}")
elif b64:
print("ERROR: 收到 Base64 但未指定 --save无法直接展示。请加 --save 保存。")
sys.exit(1)
else:
print(f"ERROR: 无 url 也无 b64_json。响应: {json.dumps(result)[:500]}")
sys.exit(1)
return
# 保存到本地
try:
if b64:
import base64
img_bytes = base64.b64decode(b64)
elif url:
img_bytes = fetch_bytes(url)
else:
print("ERROR: 无可保存的图像数据。")
sys.exit(1)
if args.transparent:
try:
img_bytes = make_transparent(img_bytes, thresh=args.thresh)
except ImportError:
print("ERROR: --transparent 需要 Pillow请先 `pip install pillow`。")
sys.exit(1)
os.makedirs(os.path.dirname(os.path.abspath(args.save)), exist_ok=True)
with open(args.save, "wb") as f:
f.write(img_bytes)
# 即使保存到本地,也输出原始图片链接(便于直接引用/展示)
if url:
print(f"MEDIA_URL: {url}")
print(f"SAVED: {args.save}")
except Exception as e:
print(f"ERROR: 保存失败: {e}")
sys.exit(1)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,206 @@
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "requests>=2.31.0",
# ]
# ///
"""使用 Agnes AI 生成视频(异步任务:创建 -> 轮询 -> 取结果)。
支持文生视频图生视频多图视频关键帧动画
"""
import argparse
import json
import os
import shutil
import sys
import time
import urllib.parse
import urllib.request
import requests
CREATE_URL = "https://apihub.agnes-ai.com/v1/videos"
QUERY_URL = "https://apihub.agnes-ai.com/agnesapi"
DEFAULT_MODEL = "agnes-video-v2.0"
def log(msg):
"""进度信息打到 stderr保持 stdout 只输出最终结果。"""
print(msg, file=sys.stderr, flush=True)
def normalize_frames(nf):
"""num_frames 必须 <=441 且满足 8n+1规整到最接近的合法值。"""
nf = max(9, min(441, int(nf)))
n = round((nf - 1) / 8)
nf = 8 * n + 1
return max(9, min(441, nf))
def create_task(payload, api_key, retries=3):
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}",
}
last_err = None
for attempt in range(1, retries + 1):
try:
resp = requests.post(CREATE_URL, headers=headers, json=payload, timeout=120)
resp.raise_for_status()
return resp.json()
except (requests.exceptions.SSLError,
requests.exceptions.ConnectionError,
requests.exceptions.Timeout) as e:
last_err = e
if attempt < retries:
time.sleep(attempt * 2)
continue
raise
raise last_err
def query_result(video_id, api_key, model=None):
"""用 video_id 查询任务结果(推荐方式)。"""
params = f"?video_id={urllib.parse.quote(video_id)}"
if model:
params += f"&model_name={urllib.parse.quote(model)}"
req = urllib.request.Request(
QUERY_URL + params,
headers={"Authorization": f"Bearer {api_key}"},
)
with urllib.request.urlopen(req, timeout=60) as r:
return json.loads(r.read())
def download(url, path):
os.makedirs(os.path.dirname(os.path.abspath(path)), exist_ok=True)
with urllib.request.urlopen(url, timeout=300) as r, open(path, "wb") as f:
shutil.copyfileobj(r, f)
def main():
parser = argparse.ArgumentParser(description="使用 Agnes AI 生成视频。")
parser.add_argument("--prompt", required=True, help="视频内容的文本描述(必选)")
parser.add_argument("--model", default=DEFAULT_MODEL, help=f"模型 ID默认 {DEFAULT_MODEL}")
parser.add_argument("--image", action="append", default=None,
help="参考图 URL图生视频/多图/关键帧,可多次指定)")
parser.add_argument("--keyframes", action="store_true",
help="关键帧动画模式(配合多张 --image 使用)")
parser.add_argument("--width", type=int, default=1152, help="视频宽度,默认 1152")
parser.add_argument("--height", type=int, default=768, help="视频高度,默认 768")
parser.add_argument("--num-frames", type=int, default=121,
help="帧数,<=441 且满足 8n+1默认 121约 5 秒)")
parser.add_argument("--frame-rate", type=float, default=24, help="帧率 1-60默认 24")
parser.add_argument("--duration", type=float,
help="目标时长(秒),会按帧率换算 num_frames覆盖 --num-frames")
parser.add_argument("--negative-prompt", help="负向提示词,描述要避免的内容")
parser.add_argument("--seed", type=int, help="随机种子,用于结果复现")
parser.add_argument("--api-key", help="Agnes API Key也可用 AGNES_API_KEY 环境变量)")
parser.add_argument("--save", help="下载视频并保存到本地路径")
parser.add_argument("--poll-interval", type=float, default=5, help="轮询间隔秒数,默认 5")
parser.add_argument("--max-wait", type=float, default=600, help="最大等待秒数,默认 600")
args = parser.parse_args()
api_key = args.api_key or os.environ.get("AGNES_API_KEY")
if not api_key:
print("ERROR: 缺少 API Key请用 --api-key 或设置 AGNES_API_KEY 环境变量。")
sys.exit(1)
# 计算帧数
if args.duration:
num_frames = normalize_frames(args.duration * args.frame_rate)
else:
num_frames = normalize_frames(args.num_frames)
# 组装请求体
payload = {
"model": args.model,
"prompt": args.prompt,
"width": args.width,
"height": args.height,
"num_frames": num_frames,
"frame_rate": args.frame_rate,
}
if args.negative_prompt:
payload["negative_prompt"] = args.negative_prompt
if args.seed is not None:
payload["seed"] = args.seed
images = args.image or []
# 视频接口的参考图只支持可公网访问的 URL不支持本地文件 / Base64
for u in images:
if not (u.startswith("http://") or u.startswith("https://")):
print("ERROR: 视频生成的参考图必须是可公网访问的图片 URLhttp/https"
"不支持本地文件路径或 Base64。")
print(f" 问题输入: {u}")
print(" 建议:先用 generate_image.py 生成图片拿到其公网 URL"
"或把本地图片上传到图床/对象存储后再用其 URL 传入。")
sys.exit(1)
if args.keyframes:
# 关键帧动画extra_body.image + mode=keyframes
payload["extra_body"] = {"image": images, "mode": "keyframes"}
elif len(images) == 1:
# 图生视频:顶层 image
payload["image"] = images[0]
elif len(images) >= 2:
# 多图视频extra_body.image
payload["extra_body"] = {"image": images}
# 1) 创建任务
log(f"创建视频任务({num_frames} 帧 @ {args.frame_rate}fps ≈ {num_frames/args.frame_rate:.1f}s...")
try:
task = create_task(payload, api_key)
except requests.exceptions.RequestException as e:
print(f"ERROR: 创建任务失败: {e}")
if getattr(e, "response", None) is not None:
print(f"Response body: {e.response.text[:500]}")
sys.exit(1)
video_id = task.get("video_id")
task_id = task.get("task_id") or task.get("id")
if not video_id and not task_id:
print(f"ERROR: 创建任务响应缺少 video_id/task_id: {json.dumps(task)[:500]}")
sys.exit(1)
log(f"任务已创建 video_id={video_id} task_id={task_id} status={task.get('status')}")
# 2) 轮询结果
start = time.time()
video_url = None
while time.time() - start < args.max_wait:
time.sleep(args.poll_interval)
try:
data = query_result(video_id or task_id, api_key, model=args.model)
except Exception as e:
log(f" 查询出错(将重试): {e}")
continue
status = data.get("status")
progress = data.get("progress", 0)
log(f" 状态={status} 进度={progress}%")
if status == "completed":
video_url = data.get("remixed_from_video_id") # 文档:该字段为最终视频 URL
break
if status == "failed":
print(f"ERROR: 视频生成失败: {data.get('error')}")
sys.exit(1)
if not video_url:
print(f"ERROR: 等待超时({args.max_wait}s或未返回视频 URL。")
sys.exit(1)
# 3) 输出结果
print(f"MEDIA_URL: {video_url}")
if args.save:
try:
log("下载视频中 ...")
download(video_url, args.save)
print(f"SAVED: {args.save}")
except Exception as e:
print(f"ERROR: 下载保存失败: {e}")
sys.exit(1)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,126 @@
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "requests>=2.31.0",
# ]
# ///
"""使用 Agnes AI 的多模态模型理解图片。
传入图片公网 URL 或本地文件+ 文本问题模型基于图片进行描述分析问答或信息提取
"""
import argparse
import base64
import json
import mimetypes
import os
import sys
import time
import requests
API_URL = "https://apihub.agnes-ai.com/v1/chat/completions"
DEFAULT_MODEL = "agnes-2.0-flash"
def file_to_data_uri(path):
"""把本地图片文件转成 data:image Base64。"""
mime = mimetypes.guess_type(path)[0] or "image/png"
with open(path, "rb") as f:
b64 = base64.b64encode(f.read()).decode()
return f"data:{mime};base64,{b64}"
def call_api(messages, model, api_key, temperature=None, max_tokens=None, retries=3):
"""调用 Agnes chat/completions返回解析后的 JSON。瞬时网络抖动自动重试。"""
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}",
}
payload = {"model": model, "messages": messages}
if temperature is not None:
payload["temperature"] = temperature
if max_tokens is not None:
payload["max_tokens"] = max_tokens
last_err = None
for attempt in range(1, retries + 1):
try:
resp = requests.post(API_URL, headers=headers, json=payload, timeout=180)
resp.raise_for_status()
return resp.json()
except (requests.exceptions.SSLError,
requests.exceptions.ConnectionError,
requests.exceptions.Timeout) as e:
last_err = e
if attempt < retries:
time.sleep(attempt * 2)
continue
raise
raise last_err
def main():
parser = argparse.ArgumentParser(description="使用 Agnes AI 理解/分析图片。")
parser.add_argument("--prompt", required=True, help="对图片的问题或指令(必选)")
parser.add_argument("--image", action="append", default=None,
help="图片公网 URL 或 data:image Base64可多次指定")
parser.add_argument("--image-file", action="append", default=None,
help="本地图片文件路径,自动转 Base64可多次指定")
parser.add_argument("--system", help="可选的系统提示system 消息)")
parser.add_argument("--model", default=DEFAULT_MODEL, help=f"模型 ID默认 {DEFAULT_MODEL}")
parser.add_argument("--temperature", type=float, help="采样温度0~1越低越确定")
parser.add_argument("--max-tokens", type=int, help="最多生成的 token 数")
parser.add_argument("--api-key", help="Agnes API Key也可用 AGNES_API_KEY 环境变量)")
args = parser.parse_args()
api_key = args.api_key or os.environ.get("AGNES_API_KEY")
if not api_key:
print("ERROR: 缺少 API Key请用 --api-key 或设置 AGNES_API_KEY 环境变量。")
sys.exit(1)
# 收集所有图片输入
image_urls = list(args.image or [])
for p in (args.image_file or []):
if not os.path.isfile(p):
print(f"ERROR: 本地图片不存在: {p}")
sys.exit(1)
image_urls.append(file_to_data_uri(p))
# 组装多模态 content文本 + 若干图片
content = [{"type": "text", "text": args.prompt}]
for url in image_urls:
content.append({"type": "image_url", "image_url": {"url": url}})
messages = []
if args.system:
messages.append({"role": "system", "content": args.system})
# 没有图片时退化为纯文本content 用字符串更稳妥
messages.append({"role": "user", "content": content if image_urls else args.prompt})
try:
result = call_api(
messages=messages,
model=args.model,
api_key=api_key,
temperature=args.temperature,
max_tokens=args.max_tokens,
)
except requests.exceptions.RequestException as e:
print(f"ERROR: API 请求失败: {e}")
if getattr(e, "response", None) is not None:
print(f"Response body: {e.response.text[:500]}")
sys.exit(1)
try:
answer = result["choices"][0]["message"]["content"]
except (KeyError, IndexError, TypeError):
print(f"ERROR: 无法解析响应。完整响应: {json.dumps(result, ensure_ascii=False)[:500]}")
sys.exit(1)
print(answer)
if __name__ == "__main__":
main()