bookworm-smart-assistant/scripts/multi_ai.py

443 lines
14 KiB
Python
Raw Permalink 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
"""
多 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()