understand_image_provider.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. from openai import OpenAI
  2. from ..schemas.base import DataResponse
  3. from ..core.config import get_settings
  4. from ..core.logger import get_logger
  5. from openai.types.chat import ChatCompletionToolParam
  6. import json
  7. settings = get_settings()
  8. logger = get_logger("understand_image_provider")
  9. SYSTEM_PROMPT = """
  10. <SystemPrompt>
  11. <角色>
  12. 你是一名广告文案质检专家。你的任务是:根据输入的广告图片文字(OCR结果)和生成的广告文案,仅从“结构”和“内容一致性”两个维度判断文案是否合格。
  13. </角色>
  14. <校验标准>
  15. 1. 结构要求:
  16. - 文案整体应符合公式:
  17. [行动指令],[低门槛/优惠承诺],[核心价值/具体收益];[紧迫感/稀缺性提醒]
  18. - 行动指令示例(可选):长按二维码 / 扫码二维码 / 识别二维码 / 点击领取 / 立即添加
  19. - 低门槛/优惠承诺示例(可选):0元入群 / 免费进群 / 0元加入 / 限时免费加入
  20. - 核心价值/具体收益:必须出现,形式为“领取/获取/享受 + {方案/资料/课程/建议/秘方等}”
  21. - 紧迫感/稀缺性提醒:必须出现,如“名额有限”“限时”“赶快行动”“先到先得”
  22. - 标点规范:前面三部分用逗号分隔;紧迫提醒前用分号。全句 ≤ 50 字。
  23. 2. 行动指令和低门槛/优惠承诺:
  24. - 若文案中包含这两部分 → 检查是否合理表述(符合示例或页面内容)。
  25. - 若文案中没有这两部分 → 不因缺失而判定不合格。
  26. 3. 核心价值/具体收益(关键内容校验):
  27. - 必须与广告图片文字(OCR结果)保持一致。
  28. - 不得捏造页面中不存在的产品/服务/优惠。
  29. </校验标准>
  30. <判定逻辑>
  31. - 如果结构正确,且核心价值/具体收益与页面内容一致 → pass=true,reason=""。
  32. - 若轻微偏差(如分号缺失、字数略超)→ pass=true,reason="建议优化:……"。
  33. - 若结构明显错误(缺少核心价值或紧迫提醒)或核心价值与页面内容不一致 → pass=false,reason=简要说明。
  34. </判定逻辑>
  35. <输出要求>
  36. 始终调用函数 check_ad_copy,输出格式如下:
  37. {
  38. "pass": true/false,
  39. "reason": "若不通过写原因;若通过则为空字符串或给出优化建议"
  40. }
  41. </输出要求>
  42. </SystemPrompt>
  43. """
  44. tools: list[ChatCompletionToolParam] = [
  45. {
  46. "type": "function",
  47. "function": {
  48. "name": "generate_ocr_text",
  49. "description": "生成一句适合中老年用户的广告文案(遵循结构公式与约束)",
  50. "parameters": {
  51. "type": "object",
  52. "properties": {
  53. "ocr_text": {
  54. "type": "string",
  55. "description": "最终的一句广告文案(中文,简短醒目,合规)"
  56. }
  57. },
  58. "required": ["ocr_text"],
  59. "additionalProperties": False
  60. }
  61. }
  62. }
  63. ]
  64. class UnderstandImageProvider:
  65. print("UnderstandImageProvider called")
  66. def understand_image(self, image_url: str, *, model: str) -> DataResponse:
  67. client = OpenAI(
  68. api_key = settings.dashscope_api_key or "",
  69. base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
  70. )
  71. if not client:
  72. logger.error("OpenAI client is not initialized.")
  73. return DataResponse(code=1, data=None, msg=f"OpenAI client is not initialized")
  74. completion = client.chat.completions.create(
  75. model=model,
  76. messages=[
  77. {"role": "system", "content": SYSTEM_PROMPT},
  78. {
  79. "role": "user",
  80. "content": [{ "type": "image_url", "image_url": { "url": image_url } }],
  81. },
  82. ],
  83. tools=tools,
  84. tool_choice={
  85. "type": "function",
  86. "function": {"name": "generate_ocr_text"}
  87. },
  88. temperature=0.5
  89. )
  90. msg = completion.choices[0].message
  91. # Safely parse tool call arguments (if any)
  92. ocr_text = ""
  93. try:
  94. tool_calls = getattr(msg, "tool_calls", None) or []
  95. if tool_calls:
  96. call = tool_calls[0]
  97. arg_str = getattr(getattr(call, "function", None), "arguments", None)
  98. if isinstance(arg_str, str) and arg_str.strip():
  99. args = json.loads(arg_str)
  100. if isinstance(args, dict):
  101. ocr_text = str(args.get("ocr_text", "")).strip()
  102. except Exception as e:
  103. logger.error("parse tool call failed: %s", e, exc_info=True)
  104. return DataResponse(code=1, data=None, msg=f"parse tool call failed: {e}")
  105. # Fallback: if no tool-calls returned, try to read text content
  106. content = getattr(msg, "content", None)
  107. if not ocr_text and isinstance(content, str):
  108. ocr_text = content.strip()
  109. print("✅ OCR_TEXT:\n", ocr_text)
  110. return DataResponse(code=0, data=ocr_text, msg="success")