modalDeploy/tests/google_test_case.py

260 lines
20 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import json
import os
import unittest
from typing import Optional, List
from loguru import logger
from google.genai import types
from pydantic import BaseModel, Field, computed_field
from langfuse import Langfuse
from jinja2 import Template
from BowongModalFunctions.utils.HTTPUtils import GoogleAuthUtils, FlatJsonSchemaGenerator
class VisualFeatureColor(BaseModel):
pattern: str = Field(description="商品详细图案纹理等有辨识度的材质特征")
style: str = Field(description="商品详细款式版型等有辨识度的风格特征")
class VisualFeature(BaseModel):
color: VisualFeatureColor = Field(description="商品详细颜色配色等有辨识度的色彩特征")
class VisualRecognizeResult(BaseModel):
image_order: int = Field(description="图片的顺序, 从0开始")
image_name: str = Field(description="图片上显示的原始文字")
matched_product: Optional[str] = Field(description="匹配到的标准商品名称或null")
match_confidence: int = Field(description="0到100的可信度评分")
visual_features: List[VisualFeature] = Field(description="所有识别到的商品详细颜色配色等有辨识度的色彩特征")
class VisualRecognizeResults(BaseModel):
results: VisualRecognizeResult = Field(description="每一个图片识别的结果")
image_count: int = Field(description="输入待识别图片的总数")
product_count: int = Field(description="输入待识别商品的总数")
class PromptVariables(BaseModel):
product_list: List[str] = Field(description="商品列表")
@computed_field(description="xml格式排列的商品列表")
@property
def product_list_xml(self) -> str:
xml_items = [f"<product>{product}</product>" for product in self.product_list]
xml_string = "\n".join(xml_items)
return f"<products>\n{xml_string}\n</products>"
class GoogleTestCase(unittest.IsolatedAsyncioTestCase):
service_account_info: dict = {
"type": "service_account",
"project_id": "gen-lang-client-0413414134",
"private_key_id": "48c91fc4cae8158edaad1f52577e4c98143a8cd9",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCzNfkzjGOSAv+e\nHSWOEq87sE8cNdt0AXdAyRL66rMuerGjGpOoP5Ok/LfZrx7DdGg7f9w1DZmw8P81\nvj7s2ZchEGfRrDVQNigaogJzDWQBnCUZBMmaFBcnMndPDb9gqM9fP4gWJoAcRoxw\nFzBi7sPdl5C5Y24UdoHky6z+YKHtLqo3kdB+qXCsJR8U4eqJG16EW/OlS26L/hSP\n8tLNFI3SgcJiRWeCO5pRRpX6nfGf5wju0KMaJKzBRbDJwF3NEj3nmoXSfyoD+itV\nuv5DDCwojB/4nLT2EuxAr5vyY+JY6LmCZhWgPcXy60nsDcUxJzcvRaCsb+C2exZR\n9q4jgXUdAgMBAAECggEAOfle6Zcj6us/aBYDvSc8OvH5VaXynV+QBYxGsJdWadXV\nO29wjwAqMjhy/V/ScuZohb8CLMN+kagU13z4/EQTyOV2wHSWNqGebac1ZaTSUlcC\nBUrwMQEI0GxZ/l/zJkDV/PkffBLuZLdJ3UUTKR4WjMvoTKDmzoXb1XkyOIRoPcLN\nQXqGRl2A/BLgL0mxZsnvBXavcp0o4TfIxC73+ZEnJmrbuoHlDGbXWQvSOGJbi3gE\nLLSJ/+Sn2o9nhHPJI3M9xfMHnU7Fwo5Dt+vSl/Vn4+dvNd2djEjefQTSU19yE6n8\nW5/QzriG4IClBEjqTxYxnL+VQNmUm5dwXqB0C2ph/QKBgQDZ8gKJgU4xH0Woxh+d\nh5AjjnKVk0YlCS9MNA9VlGu2O8ohVj9LZ4azNfYyZMp5DP4gXW21elZWwRyfvSv0\nRlo1CrQIZwY/ETw8NOp9L8+OXQinjL8pLuqYo8rWU4jwdyHrBlwic0KSSOq785Xk\nmdSdU3NOPqTnnUHnDrJLFyEFowKBgQDSgI9v798XAgNhwRLVAwyXTf1N39R8jAl6\nm+m3xzPEblnOKp04cMcjjhV8AqNadg5bZ1Io5Qwy30PofcQmNLCCXs31gCn+0c0i\nEehSXz+QgkNmSxWLXl3SODWY4XN7ThmLJcL36iebKF8t50xvzKbER0xEMwzrrmVq\nW0YwpjfGPwKBgF7Plyb2Z2ubLRSUy+AdvyiYqWREYzltW3QNGbajEJCARhhmirZk\n3QZNLUMS8bnjWxH9UuKly7WF4Mvk4aAsksWMWHFnUCJTfx657mBzUhmeg0tQQUDL\nNicc6fp+8I2bZdf2NlKOTaGRsvv8pXKDMSkXyot5WQehM7AuhoWAFE99AoGAWJ8F\nREQBcQdI8zO8wO8asuyDkvCD3bd7GiJfwB5eXflzV4e7TxKz0/UyeFYH/cKsArE5\n9ruPai9ywIOKO+d81DYjkZLWm1Aqg4h0fZFaCnW8+Gjt9hHRf/poHif0XVohCOLp\n9UOgTwMtJv80v/Cx2PqHUkMH0oVGbwNkRoEEBDMCgYEAvf7a3Xxjl3Ymmy+15oOW\n+iX0/3Ntmr6TUjxFzRRnamO3CO7Vm3qCLOceE2C5/TCi07NxGXkY/NIEUywBGrLe\nSmm2ny5/u6vrDygZMGSB59RVnrAiX7zkqaIy6pY6cgPRQhclHZ9s34Nnd1J2GM4v\nxfdWj16ZNTMAaaWd9u+nZhg=\n-----END PRIVATE KEY-----\n",
"client_email": "gemini-api@gen-lang-client-0413414134.iam.gserviceaccount.com",
"client_id": "116149182781835050625",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/gemini-api%40gen-lang-client-0413414134.iam.gserviceaccount.com",
"universe_domain": "googleapis.com"
}
bucket_name = "dy-media-storage"
cloudflare_project_id = "67720b647ff2b55cf37ba3ef9e677083"
cloudflare_gateway_id = "bowong-dev"
async def test_google_new_token(self):
cred = await GoogleAuthUtils.get_google_auth_jwt(service_account_info=self.service_account_info,
scopes=[
'https://www.googleapis.com/auth/cloud-platform'])
self.assertIsNotNone(cred)
async def test_google_cloud_storage_upload(self):
filepath = "./videos/input_1.mp4"
cred = await GoogleAuthUtils.get_google_auth_jwt(service_account_info=self.service_account_info,
scopes=[
'https://www.googleapis.com/auth/cloud-platform'])
prefix = "test/123"
filename = os.path.basename(filepath)
with open(filepath, 'rb') as file:
response = await GoogleAuthUtils.google_upload_file(file_stream=file,
content_type="video/mp4",
google_api_key=cred.access_token,
bucket_name=self.bucket_name,
filename=f"{prefix}/{filename}")
logger.info(response.model_dump_json(indent=2))
self.assertIsNotNone(response)
async def test_google_inference_with_sdk(self):
cred = await GoogleAuthUtils.get_google_auth_jwt(service_account_info=self.service_account_info,
scopes=[
'https://www.googleapis.com/auth/cloud-platform'])
logger.info(cred.model_dump_json(indent=2))
client = GoogleAuthUtils.GoogleGenaiClient(
cloudflare_project_id=self.cloudflare_project_id,
cloudflare_gateway_id=self.cloudflare_gateway_id,
google_project_id=self.service_account_info.get('project_id'),
regions=['us-central1'], access_token=cred.access_token,
)
config = types.GenerateContentConfig(temperature=0.1,
top_p=0.7,
safety_settings=[
types.SafetySetting(
category=types.HarmCategory.HARM_CATEGORY_HARASSMENT,
threshold=types.HarmBlockThreshold.BLOCK_NONE,
),
types.SafetySetting(
category=types.HarmCategory.HARM_CATEGORY_CIVIC_INTEGRITY,
threshold=types.HarmBlockThreshold.BLOCK_NONE,
),
types.SafetySetting(
category=types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
threshold=types.HarmBlockThreshold.BLOCK_NONE,
),
types.SafetySetting(
category=types.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
threshold=types.HarmBlockThreshold.BLOCK_NONE,
),
types.SafetySetting(
category=types.HarmCategory.HARM_CATEGORY_HATE_SPEECH,
threshold=types.HarmBlockThreshold.BLOCK_NONE,
)
],
response_mime_type="application/json",
response_schema=VisualRecognizeResult)
result = client.generate_content(model_id="gemini-2.5-flash",
contents=[types.Content(role='user',
parts=[
types.Part(file_data=types.FileData(
mime_type="video/mp4",
file_uri="gs://dy-media-storage/videos/035b3053-73f8-45b7-9bf8-428df9025608.mp4"
)),
types.Part.from_text(
text="帮我总结一下这个视频里有什么"),
])],
config=config)
logger.info(result.model_dump_json(indent=2, exclude_none=True))
self.assertIsNotNone(result)
async def test_google_save_prompt(self):
config = types.GenerateContentConfig(temperature=0.1, top_p=0.7,
safety_settings=[
types.SafetySetting(
category=types.HarmCategory.HARM_CATEGORY_HARASSMENT,
threshold=types.HarmBlockThreshold.BLOCK_NONE,
),
types.SafetySetting(
category=types.HarmCategory.HARM_CATEGORY_CIVIC_INTEGRITY,
threshold=types.HarmBlockThreshold.BLOCK_NONE,
),
types.SafetySetting(
category=types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
threshold=types.HarmBlockThreshold.BLOCK_NONE,
),
types.SafetySetting(
category=types.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
threshold=types.HarmBlockThreshold.BLOCK_NONE,
),
types.SafetySetting(
category=types.HarmCategory.HARM_CATEGORY_HATE_SPEECH,
threshold=types.HarmBlockThreshold.BLOCK_NONE,
)
],
response_mime_type="application/json",
response_schema=VisualRecognizeResults.model_json_schema(
schema_generator=FlatJsonSchemaGenerator)
)
logger.info(config.model_dump_json(indent=2))
langfuse = Langfuse(host="https://us.cloud.langfuse.com",
secret_key="sk-lf-dd20cb0b-ef2e-49f6-80f0-b2d9cff1bb11",
public_key="pk-lf-15f9d809-0bf6-4a84-ae1c-18f7a7d927c7",
tracing_enabled=False)
prompt = """
<prompt><instruction>你是专业的商品识别专家。我上传了商品图片网格,需要你识别图片中的商品并与商品列表进行匹配。 **输入材料** - 🖼️ **商品图片网格**:包含多个黑色边框区域,每个区域内有商品图片+商品名称文字 - 📋 **商品列表**:标准商品名称参考清单 **核心任务** 1. **扫描黑色边框区域**:从左上角开始,按行扫描每个黑色边框区域 2. **提取文字信息**:精确提取每个区域内的所有文字信息 3. **与商品列表匹配**:将图片文字与商品列表进行高相似度匹配 4. **提取商品图片特征**:从商品图片提取详细可识别特征,包括颜色、图案、纹理、材质、版型、款式等 **严格约束** - 🚫 只识别有黑色边框包围的商品区域 - 🚫 每个商品必须有清晰可见的文字标注 - 🚫 不得推测或添加图片中不存在的商品 - ✅ 输出商品数量不得超过图片中的黑色边框区域数量 **商品列表** {{PRODUCT_LIST}}
</instruction></prompt>
"""
prompt = langfuse.create_prompt(name="Gemini自动切条", prompt=prompt, type="text", labels=["production"],
config=config.model_dump(exclude_none=True))
logger.info(prompt)
async def test_google_get_prompt(self):
langfuse = Langfuse(host="https://us.cloud.langfuse.com",
secret_key="sk-lf-dd20cb0b-ef2e-49f6-80f0-b2d9cff1bb11",
public_key="pk-lf-15f9d809-0bf6-4a84-ae1c-18f7a7d927c7",
tracing_enabled=False)
product_title_list = [
"A美洋MEIYANG【商场同款】碧螺春墨镜 醋酸纤维素防晒太阳眼镜-周四",
"A美洋MEIYANG【欧若风】微风背心 慵懒百搭圆领无袖上衣-周二",
"A美洋MEIYANG【商场同款】幸运T恤 复古做旧印花圆领短袖上衣-周四",
"A美洋MEIYANG 黑武士厚底老爹鞋 赛博末日风~厚底增高运动休闲鞋",
"合金项链 A美洋MEIYANG 香水瓶毛衣链 个性吊坠麻花链项链-周四",
"A美洋MEIYANG【商场同款】纱暮半裙 抗起球镂空蕾丝半身中长裙-周四",
]
variables = PromptVariables(product_list=product_title_list, )
latest_prompt = langfuse.get_prompt("Gemini自动切条", type="text", label="latest")
logger.info(f"variables={latest_prompt.variables}")
runtime_prompt = Template(latest_prompt.prompt).render(PRODUCT_LIST=variables.product_list_xml)
cred = await GoogleAuthUtils.get_google_auth_jwt(service_account_info=self.service_account_info,
scopes=[
'https://www.googleapis.com/auth/cloud-platform'])
logger.info(cred.model_dump_json(indent=2))
client = GoogleAuthUtils.GoogleGenaiClient(
cloudflare_project_id=self.cloudflare_project_id,
cloudflare_gateway_id=self.cloudflare_gateway_id,
google_project_id=self.service_account_info.get('project_id'),
regions=['us-central1'], access_token=cred.access_token,
)
config = types.GenerateContentConfig.model_validate(latest_prompt.config)
result = client.generate_content(model_id="gemini-2.5-flash",
contents=[types.Content(role='user',
parts=[
types.Part(file_data=types.FileData(
mime_type="image/jpeg",
file_uri="gs://dy-media-storage/images/grid_56fe257b-f81f-4ad0-8958-530ad557b876.jpg"
)),
types.Part(file_data=types.FileData(
mime_type="image/jpeg",
file_uri="gs://dy-media-storage/images/grid_f74b4c4f-a305-4a96-9045-f09e0eb90a30.jpg"
)),
types.Part(file_data=types.FileData(
mime_type="image/jpeg",
file_uri="gs://dy-media-storage/images/grid_e4c10111-e9ec-4e76-88db-abd57b3bb92e.jpg"
)),
types.Part(file_data=types.FileData(
mime_type="image/jpeg",
file_uri="gs://dy-media-storage/images/grid_52b73a7d-f5e0-4a17-b7c2-194ef3852856.jpg"
)),
types.Part(file_data=types.FileData(
mime_type="image/jpeg",
file_uri="gs://dy-media-storage/images/grid_f8d8c35e-1c35-48c4-b9ce-df6365b7fc55.jpg"
)),
types.Part(file_data=types.FileData(
mime_type="image/jpeg",
file_uri="gs://dy-media-storage/images/grid_b9132a81-f4b6-45f7-8b37-c0caaee06705.jpg"
)),
types.Part(file_data=types.FileData(
mime_type="image/jpeg",
file_uri="gs://dy-media-storage/images/grid_d7ef44fc-8767-405d-a9dc-f16e4a918efd.jpg"
)),
types.Part(file_data=types.FileData(
mime_type="image/jpeg",
file_uri="gs://dy-media-storage/images/grid_564b9ff9-fa3a-4d34-9592-038d454f0834.jpg"
)),
types.Part.from_text(text=runtime_prompt, ),
])],
config=config)
logger.info(result.model_dump_json(indent=2, exclude_none=True))
json_result = result.candidates[0].content.parts[0].text
result_model = VisualRecognizeResults.model_validate_json(json_result)
logger.info(result_model.model_dump_json(indent=2, exclude_none=True))
async def test_flat_json_schema(self):
json_schema = VisualRecognizeResults.model_json_schema(schema_generator=FlatJsonSchemaGenerator)
logger.info(json.dumps(json_schema, indent=2, ensure_ascii=False))
if __name__ == '__main__':
unittest.main()