"""月报 AI 解析服务。 使用千问 LLM 从月报原始文本中提取: 1. 结构化指标数据(营收、现金流、团队等) 2. AI 摘要 3. 关注点列表 """ import logging from typing import Any from app.services.llm_client import llm_client logger = logging.getLogger(__name__) SYSTEM_PROMPT = """你是投后管理领域的专业分析师。请分析以下企业月报内容,提取结构化信息。 输出 JSON 格式如下: { "structured_data": { "revenue": {"value": "", "unit": "万元", "yoy_change": "", "note": ""}, "cash_balance": {"value": "", "unit": "万元", "runway_months": 0, "note": ""}, "burn_rate": {"value": "", "unit": "万元/月", "trend": "up/stable/down", "note": ""}, "headcount": {"total": 0, "new_hires": 0, "departures": 0, "note": ""}, "key_metrics": [{"name": "", "value": "", "change": "", "note": ""}] }, "ai_summary": "一段 100-200 字的月报摘要,概括企业经营状况和关键变化", "ai_concerns": { "items": [ {"category": "financial/operational/org/ai_specific", "severity": "low/medium/high", "description": ""} ], "highlights": ["本期亮点1", "本期亮点2"] } } 注意: - 如果月报内容不足以提取某项指标,对应字段留空或为 0 - severity 只能是 low/medium/high - category 只能是 financial/operational/org/ai_specific - 严格输出 JSON,不要包含其他文字""" USER_PROMPT_TEMPLATE = """请分析以下 {period} 月报内容: <<>> {content} <<>> """ async def parse_report( content: str, period_year: int, period_month: int, ) -> dict[str, Any]: """解析月报内容,返回结构化数据。 Args: content: 月报原始文本 period_year: 报告年份 period_month: 报告月份 Returns: 包含 structured_data / ai_summary / ai_concerns 的字典 Raises: RuntimeError: LLM 调用失败 """ if not content or not content.strip(): return { "structured_data": {}, "ai_summary": "月报内容为空", "ai_concerns": {"items": [], "highlights": []}, } user_prompt = USER_PROMPT_TEMPLATE.format( period=f"{period_year}年{period_month}月", content=content[:4000], # 限制输入长度 ) messages = [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": user_prompt}, ] try: result = await llm_client.chat_json(messages, temperature=0.3, max_tokens=2000) logger.info("月报 AI 解析成功: period=%s-%s", period_year, period_month) return result except RuntimeError as e: logger.error("月报 AI 解析失败: %s", e) # 降级:返回空结构 return { "structured_data": {}, "ai_summary": f"AI 解析失败: {e}", "ai_concerns": {"items": [], "highlights": []}, "fallback_used": True, }