Files
AIPortPilot/backend/app/services/ai_parser.py
T
selfrelease 7ec4fb0747 feat(backend): AI 服务层 — 千问流式 LLM + 月报解析 + 健康度计算 + 风险检测 + Copilot
- LLM 客户端:全部 SSE 流式输出,兼容 OpenAI 接口
- AI 月报解析:SSE 流式端点 POST /reports/{id}/parse
- 健康度计算引擎:四维评分(财务/经营/AI商业化/AI成本)
- 风险自动检测引擎:6 条规则自动检测指标越界
- AI Copilot:SSE 流式对话 POST /copilot/chat
- 权限中间件:角色级 + 字段级权限控制
- 测试:21 个新测试(健康度 8 + 风险检测 8 + 权限 5),总计 64 passed
2026-07-18 22:16:40 +08:00

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,
}