from openai import os from src.clients.llm_client import LLMClient import json import pandas as pd from pydantic import BaseModel from enum import Enum import base64 from io import BytesIO from PIL import Image from functools import cache from datetime import datetime import pytz from src.utils.tracer import customtracer def _ask_raw_hf(messages, model, response_format=None): """Compatibility wrapper: routes OpenAI-style messages through HF LLMClient.""" from src.clients.llm_client import LLMClient import json, re client = LLMClient() # Extract system prompt and user content from messages list system_prompt = None user_text = "" images = [] for msg in messages: role = msg.get("role", "") c = msg.get("content", "") if role == "system": if isinstance(c, str): system_prompt = c elif role == "user": if isinstance(c, str): user_text = c elif isinstance(c, list): for part in c: if isinstance(part, dict): if part.get("type") == "text": user_text += part.get("text", "") elif part.get("type") == "image_url": url = part.get("image_url", {}).get("url", "") if url.startswith("data:"): images.append(url.split(",", 1)[1] if "," in url else url) else: images.append(url) if response_format is not None and hasattr(response_format, "model_json_schema"): result = client.call( prompt=user_text, schema=response_format, model=model, system_prompt=system_prompt, images=images if images else None, temperature=0, ) import json return json.dumps(result.model_dump(), ensure_ascii=False) else: return client.call_raw( prompt=user_text, model=model, system_prompt=system_prompt, images=images if images else None, ) class UIoption(str, Enum): element1 = "バナー/動画" element2 = "CTA" element3 = "チEスチE element4 = "フォーム" class Component(BaseModel): component_large: str component_middle: str component_small: list[str] UIelement: UIoption class Components(BaseModel): components: list[Component] def ask_raw(messages): client = LLMClient() # HF: beta.parse not available; use _ask_raw_hf instead response = client.chat.completions.create( model='meta-llama/Llama-3.3-70B-Instruct', messages=messages, top_p=1, frequency_penalty=0, presence_penalty=0, response_format=Components, temperature=0 ) return response.choices[0].message.content @customtracer def base64img2component(p, image64, openai_key=os.environ.get('OPENAI_KEY')): """ input1 (text): 13: ※金融犯罪にご注愁E手口はこちら、E38: ▼ご利用条件はこちら、E77: ピンチE時E、E133: アコム一抁E409: WEB完結カードを作らぁE415: ご契紁EE翌日から最大30日間利0冁E421: 借りられめE0刁E 644: 今すぐお申し込み 722: 実質年玁E3.0%~18.0%ご融賁EE1丁EE~800丁EE 760: 以前ご利用があっぁE761: ご増額をご希望のお客さまはこちめE784: お客さまはこちめE819: *お申し込み時間めE査によりご希望に沿えなぁE合がござぁEす、E868: お借E可能かすぐに刁EめE秒スピEド診断 977: 侁E22 1055: ご年叁E税込) 1067: 侁E250 1146: 他社お借E顁E1249: 診断開姁E1323: ※クレジチEカードでのショチEング、E行でのお借E(銀行カードローン、住宁Eーン、E動車ローンなど)を除ぁE、キャチEングめEードローンのお借E状況をごE力ください、E1498: 借りるなめE1558: アコム一抁E1710: 20刁E借りられめE1835: アコムなら最短20刁Eお借Eが可能!※すぐにおが忁EとぁE時E、本ペEジの申込ボタンから早速お申し込みくだ 1960: ※お申し込み時間めE査によりご希望に添えなぁE合がござぁEす、E2045: カードを作らずWEB完絁E2165: お申し込み〜お借EまでWEBだけで完結できます。ご希望ぁEだければカードレスでご契紁Eただけます、E2354: |30日間利ぁE冁E2356: 契紁EE翌日から 2470: はじめてご利用のお客さまは、契紁EE翌日から最大30日間利ぁE冁E 2663: たっぁEスチEチE!(最短20刁E 2757: 申し込みから借りるまでの流れ※お申し込み時間めE査によりご希望に添えなぁE合がござぁEす、E2937: お申し込み・1忁E書類提出(審査)お申し込みぁEだぁE後、忁E書類を提EしてぁEだき審査に進みます、E3131: 2ご契紁EEお借E 3194: 審査結果の冁Eにご同意いただけましたら、契紁E続きは完亁Eなります。契紁EE、すぐにお借EぁEだけます。ご希望ぁEだければカードレスでご契紁Eただけます、E3335: 忁E書類とは? 3405: 本人確認書顁E免許証など) 3455: (該当する方のみ)+収E証明書 3488: ※「当社のご利用において50丁EEを趁Eるご契紁E行うお客さま」と「他社を含めたお借E総額が100丁EEを趁Eるお客「さま」につぁEは、収入証明書も忁Eで 3633: アコムの 3664: よくある質啁E3777: 申し込み編 3892: Q勤務Eに在籍確認E電話がかかってきま 3961: 原則、実施しません。※原則、E話での在籍確認Eせずに書面めE申告E容での確認を実施します。もし実施が忁Eとなる場合でも、お客さまの同意を得ずに実施することはありませんので、ご安忁Eださい、E4135: Q契紁Eると、忁Eカードが自宁E郵送さ 4159: れるんですか? 4205: ぁEえ。カードレスでご契紁E続きぁEだくことも可能です、E4296: 自宁E勤務Eに何か書類が送られてくる 4320: ことはありますか? 4366: 原則、E付しません、E郵送契紁E選択された場合や、書面の郵送受け取りを選 4418: んだ場合等を除ぁE 5914: は、ご返済シミュレーションをご利用ぁE5943: ださい、E5992: ペEジ上部に戻る▲ 7671: ご増額をご希望のお客さまはこちめE7671: 以前ご利用があったお客さまはこちめE8033: 今すぐお申し込み input2 (text): スクショ input3 (text): default output1 (json): 頁E """ print(datetime.now(pytz.timezone('Asia/Tokyo')).strftime("%Y-%m-%d %H:%M:%S"), f"base64img2component:", image64[0:30]) if openai_key == "default": os.environ['OPENAI_API_KEY'] = os.environ.get('OPENAI_KEY') else: os.environ['OPENAI_API_KEY'] = openai_key messages=[ { "role": "system", "content": """ ■構E要素名EアウトEチEサンプル [ {"component_large":"啁E/サービスの特徴","component_middle":"アコム", "component_small":[], "UIelement":"チEスチE}, {"component_large":"FAQ/よくある質啁E,"component_middle":"よくあるご質啁E, "component_small":["自宁E勤務Eに何か書類が送られてくることはありますかEE,"家族割などの割引EありますかEE], "UIelement":"表絁E"} ] """ }, { "role": "user", "content": [{"type": "text", "text":p}] }, ] messages[1]["content"].insert(0, {"type": "image_url", "image_url": {"url":"data:image/png;base64,"+image64}}) # OpenAI 側の認証エラーなどをE示皁EメチEージとして上位に伝搬させめE try: return ask_raw(messages) except openai.AuthenticationError as e: # API キー / 絁E設定E問題を含むエラー冁EをラチEEして投げ直ぁE # 呼び出しEEEE Origin 側などEでこEメチEージをキャチEしてユーザに表示できる raise RuntimeError(f"[base64img2component] OpenAI AuthenticationError: {e}") from e