在作为脚本运行代码时调用外部API正常,但使用FastAPI运行同样代码时却收到`500 Internal Server Error`?

0 投票
2 回答
55 浏览
提问于 2025-04-12 01:26

我有一个应用程序,可以预测图片中鱼的大小。我创建了一个FastAPI的接口,叫做/predict/,这个接口会执行多个步骤来进行预测。这些步骤包括两次调用外部的API(这些API我无法控制,所以我只能看到它们返回的结果)。

当我直接从脚本运行代码,比如通过一个开发环境(我用的是PyCharm),预测的步骤都能正常运行,并且我从两个API那里得到了合适的响应。

第一个API是Roboflow,这是我运行脚本时的输出示例(我只是从命令行调用这个,或者在PyCharm中点击运行):

2024-03-30 10:59:36,073 - DEBUG - Starting new HTTPS connection (1): detect.roboflow.com:443
2024-03-30 10:59:36,339 - DEBUG - https://detect.roboflow.com:443 "POST /fish_measure/1?api_key=AY3KX4KMynZroEOyXUEb&disable_active_learning=False HTTP/1.1" 200 914

第二个API是Fishial,这是我运行脚本时的输出示例(无论是脚本还是通过PyCharm),这个API需要获取token、url等信息:

2024-03-30 11:02:31,866 - DEBUG - Starting new HTTPS connection (1): api-users.fishial.ai:443
2024-03-30 11:02:33,273 - DEBUG - https://api-users.fishial.ai:443 "POST /v1/auth/token HTTP/1.1" 200 174
2024-03-30 11:02:33,273 - INFO - Access token: eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MTE4MTE1NTMsImtpZCI6ImIzZjNiYWZlMTg2NGNjYmM3ZmFkNmE5YSJ9.YtlaecKMyxjipBDS97xNV3hYKcF3jRpOxTAVnwrxOcE
2024-03-30 11:02:33,273 - INFO - Obtaining upload url...
2024-03-30 11:02:33,582 - DEBUG - Starting new HTTPS connection (1): api.fishial.ai:443
2024-03-30 11:02:33,828 - DEBUG - https://api.fishial.ai:443 "POST /v1/recognition/upload HTTP/1.1" 200 1120
2024-03-30 11:02:33,829 - INFO - Uploading picture to the cloud...
2024-03-30 11:02:33,852 - DEBUG - Starting new HTTPS connection (1): storage.googleapis.com:443
2024-03-30 11:02:34,179 - DEBUG - https://storage.googleapis.com:443 "PUT /backend-fishes-storage-prod/6r9p24qp4llhat8mliso8xacdxm5?GoogleAccessId=services-storage-client%40ecstatic-baton-230905.iam.gserviceaccount.com&Expires=1711811253&Signature=gCGPID7bLuw%2FzUfv%2FLrTRPeQA060CaXQEqITPvW%2FWZ5GHXYKDRNCxVrUJ7UmpHVa0m60gIMFwFSQhYqsDmP3SkjI7ZnJSIEj53zxtOpcL7o2VGv6ZUuoowWwzmzqeM9yfbCHGI3TmtuW0lMhqAyi6Pc0wYhj73P12QU28wF8sdQMblHQLQVd1kFXtPl5yjSW12ADt4WEvB7dbnl7HmUTcL8WFS2SnJ1zcLljIbXTlRWcqc88MIcklSLG69z%2FJcUSh%2BeNxRp%2Fzotv5GitJBq9pF%2BzRt25lCt%2BYHGViJ46uu4rQapZBfACxsE762a1ZcrvTasy97idKRaijLJKAtZBRQ%3D%3D HTTP/1.1" 200 0
2024-03-30 11:02:34,180 - INFO - Requesting fish recognition...
2024-03-30 11:02:34,182 - DEBUG - Starting new HTTPS connection (1): api.fishial.ai:443
2024-03-30 11:02:39,316 - DEBUG - https://api.fishial.ai:443 "GET /v1/recognition/image?q=eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBMksyUEE9PSIsImV4cCI6bnVsbCwicHVyIjoiYmxvYl9pZCJ9fQ==--d37fdc2d5c6d8943a59dbd11326bc8a651f9bd69 HTTP/1.1" 200 10195

这是我为接口写的代码:

from fastapi import FastAPI, File, UploadFile, HTTPException, BackgroundTasks
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import Union

class PredictionResult(BaseModel):
    prediction: Union[float, str]
    eyeball_estimate: Union[float, str]
    species: str
    elapsed_time: float


@app.post("/predict/", response_model=PredictionResult)
    async def predict_fish_length(file: UploadFile = File(...)):
        try:
            # capture the start of the process so we can track duration
            start_time = time.time()
            # Create a temporary file
            temp_file = tempfile.NamedTemporaryFile(delete=False)
            temp_file_path = temp_file.name
    
            with open(temp_file_path, "wb") as buffer:
                shutil.copyfileobj(file.file, buffer)
    
            temp_file.close()
    
            prediction = process_one_image(temp_file_path)
            
            end_time = time.time()  # Record the end time
            elapsed_time = end_time - start_time  # Calculate the elapsed time
    
            return PredictionResult(
                prediction=prediction["prediction"][0],
                eyeball_estimate=prediction["eye_ratio_len_est"][0],
                species=prediction["species"][0],
                elapsed_time=elapsed_time
            )
    
        except Exception as e:
            # Clean up the temp file in case of an error
            os.unlink(temp_file_path)
            raise HTTPException(status_code=500, detail=str(e)) from e

我通过uvicorn来运行这个接口,然后尝试用curl来调用接口,方法如下:

curl -X POST http://127.0.0.1:8000/predict/ -F "file=@/path/to/image.jpg"

Roboflow的API调用都没问题,但我从Fishial(第二个)API得到的响应是这样的:

2024-03-30 10:48:09,166 - DEBUG - Starting new HTTPS connection (1): api.fishial.ai:443
2024-03-30 10:48:10,558 - DEBUG - https://api.fishial.ai:443 "GET /v1/recognition/image?q=eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBMWkyUEE9PSIsImV4cCI6bnVsbCwicHVyIjoiYmxvYl9pZCJ9fQ==--36e68766cd891eb0e57610e8fb84b76e205b639e HTTP/1.1" 500 89
INFO:     127.0.0.1:49829 - "POST /predict/ HTTP/1.1" 500 Internal Server Error

我不太确定该去哪里查找,或者应该打印出什么信息来获取更多的细节。我甚至不确定这个错误是我这边的问题,还是我调用的API的问题(不过500 89在GET请求的最后让我觉得问题可能出在我调用的API上)。

非常感谢!

编辑:有人请求更多的代码。处理图片的函数其实就是一系列调用其他函数的过程。所以我在这里只包含了我用来调用第二个(Fishial)API的代码:

def recognize_fish(file_path, key_id=key_id, key_secret=key_secret, identify=False):
    if not os.path.isfile(file_path):
        err("Invalid picture file path.")

    for dep in DEPENDENCIES:
        try:
            __import__(dep)
        except ImportError:
            err(f"Unsatisfied dependency: {dep}")

    logging.info("Identifying picture metadata...")

    name = os.path.basename(file_path)
    mime = mimetypes.guess_type(file_path)[0]
    size = os.path.getsize(file_path)
    with open(file_path, "rb") as f:
        csum = base64.b64encode(hashlib.md5(f.read()).digest()).decode("utf-8")

    logging.info(f"\n  file name: {name}")
    logging.info(f"  MIME type: {mime}")
    logging.info(f"  byte size: {size}")
    logging.info(f"   checksum: {csum}\n")

    if identify:
        return

    if not key_id or not key_secret:
        err("Missing key ID or key secret.")

    logging.info("Obtaining auth token...")

    data = {
        "client_id": key_id,
        "client_secret": key_secret
    }

    response = requests.post("https://api-users.fishial.ai/v1/auth/token", json=data)
    auth_token = response.json()["access_token"]
    auth_header = f"Bearer {auth_token}"

    logging.info(f"Access token: {auth_token}")

    logging.info("Obtaining upload url...")

    data = {
        "blob": {
            "filename": name,
            "content_type": mime,
            "byte_size": size,
            "checksum": csum
        }
    }

    headers = {
        "Authorization": auth_header,
        "Content-Type": "application/json",
        "Accept": "application/json"
    }

    response = requests.post("https://api.fishial.ai/v1/recognition/upload", json=data, headers=headers)
    signed_id = response.json()["signed-id"]
    upload_url = response.json()["direct-upload"]["url"]
    content_disposition = response.json()["direct-upload"]["headers"]["Content-Disposition"]

    logging.info("Uploading picture to the cloud...")

    with open(file_path, "rb") as f:
        requests.put(upload_url, data=f, headers={
            "Content-Disposition": content_disposition,
            "Content-MD5": csum,
            "Content-Type": ""
        })

    logging.info("Requesting fish recognition...")

    response = requests.get(f"https://api.fishial.ai/v1/recognition/image?q={signed_id}",
                            headers={"Authorization": auth_header})
    fish_count = len(response.json()["results"])

    logging.info(f"Fishial Recognition found {fish_count} fish(es) on the picture.")

    if fish_count == 0:
        return []

    species_names = []

    for i in range(fish_count):
        fish_data = extract_from_json(f"results[{i}]", response.json())

        if fish_data and "species" in fish_data:
            logging.info(f"Fish {i + 1} is:")

            for j in range(len(fish_data["species"])):
                species_data = fish_data["species"][j]
                if "fishangler-data" in species_data and "metaTitleName" in species_data["fishangler-data"]:
                    species_name = species_data["fishangler-data"]["metaTitleName"]
                    accuracy = species_data["accuracy"]

                    logging.info(f"  - {species_name} [accuracy {accuracy}]")
                    species_names.append(species_name)
                else:
                    logging.error("  - Species name not found in the response.")
        else:
            logging.error(f"\nFish {i + 1}: Species data not found in the response.")

    return species_names

附言:感觉这段内容有点长。如果把这么多代码放在Pastebin上更合适,我可以进行编辑。

2 个回答

-2

你从Fishial API收到的错误信息显示是500内部服务器错误。这意味着问题可能出在服务器那边(Fishial API),而不是你的代码。

查看一下Fishial API的文档,确保你使用的接口和参数是正确的。

确认一下你在请求中传递的API密钥(如果需要的话)是正确的。

撰写回答