Add api server code and workflow

This commit is contained in:
Xin Wang 2025-04-29 15:03:02 +08:00
commit a1a4bceb9a
13 changed files with 16662 additions and 0 deletions

0
.gitignore vendored Normal file
View File

3
pytest.ini Normal file
View File

@ -0,0 +1,3 @@
[pytest]
markers =
integration: marks tests that need access to external services

8
requirements.txt Normal file
View File

@ -0,0 +1,8 @@
fastapi>=0.104.0
uvicorn>=0.24.0
pydantic>=2.4.2
python-dotenv>=1.0.0
httpx>=0.25.0
pytest>=7.4.0
pytest-asyncio>=0.21.0
pytest-cov>=4.1.0

493
src/api/endpoints.py Normal file
View File

@ -0,0 +1,493 @@
from fastapi import APIRouter, HTTPException
from ..schemas.models import ProcessRequest_chat, ProcessResponse_chat, ProcessRequest_get, ProcessResponse_get, ProcessRequest_set, ProcessResponse_set, ProcessResponse_delete_session, ProcessRequest_delete_session
import httpx
from datetime import datetime
import os
from dotenv import load_dotenv
from urllib import parse as parse
import json
load_dotenv()
router = APIRouter()
ANALYSIS_SERVICE_URL = os.getenv("ANALYSIS_SERVICE_URL", None)
if ANALYSIS_SERVICE_URL is None:
raise ValueError("ANALYSIS_SERVICE_URL environment variable not set")
ANALYSIS_AUTH_TOKEN = os.getenv("ANALYSIS_AUTH_TOKEN", None)
if ANALYSIS_AUTH_TOKEN is None:
raise ValueError("ANALYSIS_AUTH_TOKEN environment variable not set")
DELETE_SESSION_URL = os.getenv("DELETE_SESSION_URL", None)
if DELETE_SESSION_URL is None:
raise ValueError("DELETE_SESSION_URL environment variable not set")
APP_ID = os.getenv("APP_ID", None)
if APP_ID is None:
raise ValueError("APP_ID environment variable not set")
DELETE_CHAT_URL = os.getenv("DELETE_CHAT_URL", None)
if DELETE_CHAT_URL is None:
raise ValueError("DELETE_CHAT_URL environment variable not set")
GET_CHAT_RECORDS_URL = os.getenv("GET_CHAT_RECORDS_URL", None)
if GET_CHAT_RECORDS_URL is None:
raise ValueError("GET_CHAT_RECORDS_URL environment variable not set")
STATUS_CODE_MAP = {
'0000': '结束通话',
'0001': '转接人工',
'1001': '未准备好通话',
'1002': '通话中',
'2000': '进入单车拍照',
'2001': '请对准车辆碰撞部位拍摄照片',
'2002': '请对准被撞物品拍摄照片',
'2003': '请切换摄像头对准本人拍摄一张正面照片',
'2004': '确认单车车牌',
'2005': '请确认车损位置是在车辆前方、后方还是侧面',
'2010': '进入双车拍照',
'2011': '请对准第一辆车碰撞部位拍摄',
'2012': '请对准第二辆车碰撞部位拍摄',
'2013': '请对准第二方车辆侧后方,看清车牌拍摄',
'2014': '请拍摄另一方驾驶人的正面照片',
'2015': '请切换前置摄像头对准本人拍摄一张正面照片',
'2016': '确认双车中的车牌'
}
async def delete_last_two_chat_records(sessionId):
# 获取最后两条聊天记录
headers = {
"Authorization": f"Bearer {ANALYSIS_AUTH_TOKEN}",
"Content-Type": "application/json"
}
chat_data = {
"appId": APP_ID,
"chatId": sessionId,
"offset": 0,
}
async with httpx.AsyncClient() as client:
response = await client.post(
GET_CHAT_RECORDS_URL,
json=chat_data,
headers=headers,
timeout=None
)
response.raise_for_status()
data = response.json()
last_two_dataId = [x['dataId'] for x in data['data']['list'][-2:]]
# 删除最后聊天聊天记录
for contentId in last_two_dataId:
headers = {
"Authorization": f"Bearer {ANALYSIS_AUTH_TOKEN}",
"Content-Type": "application/json"
}
url = DELETE_CHAT_URL.format(contentId=contentId, chatId=sessionId, appId=APP_ID)
async with httpx.AsyncClient() as client:
response = await client.delete(
url,
headers=headers,
timeout=None
)
response.raise_for_status()
data = response.json()
# @app.exception_handler(RequestValidationError)
# async def validation_exception_handler(request: Request, exc: RequestValidationError):
# logging.error(f"Validation Error: {exc.errors()}")
# raise HTTPException(status_code=422, detail=exc.errors())
@router.post("/chat", response_model=ProcessResponse_chat)
async def chat(request: ProcessRequest_chat):
try:
headers = {
"Authorization": f"Bearer {ANALYSIS_AUTH_TOKEN}",
"Content-Type": "application/json"
}
json_data = request.model_dump()
chat_data = {
"chatId": json_data['sessionId'],
"stream": False,
"detail": True,
"messages": [
{
"role": "user",
"content": json_data['text']
}
]
}
# Call external analysis service
async with httpx.AsyncClient() as client:
response = await client.post(
ANALYSIS_SERVICE_URL,
json=chat_data,
headers=headers,
timeout=None
)
response.raise_for_status()
data = response.json()
except Exception as e:
print(e)
return ProcessResponse_chat(
sessionId=json_data['sessionId'],
timeStamp=json_data['timeStamp'],
outputText="",
nextStage="",
nextStageCode="",
code="500",
msg="大模型服务器无响应"
)
try:
# Extract content from FastGPT response
content = data['choices'][0]['message']['content']
finish_reason = data['choices'][0]['finish_reason']
transfer_to_human = data['newVariables']['state'].get("transfer_to_human", False)
ywrysw = data['newVariables']['state'].get("ywrysw", False)
ywfjdc = data['newVariables']['state'].get("ywfjdc", False)
ywmtc = data['newVariables']['state'].get("ywmtc", False)
jdcsl = data['newVariables']['state'].get("jdcsl", 0)
accident_info_complete = data['newVariables']['state'].get("accident_info_complete", False)
user_is_ready = data['newVariables']['state'].get("user_is_ready", False)
if isinstance(user_is_ready, str):
user_is_ready = user_is_ready.lower() == 'true'
driver_info_complete = data['newVariables']['state'].get("driver_info_complete", False)
drivers_info_complete = data['newVariables']['state'].get("drivers_info_complete", False)
driver_info_check = data['newVariables']['state'].get("drivers_info_check", False)
drivers_info_check = data['newVariables']['state'].get("drivers_info_check", False)
print(data['newVariables'])
nextStageCode=data['newVariables']['status_code']
nextStage=STATUS_CODE_MAP[nextStageCode]
return ProcessResponse_chat(
sessionId=json_data['sessionId'],
timeStamp=json_data['timeStamp'],
outputText=content,
nextStage=nextStage,
nextStageCode=nextStageCode,
code="200",
msg="",
)
except Exception as e:
print(e)
return ProcessResponse_chat(
sessionId=json_data['sessionId'],
timeStamp=json_data['timeStamp'],
outputText="",
nextStage="",
nextStageCode="",
code="500",
msg="大模型服务返回消息不完整"
)
@router.post("/set_info", response_model=ProcessResponse_set)
async def set_info(request: ProcessRequest_set):
# 获取当前state
try:
headers = {
"Authorization": f"Bearer {ANALYSIS_AUTH_TOKEN}",
"Content-Type": "application/json"
}
json_data = request.model_dump()
chat_data = {
"chatId": json_data['sessionId'],
"stream": False,
"detail": True,
"messages": [
{
"role": "user",
"content": ""
}
]
}
# Call external analysis service
async with httpx.AsyncClient() as client:
response = await client.post(
ANALYSIS_SERVICE_URL,
json=chat_data,
headers=headers,
timeout=None
)
response.raise_for_status()
data = response.json()
current_state = data['newVariables']['state']
if isinstance(current_state, str):
current_state = json.loads(current_state)
print(current_state)
except Exception as e:
print(e)
return ProcessResponse_set(
sessionId=json_data['sessionId'],
timeStamp=json_data['timeStamp'],
code="500",
msg="大模型服务器无响应"
)
try:
await delete_last_two_chat_records(json_data['sessionId'])
except Exception as e:
print(e)
return ProcessResponse_set(
sessionId=json_data['sessionId'],
timeStamp=json_data['timeStamp'],
code="500",
msg="大模型后台无响应"
)
# 修改当前state
try:
headers = {
"Authorization": f"Bearer {ANALYSIS_AUTH_TOKEN}",
"Content-Type": "application/json"
}
json_data = request.model_dump()
# key_map = {"xm1": "xm1", "hpzl1": "hpzl1", "hphm1": "driver1License", "sfzmhm1": "driver1ID", "sfzmwh1": "sfzmwh1", "sjhm1": "driver1Phonenumber","sjwh1": "sjwh1", "xm2":"xm2", "hpzl2": "hpzl2", "hphm2": "driver2License", "sfzmhm2": "driver2ID", "sfzmwh2": "sfzmwh2", "sjhm2": "driver2Phonenumber", "sjwh2": "sjwh2"}
# if json_data['key'] not in key_map:
# raise Exception("check your key")
# key = key_map.get(json_data['key'])
key = json_data['key']
value = json_data['value']
current_state[json_data['key']] = value
print(f'即将上传 {current_state}')
# if key == 'driver1ID' or key == 'driver2ID' or key == 'driver1Phonenumber' or key == 'driver2Phonenumber':
# current_state[json_data['key'].replace('hm','wh')] = value[-4:]
chat_data = {
"chatId": json_data['sessionId'],
"stream": False,
"detail": True,
"variables": {'state':current_state},
"messages": [
{
"role": "user",
"content": ""
}
]
}
# Call external analysis service
async with httpx.AsyncClient() as client:
response = await client.post(
ANALYSIS_SERVICE_URL,
json=chat_data,
headers=headers,
timeout=None
)
response.raise_for_status()
return ProcessResponse_set(
sessionId=json_data['sessionId'],
timeStamp=json_data['timeStamp'],
code="200",
msg=""
)
except Exception as e:
print(e)
return ProcessResponse_set(
sessionId=json_data['sessionId'],
timeStamp=json_data['timeStamp'],
code="500",
msg="大模型后台无响应"
)
try:
await delete_last_two_chat_records(json_data['sessionId'])
except Exception as e:
print(e)
return ProcessResponse_set(
sessionId=json_data['sessionId'],
timeStamp=json_data['timeStamp'],
code="500",
msg="大模型后台无响应"
)
@router.post("/get_info", response_model=ProcessResponse_get)
async def get_info(request: ProcessRequest_get):
# 获取当前state
try:
headers = {
"Authorization": f"Bearer {ANALYSIS_AUTH_TOKEN}",
"Content-Type": "application/json"
}
json_data = request.model_dump()
chat_data = {
"chatId": json_data['sessionId'],
"stream": False,
"detail": True,
"messages": [
{
"role": "user",
"content": ""
}
]
}
# Call external analysis service
async with httpx.AsyncClient() as client:
response = await client.post(
ANALYSIS_SERVICE_URL,
json=chat_data,
headers=headers,
timeout=None
)
response.raise_for_status()
data = response.json()
current_state = data['newVariables']['state']
if isinstance(current_state, str):
current_state = json.loads(current_state)
print(current_state)
except Exception as e:
print(e)
return ProcessResponse_get(
sessionId=json_data['sessionId'],
timeStamp=json_data['timeStamp'],
value="",
code="500",
msg="大模型服务器无响应"
)
try:
await delete_last_two_chat_records(json_data['sessionId'])
except Exception as e:
print(e)
return ProcessResponse_get(
sessionId=json_data['sessionId'],
timeStamp=json_data['timeStamp'],
value="",
code="500",
msg="大模型后台无响应"
)
try:
key = json_data['key']
# key_map = {"hpzl1": "hpzl1", "hphm1": "driver1License", "sfzmhm1": "driver1ID", "sfzmwh1": "sfzmwh1", "sjhm1": "driver1Phonenumber","sjwh1": "sjwh1", "hpzl2": "hpzl2", "hphm2": "driver2License", "sfzmhm2": "driver2ID", "sfzmwh2": "sfzmwh2", "sjhm2": "driver2Phonenumber", "sjwh2": "sjwh2"}
acd_keys = ['ywrysw', 'ywfjdc', 'ywmtc', 'bjrjs', 'sgfssj', 'sfsgxc', 'jdcsl', 'sgyy']
human1_keys = ['xm1', 'hpzl1', 'hphm1', 'sfzmhm1', 'sfzmwh1', 'sjhm1', 'sjwh1', 'csbw1']
human2_keys = ['xm2', 'hpzl2', 'hphm2', 'sfzmhm2', 'sfzmwh2', 'sjhm2', 'sjwh2', 'csbw2']
if key == 'all':
acd_values = {}
for k in acd_keys:
v = current_state.get(k, '')
if isinstance(v, bool):
v = '1' if v==True else '0'
acd_values[k] = v
human1_values = {}
for k in human1_keys:
v = current_state.get(k, '')
if isinstance(v, bool):
v = '1' if v==True else '0'
human1_values[k] = v
human2_values = {}
for k in human2_keys:
v = current_state.get(k, '')
if isinstance(v, bool):
v = '1' if v==True else '0'
human2_values[k] = v
value = {'acdinfo': acd_values, 'acdhuman1': human1_values, 'acdhuman2': human2_values}
value = json.dumps(value)
elif key == "acdinfo":
acd_values = {}
for k in acd_keys:
v = current_state.get(k, '')
if isinstance(v, bool):
v = '1' if v==True else '0'
acd_values[k] = v
value = json.dumps(acd_values)
elif key == 'acdhuman1':
human1_values = {}
for k in human1_keys:
v = current_state.get(k, '')
if isinstance(v, bool):
v = '1' if v==True else '0'
human1_values[k] = v
value = json.dumps(human1_value)
elif key == 'acdhuman2':
human2_values = {}
for k in human1_keys:
v = current_state.get(k, '')
if isinstance(v, bool):
v = '1' if v==True else '0'
human2_values[k] = v
value = json.dumps(human2_value)
else:
# if key in key_map:
# real_key = key_map.get(key)
# else:
# real_key = key
value = current_state.get(key, '')
value = json.dumps(value)
# if value == 'null':
# raise Exception("check your key")
print(json_data)
return ProcessResponse_get(
sessionId=json_data['sessionId'],
timeStamp=json_data['timeStamp'],
value=value,
code="200",
msg=""
)
except Exception as e:
print(e)
return ProcessResponse_get(
sessionId=json_data['sessionId'],
timeStamp=json_data['timeStamp'],
value="",
code="500",
msg="入参不规范"
)
@router.delete("/delete_session", response_model=ProcessResponse_delete_session)
async def delete_session(request: ProcessRequest_delete_session):
try:
headers = {
"Authorization": f"Bearer {ANALYSIS_AUTH_TOKEN}",
"Content-Type": "application/json"
}
json_data = request.model_dump()
chatId = json_data.get('sessionId', None)
if chatId is None:
raise ValueError(f'Error in chatId.')
url = DELETE_SESSION_URL.format(appId=APP_ID, chatId=chatId)
# Call external analysis service
async with httpx.AsyncClient() as client:
response = await client.delete(
url,
headers=headers,
timeout=None
)
response.raise_for_status()
data = response.json()
# print(data)
except Exception as e:
print(e)
return ProcessResponse_chat(
sessionId=json_data['sessionId'],
timeStamp=json_data['timeStamp'],
code="500",
msg="大模型服务器无响应"
)
try:
if data['code'] == 200:
return ProcessResponse_delete_session(
sessionId=json_data['sessionId'],
timeStamp=json_data['timeStamp'],
code="200",
msg=""
)
else:
raise ValueError("删除会话失败")
except Exception as e:
print(e)
return ProcessResponse_delete_session(
sessionId=json_data['sessionId'],
timeStamp=json_data['timeStamp'],
code="500",
msg="删除会话失败"
)

1
src/core/__init__.py Normal file
View File

@ -0,0 +1 @@
# This file is intentionally left blank.

11
src/core/config.py Normal file
View File

@ -0,0 +1,11 @@
import os
from dotenv import load_dotenv
load_dotenv()
class Config:
PROJECT_NAME = "Flexible Employment Analysis API"
API_V1_STR = "/api/v1"
SECRET_KEY = os.getenv("SECRET_KEY", "your-secret-key")
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./test.db")
DEBUG = os.getenv("DEBUG", "false").lower() in ("true", "1", "t")

16
src/main.py Normal file
View File

@ -0,0 +1,16 @@
from fastapi import FastAPI
import sys
sys.path.append('..')
from api.endpoints import router as api_router
app = FastAPI(
title="AI Accident Information Collection API",
description="AI Accident Information Collection API",
version="1.0.0"
)
@app.get("/")
def read_root():
return {"message": "Server is running."}
app.include_router(api_router)

1
src/schemas/__init__.py Normal file
View File

@ -0,0 +1 @@
# This file is intentionally left blank.

50
src/schemas/models.py Normal file
View File

@ -0,0 +1,50 @@
from pydantic import BaseModel, Field
from typing import Optional
class ProcessRequest_chat(BaseModel):
sessionId: str = Field(..., max_length=64)
timeStamp: str = Field(..., max_length=32)
text: str = Field(...)
class ProcessResponse_chat(BaseModel):
sessionId: str = Field(..., max_length=64)
timeStamp: str = Field(..., max_length=32)
outputText: str = Field(...)
nextStage: str = Field(..., max_length=32)
nextStageCode: str = Field(..., max_length=4)
code: str = Field(..., max_length=4)
msg: Optional[str] = None
class ProcessRequest_get(BaseModel):
sessionId: str = Field(..., max_length=64)
timeStamp: str = Field(..., max_length=32)
key: str = Field(...)
class ProcessResponse_get(BaseModel):
sessionId: str = Field(..., max_length=64)
timeStamp: str = Field(..., max_length=32)
value: str = Field(...)
code: str = Field(..., max_length=4)
msg: Optional[str] = None
class ProcessRequest_set(BaseModel):
sessionId: str = Field(..., max_length=64)
timeStamp: str = Field(..., max_length=32)
key: str = Field(...)
value: str = Field(...)
class ProcessResponse_set(BaseModel):
sessionId: str = Field(..., max_length=64)
timeStamp: str = Field(..., max_length=32)
code: str = Field(..., max_length=4)
msg: Optional[str] = None
class ProcessRequest_delete_session(BaseModel):
sessionId: str = Field(..., max_length=64)
timeStamp: str = Field(..., max_length=32)
class ProcessResponse_delete_session(BaseModel):
sessionId: str = Field(..., max_length=64)
timeStamp: str = Field(..., max_length=32)
code: str = Field(..., max_length=4)
msg: Optional[str] = None

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

2687
workflow/单车插件.json Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff