astock-agent/backend/app/llm/enhancer.py
2026-04-07 20:51:00 +08:00

136 lines
4.8 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.

"""推荐结果 LLM 增强
扫描完成后异步调用 LLM为每只推荐股票生成深度分析。
"""
import asyncio
import json
import logging
from app.llm.client import chat_completion
from app.llm.prompts import ENHANCE_SYSTEM_PROMPT, ENHANCE_USER_TEMPLATE
from app.config import settings
logger = logging.getLogger(__name__)
async def enhance_recommendations(result: dict) -> None:
"""对推荐结果进行 LLM 增强分析fire-and-forget"""
if not settings.deepseek_api_key:
return
recommendations = result.get("recommendations", [])
if not recommendations:
return
market_temp = result.get("market_temp")
hot_sectors = result.get("hot_sectors", [])
# 构建板块文本
sectors_text = "\n".join(
f"- {s.sector_name}: 涨幅{s.pct_change}%, 资金流入{s.capital_inflow}万, "
f"涨停{s.limit_up_count}家, 热度{s.heat_score}分, 阶段={s.stage}"
for s in hot_sectors[:5]
) if hot_sectors else "暂无板块数据"
# 温度等级
temp_val = market_temp.temperature if market_temp else 0
if temp_val >= 60:
temp_level = "积极"
elif temp_val >= 30:
temp_level = "谨慎"
else:
temp_level = "低迷"
enhanced_count = 0
for rec in recommendations:
try:
user_msg = ENHANCE_USER_TEMPLATE.format(
temperature=market_temp.temperature if market_temp else "N/A",
temp_level=temp_level,
up_count=market_temp.up_count if market_temp else 0,
down_count=market_temp.down_count if market_temp else 0,
limit_up_count=market_temp.limit_up_count if market_temp else 0,
max_streak=market_temp.max_streak if market_temp else 0,
broken_rate=market_temp.broken_rate if market_temp else 0,
sectors_text=sectors_text,
name=rec.name,
ts_code=rec.ts_code,
sector=rec.sector,
score=rec.score,
level=rec.level,
market_temp_score=rec.market_temp_score,
sector_score=rec.sector_score,
capital_score=rec.capital_score,
technical_score=rec.technical_score,
position_score=rec.position_score,
valuation_score=rec.valuation_score,
signal=rec.signal,
entry_price=rec.entry_price or "N/A",
target_price=rec.target_price or "N/A",
stop_loss=rec.stop_loss or "N/A",
reasons="".join(rec.reasons) if rec.reasons else "",
)
messages = [
{"role": "system", "content": ENHANCE_SYSTEM_PROMPT},
{"role": "user", "content": user_msg},
]
resp = await chat_completion(messages)
if resp and resp.content:
rec.llm_analysis = resp.content.strip()
enhanced_count += 1
except asyncio.CancelledError:
logger.warning(f"LLM 增强 {rec.ts_code} 被取消")
break
except Exception as e:
logger.error(f"LLM 增强 {rec.ts_code} 失败: {e}")
if enhanced_count > 0:
# 更新数据库
await _save_llm_analysis_to_db(recommendations)
# 通过 WebSocket 通知前端
await _broadcast_llm_ready(recommendations)
logger.info(f"LLM 增强完成: {enhanced_count}/{len(recommendations)}")
async def _save_llm_analysis_to_db(recommendations: list) -> None:
"""将 LLM 分析结果更新到数据库"""
try:
from app.db.database import get_db
from sqlalchemy import text
async with get_db() as db:
for rec in recommendations:
if not rec.llm_analysis:
continue
await db.execute(
text(
"UPDATE recommendations SET llm_analysis = :analysis "
"WHERE ts_code = :code AND date(created_at) = date('now', 'localtime') "
"AND scan_session = :session"
),
{
"analysis": rec.llm_analysis,
"code": rec.ts_code,
"session": rec.scan_session,
},
)
await db.commit()
except Exception as e:
logger.error(f"保存 LLM 分析到数据库失败: {e}")
async def _broadcast_llm_ready(recommendations: list) -> None:
"""通过 WebSocket 广播 LLM 分析完成事件"""
try:
from app.api.websocket import broadcast_update
await broadcast_update({
"type": "llm_analysis_ready",
"count": len([r for r in recommendations if r.llm_analysis]),
})
except Exception as e:
logger.error(f"广播 LLM 分析完成失败: {e}")