| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596 |
- from fastapi import APIRouter, HTTPException
- from pydantic import BaseModel
- from typing import Optional, Any
- from app.agents.report_parse_agent import ReportParseAgent
- import logging
- import os
- import json
- import tempfile
- logger = logging.getLogger(__name__)
- router = APIRouter(prefix="/api/v1", tags=["report_parse"])
- _agent = None
- def get_agent():
- global _agent
- if _agent is None:
- _agent = ReportParseAgent()
- return _agent
- class ParseRequest(BaseModel):
- file_path: str
- family_id: Optional[int] = None
- user_id: Optional[int] = None
- class ParseResponse(BaseModel):
- code: int = 200
- message: str = "ok"
- data: dict = {}
- async def resolve_local_path(file_path: str) -> str:
- """将 file_path 解析为本地文件路径。
- 支持:本地路径、/uploads 共享卷路径、http(s) 远程 OSS URL(下载到临时文件)。"""
- if file_path.startswith(('http://', 'https://')):
- import httpx
- logger.info("report_parse: 远程文件 %s,下载到临时文件", file_path)
- async with httpx.AsyncClient(timeout=120, follow_redirects=True) as client:
- resp = await client.get(file_path)
- resp.raise_for_status()
- ext = os.path.splitext(file_path.split('?')[0])[1] or '.pdf'
- fd, tmp = tempfile.mkstemp(suffix=ext)
- with os.fdopen(fd, 'wb') as f:
- f.write(resp.content)
- logger.info("report_parse: 下载完成 -> %s", tmp)
- return tmp
- return file_path
- @router.post("/report/parse", response_model=ParseResponse)
- async def parse_report(req: ParseRequest):
- local = await resolve_local_path(req.file_path)
- if not os.path.exists(local):
- raise HTTPException(status_code=400, detail=f"文件不存在: {req.file_path}")
- logger.info("report_parse: file_path=%s family_id=%s user_id=%s", local, req.family_id, req.user_id)
- agent = get_agent()
- try:
- result = await agent.parse(local)
- return ParseResponse(data=result)
- except Exception as e:
- logger.error("报告解析失败: %s", e, exc_info=True)
- return ParseResponse(code=500, message=f"解析失败: {str(e)}", data={})
- class GenericParseRequest(BaseModel):
- file_path: str
- extra_context: Optional[dict] = None
- class GenericParseResponse(BaseModel):
- code: int = 200
- message: str = "ok"
- data: dict = {}
- @router.post("/report/parse-generic", response_model=GenericParseResponse)
- async def parse_report_generic(req: GenericParseRequest):
- """通用报告 LLM 兜底解析。
- 适用于指纹检测未匹配的未知类型报告。
- 直接交给 LLM 提取结构化数据,不经过算法预解析。
- """
- if not os.path.exists(req.file_path):
- raise HTTPException(status_code=400, detail=f"文件不存在: {req.file_path}")
- logger.info("report_parse_generic: file_path=%s", req.file_path)
- agent = get_agent()
- try:
- result = await agent.parse_generic(req.file_path, req.extra_context)
- return GenericParseResponse(data=result)
- except Exception as e:
- logger.error("通用报告解析失败: %s", e, exc_info=True)
- return GenericParseResponse(code=500, message=f"解析失败: {str(e)}", data={})
|