185 lines
9.7 KiB
Python
185 lines
9.7 KiB
Python
import base64
|
||
import json
|
||
|
||
from openai import AsyncOpenAI
|
||
|
||
from app.core.config import settings
|
||
|
||
DISCLAIMER = "本报告仅用于娱乐占卜与自我反思,不构成医学、心理、职业、财务、投资或任何人生决策建议。"
|
||
SYSTEM_PROMPT = (
|
||
"你是“赛博先生”,一个面向普通人的娱乐型 AI 命理解读助手。"
|
||
"你的风格要像一个会聊天、懂生活的朋友:温和、具体、接地气,有一点玄学仪式感,但不要装神秘。"
|
||
"你会根据掌心照片做象征性手相解读,但所有表达都必须使用“可能、倾向、适合、提醒你”这类非确定性措辞。"
|
||
"禁止给出医疗、心理诊断、投资、职业成败、婚恋结果、寿命、灾祸等确定性判断。"
|
||
"不要堆砌专业术语,不要写得像教材;每个结论都要落到生活、学习、事业、关系或近期行动里的具体场景。"
|
||
)
|
||
|
||
USER_PROMPT_TEMPLATE = (
|
||
"请分析这张{hand_side}手掌照片,生成中文手相报告。"
|
||
"必须覆盖生命线、智慧线、感情线、命运线、手型与手指比例、特殊纹路与丘位特征。"
|
||
"写作要求:"
|
||
"1. overall_summary 用 2 到 4 句话,像给用户本人看的开场结论,要贴近日常生活。"
|
||
"2. dimensions 中每个 interpretation 不要只解释纹路含义,必须关联一个现实场景,例如学习效率、工作节奏、沟通方式、情绪恢复、计划执行、关系相处。"
|
||
"3. 每个 advice 必须是用户今天或本周能做的小建议,不要空泛。"
|
||
"4. strengths 写成用户容易感受到的优势,例如做事方式、学习方式、职场协作、情感表达。"
|
||
"5. challenges 写成温和提醒,不要吓人,不要宿命论。"
|
||
"6. suggestions 必须包含生活、学习/成长、事业/工作、关系沟通中的至少三个方向。"
|
||
"7. lucky_keywords 要短、有记忆点、生活化。"
|
||
"8. 如果照片不够真实或不够清晰,要诚实降低 confidence,并在 reason 里说明。"
|
||
)
|
||
|
||
|
||
REPORT_SCHEMA = {
|
||
"name": "palm_report",
|
||
"schema": {
|
||
"type": "object",
|
||
"additionalProperties": False,
|
||
"properties": {
|
||
"quality_check": {
|
||
"type": "object",
|
||
"additionalProperties": False,
|
||
"properties": {
|
||
"can_analyze": {"type": "boolean"},
|
||
"reason": {"type": "string"},
|
||
"confidence": {"type": "number"},
|
||
},
|
||
"required": ["can_analyze", "reason", "confidence"],
|
||
},
|
||
"overall_summary": {"type": "string"},
|
||
"dimensions": {
|
||
"type": "array",
|
||
"items": {
|
||
"type": "object",
|
||
"additionalProperties": False,
|
||
"properties": {
|
||
"name": {"type": "string"},
|
||
"observations": {"type": "array", "items": {"type": "string"}},
|
||
"interpretation": {"type": "string"},
|
||
"confidence": {"type": "number"},
|
||
"advice": {"type": "string"},
|
||
},
|
||
"required": ["name", "observations", "interpretation", "confidence", "advice"],
|
||
},
|
||
},
|
||
"strengths": {"type": "array", "items": {"type": "string"}},
|
||
"challenges": {"type": "array", "items": {"type": "string"}},
|
||
"suggestions": {"type": "array", "items": {"type": "string"}},
|
||
"lucky_keywords": {"type": "array", "items": {"type": "string"}},
|
||
"disclaimer": {"type": "string"},
|
||
},
|
||
"required": [
|
||
"quality_check",
|
||
"overall_summary",
|
||
"dimensions",
|
||
"strengths",
|
||
"challenges",
|
||
"suggestions",
|
||
"lucky_keywords",
|
||
"disclaimer",
|
||
],
|
||
},
|
||
"strict": True,
|
||
}
|
||
|
||
|
||
class PalmAnalyzer:
|
||
def __init__(self) -> None:
|
||
self.client = (
|
||
AsyncOpenAI(api_key=settings.openai_api_key, base_url=settings.openai_base_url)
|
||
if settings.openai_api_key
|
||
else None
|
||
)
|
||
|
||
async def analyze(self, image_bytes: bytes, content_type: str, hand_side: str) -> dict:
|
||
if not self.client:
|
||
return self._mock_report(hand_side)
|
||
|
||
image_data = base64.b64encode(image_bytes).decode("ascii")
|
||
response = await self.client.responses.create(
|
||
model=settings.openai_model,
|
||
input=[
|
||
{
|
||
"role": "system",
|
||
"content": [
|
||
{
|
||
"type": "input_text",
|
||
"text": SYSTEM_PROMPT,
|
||
}
|
||
],
|
||
},
|
||
{
|
||
"role": "user",
|
||
"content": [
|
||
{
|
||
"type": "input_text",
|
||
"text": USER_PROMPT_TEMPLATE.format(hand_side=hand_side),
|
||
},
|
||
{
|
||
"type": "input_image",
|
||
"image_url": f"data:{content_type};base64,{image_data}",
|
||
"detail": "high",
|
||
},
|
||
],
|
||
},
|
||
],
|
||
text={"format": {"type": "json_schema", **REPORT_SCHEMA}},
|
||
)
|
||
data = json.loads(response.output_text)
|
||
data["disclaimer"] = DISCLAIMER
|
||
return data
|
||
|
||
def _mock_report(self, hand_side: str) -> dict:
|
||
return {
|
||
"quality_check": {"can_analyze": True, "reason": "mock mode", "confidence": 0.72},
|
||
"overall_summary": f"这是一份{hand_side}手娱乐手相报告。整体看,你像是那种遇到事情会先稳住节奏的人,适合把目标拆小、慢慢推进。最近如果在学习、工作或关系里有点卡,不必急着推翻重来,先把手边一件具体的小事做好,会更容易找回状态。",
|
||
"dimensions": [
|
||
{
|
||
"name": "生命线",
|
||
"observations": ["弧度较完整", "线条延展感较强"],
|
||
"interpretation": "这类线条象征恢复力还不错。放到生活里,你可能不是一直满电的人,但只要睡眠、饮食和节奏回到正轨,状态通常能慢慢补回来。",
|
||
"confidence": 0.68,
|
||
"advice": "这周先固定一个睡前时间,别把恢复力浪费在反复熬夜上。",
|
||
},
|
||
{
|
||
"name": "智慧线",
|
||
"observations": ["走向偏平稳", "中段纹理较清晰"],
|
||
"interpretation": "智慧线偏稳,象征你适合用步骤感处理问题。学习或工作上,如果任务太大,你更适合列清单推进,而不是靠临场爆发。",
|
||
"confidence": 0.66,
|
||
"advice": "今天把最烦的一件事拆成 3 步,只完成第一步就算开局。",
|
||
},
|
||
{
|
||
"name": "感情线",
|
||
"observations": ["线条柔和", "末端略有分支感"],
|
||
"interpretation": "感情线柔和,象征你在关系里比较在意感受,也容易替别人想。好处是共情强,提醒是别把所有情绪都自己消化。",
|
||
"confidence": 0.64,
|
||
"advice": "这周有不舒服的地方,试着用一句具体的话说出来:我希望你下次可以怎样。",
|
||
},
|
||
{
|
||
"name": "命运线",
|
||
"observations": ["可见纵向纹理", "深浅变化较明显"],
|
||
"interpretation": "命运线有阶段变化感,象征你的事业或成长路径可能不是一条直线。你适合边做边调整,在变化里积累自己的方法。",
|
||
"confidence": 0.58,
|
||
"advice": "工作或学习上,优先做一件能留下作品、笔记或案例的事。",
|
||
},
|
||
{
|
||
"name": "手型与手指比例",
|
||
"observations": ["掌形比例均衡", "手指伸展感自然"],
|
||
"interpretation": "手型比例均衡,象征你在规则和灵感之间都有一点能力。适合做既要审美判断、也要实际落地的任务。",
|
||
"confidence": 0.61,
|
||
"advice": "如果最近有想法,别只停在脑子里,先做一个草稿或简单版本。",
|
||
},
|
||
{
|
||
"name": "特殊纹路与丘位特征",
|
||
"observations": ["局部细纹较丰富", "掌丘起伏需更清晰照片确认"],
|
||
"interpretation": "细纹较丰富,象征你对环境和他人反馈比较敏感。学习、工作或社交里,你可能很会察觉气氛,但也容易被杂音影响。",
|
||
"confidence": 0.52,
|
||
"advice": "给自己设一个免打扰时段,把注意力留给真正重要的任务。",
|
||
},
|
||
],
|
||
"strengths": ["适合按计划推进学习和工作", "能照顾到别人的感受", "遇到变化时有重新调整的能力"],
|
||
"challenges": ["想得多的时候,行动容易变慢", "太在意反馈时,会消耗自己的专注力"],
|
||
"suggestions": ["生活上先把作息拉回稳定线", "学习成长上把大目标拆成今天能完成的一页笔记", "事业工作上优先沉淀一个可展示的小成果", "关系沟通上把需求说具体,不要只等别人猜"],
|
||
"lucky_keywords": ["先做一步", "稳住节奏", "说清需求"],
|
||
"disclaimer": DISCLAIMER,
|
||
}
|