""" 智能Agent - 真正使用LLM进行全面分析 """ import re import json import asyncio from typing import Dict, Any, Optional, List from app.config import get_settings from app.agent.context import ContextManager from app.agent.skill_manager import skill_manager from app.agent.question_analyzer import QuestionAnalyzer from app.agent.skill_planner import SkillPlanner from app.skills.market_data import MarketDataSkill from app.skills.technical_analysis import TechnicalAnalysisSkill from app.skills.fundamental import FundamentalSkill from app.skills.visualization import VisualizationSkill from app.skills.advanced_data import AdvancedDataSkill from app.skills.us_stock_skill import USStockSkill from app.services.llm_service import llm_service from app.services.tushare_service import tushare_service from app.utils.logger import logger class SmartStockAgent: """智能股票分析Agent - 深度集成LLM""" def __init__(self): """初始化Agent""" self.context_manager = ContextManager() self.settings = get_settings() # 初始化智能组件 self.question_analyzer = QuestionAnalyzer() self.skill_planner = SkillPlanner() # 注册技能 self._register_skills() # 检查LLM是否可用 self.use_llm = bool(self.settings.zhipuai_api_key) and llm_service.client is not None if self.use_llm: logger.info("Smart Agent初始化完成(智能模式 + LLM深度集成 + Tushare Pro高级数据)") else: logger.warning("Smart Agent初始化完成(规则模式,建议配置LLM)") async def _call_llm_async(self, messages: List[Dict[str, str]], temperature: float = 0.7, max_tokens: int = 2000) -> Optional[str]: """异步调用LLM,避免阻塞事件循环""" loop = asyncio.get_event_loop() return await loop.run_in_executor( None, lambda: llm_service.chat(messages, temperature, max_tokens) ) def _register_skills(self): """注册所有技能""" skill_manager.register(MarketDataSkill()) skill_manager.register(TechnicalAnalysisSkill()) skill_manager.register(FundamentalSkill()) skill_manager.register(VisualizationSkill()) skill_manager.register(AdvancedDataSkill()) skill_manager.register(USStockSkill()) logger.info("技能注册完成(Tushare Pro高级数据 + 美股支持)") async def process_message( self, message: str, session_id: str, user_id: Optional[str] = None ) -> Dict[str, Any]: """ 处理用户消息(非流式,已废弃,保留用于兼容) 实际使用 process_message_stream 进行流式输出 """ # 收集流式输出 full_response = "" async for chunk in self.process_message_stream(message, session_id, user_id): full_response += chunk return { "message": full_response, "metadata": {"type": "text"} } def _is_comprehensive_analysis(self, message: str) -> bool: """ 判断是否需要全面分析 默认情况下,如果用户只是简单提到股票名称或代码,就进行全面分析 只有明确要求特定信息时(如"技术指标"、"K线图"等),才做单一查询 """ # 明确要求单一查询的关键词 single_query_keywords = [ "k线", "图表", "走势图", "kline", "技术指标", "macd", "rsi", "均线", "kdj", "基本面", "公司信息", "行业", "实时行情", "价格", "涨跌" ] # 如果明确要求单一查询,返回False if any(keyword in message.lower() for keyword in single_query_keywords): return False # 默认进行全面分析 return True async def _comprehensive_analysis( self, stock_code: str, stock_name: Optional[str], message: str ) -> Dict[str, Any]: """ 全面分析:整合多个数据源 + LLM深度分析 Args: stock_code: 股票代码 stock_name: 股票名称 message: 用户消息 Returns: 综合分析结果 """ logger.info(f"执行全面分析: {stock_code}") display_name = stock_name or stock_code # 1. 并行获取所有数据 try: # 获取实时行情 quote_result = await skill_manager.execute_skill( "market_data", stock_code=stock_code, data_type="quote" ) # 获取技术指标 technical_result = await skill_manager.execute_skill( "technical_analysis", stock_code=stock_code, indicators=["ma", "macd", "rsi", "kdj"] ) # 获取基本面 fundamental_result = await skill_manager.execute_skill( "fundamental", stock_code=stock_code ) # 获取高级数据(Tushare Pro 5000+积分) advanced_result = await skill_manager.execute_skill( "advanced_data", stock_code=stock_code, data_type="all" ) # 整合数据 all_data = { "stock_code": stock_code, "stock_name": display_name, "quote": quote_result.get("data") if quote_result.get("success") else None, "technical": technical_result.get("data") if technical_result.get("success") else None, "fundamental": fundamental_result.get("data") if fundamental_result.get("success") else None, "advanced": advanced_result.get("data") if advanced_result.get("success") else None } # 2. 使用LLM进行深度分析 if self.use_llm: analysis = await self._llm_comprehensive_analysis(all_data, message) else: analysis = self._rule_based_analysis(all_data) return { "message": analysis, "metadata": { "type": "comprehensive", "data": all_data } } except Exception as e: logger.error(f"全面分析失败: {e}") return { "message": f"分析{display_name}时出错:{str(e)}", "metadata": {"type": "error"} } async def _llm_comprehensive_analysis( self, data: Dict[str, Any], user_message: str ) -> str: """使用LLM进行深度综合分析""" # 获取当前时间 from datetime import datetime current_time = datetime.now().strftime("%Y-%m-%d %H:%M") # 获取行情数据的交易日期 quote_date = "未知" if data.get('quote') and data['quote'].get('trade_date'): quote_date = data['quote']['trade_date'] # 构建高级数据摘要 advanced_summary = "" if data.get('advanced'): advanced_data = data['advanced'] advanced_summary = "\n【高级财务数据】(Tushare Pro 5000+积分)\n" # 财务指标 if advanced_data.get('financial'): financial = advanced_data['financial'] if financial.get('indicators'): indicators = financial['indicators'].get('indicators', {}) advanced_summary += f"财务指标(截止:{financial['indicators'].get('end_date', '未知')}):\n" advanced_summary += f" ROE: {indicators.get('roe', 'N/A')}%\n" advanced_summary += f" ROA: {indicators.get('roa', 'N/A')}%\n" advanced_summary += f" 毛利率: {indicators.get('gross_margin', 'N/A')}%\n" advanced_summary += f" 资产负债率: {indicators.get('debt_to_assets', 'N/A')}%\n" advanced_summary += f" 流动比率: {indicators.get('current_ratio', 'N/A')}\n\n" # 估值数据 if advanced_data.get('valuation'): valuation = advanced_data['valuation'] advanced_summary += f"估值指标:\n" advanced_summary += f" PE(市盈率): {valuation.get('pe', 'N/A')}\n" advanced_summary += f" PB(市净率): {valuation.get('pb', 'N/A')}\n" advanced_summary += f" PS(市销率): {valuation.get('ps', 'N/A')}\n" advanced_summary += f" 总市值: {valuation.get('total_mv', 'N/A')}万元\n" advanced_summary += f" 流通市值: {valuation.get('circ_mv', 'N/A')}万元\n" advanced_summary += f" 换手率: {valuation.get('turnover_rate', 'N/A')}%\n\n" # 资金流向(最近一天) if advanced_data.get('money_flow') and len(advanced_data['money_flow']) > 0: latest_flow = advanced_data['money_flow'][0] advanced_summary += f"资金流向({latest_flow.get('trade_date', '最近')}):\n" advanced_summary += f" 主力净流入: {latest_flow.get('net_mf_amount', 'N/A')}万元\n" advanced_summary += f" 超大单净流入: {latest_flow.get('buy_elg_amount', 0) - latest_flow.get('sell_elg_amount', 0):.2f}万元\n" advanced_summary += f" 大单净流入: {latest_flow.get('buy_lg_amount', 0) - latest_flow.get('sell_lg_amount', 0):.2f}万元\n\n" # 融资融券 if advanced_data.get('margin') and len(advanced_data['margin']) > 0: latest_margin = advanced_data['margin'][0] advanced_summary += f"融资融券({latest_margin.get('trade_date', '最近')}):\n" advanced_summary += f" 融资余额: {latest_margin.get('rzye', 'N/A')}元\n" advanced_summary += f" 融券余额: {latest_margin.get('rqye', 'N/A')}元\n\n" # 重大公告 if advanced_data.get('announcements') and len(advanced_data['announcements']) > 0: advanced_summary += "最近公告:\n" for ann in advanced_data['announcements'][:3]: advanced_summary += f" · {ann.get('title', '无标题')} ({ann.get('ann_date', '')})\n" advanced_summary += "\n" else: advanced_summary = "\n【高级财务数据】\n暂无高级数据\n" # 构建详细的分析提示 prompt = f"""你是一位专业的股票分析师。请对{data['stock_name']}({data['stock_code']})进行全面分析,用简洁专业但易懂的语言回答。 用户问题:{user_message} 【实时行情数据】 数据来源:Tushare Pro API 交易日期:{quote_date} {json.dumps(data.get('quote'), ensure_ascii=False, indent=2) if data.get('quote') else '数据获取失败'} 【技术指标数据】 数据来源:Tushare Pro API(基于历史K线数据计算) 计算截止日期:{quote_date} {json.dumps(data.get('technical'), ensure_ascii=False, indent=2) if data.get('technical') else '数据获取失败'} 【基本面数据】 数据来源:Tushare Pro API {json.dumps(data.get('fundamental'), ensure_ascii=False, indent=2) if data.get('fundamental') else '数据获取失败'} {advanced_summary} 请按以下结构进行分析,并在每个部分明确标注数据来源和时效性: ## 一、基本面分析 分段说明公司情况,每个要点独立成段: - 第一段:公司主营业务和行业地位 - 第二段:财务健康度分析(基于ROE、资产负债率、流动比率等财务指标) - 第三段:估值水平分析(PE、PB、PS是否合理) - 第四段:所属行业发展前景 - 第五段:如果有公告,简要分析对公司的影响 ## 二、技术面分析(数据截止:{quote_date}) 使用清晰的分段结构,每个技术指标独立成段: **价格走势** 当前价格走势特征(上涨/下跌/震荡),结合成交量分析。 **均线系统** 短期均线(MA5、MA10)与长期均线(MA20、MA60)的位置关系,判断当前趋势(多头/空头/震荡)。 **MACD指标** DIF和DEA的位置关系,MACD柱状图变化,判断动能强弱和买卖信号。 **RSI指标** 当前RSI值的位置,是否超买(>70)或超卖(<30),短期走势预判。 **支撑与压力** 关键支撑位和压力位的具体价格区间。 ## 三、市场情绪分析 分段分析市场情绪: - 第一段:资金流向分析(主力资金、大单资金流入/流出情况) - 第二段:融资融券情况(如有) - 第三段:当前市场情绪(乐观/谨慎/悲观)及原因 - 第四段:短期可能的催化因素 ## 四、投资建议 基于技术面分析,给出具体的操作建议和点位: **短期(1-2周)操作建议** - 明确的操作建议:买入/持有/观望/减仓 - **具体点位建议**: - 如果建议买入:给出建议买入价格区间(基于支撑位) - 如果建议卖出:给出建议卖出价格区间(基于压力位) - 止损位:明确的止损价格点位 - 止盈位:明确的止盈价格点位 - 操作理由:基于技术指标的具体分析 **中期(1-3个月)策略** - 趋势判断(上涨/下跌/震荡) - 关键价格区间: - 上方目标位:具体价格 - 下方支撑位:具体价格 - 策略建议 **长期(半年以上)** 投资价值评估和长期持有建议。 **风险提示** - 主要风险点和注意事项 - 需要关注的关键价格位 ## 五、总结 用一句话概括核心观点。 --- **数据说明** - 行情数据来源:Tushare Pro(截止{quote_date}) - 技术指标:基于历史K线数据计算(截止{quote_date}) - 财务数据:Tushare Pro 5000+积分接口(利润表、资产负债表、财务指标) - 估值数据:Tushare Pro(PE、PB、PS、市值等) - 资金流向:Tushare Pro(主力资金、大单资金) - 融资融券:Tushare Pro(如有) - 公告数据:Tushare Pro(重大公告) 写作要求: 1. 语言简洁专业,避免过度修饰和比喻 2. 专业术语后用括号简单解释,例如"RSI超买(指标>70,股价可能回调)" 3. **重要:每个分析点必须独立成段,段落之间用空行分隔** 4. **技术面分析部分,每个指标必须使用加粗标题(**标题**)并独立成段** 5. 分析要客观理性,基于数据而非情绪 6. 充分利用提供的财务数据、估值数据、资金流向等高级数据进行分析 7. 结论要明确,不要模棱两可 8. 控制在800-1000字(由于数据更丰富,可以写得更详细) 9. 最后必须声明:"以上分析仅供参考,不构成投资建议。股市有风险,投资需谨慎。" """ try: analysis = await self._call_llm_async( messages=[{"role": "user", "content": prompt}], temperature=0.7, max_tokens=3000 # 增加到3000,因为分析更详细了 ) if analysis: return f"【{data['stock_name']}({data['stock_code']}) - AI深度分析】\n\n{analysis}" else: return self._rule_based_analysis(data) except Exception as e: logger.error(f"LLM分析失败: {e}") return self._rule_based_analysis(data) async def _llm_single_analysis( self, intent: Dict[str, Any], result: Dict[str, Any], stock_code: str, stock_name: Optional[str], user_message: str ) -> Dict[str, Any]: """使用LLM对单一查询进行分析""" data = result.get("data", result) display_name = stock_name or stock_code # 根据查询类型构建不同的prompt if intent["type"] == "technical": prompt = f"""你是一位专业的股票分析师。用户询问了{display_name}({stock_code})的技术指标。 用户问题:{user_message} 【技术指标数据】 {json.dumps(data, ensure_ascii=False, indent=2)} 请进行专业的技术分析: ## 技术指标解读 1. 均线系统分析: - 短期均线(MA5、MA10)与长期均线(MA20、MA60)的位置关系 - 判断当前趋势(多头/空头/震荡) 2. MACD指标分析: - DIF和DEA的位置关系 - MACD柱状图的变化趋势 - 判断动能强弱 3. RSI指标分析: - 当前RSI值的位置(超买/超卖/中性) - 短期可能的走势 4. KDJ指标分析(如有): - K、D、J值的位置关系 - 金叉/死叉信号 ## 综合判断 - 短期走势预判(1-2周) - 关键支撑位和压力位 - 操作建议(买入/持有/观望/减仓) ## 风险提示 - 主要技术风险点 写作要求: 1. 语言简洁专业,直接给出分析结论 2. 基于数据进行分析,不要编造 3. 控制在300-400字 4. 最后声明:"以上分析仅供参考,不构成投资建议。股市有风险,投资需谨慎。" """ elif intent["type"] == "quote": prompt = f"""你是一位专业的股票分析师。用户询问了{display_name}({stock_code})的实时行情。 用户问题:{user_message} 【实时行情数据】 {json.dumps(data, ensure_ascii=False, indent=2)} 请进行专业的行情分析: ## 行情解读 1. 当日表现: - 涨跌幅分析 - 成交量分析 - 振幅分析 2. 价格位置: - 当前价格相对开盘价、最高价、最低价的位置 - 判断多空力量对比 3. 短期判断: - 当日走势特征 - 短期可能的走势 - 操作建议 写作要求: 1. 语言简洁专业,直接给出分析结论 2. 基于数据进行分析,不要编造 3. 控制在200-300字 4. 最后声明:"以上分析仅供参考,不构成投资建议。股市有风险,投资需谨慎。" """ elif intent["type"] == "fundamental": prompt = f"""你是一位专业的股票分析师。用户询问了{display_name}({stock_code})的基本面信息。 用户问题:{user_message} 【基本面数据】 {json.dumps(data, ensure_ascii=False, indent=2)} 请进行专业的基本面分析: ## 公司概况 - 公司主营业务 - 所属行业和地域 - 上市时间和市场 ## 行业分析 - 所属行业的发展前景 - 行业地位和竞争优势 ## 投资价值 - 基本面评估 - 长期投资价值 - 关注要点 写作要求: 1. 语言简洁专业,直接给出分析结论 2. 基于数据进行分析,不要编造 3. 控制在200-300字 4. 最后声明:"以上分析仅供参考,不构成投资建议。股市有风险,投资需谨慎。" """ else: # 其他类型,使用通用分析 prompt = f"""你是一位专业的股票分析师。用户询问了{display_name}({stock_code})的相关信息。 用户问题:{user_message} 【数据】 {json.dumps(data, ensure_ascii=False, indent=2)} 请基于提供的数据进行专业分析,给出有价值的见解和建议。 写作要求: 1. 语言简洁专业,直接给出分析结论 2. 基于数据进行分析,不要编造 3. 控制在200-300字 4. 最后声明:"以上分析仅供参考,不构成投资建议。股市有风险,投资需谨慎。" """ try: analysis = await self._call_llm_async( messages=[{"role": "user", "content": prompt}], temperature=0.7, max_tokens=1500 ) if analysis: return { "message": f"【{display_name}({stock_code}) - AI分析】\n\n{analysis}", "metadata": {"type": intent["type"], "data": data} } else: # LLM失败,使用原始格式化 return self._format_response(intent, result, stock_code, stock_name) except Exception as e: logger.error(f"LLM单一分析失败: {e}") return self._format_response(intent, result, stock_code, stock_name) async def _comprehensive_index_analysis( self, index_code: str, index_name: str, message: str ) -> Dict[str, Any]: """ 指数全面分析:使用Tushare获取指数数据 + LLM深度分析 Args: index_code: 指数代码(如000001.SH) index_name: 指数名称 message: 用户消息 Returns: 综合分析结果 """ logger.info(f"执行指数全面分析: {index_code}") try: # 从tushare_advanced_service获取指数数据 from app.services.tushare_advanced_service import tushare_advanced_service # 获取指数日线数据 index_data = tushare_advanced_service.get_index_daily( ts_code=index_code, start_date=None, # 默认180天 end_date=None ) if not index_data or len(index_data) == 0: return { "message": f"抱歉,未能获取{index_name}的数据。", "metadata": {"type": "error"} } # 获取最新数据 latest = index_data[-1] # 计算技术指标(简单版) closes = [d['close'] for d in index_data] # 计算均线 ma5 = sum(closes[-5:]) / 5 if len(closes) >= 5 else None ma10 = sum(closes[-10:]) / 10 if len(closes) >= 10 else None ma20 = sum(closes[-20:]) / 20 if len(closes) >= 20 else None ma60 = sum(closes[-60:]) / 60 if len(closes) >= 60 else None # 整合数据 all_data = { "index_code": index_code, "index_name": index_name, "latest": latest, "ma": { "ma5": ma5, "ma10": ma10, "ma20": ma20, "ma60": ma60 }, "history_days": len(index_data) } # 使用LLM进行深度分析 if self.use_llm: analysis = await self._llm_index_analysis(all_data, message) else: analysis = f"【{index_name}】最新数据\n\n" \ f"日期:{latest['trade_date']}\n" \ f"收盘:{latest['close']:.2f}\n" \ f"涨跌幅:{latest['pct_chg']:+.2f}%\n" \ f"成交量:{latest['vol']:.0f}手" return { "message": analysis, "metadata": { "type": "index_analysis", "data": all_data } } except Exception as e: logger.error(f"指数分析失败: {e}") import traceback logger.error(traceback.format_exc()) return { "message": f"分析{index_name}时出错:{str(e)}", "metadata": {"type": "error"} } async def _llm_index_analysis( self, data: Dict[str, Any], user_message: str ) -> str: """使用LLM进行指数深度分析""" latest = data['latest'] ma = data['ma'] prompt = f"""你是一位专业的股票分析师。请对{data['index_name']}({data['index_code']})进行全面分析,用简洁专业但易懂的语言回答。 用户问题:{user_message} 【指数最新数据】 数据来源:Tushare Pro API 交易日期:{latest['trade_date']} 收盘点位:{latest['close']:.2f} 开盘点位:{latest['open']:.2f} 最高点位:{latest['high']:.2f} 最低点位:{latest['low']:.2f} 涨跌幅:{latest['pct_chg']:+.2f}% 涨跌点数:{latest['change']:+.2f} 成交量:{latest['vol']:.0f}手 成交额:{latest['amount']:.0f}千元 【技术指标】 MA5:{f"{ma['ma5']:.2f}" if ma['ma5'] else '计算中'} MA10:{f"{ma['ma10']:.2f}" if ma['ma10'] else '计算中'} MA20:{f"{ma['ma20']:.2f}" if ma['ma20'] else '计算中'} MA60:{f"{ma['ma60']:.2f}" if ma['ma60'] else '计算中'} 当前点位:{latest['close']:.2f} 请按以下结构进行分析: ## 一、市场现状 - 当前点位分析(相对历史位置) - 当日表现(涨跌幅、成交量) ## 二、技术面分析 **均线系统** 分析当前点位与各均线的关系,判断趋势(多头/空头/震荡)。 **支撑与压力** 基于近期走势,给出关键支撑位和压力位。 ## 三、市场情绪 - 当前市场情绪(乐观/谨慎/悲观) - 成交量分析 ## 四、投资建议 **短期(1-2周)** - 趋势判断 - 关键点位(支撑位、压力位) - 操作建议 **中期(1-3个月)** - 趋势展望 - 策略建议 **风险提示** 主要风险点和注意事项 ## 五、总结 用一句话概括核心观点。 --- **数据说明** - 数据来源:Tushare Pro(截止{latest['trade_date']}) - 分析周期:基于近{data['history_days']}个交易日数据 写作要求: 1. 语言简洁专业但易懂 2. 每个分析点独立成段 3. 控制在600-800字 4. 最后声明:"以上分析仅供参考,不构成投资建议。股市有风险,投资需谨慎。" """ try: analysis = await self._call_llm_async( messages=[{"role": "user", "content": prompt}], temperature=0.7, max_tokens=2500 ) if analysis: return f"【{data['index_name']}({data['index_code']}) - AI深度分析】\n\n{analysis}" else: return f"【{data['index_name']}】分析生成失败" except Exception as e: logger.error(f"LLM指数分析失败: {e}") return f"【{data['index_name']}】分析生成失败" def _rule_based_analysis(self, data: Dict[str, Any]) -> str: """基于规则的分析(LLM不可用时的备选方案)""" parts = [f"【{data['stock_name']}({data['stock_code']}) - 综合分析】\n"] # 行情信息 if data.get('quote'): quote = data['quote'] parts.append("## 一、实时行情") parts.append(f"最新价:{quote.get('close', 0):.2f}元") parts.append(f"涨跌幅:{quote.get('pct_chg', 0):.2f}%") parts.append(f"成交量:{quote.get('vol', 0):.0f}手") parts.append("") # 技术分析 if data.get('technical'): tech = data['technical'].get('indicators', {}) parts.append("## 二、技术指标") if 'ma' in tech: ma = tech['ma'] parts.append(f"均线系统:MA5={ma.get('ma5')}, MA10={ma.get('ma10')}, MA20={ma.get('ma20')}") if 'macd' in tech: macd = tech['macd'] parts.append(f"MACD:DIF={macd.get('dif')}, DEA={macd.get('dea')}") if 'rsi' in tech: rsi = tech['rsi'] rsi6 = rsi.get('rsi6', 50) if rsi6 > 70: parts.append(f"RSI:{rsi6:.1f}(超买区域,注意回调风险)") elif rsi6 < 30: parts.append(f"RSI:{rsi6:.1f}(超卖区域,可能存在反弹机会)") else: parts.append(f"RSI:{rsi6:.1f}(中性区域)") parts.append("") # 基本面 if data.get('fundamental'): fund = data['fundamental'] parts.append("## 三、基本信息") parts.append(f"所属行业:{fund.get('industry', '未知')}") parts.append(f"上市日期:{fund.get('list_date', '未知')}") parts.append("") # 简单建议 parts.append("## 四、参考建议") parts.append("建议结合更多信息进行综合判断。") parts.append("") parts.append("⚠️ 以上分析仅供参考,不构成投资建议。股市有风险,投资需谨慎。") return "\n".join(parts) async def _single_query( self, stock_code: str, stock_name: Optional[str], message: str ) -> Dict[str, Any]: """单一查询处理 - 使用LLM进行分析""" # 识别意图 intent = self._recognize_intent(message, stock_code) # 执行技能 result = await skill_manager.execute_skill( intent["skill"], **intent["params"] ) # 格式化响应 if not result.get("success", True): return { "message": f"查询失败:{result.get('error', '未知错误')}", "metadata": {"type": "error"} } # 所有查询都使用LLM进行分析(除了可视化) if intent["type"] != "visualization" and self.use_llm: return await self._llm_single_analysis(intent, result, stock_code, stock_name, message) else: return self._format_response(intent, result, stock_code, stock_name) def _recognize_intent(self, message: str, stock_code: str) -> Dict[str, Any]: """识别查询意图""" message_lower = message.lower() # K线图 if any(kw in message_lower for kw in ["k线", "图表", "走势图", "kline"]): return { "type": "visualization", "skill": "visualization", "params": {"stock_code": stock_code} } # 技术分析 if any(kw in message_lower for kw in ["技术", "指标", "macd", "rsi", "均线"]): return { "type": "technical", "skill": "technical_analysis", "params": {"stock_code": stock_code, "indicators": ["ma", "macd", "rsi"]} } # 基本面 if any(kw in message_lower for kw in ["基本面", "公司", "行业", "信息"]): return { "type": "fundamental", "skill": "fundamental", "params": {"stock_code": stock_code} } # 默认:实时行情 return { "type": "quote", "skill": "market_data", "params": {"stock_code": stock_code, "data_type": "quote"} } def _format_response( self, intent: Dict[str, Any], result: Dict[str, Any], stock_code: str, stock_name: Optional[str] ) -> Dict[str, Any]: """格式化响应""" data = result.get("data", result) display_name = stock_name or stock_code if intent["type"] == "quote": message = f"""【{display_name}】实时行情 交易日期:{data.get('trade_date', '')} 最新价:{data.get('close', 0):.2f}元 涨跌幅:{data.get('pct_chg', 0):+.2f}% 涨跌额:{data.get('change', 0):+.2f}元 开盘价:{data.get('open', 0):.2f}元 最高价:{data.get('high', 0):.2f}元 最低价:{data.get('low', 0):.2f}元 成交量:{data.get('vol', 0):.0f}手 成交额:{data.get('amount', 0):.0f}千元""" return { "message": message, "metadata": {"type": "quote", "data": data} } elif intent["type"] == "technical": indicators = data.get("indicators", {}) parts = [f"【{display_name}】技术指标\n"] if "ma" in indicators: ma = indicators["ma"] parts.append(f"均线:MA5={ma.get('ma5')}, MA10={ma.get('ma10')}, MA20={ma.get('ma20')}") if "macd" in indicators: macd = indicators["macd"] parts.append(f"MACD:DIF={macd.get('dif')}, DEA={macd.get('dea')}, MACD={macd.get('macd')}") if "rsi" in indicators: rsi = indicators["rsi"] parts.append(f"RSI:RSI6={rsi.get('rsi6')}, RSI12={rsi.get('rsi12')}") return { "message": "\n".join(parts), "metadata": {"type": "technical", "data": data} } elif intent["type"] == "visualization": return { "message": f"已生成【{display_name}】的K线图", "metadata": {"type": "chart", "data": data} } elif intent["type"] == "fundamental": message = f"""【{display_name}】基本信息 股票代码:{data.get('ts_code', '')} 所属地域:{data.get('area', '')} 所属行业:{data.get('industry', '')} 上市市场:{data.get('market', '')} 上市日期:{data.get('list_date', '')}""" return { "message": message, "metadata": {"type": "fundamental", "data": data} } return { "message": "查询完成", "metadata": {"type": "data", "data": data} } async def _analyze_question_intent(self, message: str, session_id: str) -> Optional[Dict[str, Any]]: """ 使用LLM分析问题意图(支持上下文) Args: message: 用户消息 session_id: 会话ID Returns: 意图分析结果: { 'type': 'stock_specific' | 'macro_finance' | 'knowledge' | 'general_chat', 'description': '问题描述', 'keywords': ['关键词列表'], 'stock_names': ['股票名称'] (如果是stock_specific类型) } """ if not self.use_llm: logger.warning("LLM未配置,无法分析意图") return None # 获取历史对话上下文 history = self.context_manager.get_context(session_id) context_str = "" if history: context_str = "\n\n【对话历史】\n" # 只取最近4条消息 for msg in history[-4:]: role = "用户" if msg["role"] == "user" else "助手" content = msg['content'][:100] # 限制长度 context_str += f"{role}: {content}\n" prompt = f"""你是一个专业的金融智能助手。分析用户的问题,理解用户意图。 {context_str} 【当前问题】 用户: {message} 请分析这个问题属于以下哪一类: 1. **stock_specific** - 针对特定股票或指数的问题 例如:"贵州茅台怎么样"、"分析一下比亚迪"、"600519的技术指标"、"帮我看看这只股票" **重要**:指数查询也属于此类,例如:"上证指数怎么样"、"分析大盘"、"A股指数走势"、"深证成指" **美股支持**:美股查询也属于此类,例如:"苹果股票怎么样"、"AAPL分析"、"特斯拉走势"、"TSLA技术指标" 2. **macro_finance** - 宏观金融/市场问题(不针对特定股票或指数) 例如:"最近有什么投资机会"、"现在适合买股票吗"、"市场情绪如何" 3. **knowledge** - 金融知识问答 例如:"什么是MACD"、"如何看K线图"、"价值投资是什么"、"市盈率怎么算" 4. **general_chat** - 一般对话/问候/不明确的问题 例如:"你好"、"在吗"、"你能做什么"、"帮我"(没有具体说明) **重要提示**: - 如果用户问题不明确,但可能与金融相关,优先归类为 general_chat,以便引导用户 - 如果用户提到"这只股票"、"它"等代词,查看对话历史判断是否指特定股票 - **如果用户提到"大盘"、"上证"、"深证"、"A股指数"等,归类为 stock_specific,并在stock_names中填入对应的指数名称** - **如果用户提到美股公司名称(如苹果、特斯拉、微软)或美股代码(如AAPL、TSLA、MSFT),归类为 stock_specific,并在stock_names中填入对应的股票名称或代码** - 对于模糊的问题,不要强行归类,使用 general_chat 类型 请以JSON格式返回分析结果: {{ "type": "问题类型", "description": "问题的简要描述(用一句话概括用户想了解什么)", "keywords": ["关键词1", "关键词2"], "stock_names": ["股票名称或指数名称或美股代码"] (仅当type为stock_specific时,如果有的话), "market": "A股" 或 "美股" (仅当type为stock_specific时,根据股票类型判断) }} 只返回JSON,不要有任何其他内容。""" try: result = await self._call_llm_async( messages=[{"role": "user", "content": prompt}], temperature=0.3, max_tokens=300 ) if not result: logger.warning("LLM返回空结果") return None # 清理结果,移除可能的markdown代码块标记 result = result.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: logger.warning("LLM返回内容为空") return None # 解析JSON intent = json.loads(result) logger.info(f"意图分析结果: {intent}") return intent except json.JSONDecodeError as e: logger.error(f"意图分析JSON解析失败: {e}, 原始响应: {result[:200] if result else 'None'}") return None except Exception as e: logger.error(f"意图分析失败: {e}") return None async def _handle_stock_question( self, intent_analysis: Dict[str, Any], message: str ) -> Dict[str, Any]: """处理针对特定股票或指数的问题""" stock_names = intent_analysis.get('stock_names', []) market = intent_analysis.get('market', 'A股') # 默认A股 if not stock_names: return { "message": "抱歉,我没有识别到您提到的股票或指数。请提供更明确的股票代码、名称或指数名称。", "metadata": {"type": "error"} } # 提取第一个股票或指数 stock_keyword = stock_names[0] # 检测是否为美股 is_us_stock = self._is_us_stock(stock_keyword, market) if is_us_stock: # 处理美股 return await self._handle_us_stock(stock_keyword, message) # 处理A股和指数 - 使用LLM进行智能匹配 stock_info = await self._match_stock_with_llm(stock_keyword) if not stock_info: return { "message": f"抱歉,未找到股票或指数\"{stock_keyword}\"。请确认名称或代码是否正确。", "metadata": {"type": "error"} } stock_code = stock_info['code'] stock_name = stock_info['name'] is_index = stock_info['is_index'] logger.info(f"处理{'指数' if is_index else '股票'}问题: {stock_name}({stock_code})") # 判断是否需要全面分析 is_comprehensive = self._is_comprehensive_analysis(message) if is_comprehensive: if is_index: return await self._comprehensive_index_analysis(stock_code, stock_name, message) else: return await self._comprehensive_analysis(stock_code, stock_name, message) else: return await self._single_query(stock_code, stock_name, message) async def _match_stock_with_llm(self, keyword: str) -> Optional[Dict[str, Any]]: """ 使用LLM智能匹配股票或指数 Args: keyword: 用户输入的关键词 Returns: 匹配结果: {'code': '股票代码', 'name': '股票名称', 'is_index': bool} """ if not self.use_llm: # 降级方案:使用Tushare搜索 search_results = tushare_service.search_stock(keyword) if search_results: return { 'code': search_results[0]['symbol'], 'name': search_results[0]['name'], 'is_index': False } return None prompt = f"""你是一个专业的A股市场专家。请根据用户输入的关键词,识别对应的股票代码或指数代码。 用户输入:{keyword} 常见指数代码: - 上证指数/大盘/沪指/A股 → 000001.SH - 深证成指/深证/深指 → 399001.SZ - 创业板指/创业板 → 399006.SZ - 科创50 → 000688.SH - 沪深300 → 000300.SH - 中证500 → 000905.SH 如果是指数,请直接返回对应的指数代码。 如果是股票名称或代码,请使用Tushare数据库进行搜索匹配。 请以JSON格式返回: {{ "is_index": true/false, "code": "股票或指数代码(如000001.SH)", "name": "股票或指数名称", "confidence": 0.0-1.0 }} 如果无法匹配,返回: {{ "is_index": false, "code": null, "name": null, "confidence": 0.0 }} 只返回JSON,不要有任何其他内容。""" try: result = await self._call_llm_async( messages=[{"role": "user", "content": prompt}], temperature=0.3, max_tokens=200 ) if not result: logger.warning("LLM匹配返回空结果") return None # 清理结果 result = result.strip() if result.startswith("```json"): result = result[7:] if result.startswith("```"): result = result[3:] if result.endswith("```"): result = result[:-3] result = result.strip() # 解析JSON match_result = json.loads(result) # 如果LLM无法匹配或置信度太低,使用Tushare搜索 if not match_result.get('code') or match_result.get('confidence', 0) < 0.5: logger.info(f"LLM匹配置信度低,使用Tushare搜索: {keyword}") search_results = tushare_service.search_stock(keyword) if search_results: return { 'code': search_results[0]['symbol'], 'name': search_results[0]['name'], 'is_index': False } return None logger.info(f"LLM匹配成功: {keyword} -> {match_result['name']}({match_result['code']})") return { 'code': match_result['code'], 'name': match_result['name'], 'is_index': match_result['is_index'] } except json.JSONDecodeError as e: logger.error(f"LLM匹配JSON解析失败: {e}, 原始响应: {result[:200] if result else 'None'}") # 降级方案 search_results = tushare_service.search_stock(keyword) if search_results: return { 'code': search_results[0]['symbol'], 'name': search_results[0]['name'], 'is_index': False } return None except Exception as e: logger.error(f"LLM匹配失败: {e}") # 降级方案 search_results = tushare_service.search_stock(keyword) if search_results: return { 'code': search_results[0]['symbol'], 'name': search_results[0]['name'], 'is_index': False } return None async def _handle_macro_question( self, intent_analysis: Dict[str, Any], message: str ) -> Dict[str, Any]: """处理宏观金融问题(智能模式)""" description = intent_analysis.get('description', '') keywords = intent_analysis.get('keywords', []) logger.info(f"[智能模式] 处理宏观问题: {description}") try: # 使用智能、自然的prompt prompt = f"""你是一位专业的金融分析师。用户询问了宏观金融问题。 用户问题:{message} 请用自然、专业的语言回答用户的问题。不要使用固定的格式或标题,而是像和朋友聊天一样,直接回答用户关心的内容。 要求: - 直接回答用户的问题,不要添加"【宏观市场分析】"等标题 - 根据问题的具体内容调整回答的重点 - 语言自然、专业但易懂 - 如果用户问的是短期趋势,重点讲短期;如果问的是投资机会,重点讲机会 - 控制在300-500字 - 最后声明:"以上分析仅供参考,不构成投资建议。" """ analysis = await self._call_llm_async( messages=[{"role": "user", "content": prompt}], temperature=0.7, max_tokens=1500 ) if analysis: return { "message": analysis, "metadata": {"type": "macro_analysis"} } except Exception as e: logger.error(f"宏观问题处理失败: {e}") # 降级方案 return { "message": f"抱歉,我暂时无法回答这个问题。您可以问我具体股票或指数的分析。", "metadata": {"type": "error"} } async def _handle_knowledge_question( self, intent_analysis: Dict[str, Any], message: str ) -> Dict[str, Any]: """处理金融知识问答(智能模式)""" description = intent_analysis.get('description', '') logger.info(f"[智能模式] 处理知识问答: {description}") # 使用智能、自然的prompt prompt = f"""你是一位专业的金融教育专家。用户询问了金融知识问题。 用户问题:{message} 请用自然、通俗的语言回答用户的问题。不要使用固定的格式(如"## 核心概念"),而是像老师给学生讲解一样,直接、清晰地解释。 要求: - 直接回答问题,不要添加"【金融知识解答】"等标题 - 用通俗易懂的语言,避免过多专业术语 - 如果必须用专业术语,简单解释一下 - 可以举例子帮助理解 - 控制在200-400字 """ try: answer = await self._call_llm_async( messages=[{"role": "user", "content": prompt}], temperature=0.7, max_tokens=1200 ) if answer: return { "message": answer, "metadata": {"type": "knowledge"} } except Exception as e: logger.error(f"知识问答处理失败: {e}") return { "message": "抱歉,暂时无法回答您的问题。请稍后再试。", "metadata": {"type": "error"} } async def _handle_general_chat( self, intent_analysis: Dict[str, Any], message: str ) -> Dict[str, Any]: """处理一般对话(智能模式)""" description = intent_analysis.get('description', '') logger.info(f"[智能模式] 处理一般对话: {description}") # 使用智能、自然的prompt prompt = f"""你是一位专业且友好的金融智能助手。用户发来了一条消息。 用户消息:{message} 请用自然、友好的语言回应用户。不要使用固定的格式,而是像真人对话一样。 要求: - 如果是问候(如"你好"),友好回应并简单介绍你能做什么 - 如果问题不明确,礼貌地询问用户想了解什么 - 语言自然、友好,不要太正式 - 控制在100-200字 """ try: response = await self._call_llm_async( messages=[{"role": "user", "content": prompt}], temperature=0.8, max_tokens=500 ) if response: return { "message": response, "metadata": {"type": "chat"} } except Exception as e: logger.error(f"一般对话处理失败: {e}") # 降级方案 return { "message": """您好!我是您的金融智能助手 👋 我可以帮您: • 📊 分析具体股票(如"分析比亚迪") • 📈 解读市场走势(如"现在大盘怎么样") • 📚 解答金融知识(如"什么是市盈率") 请告诉我您想了解什么?""", "metadata": {"type": "chat"} } def _is_us_stock(self, keyword: str, market: str) -> bool: """ 判断是否为美股 Args: keyword: 股票关键词 market: 市场类型(从LLM意图分析中获取) Returns: 是否为美股 """ # 如果LLM已经判断为美股 if market == "美股": return True # 检查是否为全大写字母(美股代码特征) if keyword.isupper() and keyword.isalpha() and len(keyword) <= 5: return True # 常见美股公司名称映射 us_stock_names = { "苹果": "AAPL", "特斯拉": "TSLA", "微软": "MSFT", "谷歌": "GOOGL", "亚马逊": "AMZN", "Meta": "META", "脸书": "META", "英伟达": "NVDA", "奈飞": "NFLX", "网飞": "NFLX", "迪士尼": "DIS", "可口可乐": "KO", "麦当劳": "MCD", "星巴克": "SBUX", "耐克": "NKE", "波音": "BA", "英特尔": "INTC", "AMD": "AMD", "高通": "QCOM", "推特": "TWTR", "优步": "UBER", "Uber": "UBER", "Airbnb": "ABNB", "爱彼迎": "ABNB", } if keyword in us_stock_names: return True return False def _get_us_stock_symbol(self, keyword: str) -> str: """ 获取美股代码 Args: keyword: 股票关键词 Returns: 美股代码 """ # 如果已经是代码格式,直接返回 if keyword.isupper() and keyword.isalpha(): return keyword # 中文名称映射 us_stock_names = { "苹果": "AAPL", "特斯拉": "TSLA", "微软": "MSFT", "谷歌": "GOOGL", "亚马逊": "AMZN", "Meta": "META", "脸书": "META", "英伟达": "NVDA", "奈飞": "NFLX", "网飞": "NFLX", "迪士尼": "DIS", "可口可乐": "KO", "麦当劳": "MCD", "星巴克": "SBUX", "耐克": "NKE", "波音": "BA", "英特尔": "INTC", "AMD": "AMD", "高通": "QCOM", "推特": "TWTR", "优步": "UBER", "Uber": "UBER", "Airbnb": "ABNB", "爱彼迎": "ABNB", } return us_stock_names.get(keyword, keyword.upper()) async def _handle_us_stock(self, keyword: str, message: str) -> Dict[str, Any]: """ 处理美股查询 Args: keyword: 股票关键词 message: 用户消息 Returns: 分析结果 """ # 获取美股代码 symbol = self._get_us_stock_symbol(keyword) logger.info(f"处理美股查询: {keyword} -> {symbol}") try: # 调用美股分析技能 result = await skill_manager.execute_skill( "us_stock_analysis", symbol=symbol, analysis_type="comprehensive" ) if not result.get("success"): return { "message": f"抱歉,未找到美股 {symbol}。请确认股票代码是否正确。\n\n提示:美股代码通常为大写字母,如 AAPL(苹果)、TSLA(特斯拉)、MSFT(微软)等。", "metadata": {"type": "error"} } # 使用LLM分析美股数据 if self.use_llm: analysis = await self._llm_us_stock_analysis(result["data"], message) else: analysis = self._format_us_stock_data(result["data"]) return { "message": analysis, "metadata": { "type": "us_stock_analysis", "data": result["data"] } } except Exception as e: logger.error(f"美股查询失败: {e}") return { "message": f"查询美股 {symbol} 时出错:{str(e)}", "metadata": {"type": "error"} } async def _llm_us_stock_analysis(self, data: Dict[str, Any], user_message: str) -> str: """使用LLM分析美股数据""" from datetime import datetime current_time = datetime.now().strftime("%Y-%m-%d %H:%M") # 提取关键数据 symbol = data.get("symbol", "") name = data.get("name", "") sector = data.get("sector", "") industry = data.get("industry", "") current_price = data.get("current_price", 0) change = data.get("change", 0) change_pct = data.get("change_percent", 0) volume = data.get("volume", 0) market_cap = data.get("market_cap", 0) pe_ratio = data.get("pe_ratio", 0) pb_ratio = data.get("pb_ratio", 0) dividend_yield = data.get("dividend_yield", 0) week_52_high = data.get("52_week_high", 0) week_52_low = data.get("52_week_low", 0) technical = data.get("technical_indicators", {}) description = data.get("description", "") # 格式化市值 market_cap_str = f"${market_cap / 1e9:.2f}B" if market_cap > 1e9 else f"${market_cap / 1e6:.2f}M" # 构建分析提示 prompt = f"""你是一位专业的美股分析师。请基于以下数据对 {name} ({symbol}) 进行全面分析。 **重要提示:当前日期是 {current_time},请在分析中使用这个日期,不要使用其他日期。** 【基本信息】 股票代码:{symbol} 公司名称:{name} 所属行业:{sector} - {industry} 公司简介:{description[:300] if description else '暂无'} 【实时行情】(数据时间:{current_time}) 当前价格:${current_price:.2f} 涨跌额:${change:.2f} 涨跌幅:{change_pct:.2f}% 成交量:{volume:,} 市值:{market_cap_str} 【估值指标】 市盈率(PE):{f"{pe_ratio:.2f}" if pe_ratio else '暂无'} 市净率(PB):{f"{pb_ratio:.2f}" if pb_ratio else '暂无'} 股息率:{f"{dividend_yield * 100:.2f}%" if dividend_yield else '暂无'} 52周最高:${week_52_high:.2f} 52周最低:${week_52_low:.2f} 【技术指标】 MA5:{f"${technical.get('ma5'):.2f}" if technical.get('ma5') else '计算中'} MA10:{f"${technical.get('ma10'):.2f}" if technical.get('ma10') else '计算中'} MA20:{f"${technical.get('ma20'):.2f}" if technical.get('ma20') else '计算中'} MA60:{f"${technical.get('ma60'):.2f}" if technical.get('ma60') else '计算中'} RSI:{f"{technical.get('rsi'):.2f}" if technical.get('rsi') else '计算中'} MACD:{f"{technical.get('macd'):.4f}" if technical.get('macd') else '计算中'} 用户问题:{user_message} 请提供专业的分析报告,包括: ## 📊 行情概览 简要总结当前股价表现和市场表现(2-3句话) ## 💼 公司基本面 - 行业地位和竞争优势 - 估值水平分析(PE、PB是否合理) - 盈利能力和成长性 ## 📈 技术面分析 - 当前趋势判断(基于均线系统) - 关键支撑位和压力位 - RSI和MACD信号解读 ## 💡 投资建议 - 短期操作建议(1-2周) - 中期投资价值(1-3个月) - 风险提示 写作要求: 1. 语言专业但易懂,避免过度修饰 2. 分析客观理性,基于数据和事实 3. 每个部分独立成段,段落间用空行分隔 4. 控制在500-600字 5. **不要在报告中添加日期标题,直接开始分析内容** 6. 最后声明:"以上分析仅供参考,不构成投资建议。美股投资有风险,请谨慎决策。" """ try: analysis = await self._call_llm_async( messages=[{"role": "user", "content": prompt}], temperature=0.7, max_tokens=2000 ) if analysis: return f"【美股分析】{name} ({symbol})\n\n{analysis}" else: return self._format_us_stock_data(data) except Exception as e: logger.error(f"LLM美股分析失败: {e}") return self._format_us_stock_data(data) def _format_us_stock_data(self, data: Dict[str, Any]) -> str: """格式化美股数据(降级方案)""" symbol = data.get("symbol", "") name = data.get("name", "") current_price = data.get("current_price", 0) change = data.get("change", 0) change_pct = data.get("change_percent", 0) market_cap = data.get("market_cap", 0) pe_ratio = data.get("pe_ratio", 0) technical = data.get("technical_indicators", {}) market_cap_str = f"${market_cap / 1e9:.2f}B" if market_cap > 1e9 else f"${market_cap / 1e6:.2f}M" change_emoji = "📈" if change >= 0 else "📉" return f"""【美股行情】{name} ({symbol}) {change_emoji} 当前价格:${current_price:.2f} 涨跌:${change:.2f} ({change_pct:+.2f}%) 市值:{market_cap_str} 市盈率:{pe_ratio:.2f if pe_ratio else '暂无'} 【技术指标】 MA5:${technical.get('ma5', 0):.2f if technical.get('ma5') else '计算中'} MA20:${technical.get('ma20', 0):.2f if technical.get('ma20') else '计算中'} RSI:{technical.get('rsi', 0):.2f if technical.get('rsi') else '计算中'} 以上数据仅供参考,不构成投资建议。""" async def process_message_stream( self, message: str, session_id: str, user_id: Optional[str] = None ): """ 流式处理用户消息(智能模式) Args: message: 用户消息 session_id: 会话ID user_id: 用户ID Yields: 响应文本片段 """ logger.info(f"[智能模式-流式] 处理消息: {message[:50]}...") # 转换 user_id 为整数(如果是字符串) user_id_int = int(user_id) if user_id else None # 1. 保存用户消息 self.context_manager.add_message(session_id, "user", message, user_id=user_id_int) # 2. 提取上下文信息 context_info = self.context_manager.extract_context_info(session_id) logger.info(f"[智能模式-流式] 上下文信息: last_stock={context_info.get('last_stock')}") # 3. 深度问题分析 intent = await self.question_analyzer.analyze_question( question=message, context=self.context_manager.get_context(session_id), session_id=session_id ) logger.info(f"[智能模式-流式] 问题分析: type={intent.get('type')}, dimensions={intent.get('dimensions')}") # 4. 处理上下文引用(代词解析) if intent.get('context_references', {}).get('refers_to_previous'): intent = self._resolve_context_references(intent, context_info) logger.info(f"[智能模式-流式] 上下文解析后: target={intent.get('target')}") # 5. 根据问题类型分发(流式) full_response = "" if intent['type'] == 'stock_analysis': async for chunk in self._handle_stock_analysis_stream(intent, message): full_response += chunk yield chunk elif intent['type'] == 'market_overview': response = await self._handle_macro_question(intent, message) full_response = response["message"] for char in full_response: yield char elif intent['type'] == 'knowledge': response = await self._handle_knowledge_question(intent, message) full_response = response["message"] for char in full_response: yield char else: response = await self._handle_general_chat(intent, message) full_response = response["message"] for char in full_response: yield char # 6. 保存助手响应 self.context_manager.add_message(session_id, "assistant", full_response, user_id=user_id_int) async def _handle_other_question( self, question_type: str, intent_analysis: Dict[str, Any], message: str ) -> Dict[str, Any]: """处理非股票分析的其他问题""" if question_type == 'macro_finance': return await self._handle_macro_question(intent_analysis, message) elif question_type == 'knowledge': return await self._handle_knowledge_question(intent_analysis, message) elif question_type == 'general_chat': return await self._handle_general_chat(intent_analysis, message) else: return {"message": "抱歉,我无法理解您的问题。"} async def _handle_stock_question_stream( self, intent_analysis: Dict[str, Any], message: str ): """流式处理股票问题""" stock_names = intent_analysis.get('stock_names', []) market = intent_analysis.get('market', 'A股') if not stock_names: yield "抱歉,我没有识别到您提到的股票或指数。请提供更明确的股票代码、名称或指数名称。" return stock_keyword = stock_names[0] is_us_stock = self._is_us_stock(stock_keyword, market) if is_us_stock: # 美股分析 - 流式 async for chunk in self._handle_us_stock_stream(stock_keyword, message): yield chunk else: # A股分析 - 流式 async for chunk in self._handle_a_stock_stream(stock_keyword, message): yield chunk async def _handle_a_stock_stream(self, stock_keyword: str, message: str): """流式处理A股分析""" # 使用LLM进行智能匹配 stock_info = await self._match_stock_with_llm(stock_keyword) if not stock_info: yield f"抱歉,未找到股票或指数\"{stock_keyword}\"。请确认名称或代码是否正确。" return stock_code = stock_info['code'] stock_name = stock_info['name'] is_index = stock_info['is_index'] # 获取数据(非流式) try: quote_result = await skill_manager.execute_skill("market_data", stock_code=stock_code, data_type="quote") technical_result = await skill_manager.execute_skill("technical_analysis", stock_code=stock_code, indicators=["ma", "macd", "rsi", "kdj"]) fundamental_result = await skill_manager.execute_skill("fundamental", stock_code=stock_code) advanced_result = await skill_manager.execute_skill("advanced_data", stock_code=stock_code, data_type="all") all_data = { "stock_code": stock_code, "stock_name": stock_name, "quote": quote_result.get("data") if quote_result.get("success") else None, "technical": technical_result.get("data") if technical_result.get("success") else None, "fundamental": fundamental_result.get("data") if fundamental_result.get("success") else None, "advanced": advanced_result.get("data") if advanced_result.get("success") else None } # 使用LLM流式分析 if self.use_llm: async for chunk in self._llm_comprehensive_analysis_stream(all_data, message, is_index): yield chunk else: yield self._rule_based_analysis(all_data) except Exception as e: logger.error(f"A股分析失败: {e}") yield f"分析{stock_name}时出错:{str(e)}" async def _handle_us_stock_stream(self, keyword: str, message: str): """流式处理美股分析(智能模式)""" symbol = self._get_us_stock_symbol(keyword) logger.info(f"[智能模式-流式] 美股查询: {keyword} -> {symbol}") try: result = await skill_manager.execute_skill("us_stock_analysis", symbol=symbol, analysis_type="comprehensive") if not result.get("success"): yield f"抱歉,未找到美股 {symbol}。请确认股票代码是否正确。" return # 使用智能模式的动态prompt生成 if self.use_llm: # 构建美股数据的动态prompt us_data = result["data"] prompt = self._build_us_stock_dynamic_prompt(us_data, symbol, message) # 流式生成 stream = llm_service.chat_stream( messages=[{"role": "user", "content": prompt}], temperature=0.7, max_tokens=2000 ) for chunk in stream: yield chunk else: yield self._format_us_stock_data(result["data"]) except Exception as e: logger.error(f"美股查询失败: {e}") yield f"查询美股 {symbol} 时出错:{str(e)}" def _build_us_stock_dynamic_prompt( self, data: Dict[str, Any], symbol: str, user_message: str ) -> str: """ 为美股构建动态prompt Args: data: 美股数据 symbol: 股票代码 user_message: 用户消息 Returns: prompt字符串 """ # 提取数据 name = data.get('name', symbol) current_price = data.get('current_price', 0) change = data.get('change', 0) change_percent = data.get('change_percent', 0) volume = data.get('volume', 0) market_cap = data.get('market_cap', 0) pe_ratio = data.get('pe_ratio', 0) # 技术指标 technical = data.get('technical_indicators', {}) ma5 = technical.get('ma5', 0) ma10 = technical.get('ma10', 0) ma20 = technical.get('ma20', 0) rsi = technical.get('rsi', 0) macd = technical.get('macd', 0) prompt = f"""你是一个专业的美股分析师。请根据以下数据分析【{name}({symbol})】。 **用户问题**: {user_message} ## 数据信息 **行情数据**: - 最新价: ${current_price:.2f} - 涨跌: ${change:+.2f} ({change_percent:+.2f}%) - 成交量: {volume:,.0f} - 市值: ${market_cap:,.0f} - 市盈率(PE): {pe_ratio:.2f} **技术指标**: - 均线: MA5=${ma5:.2f}, MA10=${ma10:.2f}, MA20=${ma20:.2f} - RSI: {rsi:.2f} - MACD: {macd:.4f} ## 分析要求 请根据用户的问题,提供自然、有针对性的分析。不要使用固定格式,而是像专业分析师一样,用自然的语言回答用户的问题。 - 如果用户关注价格走势,重点分析价格和趋势 - 如果用户关注技术指标,重点分析技术面 - 如果用户关注基本面,重点分析公司情况和估值 请直接开始分析,不要添加日期标题。最后声明:"以上分析仅供参考,不构成投资建议。美股投资有风险,请谨慎决策。" """ return prompt async def _llm_comprehensive_analysis_stream(self, data: Dict[str, Any], user_message: str, is_index: bool = False): """使用LLM流式进行综合分析""" from datetime import datetime import json current_time = datetime.now().strftime("%Y-%m-%d %H:%M") # 获取行情数据的交易日期 quote_date = "未知" if data.get('quote') and data['quote'].get('trade_date'): quote_date = data['quote']['trade_date'] # 构建高级数据摘要 advanced_summary = "" if data.get('advanced'): advanced_data = data['advanced'] advanced_summary = "\n【高级财务数据】(Tushare Pro 5000+积分)\n" # 财务指标 if advanced_data.get('financial'): financial = advanced_data['financial'] if financial.get('indicators'): indicators = financial['indicators'].get('indicators', {}) advanced_summary += f"财务指标(截止:{financial['indicators'].get('end_date', '未知')}):\n" advanced_summary += f" ROE: {indicators.get('roe', 'N/A')}%\n" advanced_summary += f" ROA: {indicators.get('roa', 'N/A')}%\n" advanced_summary += f" 毛利率: {indicators.get('gross_margin', 'N/A')}%\n" advanced_summary += f" 资产负债率: {indicators.get('debt_to_assets', 'N/A')}%\n" advanced_summary += f" 流动比率: {indicators.get('current_ratio', 'N/A')}\n\n" # 估值数据 if advanced_data.get('valuation'): valuation = advanced_data['valuation'] advanced_summary += f"估值指标:\n" advanced_summary += f" PE(市盈率): {valuation.get('pe', 'N/A')}\n" advanced_summary += f" PB(市净率): {valuation.get('pb', 'N/A')}\n" advanced_summary += f" PS(市销率): {valuation.get('ps', 'N/A')}\n" advanced_summary += f" 总市值: {valuation.get('total_mv', 'N/A')}万元\n" advanced_summary += f" 流通市值: {valuation.get('circ_mv', 'N/A')}万元\n" advanced_summary += f" 换手率: {valuation.get('turnover_rate', 'N/A')}%\n\n" # 资金流向(最近一天) if advanced_data.get('money_flow') and len(advanced_data['money_flow']) > 0: latest_flow = advanced_data['money_flow'][0] advanced_summary += f"资金流向({latest_flow.get('trade_date', '最近')}):\n" advanced_summary += f" 主力净流入: {latest_flow.get('net_mf_amount', 'N/A')}万元\n" advanced_summary += f" 超大单净流入: {latest_flow.get('buy_elg_amount', 0) - latest_flow.get('sell_elg_amount', 0):.2f}万元\n" advanced_summary += f" 大单净流入: {latest_flow.get('buy_lg_amount', 0) - latest_flow.get('sell_lg_amount', 0):.2f}万元\n\n" # 融资融券 if advanced_data.get('margin') and len(advanced_data['margin']) > 0: latest_margin = advanced_data['margin'][0] advanced_summary += f"融资融券({latest_margin.get('trade_date', '最近')}):\n" advanced_summary += f" 融资余额: {latest_margin.get('rzye', 'N/A')}元\n" advanced_summary += f" 融券余额: {latest_margin.get('rqye', 'N/A')}元\n\n" else: advanced_summary = "\n【高级财务数据】\n暂无高级数据\n" # 构建详细的分析提示 prompt = f"""你是一位专业的股票分析师。请对{data['stock_name']}({data['stock_code']})进行全面分析,用简洁专业但易懂的语言回答。 用户问题:{user_message} 【实时行情数据】 数据来源:Tushare Pro API 交易日期:{quote_date} {json.dumps(data.get('quote'), ensure_ascii=False, indent=2) if data.get('quote') else '数据获取失败'} 【技术指标数据】 数据来源:Tushare Pro API(基于历史K线数据计算) 计算截止日期:{quote_date} {json.dumps(data.get('technical'), ensure_ascii=False, indent=2) if data.get('technical') else '数据获取失败'} 【基本面数据】 数据来源:Tushare Pro API {json.dumps(data.get('fundamental'), ensure_ascii=False, indent=2) if data.get('fundamental') else '数据获取失败'} {advanced_summary} 请按以下结构进行分析: ## 一、基本面分析 分段说明公司情况,每个要点独立成段。 ## 二、技术面分析(数据截止:{quote_date}) 使用清晰的分段结构,每个技术指标独立成段。 ## 三、市场情绪分析 分段分析市场情绪和资金流向。 ## 四、投资建议 基于分析给出具体的操作建议和点位。 写作要求: 1. 语言简洁专业但易懂 2. 分析客观理性,基于数据 3. 每个分析点独立成段 4. 控制在600-800字 5. 最后声明:"以上分析仅供参考,不构成投资建议。股市有风险,投资需谨慎。" """ # 流式调用LLM(同步生成器,使用线程避免阻塞) import asyncio stream = llm_service.chat_stream( messages=[{"role": "user", "content": prompt}], temperature=0.7, max_tokens=2000 ) # 在线程中迭代同步生成器,避免阻塞事件循环 loop = asyncio.get_event_loop() for chunk in stream: # 每次yield后让出控制权 await asyncio.sleep(0) yield chunk async def _llm_us_stock_analysis_stream(self, data: Dict[str, Any], user_message: str): """使用LLM流式分析美股""" from datetime import datetime current_time = datetime.now().strftime("%Y-%m-%d %H:%M") symbol = data.get("symbol", "") name = data.get("name", "") sector = data.get("sector", "") industry = data.get("industry", "") current_price = data.get("current_price", 0) change = data.get("change", 0) change_pct = data.get("change_percent", 0) volume = data.get("volume", 0) market_cap = data.get("market_cap", 0) pe_ratio = data.get("pe_ratio", 0) pb_ratio = data.get("pb_ratio", 0) dividend_yield = data.get("dividend_yield", 0) week_52_high = data.get("52_week_high", 0) week_52_low = data.get("52_week_low", 0) technical = data.get("technical_indicators", {}) description = data.get("description", "") market_cap_str = f"${market_cap / 1e9:.2f}B" if market_cap > 1e9 else f"${market_cap / 1e6:.2f}M" prompt = f"""你是一位专业的美股分析师。请基于以下数据对 {name} ({symbol}) 进行全面分析。 **重要提示:当前日期是 {current_time},请在分析中使用这个日期,不要使用其他日期。** 【基本信息】 股票代码:{symbol} 公司名称:{name} 所属行业:{sector} - {industry} 公司简介:{description[:300] if description else '暂无'} 【实时行情】(数据时间:{current_time}) 当前价格:${current_price:.2f} 涨跌额:${change:.2f} 涨跌幅:{change_pct:.2f}% 成交量:{volume:,} 市值:{market_cap_str} 【估值指标】 市盈率(PE):{f"{pe_ratio:.2f}" if pe_ratio else '暂无'} 市净率(PB):{f"{pb_ratio:.2f}" if pb_ratio else '暂无'} 股息率:{f"{dividend_yield * 100:.2f}%" if dividend_yield else '暂无'} 52周最高:${week_52_high:.2f} 52周最低:${week_52_low:.2f} 【技术指标】 MA5:{f"${technical.get('ma5'):.2f}" if technical.get('ma5') else '计算中'} MA10:{f"${technical.get('ma10'):.2f}" if technical.get('ma10') else '计算中'} MA20:{f"${technical.get('ma20'):.2f}" if technical.get('ma20') else '计算中'} MA60:{f"${technical.get('ma60'):.2f}" if technical.get('ma60') else '计算中'} RSI:{f"{technical.get('rsi'):.2f}" if technical.get('rsi') else '计算中'} MACD:{f"{technical.get('macd'):.4f}" if technical.get('macd') else '计算中'} 用户问题:{user_message} 请提供专业的分析报告,包括: ## 📊 行情概览 简要总结当前股价表现和市场表现(2-3句话) ## 💼 公司基本面 - 行业地位和竞争优势 - 估值水平分析(PE、PB是否合理) - 盈利能力和成长性 ## 📈 技术面分析 - 当前趋势判断(基于均线系统) - 关键支撑位和压力位 - RSI和MACD信号解读 ## 💡 投资建议 - 短期操作建议(1-2周) - 中期投资价值(1-3个月) - 风险提示 写作要求: 1. 语言专业但易懂,避免过度修饰 2. 分析客观理性,基于数据和事实 3. 每个部分独立成段,段落间用空行分隔 4. 控制在500-600字 5. **不要在报告中添加日期标题,直接开始分析内容** 6. 最后声明:"以上分析仅供参考,不构成投资建议。美股投资有风险,请谨慎决策。" """ # 流式调用LLM(同步生成器,使用线程避免阻塞) import asyncio stream = llm_service.chat_stream( messages=[{"role": "user", "content": prompt}], temperature=0.7, max_tokens=2000 ) # 在线程中迭代同步生成器,避免阻塞事件循环 for chunk in stream: # 每次yield后让出控制权 await asyncio.sleep(0) yield chunk # ==================== 新增:智能模式方法 ==================== async def _handle_stock_analysis_v2( self, intent: Dict[str, Any], message: str ) -> Dict[str, Any]: """ 处理股票分析请求(智能模式 V2) Args: intent: 问题意图 message: 用户消息 Returns: 响应结果 """ target = intent.get('target', {}) stock_code = target.get('stock_code') stock_name = target.get('stock_name') market = target.get('market', 'A股') # 如果没有股票代码,尝试匹配 if not stock_code and stock_name: # 检测是否为美股 is_us_stock = self._is_us_stock(stock_name, market) if is_us_stock: return await self._handle_us_stock(stock_name, message) # A股匹配 stock_info = await self._match_stock_with_llm(stock_name) if not stock_info: return { "message": f"抱歉,未找到股票\"{stock_name}\"。请确认名称或代码是否正确。", "metadata": {"type": "error"} } stock_code = stock_info['code'] stock_name = stock_info['name'] if not stock_code: return { "message": "抱歉,我没有识别到您提到的股票。请提供更明确的股票代码或名称。", "metadata": {"type": "error"} } logger.info(f"[智能模式] 分析股票: {stock_name}({stock_code})") # 1. 技能规划 plan = self.skill_planner.plan_skills(intent) logger.info(f"[智能模式] 技能规划: {[s['name'] for s in plan['skills']]}") # 2. 执行技能 execution_results = await skill_manager.execute_plan( plan=plan, stock_code=stock_code ) if execution_results['errors']: logger.warning(f"[智能模式] 技能执行有错误: {execution_results['errors']}") # 3. 智能生成回答 analysis = await self._generate_intelligent_response( intent=intent, execution_results=execution_results['results'], stock_code=stock_code, stock_name=stock_name, user_message=message ) return { "message": analysis, "metadata": { "type": "stock_analysis", "intent": intent, "plan": plan, "data": { "stock_code": stock_code, "stock_name": stock_name, **execution_results['results'] } } } async def _generate_intelligent_response( self, intent: Dict[str, Any], execution_results: Dict[str, Any], stock_code: str, stock_name: str, user_message: str ) -> str: """ 智能生成回答 - 根据用户意图定制 Args: intent: 问题意图 execution_results: 技能执行结果 stock_code: 股票代码 stock_name: 股票名称 user_message: 用户消息 Returns: 分析报告 """ # 1. 构建动态prompt prompt = self._build_dynamic_prompt( intent=intent, data=execution_results, stock_code=stock_code, stock_name=stock_name, user_message=user_message ) # 2. 调用LLM生成 max_tokens = self._calculate_max_tokens(intent) response = await self._call_llm_async( messages=[{"role": "user", "content": prompt}], temperature=0.7, max_tokens=max_tokens ) if not response: # 降级到规则化格式 return self._format_fallback_response(execution_results, stock_name) return response def _build_dynamic_prompt( self, intent: Dict[str, Any], data: Dict[str, Any], stock_code: str, stock_name: str, user_message: str ) -> str: """ 根据意图动态构建prompt Args: intent: 问题意图 data: 执行结果数据 stock_code: 股票代码 stock_name: 股票名称 user_message: 用户消息 Returns: prompt字符串 """ dimensions = intent.get('dimensions', {}) time_scope = intent.get('time_scope', {}) specific_concerns = intent.get('specific_concerns', []) user_style = intent.get('user_style', {}) # 基础部分 prompt_parts = [ f"你是一个专业的股票分析师。请根据以下数据分析【{stock_name}({stock_code})】。", "", f"**用户问题**: {user_message}", "" ] # 添加用户关注点 if specific_concerns: prompt_parts.append(f"**用户特别关注**: {', '.join(specific_concerns)}") prompt_parts.append("") # 添加数据部分 prompt_parts.append("## 数据信息") prompt_parts.append("") # 根据维度添加相应数据 if dimensions.get('price_trend') and 'market_data' in data: prompt_parts.append(self._format_market_data_section(data['market_data'])) if dimensions.get('technical') and 'technical_analysis' in data: prompt_parts.append(self._format_technical_section(data['technical_analysis'])) if dimensions.get('fundamental') and 'fundamental' in data: prompt_parts.append(self._format_fundamental_section(data['fundamental'])) if dimensions.get('valuation') or dimensions.get('money_flow'): if 'advanced_data' in data: prompt_parts.append(self._format_advanced_section(data['advanced_data'])) # 分析要求 prompt_parts.append("") prompt_parts.append("## 分析要求") prompt_parts.append("") # 根据时间范围调整 if time_scope.get('short_term'): prompt_parts.append("- 重点分析短期走势(1-2周)") if time_scope.get('medium_term'): prompt_parts.append("- 分析中期趋势(1-3个月)") if time_scope.get('long_term'): prompt_parts.append("- 评估长期投资价值(半年以上)") # 根据用户风格调整 if user_style.get('tone') == 'casual': prompt_parts.append("- 使用通俗易懂的语言,避免过多专业术语") else: prompt_parts.append("- 使用专业的金融术语和分析方法") if user_style.get('detail_level') == 'brief': prompt_parts.append("- 简洁回答,控制在200-300字") else: prompt_parts.append("- 详细分析,控制在500-600字") # 输出格式 prompt_parts.append("") prompt_parts.append("请直接开始分析,不要添加日期标题。最后声明:\"以上分析仅供参考,不构成投资建议。\"") return "\n".join(prompt_parts) def _format_market_data_section(self, data: Dict) -> str: """格式化行情数据部分""" if 'error' in data: return "**行情数据**: 暂时无法获取" return f"""**行情数据**: - 最新价: {data.get('close', 0):.2f}元 - 涨跌幅: {data.get('pct_chg', 0):+.2f}% - 成交量: {data.get('vol', 0):.0f}手 - 成交额: {data.get('amount', 0):.0f}千元 """ def _format_technical_section(self, data: Dict) -> str: """格式化技术指标部分""" if 'error' in data: return "**技术指标**: 暂时无法获取" indicators = data.get('indicators', {}) parts = ["**技术指标**:"] if 'ma' in indicators: ma = indicators['ma'] parts.append(f"- 均线: MA5={ma.get('ma5', 0):.2f}, MA10={ma.get('ma10', 0):.2f}, MA20={ma.get('ma20', 0):.2f}") if 'macd' in indicators: macd = indicators['macd'] parts.append(f"- MACD: DIF={macd.get('dif', 0):.4f}, DEA={macd.get('dea', 0):.4f}, MACD={macd.get('macd', 0):.4f}") if 'rsi' in indicators: rsi = indicators['rsi'] parts.append(f"- RSI: RSI6={rsi.get('rsi6', 0):.2f}, RSI12={rsi.get('rsi12', 0):.2f}") return "\n".join(parts) def _format_fundamental_section(self, data: Dict) -> str: """格式化基本面部分""" if 'error' in data: return "**基本面**: 暂时无法获取" return f"""**基本面**: - 公司名称: {data.get('name', '')} - 所属行业: {data.get('industry', '')} - 所属地域: {data.get('area', '')} - 上市市场: {data.get('market', '')} """ def _format_advanced_section(self, data: Dict) -> str: """格式化高级数据部分""" if 'error' in data: return "**高级数据**: 暂时无法获取" parts = ["**高级数据**:"] if 'valuation' in data: val = data['valuation'] parts.append(f"- 估值: PE={val.get('pe', 0):.2f}, PB={val.get('pb', 0):.2f}") if 'money_flow' in data and data['money_flow']: mf = data['money_flow'][0] if isinstance(data['money_flow'], list) else data['money_flow'] parts.append(f"- 资金流向: 净流入={mf.get('net_mf_amount', 0):.2f}万元") return "\n".join(parts) def _calculate_max_tokens(self, intent: Dict[str, Any]) -> int: """根据意图计算max_tokens""" depth = intent.get('analysis_depth', 'standard') detail_level = intent.get('user_style', {}).get('detail_level', 'detailed') if depth == 'quick' or detail_level == 'brief': return 800 elif depth == 'deep' or detail_level == 'detailed': return 2000 else: return 1500 def _format_fallback_response(self, data: Dict, stock_name: str) -> str: """降级响应格式""" parts = [f"【{stock_name}】分析报告\n"] if 'market_data' in data and 'error' not in data['market_data']: md = data['market_data'] parts.append(f"最新价: {md.get('close', 0):.2f}元") parts.append(f"涨跌幅: {md.get('pct_chg', 0):+.2f}%\n") parts.append("以上分析仅供参考,不构成投资建议。") return "\n".join(parts) async def _handle_stock_analysis_stream( self, intent: Dict[str, Any], message: str ): """ 流式处理股票分析请求(智能模式) Args: intent: 问题意图 message: 用户消息 Yields: 响应文本片段 """ target = intent.get('target', {}) stock_code = target.get('stock_code') stock_name = target.get('stock_name') market = target.get('market', 'A股') # 检测是否为美股 is_us_stock = market == '美股' or self._is_us_stock(stock_name or stock_code or '', market) # 如果是美股,直接使用美股处理流程 if is_us_stock: async for chunk in self._handle_us_stock_stream(stock_name or stock_code, message): yield chunk return # A股处理流程 # 如果没有股票代码,尝试匹配 if not stock_code and stock_name: stock_info = await self._match_stock_with_llm(stock_name) if not stock_info: yield f"抱歉,未找到股票\"{stock_name}\"。请确认名称或代码是否正确。" return stock_code = stock_info['code'] stock_name = stock_info['name'] if not stock_code: yield "抱歉,我没有识别到您提到的股票。请提供更明确的股票代码或名称。" return logger.info(f"[智能模式-流式] 分析股票: {stock_name}({stock_code})") # 1. 技能规划 plan = self.skill_planner.plan_skills(intent) logger.info(f"[智能模式-流式] 技能规划: {[s['name'] for s in plan['skills']]}") # 2. 执行技能 execution_results = await skill_manager.execute_plan( plan=plan, stock_code=stock_code ) if execution_results['errors']: logger.warning(f"[智能模式-流式] 技能执行有错误: {execution_results['errors']}") # 3. 智能生成回答(流式) async for chunk in self._generate_intelligent_response_stream( intent=intent, execution_results=execution_results['results'], stock_code=stock_code, stock_name=stock_name, user_message=message ): yield chunk async def _generate_intelligent_response_stream( self, intent: Dict[str, Any], execution_results: Dict[str, Any], stock_code: str, stock_name: str, user_message: str ): """ 智能生成回答(流式) - 根据用户意图定制 Args: intent: 问题意图 execution_results: 技能执行结果 stock_code: 股票代码 stock_name: 股票名称 user_message: 用户消息 Yields: 响应文本片段 """ # 1. 构建动态prompt prompt = self._build_dynamic_prompt( intent=intent, data=execution_results, stock_code=stock_code, stock_name=stock_name, user_message=user_message ) # 2. 调用LLM流式生成 if self.use_llm: stream = llm_service.chat_stream( messages=[{"role": "user", "content": prompt}], temperature=0.7, max_tokens=self._calculate_max_tokens(intent) ) for chunk in stream: yield chunk else: # 降级到规则化格式 fallback = self._format_fallback_response(execution_results, stock_name) for char in fallback: yield char def _resolve_context_references( self, intent: Dict[str, Any], context_info: Dict ) -> Dict[str, Any]: """ 解析上下文引用(代词解析) Args: intent: 问题意图 context_info: 上下文信息 Returns: 更新后的意图 """ target = intent.get('target', {}) # 如果用户说"这只股票"、"它"等,从上下文中提取 if not target.get('stock_code') and context_info.get('last_stock'): target['stock_code'] = context_info['last_stock'] intent['target'] = target logger.info(f"[智能模式] 从上下文解析股票代码: {target['stock_code']}") return intent # 创建全局实例 smart_agent = SmartStockAgent()