"""推荐结果 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}")