Files
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

102 lines
3.1 KiB
Python

"""AI Copilot 路由 — SSE 流式对话。
为投资人和创始人提供 AI 副驾驶对话能力。
"""
import json
from fastapi import APIRouter, Depends, HTTPException
from fastapi.responses import StreamingResponse
from pydantic import BaseModel, Field
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.database import get_db
from app.core.dependencies import get_current_user
from app.models.user import User
from app.services.llm_client import llm_client
router = APIRouter(prefix="/copilot", tags=["copilot"])
class CopilotMessage(BaseModel):
"""Copilot 对话请求。"""
message: str = Field(..., min_length=1, max_length=4000)
context: dict | None = Field(default=None, description="可选上下文(企业ID/月报ID等)")
SYSTEM_PROMPT_INVESTOR = """你是 AIPortPilot 投后管理系统的 AI 副驾驶,服务于投资人用户。
你的职责:
1. 分析被投企业的经营状况和财务健康度
2. 识别潜在风险并提供预警建议
3. 协助撰写投后管理报告和建议
4. 解答关于企业治理、融资策略的问题
回答要求:
- 专业、简洁、有数据支撑
- 如需引用数据,明确标注来源
- 对于不确定的信息,坦诚说明
- 使用中文回答"""
SYSTEM_PROMPT_FOUNDER = """你是 AIPortPilot 投后管理系统的 AI 副驾驶,服务于创始人用户。
你的职责:
1. 协助撰写和优化月报内容
2. 分析企业经营数据,提供改进建议
3. 解答融资、团队管理、业务增长等问题
4. 提供行业趋势和竞品分析参考
回答要求:
- 实用、可操作、接地气
- 关注创始人的实际痛点
- 使用中文回答"""
@router.post("/chat")
async def copilot_chat(
req: CopilotMessage,
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""AI Copilot 对话(SSE 流式输出)。
根据用户角色使用不同的 system prompt。
"""
system_prompt = (
SYSTEM_PROMPT_FOUNDER if user.role == "founder" else SYSTEM_PROMPT_INVESTOR
)
# 构建上下文
context_str = ""
if req.context:
context_parts = []
for key, value in req.context.items():
context_parts.append(f"{key}: {value}")
context_str = f"\n\n当前上下文:\n" + "\n".join(context_parts)
messages = [
{"role": "system", "content": system_prompt + context_str},
{"role": "user", "content": f"<<<USER_INPUT>>>\n{req.message}\n<<<END_USER_INPUT>>>"},
]
async def event_stream():
"""SSE 流式输出。"""
try:
async for token in llm_client.chat_stream(messages, temperature=0.7, max_tokens=2000):
yield f"data: {json.dumps({'type': 'token', 'content': token})}\n\n"
yield f"data: {json.dumps({'type': 'done'})}\n\n"
except Exception as e:
yield f"data: {json.dumps({'type': 'error', 'message': str(e)})}\n\n"
return StreamingResponse(
event_stream(),
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"X-Accel-Buffering": "no",
},
)