| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110 |
- """用户画像 prompt 组装服务
- 职责:
- 1. 拉取用户自定义画像文本(600s 缓存)
- 2. 拉取 profile_snapshot 并渲染为文本(900s 缓存)
- 3. 拼装成 SystemMessage 内容;无数据时返回 None
- """
- import time
- import logging
- from app.tools.java_client import JavaClient
- logger = logging.getLogger(__name__)
- _TTL_PROMPT = 600 # 用户自定义 prompt 缓存 10 分钟
- _TTL_SNAPSHOT = 900 # 画像快照缓存 15 分钟
- _cache_prompt: dict = {}
- _cache_snapshot: dict = {}
- def _render_snapshot(profile: dict) -> str:
- """将 profile Map 渲染为中文指标清单文本"""
- if not profile or "error" in profile:
- return ""
- lines = []
- member = profile.get("member") or {}
- name = member.get("name", "用户")
- age = member.get("age", "?")
- gender_raw = str(member.get("gender", ""))
- gender = "男" if gender_raw == "male" else "女" if gender_raw == "female" else "未知"
- lines.append(f"## 画像对象:{name}({age}岁,{gender})")
- dims = profile.get("dimension_scores") or {}
- if dims:
- lines.append(f"五维评分:身 {dims.get('body', '?')} 智 {dims.get('wisdom', '?')} "
- f"心 {dims.get('mind', '?')} 行 {dims.get('action', '?')} 富 {dims.get('wealth', '?')}")
- body = profile.get("body_metrics") or {}
- if body.get("sleep_dur_avg"):
- lines.append(f"平均睡眠:{body['sleep_dur_avg']}小时/天")
- if body.get("exercise_count_week"):
- lines.append(f"周运动频次:{body['exercise_count_week']}次")
- mind = profile.get("mind_metrics") or {}
- if mind.get("stress_avg"):
- lines.append(f"平均压力:{mind['stress_avg']}/10")
- problems = profile.get("problem_domains") or []
- if problems:
- lines.append(f"关注问题域:{', '.join(problems[:5])}")
- return "\n".join(lines)
- async def get_user_portrait_text(java: JavaClient, user_id: int, member_id) -> str | None:
- """返回画像 SystemMessage 内容;无画像数据时返回 None"""
- parts = []
- # 1. 用户自定义 prompt(600s 缓存)
- now = time.time()
- cached_text, cached_ts = _cache_prompt.get(user_id, (None, 0))
- if now - cached_ts < _TTL_PROMPT and cached_text is not None:
- user_text = cached_text
- else:
- try:
- client = await java._get_client()
- resp = await client.post("/api/user/portrait/get", json={"userId": user_id}, timeout=5.0)
- data = resp.json()
- user_text = (data.get("data") or {}).get("portraitPrompt") or ""
- _cache_prompt[user_id] = (user_text, now)
- except Exception as e:
- logger.warning("拉取用户画像 prompt 失败: %s", e)
- user_text = ""
- if user_text.strip():
- parts.append(user_text)
- # 2. 快照渲染文本(仅当有 member_id 时)
- if member_id:
- snap_now = time.time()
- cached_snap, cached_snap_ts = _cache_snapshot.get(int(member_id), (None, 0))
- if snap_now - cached_snap_ts < _TTL_SNAPSHOT and cached_snap is not None:
- snap_text = cached_snap
- else:
- try:
- profile = await java.get_member_profile(int(member_id))
- snap_text = _render_snapshot(profile) if profile else ""
- _cache_snapshot[int(member_id)] = (snap_text, snap_now)
- except Exception as e:
- logger.warning("拉取成员画像快照失败: %s", e)
- snap_text = ""
- if snap_text.strip():
- if user_text.strip():
- parts.append("\n---参考指标---\n" + snap_text)
- else:
- parts.append("以下是用户画像数据(系统自动生成),请结合这些数据给出更针对性的建议:\n" + snap_text)
- return "\n\n".join(parts) if parts else None
- def clear_cache(user_id: int | None = None, member_id: int | None = None):
- """清除缓存"""
- if user_id is not None:
- _cache_prompt.pop(user_id, None)
- if member_id is not None:
- _cache_snapshot.pop(member_id, None)
- if user_id is None and member_id is None:
- _cache_prompt.clear()
- _cache_snapshot.clear()
|