ai_helper.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. import json
  2. import re
  3. import tools.utils as utils
  4. from tools.utils.file_helper import encode_image
  5. from openai import OpenAI
  6. class AiHelper:
  7. _ai_api_key = None
  8. _ai_api_url = None
  9. _ai_max_tokens = 150
  10. def __init__(self, api_url: str=None, api_key: str=None, api_model: str=None):
  11. self._ai_api_url = api_url if api_url else utils.get_config_value("ai.url")
  12. self._ai_api_key = api_key if api_key else utils.get_config_value("ai.key")
  13. self._api_model = api_model if api_model else utils.get_config_value("ai.model")
  14. max_tokens = utils.get_config_value("ai.max_tokens")
  15. if max_tokens:
  16. self._ai_max_tokens = int(max_tokens)
  17. def call_openai(self, system_prompt: str, user_prompt: str,api_url: str=None,api_key: str=None,api_model: str=None) -> json:
  18. if api_url:
  19. self._ai_api_url = api_url
  20. if api_key:
  21. self._ai_api_key = api_key
  22. if api_model:
  23. self._api_model = api_model
  24. if self._ai_api_key is None:
  25. raise Exception("AI API key 没有配置")
  26. if self._ai_api_url is None:
  27. raise Exception("AI API url 没有配置")
  28. if self._api_model is None:
  29. raise Exception("AI API model 没有配置")
  30. utils.get_logger().info(f"调用AI API ==> Url:{self._ai_api_url},Model:{self._api_model}")
  31. client = OpenAI(api_key=self._ai_api_key, base_url=self._ai_api_url)
  32. completion = client.chat.completions.create(
  33. model=self._api_model,
  34. messages=[
  35. {
  36. "role": "system",
  37. "content": system_prompt,
  38. },
  39. {
  40. "role": "user",
  41. "content": user_prompt,
  42. },
  43. ],
  44. stream=False,
  45. temperature=0.7,
  46. response_format={"type": "json_object"},
  47. # max_tokens=self._ai_max_tokens,
  48. )
  49. try:
  50. response = completion.model_dump_json()
  51. result = {}
  52. response_json = json.loads(response)
  53. res_str = self._extract_message_content(response_json)
  54. result_data = self._parse_response(res_str, True)
  55. if result_data:
  56. result["data"] = result_data
  57. usage = response_json["usage"]
  58. result["completion_tokens"] = usage.get("completion_tokens", 0)
  59. result["prompt_tokens"] = usage.get("prompt_tokens", 0)
  60. result["total_tokens"] = usage.get("total_tokens", 0)
  61. utils.get_logger().info(f"AI Process JSON: {result}")
  62. else:
  63. utils.get_logger().info(f"AI Response: {response}")
  64. return result
  65. except Exception as e:
  66. raise Exception(f"解析 AI 响应错误: {e}")
  67. @staticmethod
  68. def _extract_message_content(response_json: dict) -> str:
  69. utils.get_logger().info(f"AI Response JSON: {response_json}")
  70. if "choices" in response_json and len(response_json["choices"]) > 0:
  71. choice = response_json["choices"][0]
  72. message_content = choice.get("message", {}).get("content", "")
  73. elif "message" in response_json:
  74. message_content = response_json["message"].get("content", "")
  75. else:
  76. raise Exception("AI 响应中未找到有效的 choices 或 message 数据")
  77. # 移除多余的 ```json 和 ```
  78. if message_content.startswith("```json") and message_content.endswith(
  79. "```"):
  80. message_content = message_content[6:-3]
  81. # 去除开头的 'n' 字符
  82. if message_content.startswith("n"):
  83. message_content = message_content[1:]
  84. # 移除无效的转义字符和时间戳前缀
  85. message_content = re.sub(r"\\[0-9]{2}", "",
  86. message_content) # 移除 \32 等无效转义字符
  87. message_content = re.sub(r"\d{4}-\d{2}-\dT\d{2}:\d{2}:\d{2}\.\d+Z", "",
  88. message_content) # 移除时间戳
  89. message_content = message_content.strip() # 去除首尾空白字符
  90. # 替换所有的反斜杠
  91. message_content = message_content.replace("\\", "")
  92. return message_content
  93. def _parse_response(self, response: str, first=True) -> json:
  94. # utils.get_logger().info(f"AI Response JSON STR: {response}")
  95. try:
  96. data = json.loads(response)
  97. return data
  98. except json.JSONDecodeError as e:
  99. if first:
  100. utils.get_logger().error(f"JSON 解析错误,去除部分特殊字符重新解析一次: {e}")
  101. # 替换中文引号为空
  102. message_content = re.sub(r"[“”]", "", response) # 替换双引号
  103. message_content = re.sub(r"[‘’]", "", message_content) # 替换单引号
  104. return self._parse_response(message_content, False)
  105. else:
  106. raise Exception(f"解析 AI 响应错误: {response} {e}")
  107. def analyze_image_with_ai(self,image_path, api_url: str=None,api_key: str=None,api_model: str=None):
  108. """调用OpenAI的API分析图片内容"""
  109. if api_url:
  110. self._ai_api_url = api_url
  111. if api_key:
  112. self._ai_api_key = api_key
  113. if api_model:
  114. self._api_model = api_model
  115. if self._ai_api_key is None:
  116. raise Exception("AI API key 没有配置")
  117. if self._ai_api_url is None:
  118. raise Exception("AI API url 没有配置")
  119. if self._api_model is None:
  120. raise Exception("AI API model 没有配置")
  121. try:
  122. client = OpenAI(api_key=self._ai_api_key, base_url=self._ai_api_url)
  123. base64_str = encode_image(image_path)
  124. response = client.chat.completions.create(
  125. model=self._api_model,
  126. messages=[
  127. {
  128. "role": "user",
  129. "content": [
  130. {"type": "text",
  131. "text": "请总结图片中的表格,供RAG系统embedding使用。要求以文本的信息列出,定额编号对应的详细信息,其中表格的列名中显示了定额编号,行名中显示了电算代号。定额编号所示的列代表了这一类定额,通过项目的不同条件来区分,比如长度、地质条件等;而电算代号所示的行则代表了具体的材料、人工等的消耗量,表示在特定定额编号所示的条件下,具体的资源(人力或材料)消耗量。"},
  132. {
  133. "type": "image_url",
  134. "image_url": {
  135. "url": base64_str
  136. }
  137. }
  138. ]
  139. }
  140. ],
  141. timeout=600
  142. )
  143. return response.choices[0].message.content
  144. except Exception as e:
  145. print(f"调用AI接口时出错: {e}")
  146. return ''