understand_image_provider.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. from openai import OpenAI
  2. from ..schemas.base import DataResponse
  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("understand_image_provider")
  9. SYSTEM_PROMPT = """
  10. <SystemPrompt>
  11. <角色>
  12. 你是一名资深广告文案专家。你的任务是根据输入的一张广告图片中的文字内容,生成一句简洁有力的广告文案。
  13. </角色>
  14. <受众>
  15. 目标用户:50岁以上中老年人。语言需亲切、直白、易理解,避免专业术语与复杂长句。
  16. </受众>
  17. <结构公式>
  18. [行动指令] + [低门槛/优惠承诺] + [核心价值/具体收益] + [紧迫感/稀缺性提醒]
  19. </结构公式>
  20. <约束>
  21. 1. 文案必须准确传达广告图片中的产品/服务信息,不得杜撰不存在的内容。
  22. 2. 加入紧迫感或稀缺性(如“限时”“名额有限”“马上领取”等),但不得虚构或夸大事实。
  23. 3. 避免医疗或功效的绝对化/保证性用语(如“治愈”“根治”“无副作用”“永久有效”)。
  24. 4. 不得包含违法、虚假、低俗、敏感、歧视性内容,不引导危险行为,不传播迷信。
  25. 5. 涉及健康/养生场景时,表述应为辅助/改善/建议性质,不承诺疗效;避免“祖传秘方”等违规表述。
  26. 6. 仅输出一句中文广告文案,简短醒目,适合作为宣传主标题。
  27. 7. 文案必须注意标点与短句分隔:动作、优惠承诺、核心收益之间用逗号分隔;紧迫感/稀缺性提醒用分号与前半部分隔开,避免把多个短语连写在一起,字数50字以内。
  28. </约束>
  29. <示例 few-shot="true">
  30. 长按二维码,0元入群,领取中医调理养生秘方;名额有限,赶快行动吧
  31. </示例>
  32. <输出要求>
  33. 始终通过工具调用(function calling)输出,参数仅包含生成的一句文案。
  34. </输出要求>
  35. </SystemPrompt>
  36. """
  37. tools: list[ChatCompletionToolParam] = [
  38. {
  39. "type": "function",
  40. "function": {
  41. "name": "generate_ocr_text",
  42. "description": "生成一句适合中老年用户的广告文案(遵循结构公式与约束)",
  43. "parameters": {
  44. "type": "object",
  45. "properties": {
  46. "ocr_text": {
  47. "type": "string",
  48. "description": "最终的一句广告文案(中文,简短醒目,合规)"
  49. }
  50. },
  51. "required": ["ocr_text"],
  52. "additionalProperties": False
  53. }
  54. }
  55. }
  56. ]
  57. class UnderstandImageProvider:
  58. print("UnderstandImageProvider called")
  59. def understand_image(self, image_url: str, *, model: str) -> DataResponse:
  60. client = OpenAI(
  61. api_key = settings.dashscope_api_key or "",
  62. base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
  63. )
  64. if not client:
  65. logger.error("OpenAI client is not initialized.")
  66. completion = client.chat.completions.create(
  67. model=model,
  68. messages=[
  69. {"role": "system", "content": SYSTEM_PROMPT},
  70. {
  71. "role": "user",
  72. "content": [{ "type": "image_url", "image_url": { "url": image_url } }],
  73. },
  74. ],
  75. tools=tools,
  76. tool_choice={
  77. "type": "function",
  78. "function": {"name": "generate_ocr_text"}
  79. },
  80. temperature=1.3
  81. )
  82. msg = completion.choices[0].message
  83. # Safely parse tool call arguments (if any)
  84. ocr_text = ""
  85. try:
  86. tool_calls = getattr(msg, "tool_calls", None) or []
  87. if tool_calls:
  88. call = tool_calls[0]
  89. arg_str = getattr(getattr(call, "function", None), "arguments", None)
  90. if isinstance(arg_str, str) and arg_str.strip():
  91. args = json.loads(arg_str)
  92. if isinstance(args, dict):
  93. ocr_text = str(args.get("ocr_text", "")).strip()
  94. except Exception as e:
  95. logger.error("parse tool call failed: %s", e, exc_info=True)
  96. # Fallback: if no tool-calls returned, try to read text content
  97. content = getattr(msg, "content", None)
  98. if not ocr_text and isinstance(content, str):
  99. ocr_text = content.strip()
  100. print("✅ OCR_TEXT:\n", ocr_text)
  101. return DataResponse(code=200, data=ocr_text, msg="Image understood successfully")