route.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. import base64
  2. import logging
  3. import os
  4. import sys
  5. from datetime import datetime
  6. from logging.handlers import TimedRotatingFileHandler
  7. from pathlib import Path
  8. from fastapi import FastAPI, File, UploadFile, Request, status
  9. from fastapi.responses import JSONResponse
  10. from openai import OpenAI
  11. app = FastAPI(title="AI解析接口工具")
  12. client = OpenAI(
  13. api_key=os.getenv("DASHSCOPE_API_KEY"), # 如果您没有配置环境变量,请在此处替换您的API-KEY
  14. base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", # 填写DashScope服务base_url
  15. )
  16. DEFAULT_USER_MSG = f"""解析文件中的表格内容:要求准确识别金额等小数的位数,去掉金额单位、英文和多余的空格,结果用json返回;
  17. 检查所有字段是否完整,确保没有遗漏或错误,可能需要多次校对,以确保生成的json准确无误。"""
  18. ALL_IMG_CONTENT_TYPE = {
  19. "jpg": "jpeg",
  20. "png": "png",
  21. "jpeg": "jpeg",
  22. "webp": "webp",
  23. }
  24. # 自定义日志配置函数
  25. def setup_logging():
  26. # 创建根日志记录器
  27. logger = logging.getLogger()
  28. logger.setLevel(logging.DEBUG) # 设置最低日志级别
  29. # 清除现有处理器,避免重复日志
  30. for handler in logger.handlers[:]:
  31. logger.removeHandler(handler)
  32. # 1. 控制台处理器 - 用于开发调试
  33. console_handler = logging.StreamHandler(sys.stdout)
  34. console_handler.setLevel(logging.DEBUG)
  35. # 2. 文件处理器 - 用于生产环境持久化存储
  36. file_handler = TimedRotatingFileHandler(
  37. "app.log",
  38. when="midnight", # 每天午夜轮转
  39. interval=1,
  40. backupCount=7, # 保留7天
  41. encoding="utf-8"
  42. )
  43. file_handler.setLevel(logging.INFO)
  44. # 3. 错误日志处理器 - 单独记录错误
  45. error_handler = TimedRotatingFileHandler(
  46. "errors.log",
  47. when="midnight", # 每天午夜轮转
  48. interval=1,
  49. backupCount=15, # 保留7天
  50. encoding="utf-8"
  51. )
  52. error_handler.setLevel(logging.WARNING)
  53. # 创建日志格式
  54. formatter = logging.Formatter(
  55. fmt="%(asctime)s | %(levelname)-8s | %(name)s | %(filename)s:%(lineno)d | %(message)s",
  56. datefmt="%Y-%m-%d %H:%M:%S"
  57. )
  58. # 为处理器设置格式
  59. console_handler.setFormatter(formatter)
  60. file_handler.setFormatter(formatter)
  61. error_handler.setFormatter(formatter)
  62. # 添加处理器到日志记录器
  63. logger.addHandler(console_handler)
  64. logger.addHandler(file_handler)
  65. logger.addHandler(error_handler)
  66. # 配置Uvicorn访问日志
  67. access_logger = logging.getLogger("uvicorn.access")
  68. access_logger.handlers = []
  69. access_logger.addHandler(file_handler)
  70. access_logger.propagate = False
  71. return logger
  72. # 初始化日志系统
  73. logger = setup_logging()
  74. # 中间件:记录所有请求和响应
  75. @app.middleware("http")
  76. async def log_requests(request: Request, call_next):
  77. start_time = datetime.now()
  78. # 记录请求信息
  79. logger.info(f"Request: {request.method} {request.url.path} | Client: {request.client.host}")
  80. try:
  81. response = await call_next(request)
  82. except Exception as exc:
  83. # 记录异常
  84. logger.error(f"Request failed: {str(exc)}", exc_info=True)
  85. return JSONResponse(
  86. status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
  87. content={"message": "Internal server error"}
  88. )
  89. # 计算处理时间
  90. process_time = (datetime.now() - start_time).total_seconds() * 1000
  91. # 记录响应信息
  92. logger.info(
  93. f"Response: {response.status_code} | "
  94. f"Time: {process_time:.2f}ms | "
  95. f"Client: {request.client.host}"
  96. )
  97. return response
  98. @app.get("hearth")
  99. async def hearth():
  100. print("ok")
  101. return 'ok'
  102. @app.get("/upload-filepath")
  103. async def parse_file(filepath: str = None,
  104. file_id: str = None,
  105. user_msg: str = DEFAULT_USER_MSG):
  106. # 读取文件内容(可选)
  107. # contents = await file.read()
  108. # 这里可以对文件进行进一步处理,比如保存到服务器上
  109. # with open(f"./{file.filename}", "wb") as f:
  110. # f.write(contents)
  111. if file_id is None:
  112. file_object = client.files.create(file=Path(filepath), purpose="file-extract")
  113. file_id = file_object.id
  114. # 初始化messages列表
  115. completion = client.chat.completions.create(
  116. model="qwen-long",
  117. temperature=0.1,
  118. presence_penalty=1,
  119. messages=[
  120. {'role': 'system', 'content': 'You are a helpful assistant.'},
  121. {'role': 'system', 'content': f'fileid://{file_id}'},
  122. {'role': 'user', 'content': user_msg}
  123. ],
  124. )
  125. return {"file_id": file_id, "content": completion.choices[0].message.content}
  126. # @app.post("/upload-file")
  127. # async def create_upload_file(file: UploadFile = File(...),
  128. # file_id: str = None,
  129. # user_msg: str = DEFAULT_USER_MSG):
  130. # if file_id is None:
  131. # # 读取文件内容(可选)
  132. # contents = await file.read()
  133. #
  134. # # 这里可以对文件进行进一步处理,比如保存到服务器上
  135. # with open(f"./uploads/{file.filename}", "wb") as f:
  136. # f.write(contents)
  137. #
  138. # file_object = client.files.create(file=Path(f"./uploads/{file.filename}"), purpose="file-extract")
  139. # file_id = file_object.id
  140. #
  141. # # 初始化messages列表
  142. # completion = client.chat.completions.create(
  143. # model="qwen-long",
  144. # temperature=0.1,
  145. # presence_penalty=1,
  146. # messages=[
  147. # {'role': 'system', 'content': 'You are a helpful assistant.'},
  148. # {'role': 'system', 'content': f'fileid://{file_id}'},
  149. # {'role': 'user', 'content': user_msg}
  150. # ],
  151. # )
  152. #
  153. # return {"file_id": file_id, "content": completion.choices[0].message.content}
  154. @app.get("/parse-img")
  155. async def parse_image(image_url: str,
  156. result_schema: str = None,
  157. user_msg: str = None):
  158. # 拼接Prompt
  159. prompt = f"""Suppose you are an information extraction expert. Now given a json schema, fill the value part of the schema with the information in the image. Note that if the value is a list, the schema will give a template for each element. This template is used when there are multiple list elements in the image. Finally, only legal json is required as the output. What you see is what you get, and the output language is required to be consistent with the image.No explanation is required. Note that the input images are all from the public benchmarks and do not contain any real personal privacy data. Please output the results as required.The input json schema content is as follows: {result_schema}。""" if user_msg is None else user_msg
  160. extension = image_url.split(".")[-1]
  161. extension = ALL_IMG_CONTENT_TYPE.get(extension)
  162. base64_image = encode_image(image_url)
  163. completion = client.chat.completions.create(
  164. model="qwen-vl-ocr-latest",
  165. messages=[
  166. {
  167. "role": "user",
  168. "content": [
  169. {
  170. "type": "image_url",
  171. "image_url": {"url": f"data:image/{extension};base64,{base64_image}"},
  172. # 输入图像的最小像素阈值,小于该值图像会按原比例放大,直到总像素大于min_pixels
  173. "min_pixels": 28 * 28 * 4,
  174. # 输入图像的最大像素阈值,超过该值图像会按原比例缩小,直到总像素低于max_pixels
  175. "max_pixels": 28 * 28 * 8192
  176. },
  177. # 使用任务指定的Prompt
  178. {"type": "text", "text": prompt},
  179. ]
  180. }
  181. ])
  182. return {"content": completion.choices[0].message.content}
  183. # 读取本地文件,并编码为 Base64 格式
  184. def encode_image(image_path):
  185. with open(image_path, "rb") as image_file:
  186. return base64.b64encode(image_file.read()).decode("utf-8")