""" 测试 8 个百炼 AI 应用的连通性。 通过 DashScope Application API 对每个应用发送一轮符合 P5 spec 首条 Prompt 结构的示例数据,验证百炼应用能正常返回。 调用方式:POST https://dashscope.aliyuncs.com/api/v1/apps/{APP_ID}/completion 参考文档:docs/reference/bailian-dashscope-api.md 用法: cd C:\\NeoZQYY && python scripts/ops/test_bailian_apps.py """ import json import os import sys import time from http import HTTPStatus from pathlib import Path from dotenv import load_dotenv # 加载根 .env load_dotenv(Path(__file__).resolve().parents[2] / ".env") BAILIAN_API_KEY = os.environ.get("BAILIAN_API_KEY", "") if not BAILIAN_API_KEY: raise RuntimeError("BAILIAN_API_KEY 未设置,请检查 .env 文件") # 8 个应用 ID 映射 APP_CONFIGS = [ ("BAILIAN_APP_ID_1_CHAT", "应用1-通用对话"), ("BAILIAN_APP_ID_2_FINANCE", "应用2-财务洞察"), ("BAILIAN_APP_ID_3_CLUE", "应用3-客户数据维客线索分析"), ("BAILIAN_APP_ID_4_ANALYSIS", "应用4-关系分析/任务建议"), ("BAILIAN_APP_ID_5_TACTICS", "应用5-话术参考"), ("BAILIAN_APP_ID_6_NOTE", "应用6-备注分析"), ("BAILIAN_APP_ID_7_CUSTOMER", "应用7-客户分析"), ("BAILIAN_APP_ID_8_CONSOLIDATE", "应用8-维客线索整理"), ] # ── 首条 Prompt 示例数据(符合 P5 spec 定义的 JSON 结构) ────────────────────── FIRST_PROMPTS: dict[str, str] = { # 应用 1:通用对话 — 前端发起,首条消息为页面上下文 "BAILIAN_APP_ID_1_CHAT": json.dumps({ "source_page": "board-finance", "page_context": "当前在财务看板页面,本月收入 12,580 元,环比上月增长 8.3%", "screen_content": "财务看板:本月收入 12,580 元 | 台费 6,200 | 陪打 2,100 | 超休 1,000 | 商品 1,800 | 充值 1,480" }, ensure_ascii=False), # 应用 2:财务洞察 "BAILIAN_APP_ID_2_FINANCE": json.dumps({ "site_id": 1, "time_dimension": "本月", "date_range": {"start": "2026-03-01 08:00:00", "end": "2026-04-01 08:00:00"}, "current_period": { "income_structure": {"table_fee": 6200, "assistant_pd": 2100, "assistant_cx": 1000, "goods": 1800, "recharge": 1480}, "prepaid_assets": {"total_balance": 28500, "recharge_amount": 1480, "consumption_deduction": 2100}, "expense_summary": {"rent": 3000, "utilities": 800, "supplies": 500, "salary": 4200, "other": 300}, "platform_settlement": {"groupbuy_income": 1200, "platform_fee": 120, "net_income": 1080} }, "previous_period": { "time_dimension": "上月", "date_range": {"start": "2026-02-01 08:00:00", "end": "2026-03-01 08:00:00"}, "income_structure": {"table_fee": 5800, "assistant_pd": 1950, "assistant_cx": 950, "goods": 1600, "recharge": 1300}, "prepaid_assets": {"total_balance": 29120, "recharge_amount": 1300, "consumption_deduction": 1950}, "expense_summary": {"rent": 3000, "utilities": 750, "supplies": 450, "salary": 4200, "other": 250}, "platform_settlement": {"groupbuy_income": 1000, "platform_fee": 100, "net_income": 900} } }, ensure_ascii=False), # 应用 3:客户数据维客线索分析 "BAILIAN_APP_ID_3_CLUE": json.dumps({ "member_nickname": "张三", "main_data": { "consumption_records": "近3个月消费12次,总消费2,860元。台费占比55%,助教费占比30%(陪打20%+超休10%),商品占比15%。偏好周末晚间时段,平均客单价238元。最近一次消费:2026-02-20,消费280元(台费+1小时助教)。", "member_cards": "持有银卡会员,2025-06-15注册", "card_balance_total": 580, "stored_value_balance_total": 580, "expected_visit_date": "2026-03-08", "days_since_last_visit": 13 }, "reference": {"app6_clues": None, "app8_history": []} }, ensure_ascii=False), # 应用 4:关系分析 / 任务建议 "BAILIAN_APP_ID_4_ANALYSIS": json.dumps({ "assistant_info": "花名:小李,级别:中级助教,工龄:1年3个月,擅长中式台球教学", "service_history": "累计服务张三8次,最近一次2026-02-20,平均每次服务1.5小时,客户评价良好", "task_assignment_basis": "优先召回:距上次到店13天,超过客户平均到店间隔(10天)", "customer_data": { "system_data": "近3个月消费12次,总消费2,860元,客单价238元,储值余额580元", "notes": [ {"recorded_by": "小李", "content": "张三喜欢打中式台球,经常带朋友来,上次提到想学斯诺克", "created_at": "2026-02-20 21:30:00"}, {"recorded_by": "小王", "content": "张三对充值活动比较感兴趣,上次充了500", "created_at": "2026-02-10 20:00:00"} ] }, "reference": {"app8_current": None, "app8_history": []} }, ensure_ascii=False), # 应用 5:话术参考 "BAILIAN_APP_ID_5_TACTICS": json.dumps({ "assistant_info": "花名:小李,级别:中级助教,工龄:1年3个月", "service_history": "累计服务张三8次,最近一次2026-02-20", "task_assignment_basis": "优先召回:距上次到店13天", "customer_data": { "system_data": "近3个月消费12次,总消费2,860元,储值余额580元", "notes": [{"recorded_by": "小李", "content": "张三喜欢打中式台球,经常带朋友来", "created_at": "2026-02-20 21:30:00"}] }, "task_suggestion": "{\"relationship_summary\":\"主要服务助教,累计服务8次,关系紧密\",\"task_description\":\"客户张三距上次到店已13天,超过其平均到店间隔10天,存在轻度流失风险。\",\"actions\":[{\"suggestion\":\"本周内通过微信联系张三,以斯诺克体验课为切入点邀请回店\"}],\"summary\":\"以斯诺克体验为切入点,本周内微信召回\"}", "reference": {"app8_history": []} }, ensure_ascii=False), # 应用 6:备注分析 "BAILIAN_APP_ID_6_NOTE": json.dumps({ "current_note": { "content": "张三今天带了两个朋友来,说是公司同事,三个人打了两个小时中式,走的时候说下周还想来,问有没有团建套餐", "recorded_by": "小李", "created_at": "2026-03-05 21:30:00" }, "reference": { "member_nickname": "张三", "consumption_data": "近3个月消费12次,总消费2,860元,客单价238元", "all_notes": [ {"recorded_by": "小李", "content": "张三喜欢打中式台球,经常带朋友来,上次提到想学斯诺克", "created_at": "2026-02-20 21:30:00"}, {"recorded_by": "小王", "content": "张三对充值活动比较感兴趣,上次充了500", "created_at": "2026-02-10 20:00:00"} ], "app3_clues": None, "app8_history": [] } }, ensure_ascii=False), # 应用 7:客户分析 "BAILIAN_APP_ID_7_CUSTOMER": json.dumps({ "member_id": 10086, "member_nickname": "张三", "objective_data": "近3个月消费12次,总消费2,860元,客单价238元。台费占比55%,助教费30%(陪打+超休),商品15%。偏好周末晚间,储值余额580元,银卡会员。距上次到店13天,平均到店间隔10天。", "subjective_data": { "notes": [ {"recorded_by": "小李", "content": "张三喜欢打中式台球,经常带朋友来,上次提到想学斯诺克", "created_at": "2026-02-20 21:30:00"}, {"recorded_by": "小王", "content": "张三对充值活动比较感兴趣,上次充了500", "created_at": "2026-02-10 20:00:00"}, {"recorded_by": "小李", "content": "张三今天带了两个朋友来,说是公司同事,问有没有团建套餐", "created_at": "2026-03-05 21:30:00"} ] }, "reference": {"app8_current": None, "app8_history": []} }, ensure_ascii=False), # 应用 8:维客线索整理 "BAILIAN_APP_ID_8_CONSOLIDATE": json.dumps({ "app3_clues": { "generated_at": "2026-03-05 14:30:00", "clues": [ {"detail": "客户近3个月消费12次,平均10天到店一次,当前已13天未到店,超过平均间隔30%,存在轻度流失风险。建议本周内联系召回。", "category": "消费习惯", "summary": "到店间隔异常,轻度流失风险", "emoji": "⏰"}, {"detail": "客户台费占比55%,助教费占比30%(陪打+超休),偏好周末晚间时段(18:00-22:00),中式台球为主。客单价238元,属于中等消费水平。", "category": "玩法偏好", "summary": "周末晚间中式台球爱好者", "emoji": "🎱"} ] }, "app6_clues": { "generated_at": "2026-03-05 14:25:00", "clues": [ {"category": "社交关系", "emoji": "👥", "detail": "客户经常带朋友来店,最近一次带了两位公司同事,主动询问团建套餐,具有社交裂变潜力和团建需求。", "summary": "常带朋友,有团建需求"}, {"category": "促销接受", "emoji": "💰", "detail": "客户对充值活动感兴趣,曾主动充值500元,储值余额580元。可在余额接近用完时推荐充值优惠。", "summary": "充值意愿强,关注优惠活动"} ] } }, ensure_ascii=False), } # ── 通过 HTTP 调用百炼应用 API ───────────────────────────────────────────────── import urllib.request import urllib.error API_BASE = "https://dashscope.aliyuncs.com/api/v1/apps" def call_app(env_key: str, label: str) -> dict: """通过 HTTP POST 调用单个百炼应用,返回结果摘要。""" app_id = os.environ.get(env_key, "") if not app_id: return {"app": label, "status": "SKIP", "reason": f"环境变量 {env_key} 未设置"} prompt = FIRST_PROMPTS.get(env_key, "你好") url = f"{API_BASE}/{app_id}/completion" body = json.dumps({ "input": {"prompt": prompt}, "parameters": {"has_thoughts": False} }).encode("utf-8") headers = { "Authorization": f"Bearer {BAILIAN_API_KEY}", "Content-Type": "application/json", } t0 = time.time() try: req = urllib.request.Request(url, data=body, headers=headers, method="POST") with urllib.request.urlopen(req, timeout=60) as resp: data = json.loads(resp.read().decode("utf-8")) elapsed = round(time.time() - t0, 2) output = data.get("output", {}) usage = data.get("usage", {}) text = output.get("text", "") finish = output.get("finish_reason", "?") models = usage.get("models", []) tokens_info = {} if models: m = models[0] tokens_info = { "model": m.get("model_id", "?"), "input": m.get("input_tokens", "?"), "output": m.get("output_tokens", "?"), } return { "app": label, "app_id": app_id[:8] + "...", "status": "OK", "elapsed_s": elapsed, "finish_reason": finish, "tokens": tokens_info, "response_preview": text[:300] + ("..." if len(text) > 300 else ""), } except urllib.error.HTTPError as e: elapsed = round(time.time() - t0, 2) err_body = "" try: err_body = e.read().decode("utf-8")[:300] except Exception: pass return { "app": label, "app_id": app_id[:8] + "...", "status": "ERROR", "elapsed_s": elapsed, "error": f"HTTP {e.code}: {err_body}", } except Exception as e: elapsed = round(time.time() - t0, 2) return { "app": label, "app_id": app_id[:8] + "...", "status": "ERROR", "elapsed_s": elapsed, "error": str(e)[:300], } def main(): print("=" * 70) print("百炼 AI 应用连通性测试(DashScope Application API)") print(f"API Key: {BAILIAN_API_KEY[:8]}...{BAILIAN_API_KEY[-4:]}") print(f"API Base: {API_BASE}") print("=" * 70) results = [] for env_key, label in APP_CONFIGS: print(f"\n▶ 测试 {label} ...") result = call_app(env_key, label) results.append(result) icon = "✅" if result["status"] == "OK" else "⏭️" if result["status"] == "SKIP" else "❌" print(f" {icon} {result['status']}", end="") if "elapsed_s" in result: print(f" ({result['elapsed_s']}s)", end="") if "tokens" in result: t = result["tokens"] print(f" | model={t.get('model','?')} in={t.get('input','?')} out={t.get('output','?')}", end="") if "finish_reason" in result: print(f" | finish={result['finish_reason']}", end="") if "error" in result: print(f"\n ⚠️ {result['error'][:150]}", end="") if "reason" in result: print(f" | {result['reason']}", end="") print() if "response_preview" in result: preview = result["response_preview"][:200] print(f" 📝 {preview}") # 汇总 print("\n" + "=" * 70) ok = sum(1 for r in results if r["status"] == "OK") err = sum(1 for r in results if r["status"] == "ERROR") skip = sum(1 for r in results if r["status"] == "SKIP") print(f"汇总: ✅ {ok} 成功 | ❌ {err} 失败 | ⏭️ {skip} 跳过 | 共 {len(results)} 个应用") print("=" * 70) if err > 0: sys.exit(1) if __name__ == "__main__": main()