Files
PolyWeather/src/analysis/ai_analyzer.py
T

129 lines
5.5 KiB
Python
Raw 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.
import os
import time
import requests
from loguru import logger
# 主力模型 + 备用模型(当主力 500 时自动降级)
MODELS = [
"llama-3.3-70b-versatile",
"llama-3.1-8b-instant",
]
def get_ai_analysis(weather_insights: str, city_name: str, temp_symbol: str) -> str:
"""
通过 Groq API (LLaMA 3.3 70B) 对天气态势进行极速交易分析
内置自动重试 + 模型降级机制
"""
api_key = os.getenv("GROQ_API_KEY")
if not api_key:
logger.warning("GROQ_API_KEY 未配置,跳过 AI 分析")
return ""
url = "https://api.groq.com/openai/v1/chat/completions"
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
prompt = f"""
你是一个专业的天气衍生品交易员。你的任务是分析当前气象实况,判断今日实测最高温的结算落点。
结算基准为合约指定源(通常为 METAR 口径整数四舍五入),以下用"结算值"代替具体源名称。
请综合以下提供的【{city_name}】气象特征进行深度推理。
【气象特征与事实】
{weather_insights}
【分析框架】(按此顺序逐项检查,前置项可约束后置项的结论)
P0 **预报失准检测**
- 若数据含"🚨 预报崩盘"或"⚠️ 预报差距"标记,判定预报失准。
- 失准等级:轻(偏差2-3°) / 中(3-5°) / 重(>5°)。
- 但"失准"≠"已定局":还需检查近2报斜率是否≤0 且 风向/云量不支持二次抬升,才能判定结算锁定。
- 若斜率仍>0或有暖平流迹象,应注明"预报偏高但仍有上行空间"。
P1 **实况节奏**
- 近2-4条METAR的温度走势:连涨/持平/回落?
- 连续2报创新高 → 升温未止;连续2报未创新高且斜率≤0 → 偏死盘。
- 升温出现在低辐射时段 → 可能有多因子叠加(平流/混合层/热岛),不可单因子归因。
P2 **阻碍因子**(需结合城市特性判断):
- 降水已出现(非trace) → 强压温。
- 高湿度+厚云层持续2报以上 → 压温可能有效,但阈值因城市(海洋型 vs 大陆型)而异,不可套用固定数值。
- 若仅单因子(如仅多云),不足以断定"升温受限"。
P3 **概率与一致性校验**
- 参考结算概率分布,与 P1 实况做一致性检查。
- 若概率分布与实况趋势矛盾,以实况为准并说明偏离原因。
- 概率可辅助判断边界进位风险(如 X.5 线附近)。
P4 **预报背景**(最低优先级):
- 可参考 DEB/预报做上沿空间评估。
- 当实测已显著偏离预报时,禁止继续引用预报值作为目标。
【输出要求】
1. 正常场景控制在 300 字左右;异常场景(预报失准/极端走势)可扩展到 450 字。
2. 严格按照以下 HTML 格式输出:
🤖 <b>Groq AI 决策</b>
- 🎲 盘口: [给出结算判断。用"已确认底线 X{temp_symbol}"表示下限确定;用"上沿待确认,关注 Y{temp_symbol}"表示仍有变数。若预报严重失准,注明失准等级和原因。禁止在升温未止时用"锁定"。]
- 💡 逻辑: [3-5 句深度分析。含具体数值。预报失准时重点分析偏差成因。正常时分析实测与预报的动态博弈。]
- 🎯 置信度: [1-10]/10
3. **禁止输出分析框架本身**。不要输出 P0/P1/P2/P3/P4 的分析过程或标题。只输出上方三行格式,不要多余内容。
"""
# Use proxy if configured
proxies = {}
proxy_url = os.getenv("HTTPS_PROXY") or os.getenv("HTTP_PROXY")
if proxy_url:
proxies = {"http": proxy_url, "https": proxy_url}
for model in MODELS:
for attempt in range(2): # 每个模型最多重试 2 次
try:
payload = {
"model": model,
"messages": [
{
"role": "system",
"content": "你是不讲废话、只看数据的专业气象分析师。",
},
{"role": "user", "content": prompt},
],
"temperature": 0.5,
"max_tokens": 400,
}
response = requests.post(
url, json=payload, headers=headers, timeout=15, proxies=proxies
)
response.raise_for_status()
result = response.json()
content = result["choices"][0]["message"]["content"].strip()
if model != MODELS[0]:
logger.info(f"Groq 降级到备用模型 {model} 成功")
return content
except requests.exceptions.HTTPError as e:
status = e.response.status_code if e.response is not None else 0
error_body = ""
try:
error_body = e.response.text
except:
pass
logger.warning(
f"Groq {model} 失败 (HTTP {status}): {error_body}. 尝试下一个..."
)
if status in (500, 502, 503) and attempt == 0:
time.sleep(1.5)
continue
else:
break # 换下一个模型
except Exception as e:
logger.warning(f"Groq {model} 异常: {str(e)},尝试下一个模型...")
break
logger.error("所有 Groq 模型均不可用")
return "\n⚠️ Groq AI 暂时不可用,请稍后再试"