mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-09-24 23:10:56 +08:00
feat: 添加 Antigravity 图像生成 API (OpenAI 兼容)
- 实现 /v1/images/generations 端点 - 添加 ImageGenerationRequest/Response 数据模型 - 实现 OpenAI 到 Antigravity 请求/响应转换器 - 支持 dall-e-3/dall-e-2 模型名映射到 gemini-3-pro-image - 支持 b64_json 和 url 两种响应格式 - 添加 Python SDK 集成测试脚本
This commit is contained in:
Executable
+369
@@ -0,0 +1,369 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
OpenAI 兼容图像生成 API 测试脚本
|
||||
|
||||
使用 OpenAI Python SDK 测试 Antigravity 图像生成 API。
|
||||
|
||||
使用方法:
|
||||
# 安装依赖
|
||||
pip install openai
|
||||
|
||||
# 运行测试(需要先启动 API Server)
|
||||
python scripts/test_image_api.py
|
||||
|
||||
# 指定自定义 API 地址和密钥
|
||||
python scripts/test_image_api.py --base-url http://localhost:8999 --api-key your-key
|
||||
|
||||
环境变量:
|
||||
PROXYCAST_BASE_URL: API 服务器地址(默认: http://localhost:8999)
|
||||
PROXYCAST_API_KEY: API 密钥(默认: pc_LXZbIv3o78WpHuQwqgmwC0U4G0cY5UtQ)
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import base64
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
try:
|
||||
from openai import OpenAI
|
||||
except ImportError:
|
||||
print("错误: 请先安装 openai 库")
|
||||
print("运行: pip install openai")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def test_image_generation_url(client: OpenAI, prompt: str) -> bool:
|
||||
"""
|
||||
测试 URL 响应格式的图像生成
|
||||
|
||||
Args:
|
||||
client: OpenAI 客户端
|
||||
prompt: 图像生成提示词
|
||||
|
||||
Returns:
|
||||
测试是否通过
|
||||
"""
|
||||
print("\n" + "=" * 60)
|
||||
print("测试 1: URL 响应格式")
|
||||
print("=" * 60)
|
||||
print(f"提示词: {prompt}")
|
||||
|
||||
try:
|
||||
response = client.images.generate(
|
||||
model="dall-e-3", # 会被映射到 gemini-3-pro-image
|
||||
prompt=prompt,
|
||||
n=1,
|
||||
size="1024x1024",
|
||||
response_format="url"
|
||||
)
|
||||
|
||||
# 验证响应结构
|
||||
print(f"\n响应时间戳: {response.created}")
|
||||
print(f"生成图片数量: {len(response.data)}")
|
||||
|
||||
if len(response.data) == 0:
|
||||
print("❌ 错误: 没有生成图片")
|
||||
return False
|
||||
|
||||
image = response.data[0]
|
||||
|
||||
# 验证 URL 格式
|
||||
if image.url:
|
||||
print(f"URL 长度: {len(image.url)} 字符")
|
||||
if image.url.startswith("data:image/"):
|
||||
print("✅ URL 格式正确 (data URL)")
|
||||
else:
|
||||
print(f"⚠️ URL 格式: {image.url[:50]}...")
|
||||
else:
|
||||
print("❌ 错误: URL 为空")
|
||||
return False
|
||||
|
||||
# 验证 revised_prompt
|
||||
if image.revised_prompt:
|
||||
print(f"修订提示词: {image.revised_prompt[:100]}...")
|
||||
else:
|
||||
print("ℹ️ 没有修订提示词")
|
||||
|
||||
print("\n✅ 测试 1 通过")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ 测试 1 失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_image_generation_b64(client: OpenAI, prompt: str) -> bool:
|
||||
"""
|
||||
测试 b64_json 响应格式的图像生成
|
||||
|
||||
Args:
|
||||
client: OpenAI 客户端
|
||||
prompt: 图像生成提示词
|
||||
|
||||
Returns:
|
||||
测试是否通过
|
||||
"""
|
||||
print("\n" + "=" * 60)
|
||||
print("测试 2: b64_json 响应格式")
|
||||
print("=" * 60)
|
||||
print(f"提示词: {prompt}")
|
||||
|
||||
try:
|
||||
response = client.images.generate(
|
||||
model="gemini-3-pro-image-preview", # 直接使用 Gemini 模型名
|
||||
prompt=prompt,
|
||||
n=1,
|
||||
response_format="b64_json"
|
||||
)
|
||||
|
||||
# 验证响应结构
|
||||
print(f"\n响应时间戳: {response.created}")
|
||||
print(f"生成图片数量: {len(response.data)}")
|
||||
|
||||
if len(response.data) == 0:
|
||||
print("❌ 错误: 没有生成图片")
|
||||
return False
|
||||
|
||||
image = response.data[0]
|
||||
|
||||
# 验证 b64_json 格式
|
||||
if image.b64_json:
|
||||
print(f"Base64 数据长度: {len(image.b64_json)} 字符")
|
||||
|
||||
# 尝试解码验证
|
||||
try:
|
||||
decoded = base64.b64decode(image.b64_json)
|
||||
print(f"解码后大小: {len(decoded)} 字节")
|
||||
|
||||
# 检查图片魔数
|
||||
if decoded[:8] == b'\x89PNG\r\n\x1a\n':
|
||||
print("✅ 图片格式: PNG")
|
||||
elif decoded[:2] == b'\xff\xd8':
|
||||
print("✅ 图片格式: JPEG")
|
||||
elif decoded[:4] == b'GIF8':
|
||||
print("✅ 图片格式: GIF")
|
||||
elif decoded[:4] == b'RIFF':
|
||||
print("✅ 图片格式: WebP")
|
||||
else:
|
||||
print(f"⚠️ 未知图片格式: {decoded[:8].hex()}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ Base64 解码失败: {e}")
|
||||
else:
|
||||
print("❌ 错误: b64_json 为空")
|
||||
return False
|
||||
|
||||
# 验证 revised_prompt
|
||||
if image.revised_prompt:
|
||||
print(f"修订提示词: {image.revised_prompt[:100]}...")
|
||||
else:
|
||||
print("ℹ️ 没有修订提示词")
|
||||
|
||||
print("\n✅ 测试 2 通过")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ 测试 2 失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_error_handling(client: OpenAI) -> bool:
|
||||
"""
|
||||
测试错误处理
|
||||
|
||||
Args:
|
||||
client: OpenAI 客户端
|
||||
|
||||
Returns:
|
||||
测试是否通过
|
||||
"""
|
||||
print("\n" + "=" * 60)
|
||||
print("测试 3: 错误处理")
|
||||
print("=" * 60)
|
||||
|
||||
try:
|
||||
# 测试空提示词
|
||||
print("测试空提示词...")
|
||||
try:
|
||||
response = client.images.generate(
|
||||
model="dall-e-3",
|
||||
prompt="", # 空提示词
|
||||
n=1
|
||||
)
|
||||
print("❌ 错误: 应该拒绝空提示词")
|
||||
return False
|
||||
except Exception as e:
|
||||
error_msg = str(e).lower()
|
||||
if "prompt" in error_msg or "empty" in error_msg or "required" in error_msg:
|
||||
print(f"✅ 正确拒绝空提示词: {e}")
|
||||
else:
|
||||
print(f"⚠️ 收到错误但消息不明确: {e}")
|
||||
|
||||
print("\n✅ 测试 3 通过")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ 测试 3 失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_response_structure(client: OpenAI, prompt: str) -> bool:
|
||||
"""
|
||||
测试响应结构符合 OpenAI 规范
|
||||
|
||||
Args:
|
||||
client: OpenAI 客户端
|
||||
prompt: 图像生成提示词
|
||||
|
||||
Returns:
|
||||
测试是否通过
|
||||
"""
|
||||
print("\n" + "=" * 60)
|
||||
print("测试 4: 响应结构验证")
|
||||
print("=" * 60)
|
||||
print(f"提示词: {prompt}")
|
||||
|
||||
try:
|
||||
response = client.images.generate(
|
||||
model="dall-e-3",
|
||||
prompt=prompt,
|
||||
n=1,
|
||||
response_format="url"
|
||||
)
|
||||
|
||||
# 验证 created 字段
|
||||
if response.created:
|
||||
print(f"✅ created 字段存在: {response.created}")
|
||||
# 验证是有效的 Unix 时间戳
|
||||
if response.created > 0:
|
||||
dt = datetime.fromtimestamp(response.created)
|
||||
print(f" 时间: {dt}")
|
||||
else:
|
||||
print("❌ created 不是有效时间戳")
|
||||
return False
|
||||
else:
|
||||
print("❌ created 字段缺失")
|
||||
return False
|
||||
|
||||
# 验证 data 字段
|
||||
if response.data is not None:
|
||||
print(f"✅ data 字段存在: {len(response.data)} 项")
|
||||
if len(response.data) > 0:
|
||||
print("✅ data 数组非空")
|
||||
else:
|
||||
print("❌ data 数组为空")
|
||||
return False
|
||||
else:
|
||||
print("❌ data 字段缺失")
|
||||
return False
|
||||
|
||||
# 验证每个图片项
|
||||
for i, image in enumerate(response.data):
|
||||
print(f"\n图片 {i + 1}:")
|
||||
has_url = image.url is not None
|
||||
has_b64 = image.b64_json is not None
|
||||
|
||||
if has_url:
|
||||
print(f" ✅ url 字段存在")
|
||||
if has_b64:
|
||||
print(f" ✅ b64_json 字段存在")
|
||||
|
||||
if not has_url and not has_b64:
|
||||
print(f" ❌ 缺少 url 和 b64_json")
|
||||
return False
|
||||
|
||||
if image.revised_prompt:
|
||||
print(f" ✅ revised_prompt 字段存在")
|
||||
else:
|
||||
print(f" ℹ️ revised_prompt 字段为空")
|
||||
|
||||
print("\n✅ 测试 4 通过")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ 测试 4 失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="测试 OpenAI 兼容图像生成 API"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--base-url",
|
||||
default=os.environ.get("PROXYCAST_BASE_URL", "http://localhost:8999"),
|
||||
help="API 服务器地址"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--api-key",
|
||||
default=os.environ.get("PROXYCAST_API_KEY", "pc_LXZbIv3o78WpHuQwqgmwC0U4G0cY5UtQ"),
|
||||
help="API 密钥"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--prompt",
|
||||
default="A cute fluffy cat sitting on a windowsill, looking at the sunset",
|
||||
help="测试用的图像生成提示词"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--skip-generation",
|
||||
action="store_true",
|
||||
help="跳过实际图像生成测试(仅测试错误处理)"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
print("=" * 60)
|
||||
print("OpenAI 兼容图像生成 API 测试")
|
||||
print("=" * 60)
|
||||
print(f"API 地址: {args.base_url}")
|
||||
print(f"API 密钥: {args.api_key[:8]}...")
|
||||
print(f"测试提示词: {args.prompt[:50]}...")
|
||||
|
||||
# 创建 OpenAI 客户端
|
||||
client = OpenAI(
|
||||
base_url=f"{args.base_url}/v1",
|
||||
api_key=args.api_key
|
||||
)
|
||||
|
||||
results = []
|
||||
|
||||
if not args.skip_generation:
|
||||
# 测试 1: URL 响应格式
|
||||
results.append(("URL 响应格式", test_image_generation_url(client, args.prompt)))
|
||||
|
||||
# 测试 2: b64_json 响应格式
|
||||
results.append(("b64_json 响应格式", test_image_generation_b64(client, args.prompt)))
|
||||
|
||||
# 测试 4: 响应结构验证
|
||||
results.append(("响应结构验证", test_response_structure(client, args.prompt)))
|
||||
|
||||
# 测试 3: 错误处理
|
||||
results.append(("错误处理", test_error_handling(client)))
|
||||
|
||||
# 打印总结
|
||||
print("\n" + "=" * 60)
|
||||
print("测试总结")
|
||||
print("=" * 60)
|
||||
|
||||
passed = 0
|
||||
failed = 0
|
||||
for name, result in results:
|
||||
status = "✅ 通过" if result else "❌ 失败"
|
||||
print(f" {name}: {status}")
|
||||
if result:
|
||||
passed += 1
|
||||
else:
|
||||
failed += 1
|
||||
|
||||
print(f"\n总计: {passed} 通过, {failed} 失败")
|
||||
|
||||
if failed > 0:
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("\n🎉 所有测试通过!")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user