引入Langfuse作为prompt词管理
* - 引入了Langfuse作为prompt词管理 - 处理嵌套型pydantic model输出非嵌套json schema的报错 --------- Merge request URL: https://g-ldyi2063.coding.net/p/dev/d/modalDeploy/git/merge/4836?initial=true Co-authored-by: shuohigh@gmail.com
This commit is contained in:
@@ -1,15 +1,47 @@
|
||||
import json
|
||||
import os
|
||||
import unittest
|
||||
from typing import Optional, List
|
||||
|
||||
import jsonref
|
||||
from loguru import logger
|
||||
from google.genai import types
|
||||
from pydantic import BaseModel
|
||||
|
||||
from BowongModalFunctions.utils.HTTPUtils import GoogleAuthUtils
|
||||
from pydantic import BaseModel, Field, computed_field
|
||||
from langfuse import Langfuse
|
||||
from jinja2 import Template
|
||||
from BowongModalFunctions.utils.HTTPUtils import GoogleAuthUtils, FlatJsonSchemaGenerator
|
||||
|
||||
|
||||
class BaseResponse(BaseModel):
|
||||
message: str
|
||||
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):
|
||||
@@ -64,18 +96,7 @@ class GoogleTestCase(unittest.IsolatedAsyncioTestCase):
|
||||
google_project_id=self.service_account_info.get('project_id'),
|
||||
regions=['us-central1'], access_token=cred.access_token,
|
||||
)
|
||||
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=types.GenerateContentConfig(
|
||||
temperature=0.1,
|
||||
config = types.GenerateContentConfig(temperature=0.1,
|
||||
top_p=0.7,
|
||||
safety_settings=[
|
||||
types.SafetySetting(
|
||||
@@ -100,11 +121,140 @@ class GoogleTestCase(unittest.IsolatedAsyncioTestCase):
|
||||
)
|
||||
],
|
||||
response_mime_type="application/json",
|
||||
response_schema=BaseResponse
|
||||
))
|
||||
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()
|
||||
|
||||
Reference in New Issue
Block a user