Refine member context profiling detail

This commit is contained in:
liuwei
2026-04-08 10:27:14 +08:00
parent fa98c9e6a5
commit 6401ec02de
7 changed files with 488 additions and 16 deletions

View File

@@ -189,9 +189,13 @@ class ContextBuilder:
meta = member_context.get("meta", {}) or {}
topics = member_context.get("topics_of_interest", []) or []
recent_focus = member_context.get("recent_focus", []) or []
common_scenarios = ContextBuilder._stringify_items(meta.get("common_scenarios", []), 4)
skills = ContextBuilder._stringify_items(meta.get("skill_profile", []), 5)
problem_solving = ContextBuilder._stringify_items(meta.get("problem_solving_profile", []), 4)
stable_traits = ContextBuilder._stringify_items(meta.get("stable_traits", []), 4)
habits = ContextBuilder._stringify_items(meta.get("habit_patterns", []), 4)
expression_profile = ContextBuilder._stringify_items(meta.get("expression_profile", []), 4)
reply_entry = ContextBuilder._stringify_items(meta.get("reply_entry_profile", []), 4)
reply_prefs = ContextBuilder._stringify_items(meta.get("long_term_reply_preferences", []), 4)
recent_state = ContextBuilder._stringify_items(meta.get("recent_state", []), 4)
reply_taboos = ContextBuilder._stringify_items(meta.get("reply_taboos", []), 3)
@@ -201,9 +205,13 @@ class ContextBuilder:
f"回复偏好:{member_context.get('response_style_hint', '')}".strip(),
f"长期主题:{', '.join(topics[:5])}" if topics else "",
f"近期关注:{', '.join(recent_focus[:4])}" if recent_focus else "",
f"常见发言场景:{common_scenarios}" if common_scenarios else "",
f"技能侧重点:{skills}" if skills else "",
f"处理问题方式:{problem_solving}" if problem_solving else "",
f"稳定特征:{stable_traits}" if stable_traits else "",
f"习惯模式:{habits}" if habits else "",
f"表达标记:{expression_profile}" if expression_profile else "",
f"有效接话点:{reply_entry}" if reply_entry else "",
f"长期回复偏好:{reply_prefs}" if reply_prefs else "",
f"近期状态:{recent_state}" if recent_state else "",
f"气质倾向:{meta.get('temperament_tendency', '')}".strip(),

View File

@@ -67,10 +67,14 @@
3. 请先按 wxid 汇总该成员全天发言,再提取结果。
4. 即使成员发言以短句为主,只要样本量足够,也必须尽量提炼:
- topics
- discussion_scenarios
- skill_signals
- problem_solving_signals
- value_preferences
- habit_signals
- expression_markers
- engagement_traits
- reply_entry_points
- social_role
- temperament_signal
- summary_text
@@ -88,8 +92,10 @@
"wxid": "成员wxid",
"display_name": "显示名",
"topics": ["主题1"],
"discussion_scenarios": ["场景1"],
"identity_clues": ["身份线索1"],
"skill_signals": ["技能信号1"],
"problem_solving_signals": ["处理方式1"],
"family_signals": ["家庭线索1"],
"life_stage_signals": ["阶段线索1"],
"value_preferences": ["价值偏好1"],
@@ -97,7 +103,9 @@
"message_pattern": "一句中文",
"response_style_hint": "一句中文",
"habit_signals": ["习惯1"],
"expression_markers": ["表达标记1"],
"engagement_traits": ["参与特征1"],
"reply_entry_points": ["接话点1"],
"decision_style": "一句中文",
"social_role": "一句中文",
"reply_taboos": ["避坑1"],

View File

@@ -149,10 +149,12 @@ class MemberDigestService:
normalized = {}
list_keys = {
"topics", "identity_clues", "skill_signals", "family_signals", "life_stage_signals",
"value_preferences", "habit_signals", "engagement_traits", "reply_taboos",
"discussion_scenarios", "problem_solving_signals", "value_preferences", "habit_signals",
"expression_markers", "engagement_traits", "reply_entry_points", "reply_taboos",
"representative_messages", "stable_topics", "identity_traits", "skill_profile",
"family_profile", "life_stage_profile", "value_profile", "stable_traits",
"habit_patterns", "reply_preferences", "long_term_topics", "long_term_reply_preferences",
"problem_solving_profile", "family_profile", "life_stage_profile", "value_profile",
"stable_traits", "habit_patterns", "expression_profile", "reply_entry_profile",
"reply_preferences", "long_term_topics", "long_term_reply_preferences", "common_scenarios",
"phase_state", "recent_state"
}
for key, value in item.items():

View File

@@ -0,0 +1,351 @@
app:
description: 按群和日期提取群成员日画像,输出严格 JSON供 member_context 插件直接消费
icon: 🤖
icon_background: '#E0F2FE'
mode: workflow
name: member_context
use_icon_as_answer_icon: false
dependencies:
- current_identifier: null
type: marketplace
value:
marketplace_plugin_unique_identifier: langgenius/openai_api_compatible:0.0.27@f9ce3ff5e28f09931a3a7fca59add2d09590408f7e9a3d701b10c77a60249719
version: null
kind: app
version: 0.5.0
workflow:
conversation_variables: []
environment_variables: []
features:
file_upload:
allowed_file_extensions:
- .TXT
allowed_file_types:
- document
allowed_file_upload_methods:
- local_file
- remote_url
enabled: false
fileUploadConfig:
audio_file_size_limit: 50
batch_count_limit: 5
file_size_limit: 15
image_file_batch_limit: 10
image_file_size_limit: 10
single_chunk_attachment_limit: 10
video_file_size_limit: 100
workflow_file_upload_limit: 10
image:
enabled: false
number_limits: 3
transfer_methods:
- local_file
- remote_url
number_limits: 3
opening_statement: ''
retriever_resource:
enabled: false
sensitive_word_avoidance:
enabled: false
speech_to_text:
enabled: false
suggested_questions: []
suggested_questions_after_answer:
enabled: false
text_to_speech:
enabled: false
language: ''
voice: ''
graph:
edges:
- data:
isInIteration: false
isInLoop: false
sourceType: start
targetType: llm
id: start-source-llm-target
selected: false
source: start_node
sourceHandle: source
target: llm_node
targetHandle: target
type: custom
zIndex: 0
- data:
isInIteration: false
isInLoop: false
sourceType: llm
targetType: end
id: llm-source-end-target
selected: false
source: llm_node
sourceHandle: source
target: end_node
targetHandle: target
type: custom
zIndex: 0
- data:
isInIteration: false
isInLoop: false
sourceType: llm
targetType: llm
id: llm_node-fail-branch-1775115372864-target
source: llm_node
sourceHandle: fail-branch
target: '1775115372864'
targetHandle: target
type: custom
zIndex: 0
- data:
isInLoop: false
sourceType: llm
targetType: end
id: 1775115372864-source-end_node-target
source: '1775115372864'
sourceHandle: source
target: end_node
targetHandle: target
type: custom
zIndex: 0
nodes:
- data:
desc: ''
selected: false
title: 开始
type: start
variables:
- label: query
max_length: 120000
options: []
required: false
type: paragraph
variable: query
- label: chatroom_id
max_length: 128
options: []
required: true
type: text-input
variable: chatroom_id
- label: digest_date
max_length: 32
options: []
required: true
type: text-input
variable: digest_date
- label: member_labels
max_length: 50000
options: []
required: true
type: paragraph
variable: member_labels
- label: compressed_chat
max_length: 200000
options: []
required: true
type: paragraph
variable: compressed_chat
height: 213
id: start_node
position:
x: 0
y: 0
positionAbsolute:
x: 0
y: 0
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 242
- data:
context:
enabled: false
variable_selector: []
desc: ''
error_strategy: fail-branch
model:
completion_params:
temperature: 0.2
mode: chat
name: gpt-5.4-mini
provider: langgenius/openai_api_compatible/openai_api_compatible
prompt_template:
- id: system_prompt_member_context
role: system
text: "你是微信群后台的成员日行为证据提取器。\n\n任务\n根据给定的一天群聊记录只按 wxid 识别成员,输出每个成员当天的结构化行为观察。\n\
\n关键规则\n1. wxid 是唯一标识。display_name 仅用于展示,不用于身份判定。\n2. 每个 wxid 最终只能输出一条记录,严禁重复输出同一个\
\ wxid。\n3. 请先按 wxid 汇总该成员全天发言,再提取结果。\n4. 即使成员发言以短句为主,只要样本量足够,也必须尽量提炼:\n\
\ - topics\n - discussion_scenarios\n - skill_signals\n - problem_solving_signals\n\
\ - value_preferences\n - habit_signals\n - expression_markers\n\
\ - engagement_traits\n - reply_entry_points\n - social_role\n\
\ - temperament_signal\n - summary_text\n5. identity_clues、family_signals、life_stage_signals\
\ 没有明确公开证据时允许为空。\n6. 不允许因为“短句较多”就统一输出空数组和通用摘要。\n7. 不做心理诊断、不做隐私猜测、不把玩笑当事实。\n\
8. 只能输出候选成员列表中的 wxid。\n9. topics 更偏向反复出现的关注方向discussion_scenarios 更偏向什么情境下会发言skill_signals\
\ 更偏向能力表现problem_solving_signals 更偏向怎么处理问题value_preferences 更偏向判断偏好social_role 更偏向当天在群里的实际作用。\n\
10. expression_markers 记录可观察的表达标记,如常用句式、口头禅、是否爱列步骤/贴日志reply_entry_points 记录什么样的接话方式最容易接住他。\n\
11. 输出前自行去重,同一个 wxid 只保留一条最完整结果。\n\n输出要求\n- 只输出严格 JSON不要 markdown不要解释不要前后缀。\n\
- 输出格式:\n{\n \"members\": [\n {\n \"wxid\": \"成员wxid\",\n \
\ \"display_name\": \"显示名\",\n \"topics\": [\"主题1\"],\n \"discussion_scenarios\"\
: [\"场景1\"],\n \"identity_clues\": [\"身份线索1\"],\n \"skill_signals\"\
: [\"技能信号1\"],\n \"problem_solving_signals\": [\"处理方式1\"],\n \
\ \"family_signals\": [\"家庭线索1\"],\n \"life_stage_signals\": [\"\
阶段线索1\"],\n \"value_preferences\": [\"价值偏好1\"],\n \"interaction_style\"\
: \"一句中文\",\n \"message_pattern\": \"一句中文\",\n \"response_style_hint\"\
: \"一句中文\",\n \"habit_signals\": [\"习惯1\"],\n \"expression_markers\"\
: [\"表达标记1\"],\n \"engagement_traits\": [\"参与特征1\"],\n \"reply_entry_points\"\
: [\"接话点1\"],\n \"decision_style\": \"一句中文\",\n \"social_role\"\
: \"一句中文\",\n \"reply_taboos\": [\"避坑1\"],\n \"temperament_signal\"\
: \"一句中文\",\n \"summary_text\": \"不超过100字\",\n \"representative_messages\"\
: [\"原话1\", \"原话2\"],\n \"confidence\": 0.95\n }\n ]\n}\n\n字段约束\n\
- topics、discussion_scenarios、skill_signals、problem_solving_signals、value_preferences、habit_signals、expression_markers、engagement_traits、reply_entry_points\
\ 最多 4 个\n- identity_clues、family_signals、life_stage_signals 最多 3 个\n\
- reply_taboos 最多 3 个\n- representative_messages 最多 3 条\n- 如果某成员样本明显不足,可以不输出该成员\n"
- id: user_prompt_member_context
role: user
text: '群ID: {{#start_node.chatroom_id#}}
日期: {{#start_node.digest_date#}}
候选成员:
{{#start_node.member_labels#}}
压缩后的群聊记录:
{{#start_node.compressed_chat#}}
'
retry_config:
max_retries: 3
retry_enabled: true
retry_interval: 1000
selected: false
title: 成员画像提取
type: llm
variables: []
vision:
enabled: false
height: 154
id: llm_node
position:
x: 342
y: 30
positionAbsolute:
x: 342
y: 30
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 242
- data:
desc: ''
outputs:
- value_selector:
- llm_node
- text
variable: text
selected: false
title: 结束
type: end
height: 88
id: end_node
position:
x: 1066
y: 52
positionAbsolute:
x: 1066
y: 52
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 242
- data:
context:
enabled: false
variable_selector: []
default_value:
- key: text
type: string
value: '{"members": []}'
error_strategy: default-value
model:
completion_params:
temperature: 0.7
mode: chat
name: grok-4-fast
provider: langgenius/openai_api_compatible/openai_api_compatible
prompt_template:
- id: c5ee983e-d6e0-4790-ac3b-f4e097013b70
role: system
text: "你是微信群后台的成员日行为证据提取器。\n\n任务\n根据给定的一天群聊记录只按 wxid 识别成员,输出每个成员当天的结构化行为观察。\n\
\n关键规则\n1. wxid 是唯一标识。display_name 仅用于展示,不用于身份判定。\n2. 每个 wxid 最终只能输出一条记录,严禁重复输出同一个\
\ wxid。\n3. 请先按 wxid 汇总该成员全天发言,再提取结果。\n4. 即使成员发言以短句为主,只要样本量足够,也必须尽量提炼:\n\
\ - topics\n - discussion_scenarios\n - skill_signals\n - problem_solving_signals\n\
\ - value_preferences\n - habit_signals\n - expression_markers\n\
\ - engagement_traits\n - reply_entry_points\n - social_role\n\
\ - temperament_signal\n - summary_text\n5. identity_clues、family_signals、life_stage_signals\
\ 没有明确公开证据时允许为空。\n6. 不允许因为“短句较多”就统一输出空数组和通用摘要。\n7. 不做心理诊断、不做隐私猜测、不把玩笑当事实。\n\
8. 只能输出候选成员列表中的 wxid。\n9. topics 更偏向反复出现的关注方向discussion_scenarios 更偏向什么情境下会发言skill_signals\
\ 更偏向能力表现problem_solving_signals 更偏向怎么处理问题value_preferences 更偏向判断偏好social_role 更偏向当天在群里的实际作用。\n\
10. expression_markers 记录可观察的表达标记,如常用句式、口头禅、是否爱列步骤/贴日志reply_entry_points 记录什么样的接话方式最容易接住他。\n\
11. 输出前自行去重,同一个 wxid 只保留一条最完整结果。\n\n输出要求\n- 只输出严格 JSON不要 markdown不要解释不要前后缀。\n\
- 输出格式:\n{\n \"members\": [\n {\n \"wxid\": \"成员wxid\",\n \
\ \"display_name\": \"显示名\",\n \"topics\": [\"主题1\"],\n \"discussion_scenarios\"\
: [\"场景1\"],\n \"identity_clues\": [\"身份线索1\"],\n \"skill_signals\"\
: [\"技能信号1\"],\n \"problem_solving_signals\": [\"处理方式1\"],\n \
\ \"family_signals\": [\"家庭线索1\"],\n \"life_stage_signals\": [\"\
阶段线索1\"],\n \"value_preferences\": [\"价值偏好1\"],\n \"interaction_style\"\
: \"一句中文\",\n \"message_pattern\": \"一句中文\",\n \"response_style_hint\"\
: \"一句中文\",\n \"habit_signals\": [\"习惯1\"],\n \"expression_markers\"\
: [\"表达标记1\"],\n \"engagement_traits\": [\"参与特征1\"],\n \"reply_entry_points\"\
: [\"接话点1\"],\n \"decision_style\": \"一句中文\",\n \"social_role\"\
: \"一句中文\",\n \"reply_taboos\": [\"避坑1\"],\n \"temperament_signal\"\
: \"一句中文\",\n \"summary_text\": \"不超过100字\",\n \"representative_messages\"\
: [\"原话1\", \"原话2\"],\n \"confidence\": 0.95\n }\n ]\n}\n\n字段约束\n\
- topics、discussion_scenarios、skill_signals、problem_solving_signals、value_preferences、habit_signals、expression_markers、engagement_traits、reply_entry_points\
\ 最多 4 个\n- identity_clues、family_signals、life_stage_signals 最多 3 个\n\
- reply_taboos 最多 3 个\n- representative_messages 最多 3 条\n- 如果某成员样本明显不足,可以不输出该成员\n"
- id: 2df6d4ce-4e40-42a9-85f3-184075da24c7
role: user
text: '群ID: {{#start_node.chatroom_id#}}
日期: {{#start_node.digest_date#}}
候选成员:
{{#start_node.member_labels#}}
压缩后的群聊记录:
{{#start_node.compressed_chat#}}
'
selected: true
title: 异常分支
type: llm
vision:
enabled: false
height: 124
id: '1775115372864'
position:
x: 704
y: 132
positionAbsolute:
x: 704
y: 132
selected: true
sourcePosition: right
targetPosition: left
type: custom
width: 242
viewport:
x: 210
y: 340
zoom: 0.7
rag_pipeline_variables: []

View File

@@ -162,10 +162,14 @@ workflow:
3. 请先按 wxid 汇总该成员全天发言,再提取结果。
4. 即使成员发言以短句为主,只要样本量足够,也必须尽量提炼:
- topics
- discussion_scenarios
- skill_signals
- problem_solving_signals
- value_preferences
- habit_signals
- expression_markers
- engagement_traits
- reply_entry_points
- social_role
- temperament_signal
- summary_text
@@ -173,8 +177,9 @@ workflow:
6. 不允许因为“短句较多”就统一输出空数组和通用摘要。
7. 不做心理诊断、不做隐私猜测、不把玩笑当事实。
8. 只能输出候选成员列表中的 wxid。
9. topics 更偏向反复出现的关注方向;skill_signals 更偏向能力表现value_preferences 更偏向判断偏好social_role 更偏向当天在群里的实际作用。
10. 输出前自行去重,同一个 wxid 只保留一条最完整结果
9. topics 更偏向反复出现的关注方向;discussion_scenarios 更偏向什么情境下会发言skill_signals 更偏向能力表现problem_solving_signals 更偏向怎么处理问题value_preferences 更偏向判断偏好social_role 更偏向当天在群里的实际作用。
10. expression_markers 记录可观察的表达标记,如常用句式、口头禅、是否爱列步骤/贴日志reply_entry_points 记录什么样的接话方式最容易接住他
11. 输出前自行去重,同一个 wxid 只保留一条最完整结果。
输出要求:
- 只输出严格 JSON不要 markdown不要解释不要前后缀。
@@ -185,8 +190,10 @@ workflow:
"wxid": "成员wxid",
"display_name": "显示名",
"topics": ["主题1"],
"discussion_scenarios": ["场景1"],
"identity_clues": ["身份线索1"],
"skill_signals": ["技能信号1"],
"problem_solving_signals": ["处理方式1"],
"family_signals": ["家庭线索1"],
"life_stage_signals": ["阶段线索1"],
"value_preferences": ["价值偏好1"],
@@ -194,7 +201,9 @@ workflow:
"message_pattern": "一句中文",
"response_style_hint": "一句中文",
"habit_signals": ["习惯1"],
"expression_markers": ["表达标记1"],
"engagement_traits": ["参与特征1"],
"reply_entry_points": ["接话点1"],
"decision_style": "一句中文",
"social_role": "一句中文",
"reply_taboos": ["避坑1"],
@@ -207,7 +216,7 @@ workflow:
}
字段约束:
- topics、skill_signals、value_preferences、habit_signals、engagement_traits 最多 4 个
- topics、discussion_scenarios、skill_signals、problem_solving_signals、value_preferences、habit_signals、expression_markers、engagement_traits、reply_entry_points 最多 4 个
- identity_clues、family_signals、life_stage_signals 最多 3 个
- reply_taboos 最多 3 个
- representative_messages 最多 3 条

View File

@@ -21,8 +21,10 @@ class MemberContextPromptBuilder:
"\"wxid\":\"成员wxid\","
"\"display_name\":\"成员显示名\","
"\"topics\":[\"主题1\"],"
"\"discussion_scenarios\":[\"场景1\"],"
"\"identity_clues\":[\"身份线索1\"],"
"\"skill_signals\":[\"技能信号1\"],"
"\"problem_solving_signals\":[\"处理方式1\"],"
"\"family_signals\":[\"家庭线索1\"],"
"\"life_stage_signals\":[\"阶段线索1\"],"
"\"value_preferences\":[\"价值偏好1\"],"
@@ -30,7 +32,9 @@ class MemberContextPromptBuilder:
"\"message_pattern\":\"一句中文\","
"\"response_style_hint\":\"一句中文\","
"\"habit_signals\":[\"信号1\"],"
"\"expression_markers\":[\"表达标记1\"],"
"\"engagement_traits\":[\"特征1\"],"
"\"reply_entry_points\":[\"接话点1\"],"
"\"decision_style\":\"一句中文\","
"\"social_role\":\"一句中文,描述当天在群中的角色表现\","
"\"reply_taboos\":[\"避坑1\"],"
@@ -43,15 +47,17 @@ class MemberContextPromptBuilder:
"}\n"
"要求:\n"
"1. 只输出当天真正参与发言且能看出明确行为信号的成员;发言极少的人可以不输出。\n"
"2. 每个成员的 topics、identity_clues、skill_signals、family_signals、life_stage_signals、value_preferences、habit_signals、engagement_traits 最多4个reply_taboos 最多3个。\n"
"2. 每个成员的 topics、discussion_scenarios、identity_clues、skill_signals、problem_solving_signals、family_signals、life_stage_signals、value_preferences、habit_signals、expression_markers、engagement_traits、reply_entry_points 最多4个reply_taboos 最多3个。\n"
"3. representative_messages 只保留最能代表当天表达方式的短句最多3条。\n"
"4. 必须严格使用候选成员列表中的 wxid 和显示名。\n"
"5. identity_clues、family_signals、life_stage_signals 只能写公开聊天中出现的线索,不可把弱线索写成确定事实。\n"
"6. skill_signals 重点提炼成员解决问题、提供信息、组织表达、专业能力等信号。\n"
"7. social_role 只描述当天在群里的角色表现,例如:问题提出者、信息补充者、气氛调节者、组织推进者。\n"
"8. topics 更偏向持续关注的话题方向;habit_signals 更偏向重复表达或互动习惯engagement_traits 更偏向参与方式\n"
"9. value_preferences 只记录公开表达出的偏好,如效率优先、成本敏感、谨慎验证、乐于助人,不要写抽象大词\n"
"10. summary_text 应是后台观察摘要,不要写成对用户说的话\n"
"8. topics 更偏向持续关注的话题方向;discussion_scenarios 更偏向他通常在什么情境下发言,如求助排错、补充经验、分享资源、接梗互动\n"
"9. problem_solving_signals 要写得具体,比如先确认现象再给步骤、倾向贴链接/文档、会直接给结论和执行项,不要只写'善于解决问题'\n"
"10. expression_markers 记录可观察的表达标记,如常用句式、口头禅、是否爱列步骤/贴截图/贴日志reply_entry_points 记录什么样的接话方式最容易接住他\n"
"11. value_preferences 只记录公开表达出的偏好,如效率优先、成本敏感、谨慎验证、乐于助人,不要写抽象大词。\n"
"12. summary_text 应是后台观察摘要,不要写成对用户说的话。\n"
f"群ID: {chatroom_id}\n"
f"日期: {digest_date}\n"
"候选成员:\n" + "\n".join(member_labels[:80]) + "\n"
@@ -68,13 +74,17 @@ class MemberContextPromptBuilder:
"period_key": item.get("period_key"),
"summary_text": item.get("summary_text", ""),
"topics": structured.get("topics") or structured.get("stable_topics") or structured.get("long_term_topics") or [],
"discussion_scenarios": structured.get("discussion_scenarios") or structured.get("common_scenarios") or [],
"identity_clues": structured.get("identity_clues") or structured.get("identity_traits") or [],
"skill_signals": structured.get("skill_signals") or structured.get("skill_profile") or [],
"problem_solving_signals": structured.get("problem_solving_signals") or structured.get("problem_solving_profile") or [],
"family_signals": structured.get("family_signals") or structured.get("family_profile") or [],
"life_stage_signals": structured.get("life_stage_signals") or structured.get("life_stage_profile") or [],
"value_preferences": structured.get("value_preferences") or structured.get("value_profile") or [],
"habit_signals": structured.get("habit_signals") or structured.get("habit_patterns") or [],
"expression_markers": structured.get("expression_markers") or structured.get("expression_profile") or [],
"engagement_traits": structured.get("engagement_traits") or structured.get("stable_traits") or [],
"reply_entry_points": structured.get("reply_entry_points") or structured.get("reply_entry_profile") or [],
"reply_preferences": structured.get("reply_preferences") or structured.get("long_term_reply_preferences") or [],
"social_role": structured.get("social_role") or structured.get("group_role") or "",
"decision_style": structured.get("decision_style") or structured.get("decision_profile") or "",
@@ -87,13 +97,17 @@ class MemberContextPromptBuilder:
schema = (
"{"
"\"stable_topics\":[\"主题1\"],"
"\"common_scenarios\":[\"场景1\"],"
"\"identity_traits\":[\"身份特征1\"],"
"\"skill_profile\":[\"技能画像1\"],"
"\"problem_solving_profile\":[\"处理方式1\"],"
"\"family_profile\":[\"家庭线索1\"],"
"\"life_stage_profile\":[\"阶段线索1\"],"
"\"value_profile\":[\"价值偏好1\"],"
"\"stable_traits\":[\"特征1\"],"
"\"habit_patterns\":[\"习惯1\"],"
"\"expression_profile\":[\"表达标记1\"],"
"\"reply_entry_profile\":[\"接话点1\"],"
"\"reply_preferences\":[\"偏好1\"],"
"\"group_role\":\"一句中文\","
"\"decision_profile\":\"一句中文\","
@@ -108,13 +122,17 @@ class MemberContextPromptBuilder:
schema = (
"{"
"\"long_term_topics\":[\"主题1\"],"
"\"common_scenarios\":[\"场景1\"],"
"\"identity_traits\":[\"身份特征1\"],"
"\"skill_profile\":[\"技能画像1\"],"
"\"problem_solving_profile\":[\"处理方式1\"],"
"\"family_profile\":[\"家庭线索1\"],"
"\"life_stage_profile\":[\"阶段线索1\"],"
"\"value_profile\":[\"价值偏好1\"],"
"\"stable_traits\":[\"特征1\"],"
"\"habit_patterns\":[\"习惯1\"],"
"\"expression_profile\":[\"表达标记1\"],"
"\"reply_entry_profile\":[\"接话点1\"],"
"\"long_term_reply_preferences\":[\"偏好1\"],"
"\"group_role\":\"一句中文\","
"\"decision_profile\":\"一句中文\","
@@ -136,9 +154,11 @@ class MemberContextPromptBuilder:
"2. 只有多个下级摘要反复出现的特征,才允许写进 stable_traits / habit_patterns / long_term_reply_preferences。\n"
"3. recent_state / phase_state 只描述当前阶段状态,不要冒充长期人格。\n"
"4. identity_traits、family_profile、life_stage_profile 只能保留反复出现的公开线索,不可编造事实。\n"
"5. skill_profile 要优先提炼稳定出现的能力、专业方向、擅长处理问题类型\n"
"6. group_role 描述其在群中的长期角色位置decision_profile 描述其决策与判断风格\n"
"7. value_profile 需要优先保留真正反复出现的判断偏好,如效率优先、成本敏感、风险谨慎、愿意分享\n"
"5. common_scenarios 要总结这个人通常会在什么场景下参与problem_solving_profile 要总结其典型处理问题方式,尽量具体到行为动作\n"
"6. skill_profile 要优先提炼稳定出现的能力、专业方向、擅长处理的问题类型\n"
"7. expression_profile 要保留反复出现的表达标记,如先说结论、爱列步骤、常贴原话/日志reply_entry_profile 要总结适合接话的切入点\n"
"8. group_role 描述其在群中的长期角色位置decision_profile 描述其决策与判断风格。\n"
"9. value_profile 需要优先保留真正反复出现的判断偏好,如效率优先、成本敏感、风险谨慎、愿意分享。\n"
f"成员: {display_name} ({wxid})\n"
f"群ID: {chatroom_id}\n"
f"周期: {period_key}\n"
@@ -164,13 +184,17 @@ class MemberContextPromptBuilder:
"\"response_style_hint\":\"一句中文\","
"\"topics_of_interest\":[\"主题1\"],"
"\"recent_focus\":[\"近期主题1\"],"
"\"common_scenarios\":[\"场景1\"],"
"\"identity_traits\":[\"身份线索1\"],"
"\"skill_profile\":[\"技能画像1\"],"
"\"problem_solving_profile\":[\"处理方式1\"],"
"\"family_profile\":[\"家庭线索1\"],"
"\"life_stage_profile\":[\"阶段线索1\"],"
"\"value_profile\":[\"价值偏好1\"],"
"\"stable_traits\":[\"长期特征1\"],"
"\"habit_patterns\":[\"习惯1\"],"
"\"expression_profile\":[\"表达标记1\"],"
"\"reply_entry_profile\":[\"接话点1\"],"
"\"long_term_reply_preferences\":[\"偏好1\"],"
"\"group_role\":\"一句中文\","
"\"decision_profile\":\"一句中文\","
@@ -186,9 +210,10 @@ class MemberContextPromptBuilder:
"2. recent_focus、recent_state 更依赖最近周级和日级。\n"
"3. summary_text 要像后台备注,不要明显暴露在给用户做画像。\n"
"4. identity_traits、family_profile、life_stage_profile 必须写成公开线索或长期观察,不得伪造事实。\n"
"5. skill_profile 要尽量覆盖专业能力、问题解决能力、表达组织能力、资源协调能力等维度\n"
"6. group_role 要描述其在群中的角色定位decision_profile 要描述其决策/判断方式\n"
"7. 如果月级与周级证据不足,宁可少写,也不要把短期状态写成长期人格\n"
"5. common_scenarios、problem_solving_profile、expression_profile、reply_entry_profile 要尽量写具体行为,不要只写抽象人格词\n"
"6. skill_profile 要尽量覆盖专业能力、问题解决能力、表达组织能力、资源协调能力等维度\n"
"7. group_role 要描述其在群中的角色定位decision_profile 要描述其决策/判断方式\n"
"8. 如果月级与周级证据不足,宁可少写,也不要把短期状态写成长期人格。\n"
f"成员: {display_name} ({wxid})\n"
f"群ID: {chatroom_id}\n"
"月级摘要:\n" + ("\n".join(monthly_lines) or "暂无")

View File

@@ -130,10 +130,18 @@ class MemberContextService:
monthly_structured + weekly_structured + daily_structured,
["identity_traits", "identity_clues"], limit=5
),
"common_scenarios": self._extract_scored_items(
monthly_structured + weekly_structured + daily_structured,
["common_scenarios", "discussion_scenarios"], limit=5
),
"skill_profile": self._extract_scored_items(
monthly_structured + weekly_structured + daily_structured,
["skill_profile", "skill_signals"], limit=6
),
"problem_solving_profile": self._extract_scored_items(
monthly_structured + weekly_structured + daily_structured,
["problem_solving_profile", "problem_solving_signals"], limit=5
),
"family_profile": self._extract_scored_items(
monthly_structured + weekly_structured + daily_structured,
["family_profile", "family_signals"], limit=4
@@ -153,6 +161,14 @@ class MemberContextService:
monthly_structured + weekly_structured + daily_structured,
["habit_patterns", "habit_signals"], limit=self.stable_max_items
),
"expression_profile": self._extract_scored_items(
monthly_structured + weekly_structured + daily_structured,
["expression_profile", "expression_markers"], limit=5
),
"reply_entry_profile": self._extract_scored_items(
monthly_structured + weekly_structured + daily_structured,
["reply_entry_profile", "reply_entry_points"], limit=4
),
"long_term_reply_preferences": self._extract_scored_items(
monthly_structured + weekly_structured, ["long_term_reply_preferences", "reply_preferences"], limit=4
),
@@ -538,12 +554,16 @@ class MemberContextService:
"confidence": max(0.0, min(1.0, confidence)),
"meta": {
"identity_traits": norm_list(data.get("identity_traits"), 5),
"common_scenarios": norm_list(data.get("common_scenarios"), 5),
"skill_profile": norm_list(data.get("skill_profile"), 6),
"problem_solving_profile": norm_list(data.get("problem_solving_profile"), 5),
"family_profile": norm_list(data.get("family_profile"), 4),
"life_stage_profile": norm_list(data.get("life_stage_profile"), 4),
"value_profile": norm_list(data.get("value_profile"), 5),
"stable_traits": norm_list(data.get("stable_traits"), self.stable_max_items),
"habit_patterns": norm_list(data.get("habit_patterns"), self.stable_max_items),
"expression_profile": norm_list(data.get("expression_profile"), 5),
"reply_entry_profile": norm_list(data.get("reply_entry_profile"), 4),
"long_term_reply_preferences": norm_list(data.get("long_term_reply_preferences"), 4),
"group_role": str(data.get("group_role", "")).strip(),
"decision_profile": str(data.get("decision_profile", "")).strip(),
@@ -586,11 +606,21 @@ class MemberContextService:
meta.get("identity_traits", []),
current_context.get("confidence", 0) * 0.75,
)
merged_common_scenario_scores = self._merge_scored_items(
existing_meta.get("common_scenario_scores", {}),
meta.get("common_scenarios", []),
current_context.get("confidence", 0) * 0.85,
)
merged_skill_scores = self._merge_scored_items(
existing_meta.get("skill_profile_scores", {}),
meta.get("skill_profile", []),
current_context.get("confidence", 0) * 0.85,
)
merged_problem_solving_scores = self._merge_scored_items(
existing_meta.get("problem_solving_profile_scores", {}),
meta.get("problem_solving_profile", []),
current_context.get("confidence", 0) * 0.9,
)
merged_family_scores = self._merge_scored_items(
existing_meta.get("family_profile_scores", {}),
meta.get("family_profile", []),
@@ -606,6 +636,16 @@ class MemberContextService:
meta.get("value_profile", []),
current_context.get("confidence", 0) * 0.75,
)
merged_expression_scores = self._merge_scored_items(
existing_meta.get("expression_profile_scores", {}),
meta.get("expression_profile", []),
current_context.get("confidence", 0) * 0.95,
)
merged_reply_entry_scores = self._merge_scored_items(
existing_meta.get("reply_entry_profile_scores", {}),
meta.get("reply_entry_profile", []),
current_context.get("confidence", 0),
)
merged_reply_pref_scores = self._merge_scored_items(
existing_meta.get("long_term_reply_preference_scores", {}),
meta.get("long_term_reply_preferences", []),
@@ -621,32 +661,48 @@ class MemberContextService:
meta["stable_trait_scores"] = merged_trait_scores
meta["habit_pattern_scores"] = merged_habit_scores
meta["identity_trait_scores"] = merged_identity_scores
meta["common_scenario_scores"] = merged_common_scenario_scores
meta["skill_profile_scores"] = merged_skill_scores
meta["problem_solving_profile_scores"] = merged_problem_solving_scores
meta["family_profile_scores"] = merged_family_scores
meta["life_stage_profile_scores"] = merged_life_stage_scores
meta["value_profile_scores"] = merged_value_scores
meta["expression_profile_scores"] = merged_expression_scores
meta["reply_entry_profile_scores"] = merged_reply_entry_scores
meta["long_term_reply_preference_scores"] = merged_reply_pref_scores
meta["temperament_tendency_scores"] = merged_temperament_scores
meta["identity_traits"] = self._top_scored_items(merged_identity_scores, limit=5)
meta["common_scenarios"] = self._top_scored_items(merged_common_scenario_scores, limit=5)
meta["skill_profile"] = self._top_scored_items(merged_skill_scores, limit=6)
meta["problem_solving_profile"] = self._top_scored_items(merged_problem_solving_scores, limit=5)
meta["family_profile"] = self._top_scored_items(merged_family_scores, limit=4)
meta["life_stage_profile"] = self._top_scored_items(merged_life_stage_scores, limit=4)
meta["value_profile"] = self._top_scored_items(merged_value_scores, limit=5)
meta["stable_traits"] = self._top_scored_items(merged_trait_scores, limit=self.stable_max_items)
meta["habit_patterns"] = self._top_scored_items(merged_habit_scores, limit=self.stable_max_items)
meta["expression_profile"] = self._top_scored_items(merged_expression_scores, limit=5)
meta["reply_entry_profile"] = self._top_scored_items(merged_reply_entry_scores, limit=4)
meta["long_term_reply_preferences"] = self._top_scored_items(merged_reply_pref_scores, limit=4)
temperament = self._top_scored_items(merged_temperament_scores, limit=1)
meta["temperament_tendency"] = temperament[0] if temperament else meta.get("temperament_tendency", "")
if not meta["identity_traits"]:
meta["identity_traits"] = (existing_meta.get("identity_traits") or [])[:5]
if not meta["common_scenarios"]:
meta["common_scenarios"] = (existing_meta.get("common_scenarios") or [])[:5]
if not meta["skill_profile"]:
meta["skill_profile"] = (existing_meta.get("skill_profile") or [])[:6]
if not meta["problem_solving_profile"]:
meta["problem_solving_profile"] = (existing_meta.get("problem_solving_profile") or [])[:5]
if not meta["family_profile"]:
meta["family_profile"] = (existing_meta.get("family_profile") or [])[:4]
if not meta["life_stage_profile"]:
meta["life_stage_profile"] = (existing_meta.get("life_stage_profile") or [])[:4]
if not meta["value_profile"]:
meta["value_profile"] = (existing_meta.get("value_profile") or [])[:5]
if not meta["expression_profile"]:
meta["expression_profile"] = (existing_meta.get("expression_profile") or [])[:5]
if not meta["reply_entry_profile"]:
meta["reply_entry_profile"] = (existing_meta.get("reply_entry_profile") or [])[:4]
meta["group_role"] = meta.get("group_role") or existing_meta.get("group_role") or ""
meta["decision_profile"] = meta.get("decision_profile") or existing_meta.get("decision_profile") or ""
meta["engagement_traits"] = (meta.get("engagement_traits") or existing_meta.get("engagement_traits") or [])[:4]
@@ -706,6 +762,13 @@ class MemberContextService:
)
if preferences:
return "更适合:" + "".join(preferences[:3])
entry_points = self._extract_scored_items(
monthly_structured + weekly_structured + daily_structured,
["reply_entry_profile", "reply_entry_points"],
limit=3,
)
if entry_points:
return "优先从这些点接话:" + "".join(entry_points[:3])
return "保持自然口语化,结论和解释尽量平衡"
def _calc_digest_confidence(self, monthly_digests: List[Dict], weekly_digests: List[Dict],
@@ -745,12 +808,18 @@ class MemberContextService:
if meta.get("temperament_tendency"):
label = "长期沟通倾向" if meta.get("stable_ready") else "阶段性沟通倾向"
parts.append(f"{label}{meta.get('temperament_tendency')}")
if meta.get("common_scenarios"):
parts.append(f"常见场景:{''.join(meta.get('common_scenarios')[:3])}")
if meta.get("problem_solving_profile"):
parts.append(f"处理方式:{''.join(meta.get('problem_solving_profile')[:3])}")
if meta.get("stable_traits"):
parts.append(f"长期特征:{''.join(meta.get('stable_traits')[:3])}")
if meta.get("identity_traits"):
parts.append(f"身份线索:{''.join(meta.get('identity_traits')[:2])}")
if meta.get("skill_profile"):
parts.append(f"技能画像:{''.join(meta.get('skill_profile')[:3])}")
if meta.get("expression_profile"):
parts.append(f"表达标记:{''.join(meta.get('expression_profile')[:3])}")
if meta.get("value_profile"):
parts.append(f"判断偏好:{''.join(meta.get('value_profile')[:2])}")
if meta.get("habit_patterns"):