feat:LLM 分析文件并生成结构化建议
This commit is contained in:
+62
-2
@@ -1,20 +1,29 @@
|
||||
"""数据文件接口:上传限制、上传解析、列出、查看、删除,见 docs/screen-agent/data-files.md 第 5 节"""
|
||||
|
||||
from typing import Annotated, get_args
|
||||
from typing import Annotated, Literal, get_args
|
||||
|
||||
from fastapi import APIRouter, Depends, File, Form, Query, Request, UploadFile
|
||||
|
||||
from app.common.response import ApiResponse, success_response
|
||||
from app.core.auth import get_current_user
|
||||
from app.datafiles.service import MAX_FILES_PER_CONVERSATION, DataFileService
|
||||
from app.datafiles.service import (
|
||||
MAX_FILES_PER_CONVERSATION,
|
||||
PARSE_CONCURRENCY,
|
||||
DataFileService,
|
||||
)
|
||||
from app.schemas.base import NonBlankString
|
||||
from app.schemas.data_file import (
|
||||
MAX_AGGREGATE_ROWS,
|
||||
MAX_METRICS,
|
||||
AggregateRequest,
|
||||
AggregateResult,
|
||||
DataFileDetail,
|
||||
DataFileInfo,
|
||||
DataFileLimits,
|
||||
DataFileList,
|
||||
DataFileRecord,
|
||||
DataFormat,
|
||||
DataTableRows,
|
||||
)
|
||||
from app.schemas.user import CurrentUser
|
||||
|
||||
@@ -23,6 +32,10 @@ limits_router = APIRouter(prefix="/files", tags=["DataFile"])
|
||||
router = APIRouter(prefix="/files", tags=["DataFile"])
|
||||
|
||||
# 文件选择框的过滤条件;后端按文件内容识别格式,不看扩展名
|
||||
# 翻页取表数据:单页上限,和一张表最多的行数(100 万)
|
||||
MAX_PAGE_ROWS = 500
|
||||
MAX_OFFSET = 1_000_000
|
||||
|
||||
ACCEPT = ".csv,.tsv,.txt,.xlsx,.xls,.xlsb,.json,.jsonl,.ndjson,.parquet"
|
||||
|
||||
|
||||
@@ -43,6 +56,10 @@ async def get_limits(request: Request) -> ApiResponse[DataFileLimits]:
|
||||
max_files_per_conversation=MAX_FILES_PER_CONVERSATION,
|
||||
formats=list(get_args(DataFormat)),
|
||||
accept=ACCEPT,
|
||||
parse_concurrency=PARSE_CONCURRENCY,
|
||||
rows_max_page=MAX_PAGE_ROWS,
|
||||
aggregate_max_metrics=MAX_METRICS,
|
||||
aggregate_max_rows=MAX_AGGREGATE_ROWS,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -85,6 +102,49 @@ async def get_file(
|
||||
return success_response(_detail(await _service(request).get_file(user.id, file_id)))
|
||||
|
||||
|
||||
@router.post("/{file_id}/insights")
|
||||
async def regenerate_insights(
|
||||
file_id: str,
|
||||
request: Request,
|
||||
user: Annotated[CurrentUser, Depends(get_current_user)],
|
||||
) -> ApiResponse[DataFileDetail]:
|
||||
"""重新分析文件,上次分析失败后用;分析在后台进行,返回的 insights 为 pending"""
|
||||
return success_response(
|
||||
_detail(await _service(request).regenerate_insights(user.id, file_id))
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{file_id}/tables/{table}/rows")
|
||||
async def get_table_rows(
|
||||
file_id: str,
|
||||
table: str,
|
||||
request: Request,
|
||||
user: Annotated[CurrentUser, Depends(get_current_user)],
|
||||
offset: Annotated[int, Query(ge=0, le=MAX_OFFSET)] = 0,
|
||||
limit: Annotated[int, Query(ge=1, le=MAX_PAGE_ROWS)] = 100,
|
||||
sort_by: Annotated[str | None, Query(min_length=1)] = None,
|
||||
order: Annotated[Literal["asc", "desc"], Query()] = "asc",
|
||||
) -> ApiResponse[DataTableRows]:
|
||||
return success_response(
|
||||
await _service(request).get_rows(
|
||||
user.id, file_id, table, offset, limit, sort_by, order == "desc"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@router.post("/{file_id}/tables/{table}/aggregate")
|
||||
async def aggregate_table(
|
||||
file_id: str,
|
||||
table: str,
|
||||
body: AggregateRequest,
|
||||
request: Request,
|
||||
user: Annotated[CurrentUser, Depends(get_current_user)],
|
||||
) -> ApiResponse[AggregateResult]:
|
||||
return success_response(
|
||||
await _service(request).aggregate(user.id, file_id, table, body)
|
||||
)
|
||||
|
||||
|
||||
@router.delete("/{file_id}")
|
||||
async def delete_file(
|
||||
file_id: str,
|
||||
|
||||
Reference in New Issue
Block a user