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
102 lines
3.1 KiB
Python
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",
|
|
},
|
|
)
|