evaluation_provider.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  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. </角色>
  17. <校验标准>
  18. 1. 结构要求:
  19. - 文案应符合公式:[行动指令],[低门槛/优惠承诺],[核心价值/具体收益];[紧迫感/稀缺性提醒]。
  20. - 行动指令(可选):长按二维码 / 扫码二维码 / 识别二维码 / 点击领取 / 立即添加。
  21. - 低门槛/优惠承诺(可选):0元入群 / 免费进群 / 0元领取 / 限时免费加入。
  22. - 核心价值/具体收益(必有):例如“领取中医调理养生课程”“获取控糖方案”“享受健康调理建议”。
  23. - 紧迫感/稀缺性提醒(必有):例如“名额有限”“限时”“马上领取”“赶快行动”。
  24. - 全句 ≤ 50 字。
  25. 2. 内容一致性:
  26. - 核心价值必须与广告图片(OCR文字)中的信息一致。
  27. - 不得编造图片中不存在的优惠、产品或服务。
  28. 3. 容错规则:
  29. - 若语义顺序正确,即使标点略有不同(如逗号/分号混用),也视为合格;
  30. - 若“领取”“获取”等动词与核心价值(如“课程”“方案”“资料”)语义连贯,则不因词间逗号缺失判为不合格;
  31. - 标点仅作可读性参考,不作否决条件。
  32. </校验标准>
  33. <修正规则>
  34. - 当校验不通过时,应生成一条符合以下条件的新文案:
  35. 1. 按结构公式输出;
  36. 2. 语义清晰,逻辑顺畅;
  37. 3. 保持与图片(OCR文本)内容一致;
  38. 4. 尽量保留原文中的核心要素,调整结构与表达使其合规;
  39. 5. 输出的修正文案需是可直接使用的最终版本。
  40. </修正规则>
  41. <判定逻辑>
  42. - 若文案语义完整、符合结构且内容与图片一致 → pass=true,reason="",corrected_copy="";
  43. - 若轻微偏差(如分号缺失、字数略超) → pass=true,reason="建议优化:…",corrected_copy=优化后的文案;
  44. - 若结构明显错误或与图片内容不一致 → pass=false,reason=具体问题描述,corrected_copy=修改后的合格文案。
  45. </判定逻辑>
  46. <输出要求>
  47. 始终调用函数 check_ad_copy,输出格式如下:
  48. {
  49. "pass": true/false,
  50. "reason": "若不通过写原因;若通过则为空字符串或给出优化建议",
  51. "corrected_copy": "最终合格的一句广告文案(若原文案合格则与原文一致)"
  52. }
  53. </输出要求>
  54. </SystemPrompt>
  55. """
  56. tools: list[ChatCompletionToolParam] = [
  57. {
  58. "type": "function",
  59. "function": {
  60. "name": "check_ad_copy",
  61. "description": "校验并在必要时修正广告文案,确保结构与内容合规",
  62. "parameters": {
  63. "type": "object",
  64. "properties": {
  65. "pass": { "type": "boolean" },
  66. "reason": { "type": "string" },
  67. "corrected_copy": { "type": "string" }
  68. },
  69. "required": ["pass", "reason", "corrected_copy"],
  70. "additionalProperties": False
  71. }
  72. }
  73. }
  74. ]
  75. class EvaluationProvider:
  76. print("EvaluationProvider called")
  77. def copywriting_evaluation(self, image_url: str, text: str, model: str) -> DataResponse:
  78. client = OpenAI(
  79. api_key = settings.dashscope_api_key or "",
  80. base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
  81. )
  82. if not client:
  83. logger.error("OpenAI client is not initialized.")
  84. return DataResponse(code=1, data=None, msg=f"OpenAI client is not initialized")
  85. completion = client.chat.completions.create(
  86. model=model,
  87. messages=[
  88. {"role": "system", "content": SYSTEM_PROMPT},
  89. {
  90. "role": "user",
  91. "content": [
  92. {"type": "image_url", "image_url": {"url": image_url}},
  93. {"type": "text", "text": text}
  94. ],
  95. },
  96. ],
  97. tools=tools,
  98. tool_choice={
  99. "type": "function",
  100. "function": {"name": "check_ad_copy"}
  101. },
  102. temperature=0.3
  103. )
  104. msg = completion.choices[0].message
  105. print(msg)
  106. # Safely parse tool call arguments (if any)
  107. content = True
  108. reason = ""
  109. try:
  110. tool_calls = getattr(msg, "tool_calls", None) or []
  111. if tool_calls:
  112. call = tool_calls[0]
  113. arg_str = getattr(getattr(call, "function", None), "arguments", None)
  114. if isinstance(arg_str, str) and arg_str.strip():
  115. args = json.loads(arg_str)
  116. if isinstance(args, dict):
  117. content = bool(args.get("pass", True))
  118. reason = str(args.get("reason", "")).strip()
  119. except Exception as e:
  120. logger.error("parse tool call failed: %s", e, exc_info=True)
  121. return DataResponse(code=1, data=None, msg=f"parse tool call failed: {e}")
  122. print("✅ PASS:\n", content)
  123. print("✅ REASON:\n", reason)
  124. return DataResponse(code=0, data=CopywritingEvaluationPayload(content=content, reason=reason), msg="success")