| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138 |
- """网页测试工作台;与当前聊天接口使用相同的本地用户归属约定。"""
- import asyncio
- from typing import Any, Literal
- from fastapi import APIRouter, Depends, File, HTTPException, Query, UploadFile
- from fastapi.responses import FileResponse
- from pydantic import BaseModel, ConfigDict, Field, ValidationError
- import config
- from db import test_lab_store
- from runtime.business_tasks import get_controller
- from runtime.test_lab import files, service
- from runtime.test_lab.spreadsheets import preview_workbook
- from runtime.test_lab.main_fan_simulation import MainFanSimulation
- def enabled():
- if not config.get_bool('TEST_LAB_ENABLED',True):
- raise HTTPException(404,'测试工作台未启用')
- router=APIRouter(prefix='/test-lab',tags=['智能体测试'],dependencies=[Depends(enabled)])
- class TestRequest(BaseModel):
- model_config=ConfigDict(extra='forbid')
- agent_name:str=Field(min_length=1,max_length=64)
- scenario_id:str=Field(default='custom',max_length=64)
- label:str=Field(default='自定义测试',min_length=1,max_length=128)
- input:dict[str,Any]
- asset_ids:list[str]=Field(default_factory=list,max_length=20)
- input_asset_id:str|None=None
- execution_mode:Literal['pipeline','agent']='agent'
- request_id:str|None=Field(default=None,min_length=1,max_length=128)
- main_fan_simulation:MainFanSimulation|None=None
- def bad_request(exc):
- if isinstance(exc,ValidationError):
- return HTTPException(422,exc.errors(include_input=False,include_context=False))
- return HTTPException(400,str(exc))
- @router.get('/catalog')
- async def catalog(user_name:str=Query('default',min_length=1,max_length=128)):
- return await asyncio.to_thread(service.catalog,user_name)
- @router.get('/presets/{agent}/{identifier}')
- async def preset(agent:str,identifier:str):
- try:
- return await asyncio.to_thread(service.get_preset,agent,identifier)
- except ValueError as exc:
- raise HTTPException(404,str(exc)) from None
- @router.post('/assets',status_code=201)
- async def upload(file:UploadFile=File(...),user_name:str=Query('default',min_length=1,max_length=128)):
- try:
- return await files.upload(file,user_name)
- except (ValueError,UnicodeError) as exc:
- raise bad_request(exc) from None
- @router.get('/assets/{identifier}/content')
- async def asset_content(identifier:str,user_name:str='default',download:bool=False):
- try:
- path,description=await asyncio.to_thread(files.owned_file,identifier,user_name)
- except ValueError as exc:
- raise HTTPException(404,str(exc)) from None
- return FileResponse(path,media_type=files.media_type(path),filename=description['filename'],
- content_disposition_type='attachment' if download else 'inline',headers={'X-Content-Type-Options':'nosniff'})
- @router.get('/assets/{identifier}/preview')
- async def asset_preview(identifier:str,user_name:str='default'):
- try:
- path,description=await asyncio.to_thread(files.owned_file,identifier,user_name)
- if description['kind']=='json':
- return {'kind':'json','content':await asyncio.to_thread(files.json_value,path)}
- if description['kind']=='excel':
- return {'kind':'excel',**await asyncio.to_thread(preview_workbook,path)}
- return description
- except ValueError as exc:
- raise bad_request(exc) from None
- @router.post('/runs',status_code=202)
- async def start(payload:TestRequest,user_name:str=Query('default',min_length=1,max_length=128)):
- try:
- return await asyncio.to_thread(service.start_run,payload,user_name)
- except ValueError as exc:
- raise bad_request(exc) from None
- except RuntimeError as exc:
- raise HTTPException(503,str(exc)) from None
- @router.get('/runs')
- async def runs(user_name:str='default',limit:int=Query(50,ge=1,le=200)):
- rows=await asyncio.to_thread(test_lab_store.runs,user_name,limit)
- return {'items':[{k:v for k,v in r.items() if k!='request_json'} for r in rows]}
- @router.get('/runs/{identifier}')
- async def run(identifier:str,user_name:str='default'):
- try:
- return await asyncio.to_thread(service.public_run,identifier,user_name)
- except ValueError as exc:
- raise HTTPException(404,str(exc)) from None
- @router.post('/runs/{identifier}/cancel')
- async def cancel(identifier:str,user_name:str='default'):
- try:
- await asyncio.to_thread(test_lab_store.run,identifier,user_name)
- await get_controller().cancel_job(identifier)
- return await asyncio.to_thread(service.public_run,identifier,user_name)
- except ValueError as exc:
- raise HTTPException(404,str(exc)) from None
- @router.get('/runs/{identifier}/artifacts/{key}/content')
- async def artifact_content(identifier:str,key:str,user_name:str='default',download:bool=True):
- try:
- item=await asyncio.to_thread(service.artifact,identifier,key,user_name)
- except ValueError as exc:
- raise HTTPException(404,str(exc)) from None
- return FileResponse(item['path'],media_type=files.media_type(item['path']),filename=item['filename'],
- content_disposition_type='attachment' if download else 'inline',headers={'X-Content-Type-Options':'nosniff'})
- @router.get('/runs/{identifier}/artifacts/{key}/preview')
- async def artifact_preview(identifier:str,key:str,user_name:str='default'):
- try:
- item=await asyncio.to_thread(service.artifact,identifier,key,user_name)
- return {'kind':'excel','origin':item['origin'],**await asyncio.to_thread(preview_workbook,item['path'])}
- except ValueError as exc:
- raise bad_request(exc) from None
|