bookworm-smart-assistant/scripts/multi_ai.py

443 lines
14 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""
AI 模型调用脚本 (Universal Architect Engine v4.0)
支持双中转站 API:
- 中转站 GPT: gpt-5.2-codex
- 中转站 Gemini: gemini-2.5-pro, gemini-2.5-flash
环境变量:
AI_API_KEY - 中转站 API 密钥 (GPT 模型)
AI_BASE_URL - 中转站地址 (默认: https://api.gbro.site)
GEMINI_API_KEY - 中转站 API 密钥 (Gemini 模型可与 AI_API_KEY 相同)
GEMINI_BASE_URL - Gemini 中转站地址 (默认: AI_BASE_URL 相同)
使用方法:
python multi_ai.py --model gpt-5.2-codex --task audit --file code.py
python multi_ai.py --model gemini-2.5-pro --task frontend --prompt "生成登录组件"
python multi_ai.py --list-models
python multi_ai.py --test
"""
import os
import sys
import argparse
import requests
import json
# ============================================
# 配置
# ============================================
# 默认中转站地址
DEFAULT_BASE_URL = "https://api.gbro.site"
# 模型到 API 源的映射
MODEL_API_SOURCE = {
# GPT 模型 → GPT 中转站
"gpt-5.2-codex": "gpt",
"gpt-5.2": "gpt",
"gpt-5.1": "gpt",
"gpt-5.1-codex": "gpt",
"gpt-5.1-codex-mini": "gpt",
"gpt-5.1-codex-max": "gpt",
# Gemini 模型 → Gemini 中转站
"gemini-2.5-pro": "gemini",
"gemini-2.5-flash": "gemini",
"gemini-3-pro-preview": "gemini",
"gemini-3-pro-preview-thinking": "gemini",
"gemini-2.5-flash-image": "gemini",
"gemini-2.5-flash-image-preview": "gemini",
# Claude 模型 → GPT 中转站
"claude-sonnet-4-5-20250929": "gpt",
"claude-opus-4-5": "gpt",
"claude-haiku-4-5-20251001": "gpt",
}
# 模型别名映射
MODELS = {
# ===== OpenAI/GPT 系列 =====
"gpt-5.2-codex": "gpt-5.2-codex",
"gpt-5.2": "gpt-5.2",
"gpt-5.1": "gpt-5.1",
"codex": "gpt-5.2-codex",
"openai": "gpt-5.2-codex",
"gpt": "gpt-5.2-codex",
# ===== Gemini 系列 =====
"gemini-2.5-pro": "gemini-2.5-pro",
"gemini-2.5-flash": "gemini-2.5-flash",
"gemini-3-pro": "gemini-3-pro-preview",
"gemini-3-pro-preview": "gemini-3-pro-preview",
"gemini": "gemini-2.5-flash",
"flash": "gemini-2.5-flash",
# ===== Claude 系列 =====
"claude-sonnet": "claude-sonnet-4-5-20250929",
"claude-opus": "claude-opus-4-5",
"claude-haiku": "claude-haiku-4-5-20251001",
"claude": "claude-sonnet-4-5-20250929",
# ===== 任务别名 =====
"audit": "gpt-5.2-codex", # 代码审计用 GPT
"frontend": "gemini-2.5-flash", # 前端生成用 Gemini
"design": "gemini-2.5-flash", # 设计系统用 Gemini
"backend": "gpt-5.2-codex", # 后端逻辑用 GPT
"fast": "gemini-2.5-flash", # 快速任务用 Gemini Flash
}
# 任务提示词模板
TASK_PROMPTS = {
"audit": """你是一位首席架构师,请对以下代码进行深度审计。
审计维度:
1. 🔐 Security: 注入预防鉴权逻辑敏感数据泄露
2. Performance: N+1 查询冗余渲染索引建议
3. 🔧 Maintainability: 代码耦合度模块化清晰度
请输出结构化报告区分:
- 🚫 BLOCKER (必须修改)
- 💡 ADVICE (改进建议)
代码:
```
{content}
```
请用中文回复""",
"frontend": """你是一位高级前端架构师,请根据以下需求生成现代化前端代码。
要求:
1. 使用 React + TypeScript + Tailwind CSS
2. 组件命名具备清晰的业务语义
3. 包含完整的类型定义
4. 预留 OpenTelemetry 埋点
需求:
{content}
请用中文注释""",
"design-system": """你是一位 UI/UX 设计系统专家,请根据以下项目信息生成 Design Tokens。
项目信息:
{content}
请输出 JSON 格式的 Design Tokens包括:
1. colors (primary, secondary, semantic)
2. spacing (unit, scale)
3. typography (fontFamily, fontSize)
4. borderRadius
5. shadows
同时提供关键 UI 组件的设计规范描述""",
"backend": """你是一位后端架构专家,请根据以下需求设计后端架构。
要求:
1. 遵循 SOLID 原则和 Clean Architecture
2. 考虑高并发和幂等性设计
3. 预留分布式追踪埋点
4. 包含异常处理框架
需求:
{content}
请用中文注释""",
"contract": """你是一位全栈架构师,请根据以下 API 设计生成前后端握手契约。
要求:
1. TypeScript Interfaces Protobuf 格式
2. 包含 DTO (Data Transfer Object) 定义
3. 包含错误码枚举
4. 确保前端组件与后端 100% 类型对齐
API 设计:
{content}
请输出完整的 Handshake Contract""",
"general": """{content}"""
}
def get_config():
"""获取配置"""
# GPT 中转站配置
gpt_key = os.environ.get("AI_API_KEY")
gpt_url = os.environ.get("AI_BASE_URL") or DEFAULT_BASE_URL
# Gemini 中转站配置(可单独设置,也可复用 GPT 配置)
gemini_key = os.environ.get("GEMINI_API_KEY") or gpt_key
gemini_url = os.environ.get("GEMINI_BASE_URL") or gpt_url
return {
"gpt_key": gpt_key,
"gpt_url": gpt_url,
"gemini_key": gemini_key,
"gemini_url": gemini_url
}
def call_model(model: str, prompt: str, temperature: float = 0.3) -> str:
"""调用模型"""
config = get_config()
# 解析模型名称
actual_model = MODELS.get(model, model)
# 确定使用哪个 API 源
api_source = MODEL_API_SOURCE.get(actual_model, "gpt")
if api_source == "gemini":
return call_proxy_api(
actual_model, prompt,
config["gemini_key"], config["gemini_url"],
temperature
)
else:
return call_proxy_api(
actual_model, prompt,
config["gpt_key"], config["gpt_url"],
temperature
)
def call_proxy_api(model: str, prompt: str, api_key: str, base_url: str, temperature: float) -> str:
"""调用中转站 API (OpenAI 兼容格式)"""
if not api_key:
print("❌ 未设置 API Key 环境变量")
sys.exit(1)
url = f"{base_url}/v1/chat/completions"
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}"
}
data = {
"model": model,
"messages": [{"role": "user", "content": prompt}],
"temperature": temperature
}
response = requests.post(url, headers=headers, json=data, timeout=180)
response.raise_for_status()
result = response.json()
return result["choices"][0]["message"]["content"]
def list_models():
"""列出可用模型"""
config = get_config()
print("=" * 60)
print("📋 可用模型列表")
print("=" * 60)
# 显示 GPT 中转站模型
print("\n🔶 GPT 中转站模型:")
print(f" 地址: {config['gpt_url']}")
if config["gpt_key"]:
url = f"{config['gpt_url']}/v1/models"
headers = {"Authorization": f"Bearer {config['gpt_key']}"}
try:
response = requests.get(url, headers=headers, timeout=30)
response.raise_for_status()
models = response.json().get("data", [])
gpt_models = [m["id"] for m in models if "gpt" in m["id"].lower() or "codex" in m["id"].lower()]
for m in sorted(gpt_models)[:10]:
print(f" - {m}")
if len(gpt_models) > 10:
print(f" ... 还有 {len(gpt_models) - 10}")
except Exception as e:
print(f" ❌ 获取失败: {e}")
else:
print(" ⚠️ 未设置 AI_API_KEY")
# 显示 Gemini 中转站模型
print("\n🔷 Gemini 中转站模型:")
print(f" 地址: {config['gemini_url']}")
if config["gemini_key"]:
url = f"{config['gemini_url']}/v1/models"
headers = {"Authorization": f"Bearer {config['gemini_key']}"}
try:
response = requests.get(url, headers=headers, timeout=30)
response.raise_for_status()
models = response.json().get("data", [])
gemini_models = [m["id"] for m in models if "gemini" in m["id"].lower()]
for m in sorted(gemini_models)[:10]:
print(f" - {m}")
if len(gemini_models) > 10:
print(f" ... 还有 {len(gemini_models) - 10}")
except Exception as e:
print(f" ❌ 获取失败: {e}")
else:
print(" ⚠️ 未设置 GEMINI_API_KEY")
print("\n" + "=" * 60)
print("环境变量配置:")
print(f" AI_API_KEY: {'✅ 已设置' if config['gpt_key'] else '❌ 未设置'} (GPT)")
print(f" GEMINI_API_KEY: {'✅ 已设置' if config['gemini_key'] else '❌ 未设置'} (Gemini)")
print("=" * 60)
def test_models():
"""测试主要模型是否可用"""
config = get_config()
print("=" * 60)
print("🔍 测试模型可用性")
print("=" * 60)
# 测试 GPT 中转站
print("\n--- GPT 中转站 ---")
if config["gpt_key"]:
for model, name in [("gpt-5.2-codex", "GPT-5.2 Codex")]:
print(f"\n📝 测试 {name}...")
try:
result = call_proxy_api(model, "说你好", config["gpt_key"], config["gpt_url"], 0.1)
print(f" ✅ 可用: {result[:30]}...")
except requests.exceptions.HTTPError as e:
error_msg = str(e)
if hasattr(e, 'response'):
error_msg = e.response.text[:100]
print(f" ❌ 失败: {error_msg}")
except Exception as e:
print(f" ❌ 失败: {e}")
else:
print(" ⚠️ 未设置 AI_API_KEY")
# 测试 Gemini 中转站
print("\n--- Gemini 中转站 ---")
if config["gemini_key"]:
for model, name in [
("gemini-2.5-flash", "Gemini 2.5 Flash"),
("gemini-2.5-pro", "Gemini 2.5 Pro"),
]:
print(f"\n📝 测试 {name}...")
try:
result = call_proxy_api(model, "说你好", config["gemini_key"], config["gemini_url"], 0.1)
print(f" ✅ 可用: {result[:30]}...")
except requests.exceptions.HTTPError as e:
error_msg = str(e)
if hasattr(e, 'response'):
try:
err_json = e.response.json()
error_msg = err_json.get("error", {}).get("message", str(e))[:80]
except:
error_msg = e.response.text[:80]
print(f" ❌ 失败: {error_msg}")
except Exception as e:
print(f" ❌ 失败: {e}")
else:
print(" ⚠️ 未设置 GEMINI_API_KEY")
print("\n" + "=" * 60)
print("测试完成")
print("=" * 60)
def main():
parser = argparse.ArgumentParser(
description='多 AI 模型调用工具 (Universal Architect Engine v4.0)',
formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument('--model', '-m', default='gpt-5.2-codex',
help='模型名称 (gpt-5.2-codex, gemini-2.5-pro, etc.)')
parser.add_argument('--task', '-t', default='general',
choices=['audit', 'frontend', 'design-system', 'backend', 'contract', 'general'],
help='任务类型')
parser.add_argument('--file', '-f', help='输入文件')
parser.add_argument('--prompt', '-p', help='直接输入提示词')
parser.add_argument('--output', '-o', help='输出文件')
parser.add_argument('--temperature', type=float, default=0.3, help='温度参数')
parser.add_argument('--list-models', action='store_true', help='列出可用模型')
parser.add_argument('--test', action='store_true', help='测试主要模型可用性')
parser.add_argument('--json', '-j', action='store_true', help='JSON 格式输出')
args = parser.parse_args()
# 列出模型
if args.list_models:
list_models()
return
# 测试模型
if args.test:
test_models()
return
# 获取输入内容
if args.file:
with open(args.file, 'r', encoding='utf-8') as f:
content = f.read()
elif args.prompt:
content = args.prompt
elif not sys.stdin.isatty():
content = sys.stdin.read()
else:
parser.print_help()
return
# 构建提示词
task_template = TASK_PROMPTS.get(args.task, TASK_PROMPTS["general"])
full_prompt = task_template.format(content=content)
# 输出状态
actual_model = MODELS.get(args.model, args.model)
print(f"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━", file=sys.stderr)
print(f"[Status: Executing {args.task} via {actual_model}]", file=sys.stderr)
print(f"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━", file=sys.stderr)
try:
result = call_model(args.model, full_prompt, args.temperature)
if args.json:
output = json.dumps({
"model": actual_model,
"task": args.task,
"result": result
}, ensure_ascii=False, indent=2)
else:
output = result
# 输出结果
if args.output:
with open(args.output, 'w', encoding='utf-8') as f:
f.write(output)
print(f"✅ 结果已保存到: {args.output}", file=sys.stderr)
else:
print()
print(output)
print(f"\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━", file=sys.stderr)
print(f"[Task Complete]", file=sys.stderr)
print(f"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━", file=sys.stderr)
except requests.exceptions.HTTPError as e:
print(f"❌ API 错误: {e}")
if hasattr(e, 'response'):
print(f" 响应: {e.response.text}")
sys.exit(1)
except Exception as e:
print(f"❌ 失败: {e}")
sys.exit(1)
if __name__ == '__main__':
main()