295 lines
9.8 KiB
Python
295 lines
9.8 KiB
Python
|
|
#!/usr/bin/env python3
|
|||
|
|
# -*- coding: utf-8 -*-
|
|||
|
|
"""
|
|||
|
|
完整流程测试: 选题生成 → 正文生成
|
|||
|
|
|
|||
|
|
测试 V2 API 的完整业务流程:
|
|||
|
|
1. 生成选题
|
|||
|
|
2. 基于选题生成正文 (原创模式)
|
|||
|
|
3. 基于选题生成正文 (reference 模式)
|
|||
|
|
4. 基于选题生成正文 (rewrite 模式)
|
|||
|
|
|
|||
|
|
使用方法:
|
|||
|
|
python tests/test_topic_to_content.py [--base-url http://localhost:8001]
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import argparse
|
|||
|
|
import json
|
|||
|
|
import requests
|
|||
|
|
import time
|
|||
|
|
from typing import Dict, Any, Optional
|
|||
|
|
|
|||
|
|
|
|||
|
|
class Colors:
|
|||
|
|
"""终端颜色"""
|
|||
|
|
GREEN = '\033[92m'
|
|||
|
|
RED = '\033[91m'
|
|||
|
|
YELLOW = '\033[93m'
|
|||
|
|
BLUE = '\033[94m'
|
|||
|
|
CYAN = '\033[96m'
|
|||
|
|
RESET = '\033[0m'
|
|||
|
|
BOLD = '\033[1m'
|
|||
|
|
|
|||
|
|
|
|||
|
|
def print_header(text: str):
|
|||
|
|
print(f"\n{Colors.BOLD}{Colors.BLUE}{'='*60}{Colors.RESET}")
|
|||
|
|
print(f"{Colors.BOLD}{Colors.BLUE}{text}{Colors.RESET}")
|
|||
|
|
print(f"{Colors.BOLD}{Colors.BLUE}{'='*60}{Colors.RESET}")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def print_step(step: int, text: str):
|
|||
|
|
print(f"\n{Colors.CYAN}[Step {step}] {text}{Colors.RESET}")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def print_success(text: str):
|
|||
|
|
print(f"{Colors.GREEN}✅ {text}{Colors.RESET}")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def print_error(text: str):
|
|||
|
|
print(f"{Colors.RED}❌ {text}{Colors.RESET}")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def print_info(text: str):
|
|||
|
|
print(f"{Colors.YELLOW}ℹ️ {text}{Colors.RESET}")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def call_api(base_url: str, endpoint: str, method: str = "GET", data: Dict = None) -> Dict:
|
|||
|
|
"""调用 API"""
|
|||
|
|
url = f"{base_url}{endpoint}"
|
|||
|
|
try:
|
|||
|
|
if method == "GET":
|
|||
|
|
resp = requests.get(url, timeout=120)
|
|||
|
|
else:
|
|||
|
|
resp = requests.post(url, json=data, timeout=120)
|
|||
|
|
return resp.json()
|
|||
|
|
except requests.exceptions.Timeout:
|
|||
|
|
return {"success": False, "error": "请求超时"}
|
|||
|
|
except Exception as e:
|
|||
|
|
return {"success": False, "error": str(e)}
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_topic_generate(base_url: str, subject: Dict, style: Dict, audience: Dict) -> Optional[Dict]:
|
|||
|
|
"""测试选题生成"""
|
|||
|
|
print_step(1, "生成选题")
|
|||
|
|
|
|||
|
|
params = {
|
|||
|
|
"engine": "topic_generate",
|
|||
|
|
"params": {
|
|||
|
|
"month": "2025-02",
|
|||
|
|
"num_topics": 2,
|
|||
|
|
"prompt_version": "v2.0.0",
|
|||
|
|
"subject": subject,
|
|||
|
|
"style": style,
|
|||
|
|
"audience": audience,
|
|||
|
|
"hot_topics": {
|
|||
|
|
"events": [],
|
|||
|
|
"festivals": [
|
|||
|
|
{"name": "春节", "date": "2025-01-29", "marketing_angle": "团圆、年味"}
|
|||
|
|
],
|
|||
|
|
"trending": []
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
print_info(f"请求参数: subject={subject['name']}, style={style['name']}, audience={audience['name']}")
|
|||
|
|
|
|||
|
|
start_time = time.time()
|
|||
|
|
result = call_api(base_url, "/api/v2/aigc/execute", "POST", params)
|
|||
|
|
elapsed = time.time() - start_time
|
|||
|
|
|
|||
|
|
if result.get("success"):
|
|||
|
|
topics = result.get("data", {}).get("topics", [])
|
|||
|
|
print_success(f"生成 {len(topics)} 个选题 (耗时 {elapsed:.1f}s)")
|
|||
|
|
|
|||
|
|
for i, topic in enumerate(topics):
|
|||
|
|
print(f"\n {Colors.BOLD}选题 {i+1}:{Colors.RESET}")
|
|||
|
|
print(f" 标题: {topic.get('title', 'N/A')}")
|
|||
|
|
print(f" 日期: {topic.get('date', 'N/A')}")
|
|||
|
|
print(f" 风格: {topic.get('style', 'N/A')}")
|
|||
|
|
print(f" 人群: {topic.get('targetAudience', 'N/A')}")
|
|||
|
|
print(f" 逻辑: {topic.get('logic', 'N/A')[:50]}...")
|
|||
|
|
|
|||
|
|
return topics[0] if topics else None
|
|||
|
|
else:
|
|||
|
|
print_error(f"选题生成失败: {result.get('error', 'Unknown error')}")
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_content_generate(base_url: str, topic: Dict, subject: Dict, style: Dict, audience: Dict,
|
|||
|
|
reference: Optional[Dict] = None, mode_name: str = "原创") -> Optional[Dict]:
|
|||
|
|
"""测试正文生成"""
|
|||
|
|
print_step(2 if reference is None else (3 if reference.get('mode') == 'reference' else 4),
|
|||
|
|
f"生成正文 ({mode_name}模式)")
|
|||
|
|
|
|||
|
|
params = {
|
|||
|
|
"engine": "content_generate",
|
|||
|
|
"params": {
|
|||
|
|
"prompt_version": "v2.0.0",
|
|||
|
|
"topic": topic,
|
|||
|
|
"subject": subject,
|
|||
|
|
"style": style,
|
|||
|
|
"audience": audience,
|
|||
|
|
"enable_judge": False
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if reference:
|
|||
|
|
params["params"]["reference"] = reference
|
|||
|
|
print_info(f"参考模式: {reference.get('mode')}")
|
|||
|
|
else:
|
|||
|
|
print_info("原创模式,无参考内容")
|
|||
|
|
|
|||
|
|
start_time = time.time()
|
|||
|
|
result = call_api(base_url, "/api/v2/aigc/execute", "POST", params)
|
|||
|
|
elapsed = time.time() - start_time
|
|||
|
|
|
|||
|
|
if result.get("success"):
|
|||
|
|
content = result.get("data", {}).get("content", {})
|
|||
|
|
print_success(f"正文生成成功 (耗时 {elapsed:.1f}s)")
|
|||
|
|
|
|||
|
|
print(f"\n {Colors.BOLD}标题:{Colors.RESET} {content.get('title', 'N/A')}")
|
|||
|
|
|
|||
|
|
body = content.get('content', '')
|
|||
|
|
# 截取前 200 字符显示
|
|||
|
|
preview = body[:200] + "..." if len(body) > 200 else body
|
|||
|
|
print(f"\n {Colors.BOLD}正文预览:{Colors.RESET}")
|
|||
|
|
for line in preview.split('\n')[:8]:
|
|||
|
|
print(f" {line}")
|
|||
|
|
|
|||
|
|
print(f"\n {Colors.BOLD}TAG:{Colors.RESET} {content.get('tag', 'N/A')}")
|
|||
|
|
print(f"\n {Colors.BOLD}参考模式:{Colors.RESET} {content.get('referenceMode', 'none')}")
|
|||
|
|
|
|||
|
|
return content
|
|||
|
|
else:
|
|||
|
|
print_error(f"正文生成失败: {result.get('error', 'Unknown error')}")
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
|
|||
|
|
def run_full_test(base_url: str):
|
|||
|
|
"""运行完整测试"""
|
|||
|
|
print_header("TravelContentCreator 完整流程测试")
|
|||
|
|
print(f"API 地址: {base_url}")
|
|||
|
|
|
|||
|
|
# 测试数据
|
|||
|
|
subject = {
|
|||
|
|
"id": 1,
|
|||
|
|
"name": "北京环球影城",
|
|||
|
|
"type": "scenic_spot",
|
|||
|
|
"description": "亚洲最大的环球影城主题公园,拥有哈利波特魔法世界、变形金刚基地等七大主题景区",
|
|||
|
|
"location": "北京市通州区",
|
|||
|
|
"traffic_info": "地铁1号线/7号线环球度假区站",
|
|||
|
|
"highlights": ["哈利波特魔法世界", "变形金刚基地", "小黄人乐园", "侏罗纪世界"],
|
|||
|
|
"advantages": "全球顶级主题乐园,沉浸式体验",
|
|||
|
|
"products": [
|
|||
|
|
{
|
|||
|
|
"id": 10,
|
|||
|
|
"name": "单日票",
|
|||
|
|
"price": 638,
|
|||
|
|
"original_price": 738,
|
|||
|
|
"sales_period": "2025-01-01 至 2025-03-31",
|
|||
|
|
"package_info": "含一次入园",
|
|||
|
|
"usage_rules": "入园当日有效,不可退改"
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
style = {"id": "gonglue", "name": "攻略风"}
|
|||
|
|
audience = {"id": "qinzi", "name": "亲子向"}
|
|||
|
|
|
|||
|
|
# 参考内容 (用于 reference 和 rewrite 测试)
|
|||
|
|
reference_content = {
|
|||
|
|
"title": "上海迪士尼遛娃天花板!一日游保姆级攻略🏰",
|
|||
|
|
"content": """带娃去迪士尼,这篇攻略你一定要收藏!
|
|||
|
|
|
|||
|
|
🎯 必玩项目TOP5
|
|||
|
|
1. 创极速光轮 - 全球最快迪士尼过山车
|
|||
|
|
2. 加勒比海盗 - 沉浸式体验超震撼
|
|||
|
|
3. 飞越地平线 - 裸眼4D环游世界
|
|||
|
|
4. 小飞侠天空奇遇 - 适合小朋友
|
|||
|
|
5. 七个小矮人矿山车 - 刺激又不吓人
|
|||
|
|
|
|||
|
|
⏰ 时间规划
|
|||
|
|
8:30 到达门口排队
|
|||
|
|
9:00 开园直冲创极速光轮
|
|||
|
|
12:00 午餐(建议自带)
|
|||
|
|
14:00 花车巡游
|
|||
|
|
20:00 烟花秀
|
|||
|
|
|
|||
|
|
💡 省钱tips
|
|||
|
|
- 门票提前买,便宜100+
|
|||
|
|
- 自带水和零食
|
|||
|
|
- 下载APP领快速通道"""
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Step 1: 生成选题
|
|||
|
|
topic = test_topic_generate(base_url, subject, style, audience)
|
|||
|
|
if not topic:
|
|||
|
|
print_error("选题生成失败,终止测试")
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
# Step 2: 原创模式生成正文
|
|||
|
|
content1 = test_content_generate(base_url, topic, subject, style, audience,
|
|||
|
|
reference=None, mode_name="原创")
|
|||
|
|
|
|||
|
|
# Step 3: reference 模式生成正文 (参考风格,原创内容)
|
|||
|
|
content2 = test_content_generate(base_url, topic, subject, style, audience,
|
|||
|
|
reference={"mode": "reference", **reference_content},
|
|||
|
|
mode_name="reference 参考")
|
|||
|
|
|
|||
|
|
# Step 4: rewrite 模式生成正文 (保留框架,换主体)
|
|||
|
|
content3 = test_content_generate(base_url, topic, subject, style, audience,
|
|||
|
|
reference={"mode": "rewrite", **reference_content},
|
|||
|
|
mode_name="rewrite 改写")
|
|||
|
|
|
|||
|
|
# 结果汇总
|
|||
|
|
print_header("测试结果汇总")
|
|||
|
|
|
|||
|
|
results = [
|
|||
|
|
("选题生成", topic is not None),
|
|||
|
|
("原创模式正文", content1 is not None),
|
|||
|
|
("reference 模式正文", content2 is not None),
|
|||
|
|
("rewrite 模式正文", content3 is not None),
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
all_passed = True
|
|||
|
|
for name, passed in results:
|
|||
|
|
if passed:
|
|||
|
|
print_success(f"{name}: 通过")
|
|||
|
|
else:
|
|||
|
|
print_error(f"{name}: 失败")
|
|||
|
|
all_passed = False
|
|||
|
|
|
|||
|
|
if all_passed:
|
|||
|
|
print(f"\n{Colors.GREEN}{Colors.BOLD}🎉 所有测试通过!{Colors.RESET}")
|
|||
|
|
else:
|
|||
|
|
print(f"\n{Colors.RED}{Colors.BOLD}⚠️ 部分测试失败{Colors.RESET}")
|
|||
|
|
|
|||
|
|
return all_passed
|
|||
|
|
|
|||
|
|
|
|||
|
|
def main():
|
|||
|
|
parser = argparse.ArgumentParser(description="TravelContentCreator 完整流程测试")
|
|||
|
|
parser.add_argument("--base-url", default="http://localhost:8001", help="API 基础地址")
|
|||
|
|
args = parser.parse_args()
|
|||
|
|
|
|||
|
|
# 检查服务是否可用
|
|||
|
|
print_info(f"检查服务状态: {args.base_url}")
|
|||
|
|
try:
|
|||
|
|
resp = requests.get(f"{args.base_url}/", timeout=5)
|
|||
|
|
if resp.status_code == 200:
|
|||
|
|
print_success("服务运行正常")
|
|||
|
|
else:
|
|||
|
|
print_error(f"服务异常: HTTP {resp.status_code}")
|
|||
|
|
return
|
|||
|
|
except Exception as e:
|
|||
|
|
print_error(f"无法连接服务: {e}")
|
|||
|
|
print_info("请先启动服务: PYTHONPATH=. uvicorn api.main:app --port 8001")
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
# 运行测试
|
|||
|
|
run_full_test(args.base_url)
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
main()
|