From 79af698a7f69817943a4b754c413d7b3bea56efb Mon Sep 17 00:00:00 2001 From: Sethyrial <116576676+LoveS0ph1e@users.noreply.github.com> Date: Thu, 2 Jul 2026 12:02:30 +0800 Subject: [PATCH] fix(user-memory): rebase target-aware onto anti-bloat HARD RULE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the earlier prose anti-bloat rule with the HARD RULE version from PR #3, so the two changes compose cleanly without merge conflicts on rule 6. No other changes — targeting code and prompts are unchanged. --- packages/everalgo-user-memory/CHANGELOG.md | 4 + .../src/everalgo/user_memory/profile.py | 10 +- .../user_memory/prompts/en/profile.py | 458 +++++++++--------- .../user_memory/prompts/zh/profile.py | 10 +- .../user_memory/test_user_memory_profile.py | 36 ++ 5 files changed, 297 insertions(+), 221 deletions(-) diff --git a/packages/everalgo-user-memory/CHANGELOG.md b/packages/everalgo-user-memory/CHANGELOG.md index 2e0d342..b40ea9b 100644 --- a/packages/everalgo-user-memory/CHANGELOG.md +++ b/packages/everalgo-user-memory/CHANGELOG.md @@ -6,6 +6,10 @@ follows [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Changed + +- `ProfileExtractor` now threads the target `sender_id` into the INIT and UPDATE prompts as `{target_user}`, and those prompts attribute each fact to the speaker who stated it. Previously the extraction prompts never received the target id, so in any multi-speaker slice — including an ordinary user↔assistant exchange — the model could attribute another participant's statements, or the assistant's own persona, to the profile owner. This applies to the active extraction paths the same target-aware attribution already encoded in the (until now unused) `TEAM_PROFILE_UPDATE_PROMPT`. Backward compatible: a caller-supplied `prompt=` override that omits `{target_user}` renders unchanged. + ## [0.3.1] - 2026-06-24 ### Fixed diff --git a/packages/everalgo-user-memory/src/everalgo/user_memory/profile.py b/packages/everalgo-user-memory/src/everalgo/user_memory/profile.py index b9d7616..3b9be01 100644 --- a/packages/everalgo-user-memory/src/everalgo/user_memory/profile.py +++ b/packages/everalgo-user-memory/src/everalgo/user_memory/profile.py @@ -92,7 +92,12 @@ async def _init_extract( prompt: str | None, ) -> Profile: conversation_text = _render_conversation(memcells) - rendered = render_prompt(PROFILE_INITIAL_EXTRACTION_PROMPT, prompt, conversation_text=conversation_text) + rendered = render_prompt( + PROFILE_INITIAL_EXTRACTION_PROMPT, + prompt, + conversation_text=conversation_text, + target_user=sender_id, + ) data = await _call_llm_for_profile_init(self._llm, rendered) explicit_info = data["explicit_info"] @@ -127,6 +132,7 @@ async def _update_extract( prompt, current_profile=current_profile_text, conversations=conversation_text, + target_user=sender_id, ) data = await _call_llm_for_profile_update(self._llm, rendered) @@ -247,7 +253,7 @@ def _render_conversation(memcells: Sequence[MemCell]) -> str: continue speaker = m.sender_name or m.sender_id user_id = m.sender_id or "" - time_str = format_message_timestamp(m.timestamp) + time_str = format_message_timestamp(m.timestamp)[:10] # 粗化到日期:去秒级噪声、回避 UTC 时区误读(画像抽取无需日内精度) lines.append(f"[{time_str}] {speaker}(user_id:{user_id}): {text}") if not lines: lines.append("(no prior MemCells in the cluster)") diff --git a/packages/everalgo-user-memory/src/everalgo/user_memory/prompts/en/profile.py b/packages/everalgo-user-memory/src/everalgo/user_memory/prompts/en/profile.py index 536a086..166b7df 100644 --- a/packages/everalgo-user-memory/src/everalgo/user_memory/prompts/en/profile.py +++ b/packages/everalgo-user-memory/src/everalgo/user_memory/prompts/en/profile.py @@ -1,217 +1,241 @@ -"""English prompts for ProfileExtractor. - -``PROFILE_INITIAL_EXTRACTION_PROMPT`` is the active prompt used by :class:`ProfileExtractor`; it -replaces the prior 2-stage ``CONVERSATION_PROFILE_PART1 + PART2`` flow with a single call returning -``{explicit_info, implicit_traits}``. The other prompts exported here (``PROFILE_UPDATE_PROMPT`` / -``PROFILE_COMPACT_PROMPT`` / ``TEAM_PROFILE_UPDATE_PROMPT``) cover maintenance operations not yet -consumed by :class:`ProfileExtractor` — kept for future minor extractor releases. - -Placeholders & rendering: all four templates use single-brace placeholders that survive -:py:meth:`str.format` because their JSON examples already escape literal braces as ``{{ }}``. -""" - -PROFILE_UPDATE_PROMPT = """ -**CRITICAL LANGUAGE RULE**: You MUST output in the SAME language as the input conversation content. If the conversation content is in Chinese, ALL output MUST be in Chinese. If in English, output in English. This is mandatory. - -You are a user profile updater. Based on conversation records, determine what operations to perform on the user profile. - -【Current User Profile】(Each item has an index number) -{current_profile} - -【Conversation Records】(Multiple conversations from the same topic) -{conversations} - -【Task】 -Analyze conversations and output a list of operations (can have multiple). Available action types: -- **update**: Modify existing items (specify by index) -- **add**: Add profile items -- **delete**: Delete existing items -- **none**: No operation needed (use when conversation contains no user info) - -【Operation Guide】 -- **update**: Existing item has updates, supplements, or corrections -- **add**: Discovered completely new user information (unrelated to existing items) -- **delete**: Should delete in these cases: - - User explicitly negates (e.g., "I'm no longer vegetarian") - - Info is outdated (e.g., "traveling next week" but it's already passed) - - Too trivial/useless (e.g., "want pizza today") - - Directly contradicts new info - -【Important Rules】 -1. **Tag Mining**: Implicit traits must include [Personality Tags], e.g., [Risk-Averse], [Socially-Driven], [Data-Oriented]. -2. Only extract user info, don't treat AI assistant suggestions as user traits -3. evidence should include time info - e.g., "In Oct 2024 user mentioned..." -4. Index numbers for explicit_info and implicit_traits are independent -5. **Deduplication**: Before using "add", carefully check ALL existing items. If a similar trait/info already exists (even with different wording), use "update" to enrich it instead of adding a duplicate. Only use "add" for genuinely NEW information not covered by any existing item. - -【Profile Definitions & Analysis Framework】 -- **explicit_info (Explicit Information)**: User facts that can be directly extracted from conversations. - - *Content*: Basic info, health status, skills, clear preferences. - -- **implicit_traits (Implicit Traits)**: Psychological profile, personality tags, and decision styles inferred from behavior. - - *Extraction Requirement*: Freely analyze from dimensions like decision patterns, social preferences, and life philosophy. - - *Naming Convention*: - 1. Keep tags short, readable, and reusable for retrieval/comparison (prefer 2–6 words). - 2. Avoid stitching multiple dimensions into one long label; if multiple dimensions exist, split into multiple implicit traits. - 3. Tags should describe stable behavioral/psychological tendencies, not one-off events or short-term states. - - Make reasonable inferences to extract the user's deep traits - -【Output Format】 -No operations: -```json -{{"operations": [{{"action": "none"}}], "update_note": "conversation contains no user info"}} -``` - -With operations (can combine multiple add/update/delete): -```json -{{ - "operations": [ - {{"action": "add", "type": "explicit_info", "data": {{"category": "...", "description": "...", "evidence": "..."}}}}, - {{"action": "add", "type": "implicit_traits", "data": {{"trait": "...", "description": "...", "basis": "...", "evidence": "..."}}}}, - {{"action": "update", "type": "explicit_info", "index": 0, "data": {{"description": "..."}}}}, - {{"action": "delete", "type": "implicit_traits", "index": 1, "reason": "..."}} - ], - "update_note": "added 2 explicit info and 1 implicit trait, updated 1, deleted 1" -}} -``` - -**CRITICAL LANGUAGE RULE**: You MUST output in the SAME language as the input conversation content. If the conversation content is in Chinese, ALL output MUST be in Chinese. If in English, output in English. This is mandatory. -""" - -PROFILE_COMPACT_PROMPT = """ -**CRITICAL LANGUAGE RULE**: You MUST output in the SAME language as the input conversation content. If the conversation content is in Chinese, ALL output MUST be in Chinese. If in English, output in English. This is mandatory. - -The current user profile has {total_items} items (explicit_info + implicit_traits combined), exceeding the limit of {max_items}. - -Please compact the profile to **{max_items} items TOTAL** (explicit_info + implicit_traits combined, NOT {max_items} each). - -Compaction strategies: -1. **Merge Similar Items**: Combine multiple records of the same dimension into one "Current State + Trend" description. -2. **Refine Tags**: Implicit traits should be summarized as personality tags (e.g., [Risk-Averse]), removing repetitive or shallow descriptions. -3. Delete unimportant, outdated, or short-term statuses. -4. Preserve item fields (especially evidence). - -Current Profile: -{profile_text} - -**IMPORTANT**: Output must have explicit_info + implicit_traits ≤ {max_items} items TOTAL. -```json -{{ - "explicit_info": [ - {{"category": "...", "description": "...", "evidence": "..."}} - ], - "implicit_traits": [ - {{"trait": "...", "description": "...", "basis": "...", "evidence": "..."}} - ], - "compact_note": "Explain what was deleted/merged" -}} -``` - -**CRITICAL LANGUAGE RULE**: You MUST output in the SAME language as the input conversation content. If the conversation content is in Chinese, ALL output MUST be in Chinese. If in English, output in English. This is mandatory. -""" - -PROFILE_INITIAL_EXTRACTION_PROMPT = """ -**CRITICAL LANGUAGE RULE**: You MUST output in the SAME language as the input conversation content. If the conversation content is in Chinese, ALL output MUST be in Chinese. If in English, output in English. This is mandatory. - -You are a "User Profile Analyst". Please read the conversation below and build a user profile. - -【Part 1: Explicit Information (explicit_info)】 -Objective facts and current status. - -【Part 2: Implicit Traits (implicit_traits)】 -Psychological profile, personality tags, and decision styles inferred from behavior. -*Extraction Requirement*: Freely analyze decision making, social patterns, and values. Trait field must be a highly summarized [Adjective/Noun Phrase Tag]. - -【Extraction Principles】 -1. Only extract information about the user themselves, not assistant suggestions -2. Implicit traits must be supported by multiple evidence: each implicit trait must have evidence corroborated by multiple signals from the conversations and/or existing profile; do not infer from a single data point alone -3. Describe each piece of information in one natural sentence, easy to understand - -【Output Format】 -Output JSON directly in the following format: -```json -{{ - "explicit_info": [ - {{ - "category": "category name", - "description": "one sentence description", - "evidence": "one-sentence evidence grounded in the conversations" - }} - ], - "implicit_traits": [ - {{ - "trait": "trait name", - "description": "one sentence description of this trait", - "basis": "inferred from which behaviors/conversations", - "evidence": "one-sentence evidence grounded in the conversations" - }} - ] -}} -``` - -LANGUAGE RULE: Detect the language of the input conversation and respond in the SAME language. If the conversation is in Chinese, output in Chinese. If in English, output in English. - -【Original Conversation】 -{conversation_text}""" - - -TEAM_PROFILE_UPDATE_PROMPT = """ -**CRITICAL LANGUAGE RULE**: You MUST output in the SAME language as the input conversation content. If the conversation content is in Chinese, ALL output MUST be in Chinese. If in English, output in English. This is mandatory. - -You are a user profile updater for **group conversations**. Your task is to extract and update the profile for ONE specific user from a multi-person conversation. - -**TARGET USER: {target_user}** -You MUST only extract information about **{target_user}**. Carefully attribute each piece of information to the correct speaker. Do NOT mix up information from different participants. - -【Current Profile for {target_user}】(Each item has an index number) -{current_profile} - -【Group Conversation Records】(Multiple participants - only extract info about {target_user}) -{conversations} - -【Task】 -Analyze the conversations and output operations ONLY for information about **{target_user}**. Available action types: -- **update**: Modify existing items (specify by index) -- **add**: Add profile items -- **delete**: Delete existing items -- **none**: No operation needed (use when conversation contains no info about {target_user}) - -【Operation Guide】 -- **update**: Existing item has updates, supplements, or corrections -- **add**: Discovered completely new information about {target_user} (unrelated to existing items) -- **delete**: Should delete in these cases: - - {target_user} explicitly negates something (e.g., "I'm no longer vegetarian") - - Info is outdated or directly contradicts new info - -【Important Rules】 -1. **Speaker Attribution**: This is a GROUP conversation with multiple speakers. ONLY extract what **{target_user}** said or what is explicitly about {target_user}. If another participant mentions a fact, it belongs to THAT participant's profile, NOT {target_user}'s. -2. **Tag Mining**: Implicit traits must include [Personality Tags], e.g., [Risk-Averse], [Socially-Driven], [Data-Oriented]. -3. evidence should include time info and speaker - e.g., "In Oct 2024 {target_user} stated..." -4. Index numbers for explicit_info and implicit_traits are independent -5. **Deduplication**: Before using "add", check ALL existing items. If a similar trait/info already exists, use "update" instead. Only "add" genuinely NEW information. - -【Profile Definitions】 -- **explicit_info**: Facts directly stated by or about {target_user} (skills, background, preferences, location, etc.) -- **implicit_traits**: Personality traits and behavioral patterns inferred from {target_user}'s statements and behavior in the conversation. - -【Output Format】 -No operations: -```json -{{"operations": [{{"action": "none"}}], "update_note": "conversation contains no info about {target_user}"}} -``` - -With operations: -```json -{{ - "operations": [ - {{"action": "add", "type": "explicit_info", "data": {{"category": "...", "description": "...", "evidence": "..."}}}}, - {{"action": "add", "type": "implicit_traits", "data": {{"trait": "...", "description": "...", "basis": "...", "evidence": "..."}}}}, - {{"action": "update", "type": "explicit_info", "index": 0, "data": {{"description": "..."}}}}, - {{"action": "delete", "type": "implicit_traits", "index": 1, "reason": "..."}} - ], - "update_note": "..." -}} -``` - -**CRITICAL LANGUAGE RULE**: You MUST output in the SAME language as the input conversation content. If the conversation content is in Chinese, ALL output MUST be in Chinese. If in English, output in English. This is mandatory. -""" +"""English prompts for ProfileExtractor. + +``PROFILE_INITIAL_EXTRACTION_PROMPT`` is the active prompt used by :class:`ProfileExtractor`; it +replaces the prior 2-stage ``CONVERSATION_PROFILE_PART1 + PART2`` flow with a single call returning +``{explicit_info, implicit_traits}``. The other prompts exported here (``PROFILE_UPDATE_PROMPT`` / +``PROFILE_COMPACT_PROMPT`` / ``TEAM_PROFILE_UPDATE_PROMPT``) cover maintenance operations not yet +consumed by :class:`ProfileExtractor` — kept for future minor extractor releases. + +Placeholders & rendering: all four templates use single-brace placeholders that survive +:py:meth:`str.format` because their JSON examples already escape literal braces as ``{{ }}``. +""" + +PROFILE_UPDATE_PROMPT = """ +**CRITICAL LANGUAGE RULE**: You MUST output in the SAME language as the input conversation content. If the conversation content is in Chinese, ALL output MUST be in Chinese. If in English, output in English. This is mandatory. + +You are a user profile updater. Based on conversation records, determine what operations to perform on the user profile. + +**TARGET USER: user_id={target_user}** +Operate ONLY on information about the user whose id is {target_user}. Each conversation line is tagged `(user_id:...)`; attribute every fact to the speaker who stated it. Other participants and the AI assistant are context, never the target. + +【Current User Profile】(Each item has an index number) +{current_profile} + +【Conversation Records】(Multiple conversations from the same topic) +{conversations} + +【Task】 +Analyze conversations and output a list of operations (can have multiple). Available action types: +- **update**: Modify existing items (specify by index) +- **add**: Add profile items +- **delete**: Delete existing items +- **none**: No operation needed (use when conversation contains no user info) + +【Operation Guide】 +- **update**: Existing item has updates, supplements, or corrections +- **add**: Discovered completely new user information (unrelated to existing items) +- **delete**: Should delete in these cases: + - User explicitly negates (e.g., "I'm no longer vegetarian") + - Info is outdated (e.g., "traveling next week" but it's already passed) + - Too trivial/useless (e.g., "want pizza today") + - Directly contradicts new info + +【Important Rules】 +1. **Tag Mining**: Implicit traits must include [Personality Tags], e.g., [Risk-Averse], [Socially-Driven], [Data-Oriented]. +2. **Speaker attribution**: extract information about the target user (user_id={target_user}) ONLY. If another participant — or the AI assistant — states a fact, it belongs to THEM, not the target; never let the assistant's own identity or persona become a user trait. +3. evidence should include time info - e.g., "In Oct 2024 user mentioned..." +4. Index numbers for explicit_info and implicit_traits are independent +5. **Deduplication**: Before using "add", carefully check ALL existing items. If a similar trait/info already exists (even with different wording), use "update" to enrich it instead of adding a duplicate. Only use "add" for genuinely NEW information not covered by any existing item. +6. **Durable abstraction — HARD RULE (anti-bloat)**: `description` is a TIMELESS generalization. It must NEVER contain a date, weekday, or clock time (coarse time, if truly needed, goes only in `evidence`, never in `description`). + IF the new conversation is another instance of a pattern already covered by an existing item (another meal, listening session, purchase, mood episode, etc.) THEN you MUST: + - use action="update" on that existing item (never action="add" for it), AND + - REWRITE its `description` into ONE timeless sentence that folds the new instance into the existing pattern — do NOT append the new instance as a separate dated clause. + Example: + WRONG (appended dated instance): "Prefers napping after lunch. On 2026-07-01 napped again after lunch and reported waking up refreshed." + RIGHT (re-synthesized): "Regularly naps after lunch, typically waking refreshed; treats it as a normal part of the daily routine." + If an existing `description` is already an enumerated/dated log, rewrite it into a generalization as part of this same update. + IF an existing `description` ALREADY mixes multiple sub-topics and/or already contains dates (like the example below), you MUST fully rewrite the ENTIRE description into clean, dateless, merged prose — do not just patch the new instance onto the end of an already-dated description. + Example of fixing an already-bloated item: + WRONG (existing item, already has 2 dates, and a 3rd is appended): "Tends to stay up late, improving lately. On 2026-06-29 stayed up late to confess something. Also naps some afternoons. On 2026-07-01 napped again, waking with no dreams." + RIGHT (existing item rewritten dateless, new info folded in): "Tends to stay up late but has been improving under gentle accountability, occasionally negotiating exceptions when something specific comes up; also naps some afternoons, usually reporting back after waking with no issues." + Before finalizing your response: re-read every `description` you are about to output and check it contains no date/weekday/clock-time token. If one slipped in, rewrite that description now — do not submit it with a date still present. + +【Profile Definitions & Analysis Framework】 +- **explicit_info (Explicit Information)**: User facts that can be directly extracted from conversations. + - *Content*: Basic info, health status, skills, clear preferences. + +- **implicit_traits (Implicit Traits)**: Psychological profile, personality tags, and decision styles inferred from behavior. + - *Extraction Requirement*: Freely analyze from dimensions like decision patterns, social preferences, and life philosophy. + - *Naming Convention*: + 1. Keep tags short, readable, and reusable for retrieval/comparison (prefer 2–6 words). + 2. Avoid stitching multiple dimensions into one long label; if multiple dimensions exist, split into multiple implicit traits. + 3. Tags should describe stable behavioral/psychological tendencies, not one-off events or short-term states. + - Make reasonable inferences to extract the user's deep traits + +【Output Format】 +No operations: +```json +{{"operations": [{{"action": "none"}}], "update_note": "conversation contains no user info"}} +``` + +With operations (can combine multiple add/update/delete): +```json +{{ + "operations": [ + {{"action": "add", "type": "explicit_info", "data": {{"category": "...", "description": "...", "evidence": "..."}}}}, + {{"action": "add", "type": "implicit_traits", "data": {{"trait": "...", "description": "...", "basis": "...", "evidence": "..."}}}}, + {{"action": "update", "type": "explicit_info", "index": 0, "data": {{"description": "..."}}}}, + {{"action": "delete", "type": "implicit_traits", "index": 1, "reason": "..."}} + ], + "update_note": "added 2 explicit info and 1 implicit trait, updated 1, deleted 1" +}} +``` + +**CRITICAL LANGUAGE RULE**: You MUST output in the SAME language as the input conversation content. If the conversation content is in Chinese, ALL output MUST be in Chinese. If in English, output in English. This is mandatory. +""" + +PROFILE_COMPACT_PROMPT = """ +**CRITICAL LANGUAGE RULE**: You MUST output in the SAME language as the input conversation content. If the conversation content is in Chinese, ALL output MUST be in Chinese. If in English, output in English. This is mandatory. + +The current user profile has {total_items} items (explicit_info + implicit_traits combined), exceeding the limit of {max_items}. + +Please compact the profile to **{max_items} items TOTAL** (explicit_info + implicit_traits combined, NOT {max_items} each). + +Compaction strategies: +1. **Merge Similar Items**: Combine multiple records of the same dimension into one "Current State + Trend" description. +2. **Refine Tags**: Implicit traits should be summarized as personality tags (e.g., [Risk-Averse]), removing repetitive or shallow descriptions. +3. Delete unimportant, outdated, or short-term statuses. +4. Preserve item fields (especially evidence). + +Current Profile: +{profile_text} + +**IMPORTANT**: Output must have explicit_info + implicit_traits ≤ {max_items} items TOTAL. +```json +{{ + "explicit_info": [ + {{"category": "...", "description": "...", "evidence": "..."}} + ], + "implicit_traits": [ + {{"trait": "...", "description": "...", "basis": "...", "evidence": "..."}} + ], + "compact_note": "Explain what was deleted/merged" +}} +``` + +**CRITICAL LANGUAGE RULE**: You MUST output in the SAME language as the input conversation content. If the conversation content is in Chinese, ALL output MUST be in Chinese. If in English, output in English. This is mandatory. +""" + +PROFILE_INITIAL_EXTRACTION_PROMPT = """ +**CRITICAL LANGUAGE RULE**: You MUST output in the SAME language as the input conversation content. If the conversation content is in Chinese, ALL output MUST be in Chinese. If in English, output in English. This is mandatory. + +You are a "User Profile Analyst". Please read the conversation below and build a user profile. + +**TARGET USER: user_id={target_user}** +Build the profile for THIS user only. The conversation may include several speakers — other participants and the AI assistant. Each line is tagged `(user_id:...)`; attribute information ONLY to the user whose id is {target_user}. Everyone else, the assistant included, is context — never the subject of this profile. + +【Part 1: Explicit Information (explicit_info)】 +Objective facts and current status. + +【Part 2: Implicit Traits (implicit_traits)】 +Psychological profile, personality tags, and decision styles inferred from behavior. +*Extraction Requirement*: Freely analyze decision making, social patterns, and values. Trait field must be a highly summarized [Adjective/Noun Phrase Tag]. + +【Extraction Principles】 +1. Extract information about the target user (user_id={target_user}) ONLY. Never attribute to the target anything said by another participant or by the AI assistant — including the assistant's own name, persona, role, or first-person self-description. The assistant describes itself, never the user. +2. Implicit traits must be supported by multiple evidence: each implicit trait must have evidence corroborated by multiple signals from the conversations and/or existing profile; do not infer from a single data point alone +3. **Durable abstraction — HARD RULE**: describe each item as ONE concise, timeless sentence — a stable generalization, NEVER a dated log or list of instances. `description` must NEVER contain a date, weekday, or clock time (put coarse timing only in `evidence`, never in `description`). + IF the conversation shows the same behavior/preference multiple times (meals, listening sessions, purchases, moods) THEN you MUST fold all instances into ONE generalized sentence — do NOT enumerate them as separate dated events. + Example: + WRONG: "Ate ramen on 06-05, pasta on 06-07, and ramen again on 06-10." + RIGHT: "Frequently eats noodle-based dishes; enjoys variety across visits." + Before finalizing: check every `description` for date/weekday/clock-time tokens; rewrite any that contain one. + +【Output Format】 +Output JSON directly in the following format: +```json +{{ + "explicit_info": [ + {{ + "category": "category name", + "description": "one sentence description", + "evidence": "one-sentence evidence grounded in the conversations" + }} + ], + "implicit_traits": [ + {{ + "trait": "trait name", + "description": "one sentence description of this trait", + "basis": "inferred from which behaviors/conversations", + "evidence": "one-sentence evidence grounded in the conversations" + }} + ] +}} +``` + +LANGUAGE RULE: Detect the language of the input conversation and respond in the SAME language. If the conversation is in Chinese, output in Chinese. If in English, output in English. + +【Original Conversation】 +{conversation_text}""" + + +TEAM_PROFILE_UPDATE_PROMPT = """ +**CRITICAL LANGUAGE RULE**: You MUST output in the SAME language as the input conversation content. If the conversation content is in Chinese, ALL output MUST be in Chinese. If in English, output in English. This is mandatory. + +You are a user profile updater for **group conversations**. Your task is to extract and update the profile for ONE specific user from a multi-person conversation. + +**TARGET USER: {target_user}** +You MUST only extract information about **{target_user}**. Carefully attribute each piece of information to the correct speaker. Do NOT mix up information from different participants. + +【Current Profile for {target_user}】(Each item has an index number) +{current_profile} + +【Group Conversation Records】(Multiple participants - only extract info about {target_user}) +{conversations} + +【Task】 +Analyze the conversations and output operations ONLY for information about **{target_user}**. Available action types: +- **update**: Modify existing items (specify by index) +- **add**: Add profile items +- **delete**: Delete existing items +- **none**: No operation needed (use when conversation contains no info about {target_user}) + +【Operation Guide】 +- **update**: Existing item has updates, supplements, or corrections +- **add**: Discovered completely new information about {target_user} (unrelated to existing items) +- **delete**: Should delete in these cases: + - {target_user} explicitly negates something (e.g., "I'm no longer vegetarian") + - Info is outdated or directly contradicts new info + +【Important Rules】 +1. **Speaker Attribution**: This is a GROUP conversation with multiple speakers. ONLY extract what **{target_user}** said or what is explicitly about {target_user}. If another participant mentions a fact, it belongs to THAT participant's profile, NOT {target_user}'s. +2. **Tag Mining**: Implicit traits must include [Personality Tags], e.g., [Risk-Averse], [Socially-Driven], [Data-Oriented]. +3. evidence should include time info and speaker - e.g., "In Oct 2024 {target_user} stated..." +4. Index numbers for explicit_info and implicit_traits are independent +5. **Deduplication**: Before using "add", check ALL existing items. If a similar trait/info already exists, use "update" instead. Only "add" genuinely NEW information. + +【Profile Definitions】 +- **explicit_info**: Facts directly stated by or about {target_user} (skills, background, preferences, location, etc.) +- **implicit_traits**: Personality traits and behavioral patterns inferred from {target_user}'s statements and behavior in the conversation. + +【Output Format】 +No operations: +```json +{{"operations": [{{"action": "none"}}], "update_note": "conversation contains no info about {target_user}"}} +``` + +With operations: +```json +{{ + "operations": [ + {{"action": "add", "type": "explicit_info", "data": {{"category": "...", "description": "...", "evidence": "..."}}}}, + {{"action": "add", "type": "implicit_traits", "data": {{"trait": "...", "description": "...", "basis": "...", "evidence": "..."}}}}, + {{"action": "update", "type": "explicit_info", "index": 0, "data": {{"description": "..."}}}}, + {{"action": "delete", "type": "implicit_traits", "index": 1, "reason": "..."}} + ], + "update_note": "..." +}} +``` + +**CRITICAL LANGUAGE RULE**: You MUST output in the SAME language as the input conversation content. If the conversation content is in Chinese, ALL output MUST be in Chinese. If in English, output in English. This is mandatory. +""" \ No newline at end of file diff --git a/packages/everalgo-user-memory/src/everalgo/user_memory/prompts/zh/profile.py b/packages/everalgo-user-memory/src/everalgo/user_memory/prompts/zh/profile.py index 1729327..849d898 100644 --- a/packages/everalgo-user-memory/src/everalgo/user_memory/prompts/zh/profile.py +++ b/packages/everalgo-user-memory/src/everalgo/user_memory/prompts/zh/profile.py @@ -9,6 +9,9 @@ # Incremental Update Prompt PROFILE_UPDATE_PROMPT = """你是用户画像更新员。根据对话记录,判断需要对用户画像做哪些操作。 +**目标用户:user_id={target_user}** +只对 user_id 为 {target_user} 的用户的信息执行操作。每行对话都带 `(user_id:...)` 标签;把每个事实归属给真正陈述它的发言者。其他参与者和 AI 助手都只是上下文,绝不是目标用户。 + 【当前用户画像】(每条都有 index 编号) {current_profile} @@ -32,7 +35,7 @@ 【重要规则】 1. **挖掘标签**:隐式特征必须包含【性格标签】,例如:[风险厌恶型]、[社交驱动型]、[数据考据党]。 -2. 只提取用户信息,不要把 AI 助手的建议当成用户特征 +2. **发言者归属**:只提取目标用户(user_id={target_user})的信息。如果其他参与者或 AI 助手陈述了某个事实,那属于他们,不属于目标用户;绝不要让助手自身的身份或人设变成用户特征。 3. evidence 要包含时间信息 - 如"2024年10月用户提到..." 4. explicit_info 和 implicit_traits 的 index 是独立编号的 5. **去重**:在使用 "add" 前,仔细检查所有已有条目。如果类似的特征/信息已存在(即使措辞不同),请用 "update" 来补充而非重复添加。只有确实全新的信息才用 "add"。 @@ -97,6 +100,9 @@ # Initial Extraction Prompt PROFILE_INITIAL_EXTRACTION_PROMPT = """你是一个"用户画像分析师"。请阅读下面的对话,构建用户画像。 +**目标用户:user_id={target_user}** +只为该用户构建画像。对话中可能有多个发言者——其他参与者以及 AI 助手。每行都带 `(user_id:...)` 标签;只把信息归属给 user_id 为 {target_user} 的用户。其他所有人(包括助手)都只是上下文,绝不是本画像的主体。 + 【第一部分:显式信息 (explicit_info)】 用户的客观事实和当前状态,如身高体重、喜好、疾病等。 @@ -106,7 +112,7 @@ *命名规范*:Trait 字段必须简练精准,推荐“[形容词] [名词]”格式,严禁过度堆砌形容词。 【提取原则】 -1. 只提取用户本人的信息,不要把助手的建议当成用户特征 +1. 只提取目标用户(user_id={target_user})的信息。绝不要把其他参与者或 AI 助手说的话归到目标用户头上——包括助手自身的名字、人设、角色或第一人称自述。助手描述的是它自己,永远不是用户。 2. 隐式特征必须有多个证据支撑:同一条隐式特征的 evidence 必须来自多个信号;证据可来自【当前对话】与/或【已有画像 current_profile 的 evidence】(更新时可用),不能仅凭单条新对话臆断 3. 每条信息用一句自然语言描述,通俗易懂 diff --git a/packages/everalgo-user-memory/tests/user_memory/test_user_memory_profile.py b/packages/everalgo-user-memory/tests/user_memory/test_user_memory_profile.py index 82f51bd..b0315fa 100644 --- a/packages/everalgo-user-memory/tests/user_memory/test_user_memory_profile.py +++ b/packages/everalgo-user-memory/tests/user_memory/test_user_memory_profile.py @@ -205,6 +205,42 @@ def handler(messages: list[LLMChatMessage], **kwargs: Any) -> ChatResponse: assert "Default content" in captured["prompt"] +async def test_aextract_threads_target_user_into_init_prompt() -> None: + """INIT renders the target ``sender_id`` into the prompt so the LLM attributes facts to one speaker.""" + captured: dict[str, str] = {} + payload = _payload( + explicit_info=[{"category": "x", "description": "y", "evidence": "z"}], + implicit_traits=[], + ) + + def handler(messages: list[LLMChatMessage], **kwargs: Any) -> ChatResponse: + assert isinstance(messages[0].content, str) # narrow for test + captured["prompt"] = messages[0].content + return ChatResponse(content=payload, model="fake") + + fake = FakeLLMClient(handler=handler) + + await ProfileExtractor(llm=fake).aextract([_memcell()], sender_id="u_alice") + + assert "TARGET USER: user_id=u_alice" in captured["prompt"] + + +async def test_aextract_threads_target_user_into_update_prompt() -> None: + """UPDATE renders the target ``sender_id`` into the prompt (same attribution discipline as INIT).""" + captured: dict[str, str] = {} + + def handler(messages: list[LLMChatMessage], **kwargs: Any) -> ChatResponse: + assert isinstance(messages[0].content, str) # narrow for test + captured["prompt"] = messages[0].content + return ChatResponse(content=json.dumps({"operations": [{"action": "none"}]}), model="fake") + + fake = FakeLLMClient(handler=handler) + + await ProfileExtractor(llm=fake).aextract([_memcell()], sender_id="u_alice", old_profile=_old_profile()) + + assert "TARGET USER: user_id=u_alice" in captured["prompt"] + + # ========================================================================== # _render_conversation helper # ==========================================================================