from openai import OpenAI from ..schemas.base import DataResponse from ..core.config import get_settings from ..core.logger import get_logger from openai.types.chat import ChatCompletionToolParam import json settings = get_settings() logger = get_logger("understand_image_provider") SYSTEM_PROMPT = """ <角色> 你是一名广告文案质检专家。你的任务是:根据输入的广告图片文字(OCR结果)和生成的广告文案,仅从“结构”和“内容一致性”两个维度判断文案是否合格。 <校验标准> 1. 结构要求: - 文案整体应符合公式: [行动指令],[低门槛/优惠承诺],[核心价值/具体收益];[紧迫感/稀缺性提醒] - 行动指令示例(可选):长按二维码 / 扫码二维码 / 识别二维码 / 点击领取 / 立即添加 - 低门槛/优惠承诺示例(可选):0元入群 / 免费进群 / 0元加入 / 限时免费加入 - 核心价值/具体收益:必须出现,形式为“领取/获取/享受 + {方案/资料/课程/建议/秘方等}” - 紧迫感/稀缺性提醒:必须出现,如“名额有限”“限时”“赶快行动”“先到先得” - 标点规范:前面三部分用逗号分隔;紧迫提醒前用分号。全句 ≤ 50 字。 2. 行动指令和低门槛/优惠承诺: - 若文案中包含这两部分 → 检查是否合理表述(符合示例或页面内容)。 - 若文案中没有这两部分 → 不因缺失而判定不合格。 3. 核心价值/具体收益(关键内容校验): - 必须与广告图片文字(OCR结果)保持一致。 - 不得捏造页面中不存在的产品/服务/优惠。 <判定逻辑> - 如果结构正确,且核心价值/具体收益与页面内容一致 → pass=true,reason=""。 - 若轻微偏差(如分号缺失、字数略超)→ pass=true,reason="建议优化:……"。 - 若结构明显错误(缺少核心价值或紧迫提醒)或核心价值与页面内容不一致 → pass=false,reason=简要说明。 <输出要求> 始终调用函数 check_ad_copy,输出格式如下: { "pass": true/false, "reason": "若不通过写原因;若通过则为空字符串或给出优化建议" } """ tools: list[ChatCompletionToolParam] = [ { "type": "function", "function": { "name": "generate_ocr_text", "description": "生成一句适合中老年用户的广告文案(遵循结构公式与约束)", "parameters": { "type": "object", "properties": { "ocr_text": { "type": "string", "description": "最终的一句广告文案(中文,简短醒目,合规)" } }, "required": ["ocr_text"], "additionalProperties": False } } } ] class UnderstandImageProvider: print("UnderstandImageProvider called") def understand_image(self, image_url: str, *, model: str) -> DataResponse: client = OpenAI( api_key = settings.dashscope_api_key or "", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", ) if not client: logger.error("OpenAI client is not initialized.") return DataResponse(code=1, data=None, msg=f"OpenAI client is not initialized") completion = client.chat.completions.create( model=model, messages=[ {"role": "system", "content": SYSTEM_PROMPT}, { "role": "user", "content": [{ "type": "image_url", "image_url": { "url": image_url } }], }, ], tools=tools, tool_choice={ "type": "function", "function": {"name": "generate_ocr_text"} }, temperature=0.5 ) msg = completion.choices[0].message # Safely parse tool call arguments (if any) ocr_text = "" try: tool_calls = getattr(msg, "tool_calls", None) or [] if tool_calls: call = tool_calls[0] arg_str = getattr(getattr(call, "function", None), "arguments", None) if isinstance(arg_str, str) and arg_str.strip(): args = json.loads(arg_str) if isinstance(args, dict): ocr_text = str(args.get("ocr_text", "")).strip() except Exception as e: logger.error("parse tool call failed: %s", e, exc_info=True) return DataResponse(code=1, data=None, msg=f"parse tool call failed: {e}") # Fallback: if no tool-calls returned, try to read text content content = getattr(msg, "content", None) if not ocr_text and isinstance(content, str): ocr_text = content.strip() print("✅ OCR_TEXT:\n", ocr_text) return DataResponse(code=0, data=ocr_text, msg="success")