"""用户画像 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()