7ec4fb0747
- LLM 客户端:全部 SSE 流式输出,兼容 OpenAI 接口
- AI 月报解析:SSE 流式端点 POST /reports/{id}/parse
- 健康度计算引擎:四维评分(财务/经营/AI商业化/AI成本)
- 风险自动检测引擎:6 条规则自动检测指标越界
- AI Copilot:SSE 流式对话 POST /copilot/chat
- 权限中间件:角色级 + 字段级权限控制
- 测试:21 个新测试(健康度 8 + 风险检测 8 + 权限 5),总计 64 passed
98 lines
2.9 KiB
Python
98 lines
2.9 KiB
Python
"""月报 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} 月报内容:
|
|
|
|
<<<USER_INPUT>>>
|
|
{content}
|
|
<<<END_USER_INPUT>>>
|
|
"""
|
|
|
|
|
|
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,
|
|
}
|