""" 问题分析器 - 使用LLM深度理解用户意图 """ import json import asyncio from typing import Dict, Any, Optional, List from app.services.llm_service import llm_service from app.utils.logger import logger class QuestionAnalyzer: """智能问题分析器 - 使用LLM深度理解用户意图""" def __init__(self): """初始化问题分析器""" self.use_llm = llm_service.client is not None if not self.use_llm: logger.warning("LLM未配置,QuestionAnalyzer将使用降级模式") async def analyze_question( self, question: str, context: List[Dict], session_id: str ) -> Dict[str, Any]: """ 深度分析用户问题 Args: question: 用户问题 context: 对话历史上下文 session_id: 会话ID Returns: QuestionIntent: { 'type': 'stock_analysis' | 'market_overview' | 'knowledge' | 'chat', 'target': { 'stock_code': str, 'stock_name': str, 'market': 'A股' | '美股' }, 'dimensions': { 'price_trend': bool, # 价格走势 'technical': bool, # 技术指标 'fundamental': bool, # 基本面 'valuation': bool, # 估值 'money_flow': bool, # 资金流向 'risk': bool # 风险分析 }, 'time_scope': { 'short_term': bool, # 短期(1-2周) 'medium_term': bool, # 中期(1-3月) 'long_term': bool # 长期(半年+) }, 'analysis_depth': 'quick' | 'standard' | 'deep', 'specific_concerns': List[str], # 特定关注点 'context_references': { 'refers_to_previous': bool, 'comparison_target': str | None }, 'user_style': { 'tone': 'professional' | 'casual', 'detail_level': 'brief' | 'detailed' } } """ if not self.use_llm: # 降级模式:返回基本的意图分析 return self._fallback_analysis(question) # 构建上下文字符串 context_str = self._format_context(context) # 构建LLM prompt prompt = self._build_analysis_prompt(question, context_str) try: # 异步调用LLM result = await self._call_llm_async( messages=[{"role": "user", "content": prompt}], temperature=0.3, max_tokens=800 ) if not result: logger.warning("LLM返回空结果,使用降级模式") return self._fallback_analysis(question) # 清理和解析JSON intent = self._parse_llm_response(result) if intent: logger.info(f"问题分析成功: type={intent.get('type')}, dimensions={intent.get('dimensions')}") return intent else: logger.warning("JSON解析失败,使用降级模式") return self._fallback_analysis(question) except Exception as e: logger.error(f"问题分析失败: {e}") return self._fallback_analysis(question) async def _call_llm_async( self, messages: List[Dict[str, str]], temperature: float = 0.3, max_tokens: int = 800 ) -> Optional[str]: """异步调用LLM""" loop = asyncio.get_event_loop() return await loop.run_in_executor( None, lambda: llm_service.chat(messages, temperature, max_tokens) ) def _format_context(self, context: List[Dict]) -> str: """格式化对话历史上下文""" if not context: return "" context_str = "\n\n【对话历史】\n" # 只取最近4条消息 for msg in context[-4:]: role = "用户" if msg["role"] == "user" else "助手" content = msg['content'][:100] # 限制长度 context_str += f"{role}: {content}\n" return context_str def _build_analysis_prompt(self, question: str, context_str: str) -> str: """构建问题分析的LLM prompt""" prompt = f"""你是一个专业的金融问题分析专家。请深度分析用户的问题,提取结构化信息。 {context_str} 【当前问题】 用户: {question} 请分析以下维度: 1. **问题类型** - stock_analysis: 针对**特定单只股票**的分析(如"贵州茅台怎么样"、"分析比亚迪"、"AAPL走势"、"阿里巴巴美股") **注意**:如果用户问的是"板块"、"行业"、"概念股"等,这不是stock_analysis,而是market_overview - market_overview: 市场整体分析、行业板块分析、投资机会(如"最近有什么投资机会"、"商业航天板块怎么样"、"新能源行业走势"、"现在适合买股票吗") - knowledge: 金融知识问答(如"什么是MACD"、"如何看K线图") - chat: 一般对话(如"你好"、"在吗") **重要**:判断是stock_analysis还是market_overview的关键: - 如果提到具体的公司名称或股票代码 → stock_analysis - 如果提到"板块"、"行业"、"概念"、"赛道"、"领域" → market_overview - 如果问"哪些股票"、"什么机会" → market_overview 2. **股票识别**(如果是stock_analysis,这是最重要的部分) 请识别用户提到的股票,并返回准确的股票代码: **A股代码格式**:6位数字 - 上海主板:600xxx、601xxx、603xxx、605xxx - 深圳主板:000xxx、001xxx - 创业板:300xxx、301xxx - 科创板:688xxx - 常见示例:贵州茅台→600519,比亚迪→002594,宁德时代→300750 **美股代码格式**:1-5位大写字母 - 常见示例:苹果→AAPL,特斯拉→TSLA,微软→MSFT,谷歌→GOOGL - 中概股美股:阿里巴巴美股→BABA,京东美股→JD,拼多多→PDD,百度美股→BIDU,网易美股→NTES,哔哩哔哩美股→BILI **港股代码格式**:4-5位数字加.HK后缀 - 常见示例:腾讯→0700.HK,阿里巴巴港股→9988.HK,美团→3690.HK,小米→1810.HK,京东港股→9618.HK,百度港股→9888.HK,网易港股→9999.HK,哔哩哔哩港股→9626.HK - 注意:港股代码需要包含.HK后缀 **市场判断**: - 如果用户明确说"美股"、"纳斯达克"、"纽交所" → 美股 - 如果用户明确说"港股"、"香港"、"恒生" → 港股 - 对于同时在多地上市的公司(如阿里巴巴、京东、百度等): - 用户说"美股"或没有明确指定 → 返回美股代码(如BABA) - 用户说"港股" → 返回港股代码(如9988.HK) - 纯港股公司(如腾讯、美团、小米)→ 港股 - 默认情况下,中国公司优先考虑A股市场 3. **用户关注维度**(如果是stock_analysis) 分析用户想了解哪些方面: - price_trend: 价格走势、涨跌情况、最新价格 - technical: 技术指标(MACD、RSI、均线、KDJ等) - fundamental: 基本面(公司业务、行业地位、财务状况) - valuation: 估值水平(PE、PB、市值、估值是否合理) - money_flow: 资金流向、主力动向、大单流入流出 - risk: 风险分析、风险提示、投资风险 4. **时间范围** - short_term: 短期(1-2周)- 如"短期走势"、"近期表现" - medium_term: 中期(1-3月)- 如"中期趋势"、"未来一个月" - long_term: 长期(半年以上)- 如"长期投资"、"适合长期持有吗" 5. **分析深度** - quick: 快速查看(只需要基本信息,如"价格多少") - standard: 标准分析(常规分析,如"怎么样"、"分析一下") - deep: 深度分析(全面详细,如"全面分析"、"深度研究") 6. **特定关注点** 提取用户明确提到的关注点,如: - "支撑位在哪" - "盈利能力如何" - "适合长期持有吗" - "有没有金叉" 7. **上下文引用** - 是否引用了之前的对话("这只股票"、"它"、"那技术面呢") - 是否要求对比分析("和上次相比"、"对比一下") 8. **用户风格** - tone: professional(专业,使用专业术语)/ casual(随意,通俗易懂) - detail_level: brief(简洁,简短回答)/ detailed(详细,详细分析) 请以JSON格式返回分析结果: {{ "type": "问题类型", "target": {{ "stock_code": "股票代码(A股返回6位数字如600519,美股返回大写字母如BABA,港股返回带.HK后缀如0700.HK)", "stock_name": "股票/公司名称(如贵州茅台、阿里巴巴、腾讯)", "market": "A股/美股/港股" }}, "dimensions": {{ "price_trend": true/false, "technical": true/false, "fundamental": true/false, "valuation": true/false, "money_flow": true/false, "risk": true/false }}, "time_scope": {{ "short_term": true/false, "medium_term": true/false, "long_term": true/false }}, "analysis_depth": "quick/standard/deep", "specific_concerns": ["关注点1", "关注点2"], "context_references": {{ "refers_to_previous": true/false, "comparison_target": "对比目标(如有)" }}, "user_style": {{ "tone": "professional/casual", "detail_level": "brief/detailed" }} }} 只返回JSON,不要有任何其他内容。""" return prompt def _parse_llm_response(self, response: str) -> Optional[Dict[str, Any]]: """解析LLM返回的JSON响应""" try: # 清理结果,移除可能的markdown代码块标记 result = response.strip() if result.startswith("```json"): result = result[7:] if result.startswith("```"): result = result[3:] if result.endswith("```"): result = result[:-3] result = result.strip() # 检查是否为空 if not result: return None # 解析JSON intent = json.loads(result) return intent except json.JSONDecodeError as e: logger.error(f"JSON解析失败: {e}, 原始响应: {response[:200]}") return None except Exception as e: logger.error(f"解析LLM响应失败: {e}") return None def _fallback_analysis(self, question: str) -> Dict[str, Any]: """降级模式:基于规则的简单分析""" question_lower = question.lower() # 简单的关键词匹配 is_stock_query = any(kw in question for kw in [ "股票", "分析", "怎么样", "如何", "走势", "价格", "涨", "跌" ]) if is_stock_query: # 尝试提取股票名称(简单规则) return { 'type': 'stock_analysis', 'target': { 'stock_code': '', 'stock_name': '', 'market': 'A股' }, 'dimensions': { 'price_trend': True, 'technical': True, 'fundamental': True, 'valuation': False, 'money_flow': False, 'risk': False }, 'time_scope': { 'short_term': True, 'medium_term': True, 'long_term': False }, 'analysis_depth': 'standard', 'specific_concerns': [], 'context_references': { 'refers_to_previous': False, 'comparison_target': None }, 'user_style': { 'tone': 'casual', 'detail_level': 'detailed' } } else: # 默认为一般对话 return { 'type': 'chat', 'target': {}, 'dimensions': {}, 'time_scope': {}, 'analysis_depth': 'quick', 'specific_concerns': [], 'context_references': { 'refers_to_previous': False, 'comparison_target': None }, 'user_style': { 'tone': 'casual', 'detail_level': 'brief' } }