evaluation_provider.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  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. 当文案不符合规则时,说明原因并自动修正文案;
  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. - 允许表达层面的同义替换、语序调整、简化、省略前后重复内容(如“领取在家学唱歌课程” vs “领取家学唱歌课程”);
  29. - 若不改变核心含义或不新增虚构内容,应**视为一致并直接通过**;
  30. - ★“入群”与“加微”视为不同的用户行为,若图片中明确为“加微”,文案也必须体现;
  31. - 仅当文案引入图片中完全不存在的**新产品/服务/优惠/指令**时,才判为不一致;
  32. - 若表达中存在轻微改写但不改变原意,应视为通过。
  33. 3. 逻辑合理性:
  34. - 文案应语义自然、逻辑连贯,无明显矛盾。
  35. </校验标准>
  36. <修正规则>
  37. - 当文案未通过校验时,阅读 reason 并逐条修复;
  38. - 修正应尽量保留原文的语义与营销力,仅调整结构或措辞使其合格;
  39. - 生成的新文案应:
  40. 1. 符合结构公式:[行动指令],[低门槛/优惠承诺],[核心价值/具体收益];[紧迫感/稀缺性提醒];
  41. 2. 与图片内容语义一致(允许合理同义、表达优化);
  42. 3. 不新增图片中完全没有的概念或信息;
  43. 4. 语义自然顺畅,字数 ≤ 50。
  44. </修正规则>
  45. <判定逻辑>
  46. - 若文案语义完整、结构正确、内容与图片语义一致 → pass=true,reason="",corrected_copy=原文;
  47. - 若仅轻微表达差异(同义改写、修辞不同) → pass=true,reason="建议优化:轻微表达差异",corrected_copy=原文;
  48. - 若结构或内容存在重大问题 → pass=false,reason=问题说明,corrected_copy=修正后合格版本。
  49. </判定逻辑>
  50. <输出要求>
  51. 始终调用函数 check_ad_copy,输出格式如下:
  52. {
  53. "pass": true/false,
  54. "reason": "说明原因或留空",
  55. "corrected_copy": "最终合格的一句广告文案(若原文合格则为原文)"
  56. }
  57. </输出要求>
  58. <示例 few-shot="true">
  59. 输入OCR:"添加老师微信,领取在家学唱歌课程"
  60. 输入文案:"长按二维码,0元添加老师微信,领取家学唱歌课程;名额有限,立即领取!"
  61. 输出:
  62. {
  63. "pass": true,
  64. "reason": "",
  65. "corrected_copy": "长按二维码,0元添加老师微信,领取家学唱歌课程;名额有限,立即领取!"
  66. }
  67. </示例>
  68. <示例 few-shot="true">
  69. 输入OCR:"扫码添加老师微信,领取中老年声乐教学课程"
  70. 输入文案:"扫码二维码,0元添加老师微信,领取声乐课程;限时领取,先到先得!"
  71. 输出:
  72. {
  73. "pass": true,
  74. "reason": "",
  75. "corrected_copy": "扫码二维码,0元添加老师微信,领取声乐课程;限时领取,先到先得!"
  76. }
  77. </示例>
  78. <示例 few-shot="true">
  79. 输入OCR:"长按二维码添加微信,免费获取课程指导"
  80. 输入文案:"识别二维码,0元加微信,领取课程指导;名额有限,立即加入!"
  81. 输出:
  82. {
  83. "pass": true,
  84. "reason": "",
  85. "corrected_copy": "识别二维码,0元加微信,领取课程指导;名额有限,立即加入!"
  86. }
  87. </示例>
  88. </SystemPrompt>
  89. """
  90. tools: list[ChatCompletionToolParam] = [
  91. {
  92. "type": "function",
  93. "function": {
  94. "name": "check_ad_copy",
  95. "description": "校验并在必要时修正广告文案,确保结构与内容合规",
  96. "parameters": {
  97. "type": "object",
  98. "properties": {
  99. "pass": { "type": "boolean" },
  100. "reason": { "type": "string" },
  101. "corrected_copy": { "type": "string" }
  102. },
  103. "required": ["pass", "reason", "corrected_copy"],
  104. "additionalProperties": False
  105. }
  106. }
  107. }
  108. ]
  109. class EvaluationProvider:
  110. print("EvaluationProvider called")
  111. def copywriting_evaluation(self, image_url: str, text: str, model: str) -> DataResponse:
  112. client = OpenAI(
  113. api_key = settings.dashscope_api_key or "",
  114. base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
  115. )
  116. if not client:
  117. logger.error("OpenAI client is not initialized.")
  118. return DataResponse(code=1, data=None, msg=f"OpenAI client is not initialized")
  119. completion = client.chat.completions.create(
  120. model=model,
  121. messages=[
  122. {"role": "system", "content": SYSTEM_PROMPT},
  123. {
  124. "role": "user",
  125. "content": [
  126. {"type": "image_url", "image_url": {"url": image_url}},
  127. {"type": "text", "text": text}
  128. ],
  129. },
  130. ],
  131. tools=tools,
  132. tool_choice={
  133. "type": "function",
  134. "function": {"name": "check_ad_copy"}
  135. },
  136. temperature=0.5
  137. )
  138. msg = completion.choices[0].message
  139. print(msg)
  140. # Safely parse tool call arguments (if any)
  141. content = True
  142. reason = ""
  143. corrected_msg = ""
  144. try:
  145. tool_calls = getattr(msg, "tool_calls", None) or []
  146. if tool_calls:
  147. call = tool_calls[0]
  148. arg_str = getattr(getattr(call, "function", None), "arguments", None)
  149. if isinstance(arg_str, str) and arg_str.strip():
  150. args = json.loads(arg_str)
  151. if isinstance(args, dict):
  152. content = bool(args.get("pass", True))
  153. reason = str(args.get("reason", "")).strip()
  154. corrected_msg = str(args.get("corrected_copy", "")).strip()
  155. except Exception as e:
  156. logger.error("parse tool call failed: %s", e, exc_info=True)
  157. return DataResponse(code=1, data=None, msg=f"parse tool call failed: {e}")
  158. print("✅ PASS:\n", content)
  159. print("✅ REASON:\n", reason)
  160. return DataResponse(code=0, data=CopywritingEvaluationPayload(content=content, reason=reason, corrected_msg=corrected_msg), msg="success")