evaluation_provider.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. from openai import OpenAI
  2. from ..schemas.base import DataResponse, CopywritingEvaluationPayload
  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("evaluation_provider")
  9. SYSTEM_PROMPT = """
  10. <SystemPrompt>
  11. <角色>
  12. 你是一名广告文案质检与优化专家。你的任务是:
  13. 1. 根据输入的广告图片文字(OCR结果)和广告文案,对文案进行校验;
  14. 2. 当文案不符合规则时,给出具体原因(reason),并根据这些问题自动修正文案;
  15. 3. 当文案符合规则时,直接通过;
  16. 4. 最终输出校验状态与合格文案。
  17. </角色>
  18. <校验标准>
  19. 1. 结构要求(只看语义组成,不看标点):
  20. - 文案语义上应包含以下要素:
  21. a. 行动指令(可选):长按二维码 / 扫码二维码 / 识别二维码 / 点击领取 / 立即添加;
  22. b. 低门槛或优惠承诺(可选):0元入群 / 免费进群 / 0元加入 / 限时免费加入;
  23. c. 核心价值/具体收益(必有):例如“领取中医调理养发方案”“获取控糖建议”“享受养生课程”等;
  24. d. 紧迫感/稀缺性提醒(必有):例如“名额有限”“限时”“马上领取”“赶快行动”等;
  25. - 只要语义上具备这些组成部分即可,不因标点、顺序或语气词差异判不合格。
  26. 2. 内容一致性要求:
  27. - 文案的核心价值必须与广告图片(OCR文字)内容一致;
  28. - 优惠、产品/服务、动作入口等信息必须在图片中能找到对应要素;
  29. - 不得凭空编造页面中不存在的内容。
  30. 3. 逻辑合理性:
  31. - 文案应语义连贯,表达完整,无逻辑冲突。
  32. </校验标准>
  33. <修正规则>
  34. - 当文案未通过校验时,应严格根据 reason 中列出的问题逐条修复:
  35. 1. 阅读 reason,识别缺失或错误的要素;
  36. 2. 在修正文案中补充或修改这些要素;
  37. 3. 确保修正后的文案:
  38. - 符合公式 [行动指令],[低门槛/优惠承诺],[核心价值/具体收益];[紧迫感/稀缺性提醒];
  39. - 保持与图片(OCR结果)内容一致;
  40. - 语义自然、逻辑通顺;
  41. 4. 不可仅复述原文;修正文案必须显式解决 reason 指出的所有问题。
  42. - 修正完成后,应重新验证:
  43. - 若问题已解决,返回合格结果;
  44. - 若问题仍存在,则继续修正,直至完全合格。
  45. </修正规则>
  46. <判定逻辑>
  47. - 若文案语义完整、内容一致 → pass=true,reason="",corrected_copy="";
  48. - 若存在轻微偏差(结构略乱或词序不同但语义正确) → pass=true,reason=问题说明,corrected_copy=优化后版本;
  49. - 若存在严重问题(缺少核心价值或与图片内容不符) → pass=false,reason=问题说明(简单说明),corrected_copy=修正后合格文案;
  50. - 修正文案应完整保留图片中的核心卖点信息。
  51. </判定逻辑>
  52. <输出要求>
  53. 始终调用函数 check_ad_copy,输出格式如下:
  54. {
  55. "pass": true/false,
  56. "reason": "若不通过写原因;若通过则为空字符串或给出优化建议",
  57. "corrected_copy": "最终合格的一句广告文案(若原文合格则为原文)"
  58. }
  59. </输出要求>
  60. <示例>
  61. 输入OCR:"0元入群领取改善发质方案"
  62. 输入文案:"长按二维码,0元入群,领取中医调理养发方案;名额有限,马上行动!"
  63. reason:"核心价值与图片不一致,未体现‘改善发质’"
  64. 修正输出:
  65. {
  66. "pass": true,
  67. "reason": "",
  68. "corrected_copy": "长按二维码,0元入群,领取中医调理改善发质方案;名额有限,马上行动!"
  69. }
  70. </示例>
  71. </SystemPrompt>
  72. """
  73. tools: list[ChatCompletionToolParam] = [
  74. {
  75. "type": "function",
  76. "function": {
  77. "name": "check_ad_copy",
  78. "description": "校验并在必要时修正广告文案,确保结构与内容合规",
  79. "parameters": {
  80. "type": "object",
  81. "properties": {
  82. "pass": { "type": "boolean" },
  83. "reason": { "type": "string" },
  84. "corrected_copy": { "type": "string" }
  85. },
  86. "required": ["pass", "reason", "corrected_copy"],
  87. "additionalProperties": False
  88. }
  89. }
  90. }
  91. ]
  92. class EvaluationProvider:
  93. print("EvaluationProvider called")
  94. def copywriting_evaluation(self, image_url: str, text: str, model: str) -> DataResponse:
  95. client = OpenAI(
  96. api_key = settings.dashscope_api_key or "",
  97. base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
  98. )
  99. if not client:
  100. logger.error("OpenAI client is not initialized.")
  101. return DataResponse(code=1, data=None, msg=f"OpenAI client is not initialized")
  102. completion = client.chat.completions.create(
  103. model=model,
  104. messages=[
  105. {"role": "system", "content": SYSTEM_PROMPT},
  106. {
  107. "role": "user",
  108. "content": [
  109. {"type": "image_url", "image_url": {"url": image_url}},
  110. {"type": "text", "text": text}
  111. ],
  112. },
  113. ],
  114. tools=tools,
  115. tool_choice={
  116. "type": "function",
  117. "function": {"name": "check_ad_copy"}
  118. },
  119. temperature=0.3
  120. )
  121. msg = completion.choices[0].message
  122. print(msg)
  123. # Safely parse tool call arguments (if any)
  124. content = True
  125. reason = ""
  126. corrected_msg = ""
  127. try:
  128. tool_calls = getattr(msg, "tool_calls", None) or []
  129. if tool_calls:
  130. call = tool_calls[0]
  131. arg_str = getattr(getattr(call, "function", None), "arguments", None)
  132. if isinstance(arg_str, str) and arg_str.strip():
  133. args = json.loads(arg_str)
  134. if isinstance(args, dict):
  135. content = bool(args.get("pass", True))
  136. reason = str(args.get("reason", "")).strip()
  137. corrected_msg = str(args.get("corrected_copy", "")).strip()
  138. except Exception as e:
  139. logger.error("parse tool call failed: %s", e, exc_info=True)
  140. return DataResponse(code=1, data=None, msg=f"parse tool call failed: {e}")
  141. print("✅ PASS:\n", content)
  142. print("✅ REASON:\n", reason)
  143. return DataResponse(code=0, data=CopywritingEvaluationPayload(content=content, reason=reason, corrected_msg=corrected_msg), msg="success")