evaluation_provider.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  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. 你是一名广告文案质检专家。你的任务是:根据输入的广告图片文字(OCR结果)和生成的广告文案,仅从“格式”和“内容一致性”两个维度判断文案是否合格。
  13. </角色>
  14. <校验标准>
  15. 1. 格式要求:
  16. - 文案必须以「[行动指令],[低门槛/优惠承诺]」连续开头。
  17. - 行动指令示例:“长按二维码”“扫码二维码”“识别二维码”“长按识别”。
  18. - 低门槛/优惠承诺示例:“0元入群”“免费进群”“0元加入”“限时免费加入”。
  19. - 开头后应包含核心价值(如领取/获取/享受 + 方案/建议/课程/资料等)。
  20. - 结尾应包含紧迫感/稀缺性提醒(如“名额有限”“限时”“赶快行动”)。
  21. - 标点要求:动作、优惠、收益之间用逗号;紧迫提醒前用分号。
  22. - 全句 ≤ 50 字。
  23. 2. 内容一致性要求:
  24. - 文案内容必须与广告图片文字(OCR结果)一致。
  25. - 优惠、产品/服务、动作入口等必须能在图片中找到对应信息。
  26. - 不得凭空捏造图片中没有的要素。
  27. </校验标准>
  28. <判定逻辑>
  29. - 符合以上两条 → pass=true,reason=""。
  30. - 若仅轻微偏差(如分号缺失、字数略超) → pass=true,reason="建议优化:…"。
  31. - 若明显不符合格式或文案与图片内容不一致 → pass=false,reason=简要说明。
  32. </判定逻辑>
  33. <输出要求>
  34. 始终调用函数 check_ad_copy,输出格式如下:
  35. {
  36. "pass": true/false,
  37. "reason": "若不通过写原因;若通过则为空字符串或给出优化建议"
  38. }
  39. </输出要求>
  40. </SystemPrompt>
  41. """
  42. tools: list[ChatCompletionToolParam] = [
  43. {
  44. "type": "function",
  45. "function": {
  46. "name": "check_ad_copy",
  47. "description": "校验广告文案格式与内容是否合格",
  48. "parameters": {
  49. "type": "object",
  50. "properties": {
  51. "pass": {
  52. "type": "boolean",
  53. "description": "文案是否合格:true 表示通过,false 表示不通过"
  54. },
  55. "reason": {
  56. "type": "string",
  57. "description": "若不通过,说明原因;若通过则为空字符串"
  58. }
  59. },
  60. "required": ["pass", "reason"],
  61. "additionalProperties": False
  62. }
  63. }
  64. }
  65. ]
  66. class EvaluationProvider:
  67. print("EvaluationProvider called")
  68. def copywriting_evaluation(self, image_url: str, text: str, model: str) -> DataResponse:
  69. client = OpenAI(
  70. api_key = settings.dashscope_api_key or "",
  71. base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
  72. )
  73. if not client:
  74. logger.error("OpenAI client is not initialized.")
  75. return DataResponse(code=1, data=None, msg=f"OpenAI client is not initialized")
  76. completion = client.chat.completions.create(
  77. model=model,
  78. messages=[
  79. {"role": "system", "content": SYSTEM_PROMPT},
  80. {
  81. "role": "user",
  82. "content": [
  83. {"type": "image_url", "image_url": {"url": image_url}},
  84. {"type": "text", "text": text}
  85. ],
  86. },
  87. ],
  88. tools=tools,
  89. tool_choice={
  90. "type": "function",
  91. "function": {"name": "check_ad_copy"}
  92. },
  93. temperature=0.3
  94. )
  95. msg = completion.choices[0].message
  96. print(msg)
  97. # Safely parse tool call arguments (if any)
  98. content = True
  99. reason = ""
  100. try:
  101. tool_calls = getattr(msg, "tool_calls", None) or []
  102. if tool_calls:
  103. call = tool_calls[0]
  104. arg_str = getattr(getattr(call, "function", None), "arguments", None)
  105. if isinstance(arg_str, str) and arg_str.strip():
  106. args = json.loads(arg_str)
  107. if isinstance(args, dict):
  108. content = bool(args.get("pass", True))
  109. reason = str(args.get("reason", "")).strip()
  110. except Exception as e:
  111. logger.error("parse tool call failed: %s", e, exc_info=True)
  112. return DataResponse(code=1, data=None, msg=f"parse tool call failed: {e}")
  113. print("✅ PASS:\n", content)
  114. print("✅ REASON:\n", reason)
  115. return DataResponse(code=0, data=CopywritingEvaluationPayload(content=content, reason=reason), msg="success")