people-reading/backend/app/services/palm_analyzer.py
2026-05-11 23:26:11 +08:00

185 lines
9.7 KiB
Python
Raw 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 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,
}