member_context: split daily weekly monthly digest scheduling
This commit is contained in:
@@ -38,6 +38,8 @@ stable_ready_days = 180
|
|||||||
|
|
||||||
[schedule]
|
[schedule]
|
||||||
refresh_times = ["04:20"]
|
refresh_times = ["04:20"]
|
||||||
|
weekly_refresh_time = "04:40"
|
||||||
|
monthly_refresh_time = "04:50"
|
||||||
only_recent_active_groups = true
|
only_recent_active_groups = true
|
||||||
active_hours = 72
|
active_hours = 72
|
||||||
min_group_messages = 20
|
min_group_messages = 20
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ from db.contacts_db import ContactsDBOperator
|
|||||||
from db.member_digest_db import MemberDigestDBOperator
|
from db.member_digest_db import MemberDigestDBOperator
|
||||||
from db.message_storage import MessageStorageDB
|
from db.message_storage import MessageStorageDB
|
||||||
from plugins.member_context.dify_client import DifyClient
|
from plugins.member_context.dify_client import DifyClient
|
||||||
from plugins.member_context.prompt_builder import MemberContextPromptBuilder
|
|
||||||
from utils.compress_chat_data import compress_chat_data
|
from utils.compress_chat_data import compress_chat_data
|
||||||
|
|
||||||
|
|
||||||
@@ -107,43 +106,94 @@ class MemberDigestService:
|
|||||||
self.LOG.warning(f"[成员交互摘要] 检查群初始化状态失败,按增量处理: group={chatroom_id}, error={e}")
|
self.LOG.warning(f"[成员交互摘要] 检查群初始化状态失败,按增量处理: group={chatroom_id}, error={e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def ensure_member_digest_pipeline(self, chatroom_id: str, wxid: str, force: bool = False) -> Dict:
|
def ensure_member_digest_pipeline(self, chatroom_id: str, wxid: str, force: bool = False,
|
||||||
|
enable_weekly: bool = True, enable_monthly: bool = True) -> Dict:
|
||||||
member = self.contacts_db.get_chatroom_member_info(chatroom_id, wxid) or {}
|
member = self.contacts_db.get_chatroom_member_info(chatroom_id, wxid) or {}
|
||||||
display_name = member.get("display_name") or member.get("nick_name") or wxid
|
display_name = member.get("display_name") or member.get("nick_name") or wxid
|
||||||
|
|
||||||
daily_digests = self.digest_db.list_digests(chatroom_id, wxid, "daily", limit=400)
|
all_daily_digests = self.digest_db.list_digests(chatroom_id, wxid, "daily", limit=400)
|
||||||
if not daily_digests:
|
if not all_daily_digests:
|
||||||
return {
|
return {
|
||||||
"display_name": display_name,
|
"display_name": display_name,
|
||||||
"daily_digests": [],
|
"daily_digests": [],
|
||||||
"weekly_digests": [],
|
"weekly_digests": [],
|
||||||
"monthly_digests": [],
|
"monthly_digests": [],
|
||||||
|
"all_daily_digests": [],
|
||||||
|
"all_weekly_digests": [],
|
||||||
|
"all_monthly_digests": [],
|
||||||
"stats": {"daily": 0, "weekly": 0, "monthly": 0, "active_days": 0, "built_daily": 0},
|
"stats": {"daily": 0, "weekly": 0, "monthly": 0, "active_days": 0, "built_daily": 0},
|
||||||
}
|
}
|
||||||
|
|
||||||
built_weekly = self._ensure_weekly_digests(chatroom_id, wxid, display_name, force=force)
|
latest_daily_date = self._extract_latest_daily_date(all_daily_digests)
|
||||||
built_monthly = self._ensure_monthly_digests(chatroom_id, wxid, display_name, force=force)
|
built_weekly = 0
|
||||||
|
built_monthly = 0
|
||||||
|
if enable_weekly and (force or self._should_run_weekly(latest_daily_date)):
|
||||||
|
built_weekly = self._ensure_weekly_digests(chatroom_id, wxid, display_name, force=force)
|
||||||
|
elif enable_weekly:
|
||||||
|
self.LOG.debug(
|
||||||
|
f"[成员交互摘要][周摘要] 本次跳过(未到周处理窗口): "
|
||||||
|
f"group={chatroom_id}, wxid={wxid}, latest_daily_date={latest_daily_date}"
|
||||||
|
)
|
||||||
|
|
||||||
daily_digests = self.digest_db.list_digests(chatroom_id, wxid, "daily", limit=self.final_daily_limit)
|
if enable_monthly and (force or self._should_run_monthly(latest_daily_date)):
|
||||||
weekly_digests = self.digest_db.list_digests(chatroom_id, wxid, "weekly", limit=self.final_weekly_limit)
|
built_monthly = self._ensure_monthly_digests(chatroom_id, wxid, display_name, force=force)
|
||||||
monthly_digests = self.digest_db.list_digests(chatroom_id, wxid, "monthly", limit=self.final_monthly_limit)
|
elif enable_monthly:
|
||||||
|
self.LOG.debug(
|
||||||
|
f"[成员交互摘要][月摘要] 本次跳过(未到月处理窗口): "
|
||||||
|
f"group={chatroom_id}, wxid={wxid}, latest_daily_date={latest_daily_date}"
|
||||||
|
)
|
||||||
|
|
||||||
|
all_weekly_digests = self.digest_db.list_digests(chatroom_id, wxid, "weekly", limit=200)
|
||||||
|
all_monthly_digests = self.digest_db.list_digests(chatroom_id, wxid, "monthly", limit=120)
|
||||||
|
|
||||||
|
daily_digests = all_daily_digests[:self.final_daily_limit]
|
||||||
|
weekly_digests = all_weekly_digests[:self.final_weekly_limit]
|
||||||
|
monthly_digests = all_monthly_digests[:self.final_monthly_limit]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"display_name": display_name,
|
"display_name": display_name,
|
||||||
"daily_digests": daily_digests,
|
"daily_digests": daily_digests,
|
||||||
"weekly_digests": weekly_digests,
|
"weekly_digests": weekly_digests,
|
||||||
"monthly_digests": monthly_digests,
|
"monthly_digests": monthly_digests,
|
||||||
|
"all_daily_digests": all_daily_digests,
|
||||||
|
"all_weekly_digests": all_weekly_digests,
|
||||||
|
"all_monthly_digests": all_monthly_digests,
|
||||||
"stats": {
|
"stats": {
|
||||||
"daily": len(daily_digests),
|
"daily": len(all_daily_digests),
|
||||||
"weekly": len(weekly_digests),
|
"weekly": len(all_weekly_digests),
|
||||||
"monthly": len(monthly_digests),
|
"monthly": len(all_monthly_digests),
|
||||||
"active_days": len(self.digest_db.list_digest_keys(chatroom_id, wxid, "daily")),
|
"active_days": len(all_daily_digests),
|
||||||
"built_daily": 0,
|
"built_daily": 0,
|
||||||
"built_weekly": built_weekly,
|
"built_weekly": built_weekly,
|
||||||
"built_monthly": built_monthly,
|
"built_monthly": built_monthly,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def _extract_latest_daily_date(self, daily_digests: List[Dict]) -> Optional[datetime]:
|
||||||
|
if not daily_digests:
|
||||||
|
return None
|
||||||
|
latest_key = daily_digests[0].get("period_key") or daily_digests[0].get("period_end")
|
||||||
|
return self._parse_period_date(latest_key)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _parse_period_date(value: Optional[str]) -> Optional[datetime]:
|
||||||
|
if not value:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return datetime.strptime(str(value)[:10], "%Y-%m-%d")
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _should_run_weekly(self, latest_daily_date: Optional[datetime]) -> bool:
|
||||||
|
if not latest_daily_date:
|
||||||
|
return False
|
||||||
|
return latest_daily_date.weekday() == 6
|
||||||
|
|
||||||
|
def _should_run_monthly(self, latest_daily_date: Optional[datetime]) -> bool:
|
||||||
|
if not latest_daily_date:
|
||||||
|
return False
|
||||||
|
return (latest_daily_date + timedelta(days=1)).day == 1
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _normalize_profile_item(item: Dict) -> Dict:
|
def _normalize_profile_item(item: Dict) -> Dict:
|
||||||
normalized = {}
|
normalized = {}
|
||||||
@@ -312,17 +362,24 @@ class MemberDigestService:
|
|||||||
def _build_period_digest(self, digest_type: str, chatroom_id: str, wxid: str, display_name: str,
|
def _build_period_digest(self, digest_type: str, chatroom_id: str, wxid: str, display_name: str,
|
||||||
period_key: str, period_start: str, period_end: str,
|
period_key: str, period_start: str, period_end: str,
|
||||||
items: List[Dict]) -> Optional[Dict]:
|
items: List[Dict]) -> Optional[Dict]:
|
||||||
prompt = MemberContextPromptBuilder.build_period_digest_prompt(
|
parsed = self._request_period_json(
|
||||||
digest_type, chatroom_id, wxid, display_name, period_key, items
|
digest_type=digest_type,
|
||||||
|
chatroom_id=chatroom_id,
|
||||||
|
wxid=wxid,
|
||||||
|
display_name=display_name,
|
||||||
|
period_key=period_key,
|
||||||
|
items=items,
|
||||||
)
|
)
|
||||||
parsed = self._request_ai_json(prompt, tag=f"{digest_type}:{period_key}", chatroom_id=chatroom_id, wxid=wxid)
|
|
||||||
if not parsed:
|
if not parsed:
|
||||||
self.LOG.warning(
|
self.LOG.warning(
|
||||||
f"[成员交互摘要][{digest_type}] 跳过周期摘要(未提取到有效结果): "
|
f"[成员交互摘要][{digest_type}] 跳过周期摘要(未提取到有效结果): "
|
||||||
f"group={chatroom_id}, wxid={wxid}, period={period_key}, source_count={len(items)}"
|
f"group={chatroom_id}, wxid={wxid}, period={period_key}, source_count={len(items)}, "
|
||||||
|
f"last_error={self.dify_client.last_error}"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
parsed = self._normalize_profile_item(parsed)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"chatroom_id": chatroom_id,
|
"chatroom_id": chatroom_id,
|
||||||
"wxid": wxid,
|
"wxid": wxid,
|
||||||
@@ -355,18 +412,42 @@ class MemberDigestService:
|
|||||||
parsed["ai_usage"] = response.get("usage", {}) or {}
|
parsed["ai_usage"] = response.get("usage", {}) or {}
|
||||||
return parsed
|
return parsed
|
||||||
|
|
||||||
|
def _request_period_json(self, digest_type: str, chatroom_id: str, wxid: str,
|
||||||
|
display_name: str, period_key: str, items: List[Dict]) -> Optional[Dict]:
|
||||||
|
if not self.dify_client.is_available():
|
||||||
|
return None
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
"digest_type": digest_type,
|
||||||
|
"chatroom_id": chatroom_id,
|
||||||
|
"wxid": wxid,
|
||||||
|
"display_name": display_name,
|
||||||
|
"period_key": period_key,
|
||||||
|
"source_items_json": json.dumps(self._build_period_source_items(items), ensure_ascii=False),
|
||||||
|
"source_item_count": str(len(items)),
|
||||||
|
}
|
||||||
|
response = self.dify_client.run(
|
||||||
|
prompt="",
|
||||||
|
user=f"member-digest:{chatroom_id}:{wxid}:{digest_type}:{period_key}",
|
||||||
|
inputs=inputs,
|
||||||
|
tag=f"{digest_type}:{period_key}",
|
||||||
|
)
|
||||||
|
if not response:
|
||||||
|
return None
|
||||||
|
parsed = self._parse_ai_answer(response.get("text", ""))
|
||||||
|
if parsed:
|
||||||
|
parsed["ai_usage"] = response.get("usage", {}) or {}
|
||||||
|
return parsed
|
||||||
|
|
||||||
def _request_group_daily_json(self, chatroom_id: str, digest_date: str,
|
def _request_group_daily_json(self, chatroom_id: str, digest_date: str,
|
||||||
member_labels: List[str], compressed_chat: str) -> List[Dict]:
|
member_labels: List[str], compressed_chat: str) -> List[Dict]:
|
||||||
if not self.dify_client.is_available():
|
if not self.dify_client.is_available():
|
||||||
return []
|
return []
|
||||||
prompt = MemberContextPromptBuilder.build_group_daily_digest_prompt(
|
|
||||||
chatroom_id, digest_date, member_labels, compressed_chat
|
|
||||||
)
|
|
||||||
response = self.dify_client.run(
|
response = self.dify_client.run(
|
||||||
prompt=prompt,
|
prompt="",
|
||||||
user=f"member-digest:{chatroom_id}:group-daily:{digest_date}",
|
user=f"member-digest:{chatroom_id}:group-daily:{digest_date}",
|
||||||
inputs={
|
inputs={
|
||||||
"query": prompt,
|
"digest_type": "daily",
|
||||||
"chatroom_id": chatroom_id,
|
"chatroom_id": chatroom_id,
|
||||||
"digest_date": digest_date,
|
"digest_date": digest_date,
|
||||||
"member_labels": "\n".join(member_labels),
|
"member_labels": "\n".join(member_labels),
|
||||||
@@ -379,6 +460,34 @@ class MemberDigestService:
|
|||||||
parsed = self._parse_group_daily_answer(response.get("text", ""))
|
parsed = self._parse_group_daily_answer(response.get("text", ""))
|
||||||
return parsed
|
return parsed
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _build_period_source_items(items: List[Dict]) -> List[Dict]:
|
||||||
|
source_items = []
|
||||||
|
for item in items:
|
||||||
|
structured = item.get("structured", {}) or {}
|
||||||
|
source_items.append({
|
||||||
|
"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 "",
|
||||||
|
"temperament_signal": structured.get("temperament_signal") or structured.get("temperament_tendency") or "",
|
||||||
|
"recent_state": structured.get("recent_state") or structured.get("phase_state") or [],
|
||||||
|
})
|
||||||
|
return source_items
|
||||||
|
|
||||||
def _parse_ai_answer(self, answer: str) -> Optional[Dict]:
|
def _parse_ai_answer(self, answer: str) -> Optional[Dict]:
|
||||||
if not answer:
|
if not answer:
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -50,13 +50,32 @@ class MemberContextPlugin(MessagePluginInterface):
|
|||||||
self.LOG.debug(f"正在初始化 {self.name} 插件...")
|
self.LOG.debug(f"正在初始化 {self.name} 插件...")
|
||||||
self.service = MemberContextService(context["db_manager"], self._config)
|
self.service = MemberContextService(context["db_manager"], self._config)
|
||||||
refresh_times = self._config.get("schedule", {}).get("refresh_times", [])
|
refresh_times = self._config.get("schedule", {}).get("refresh_times", [])
|
||||||
if refresh_times and not self._job_registered:
|
weekly_refresh_time = self._config.get("schedule", {}).get("weekly_refresh_time", "")
|
||||||
@async_job.at_times(refresh_times)
|
monthly_refresh_time = self._config.get("schedule", {}).get("monthly_refresh_time", "")
|
||||||
async def refresh_member_context_job():
|
if not self._job_registered:
|
||||||
if self.service:
|
if refresh_times:
|
||||||
self.LOG.info("开始刷新成员交互摘要")
|
@async_job.at_times(refresh_times)
|
||||||
self.service.refresh_all_chatrooms()
|
async def refresh_member_context_job():
|
||||||
self.LOG.info("成员交互摘要刷新完成")
|
if self.service:
|
||||||
|
self.LOG.info("开始刷新成员交互摘要(日任务)")
|
||||||
|
self.service.refresh_all_chatrooms(enable_weekly_digest=False, enable_monthly_digest=False)
|
||||||
|
self.LOG.info("成员交互摘要刷新完成(日任务)")
|
||||||
|
|
||||||
|
if weekly_refresh_time:
|
||||||
|
@async_job.every_week_time(weekday=6, time_str=weekly_refresh_time)
|
||||||
|
async def refresh_member_context_weekly_job():
|
||||||
|
if self.service:
|
||||||
|
self.LOG.info("开始刷新成员交互摘要(周任务)")
|
||||||
|
self.service.refresh_all_chatrooms(enable_weekly_digest=True, enable_monthly_digest=False)
|
||||||
|
self.LOG.info("成员交互摘要刷新完成(周任务)")
|
||||||
|
|
||||||
|
if monthly_refresh_time:
|
||||||
|
@async_job.every_month_last_day_time(monthly_refresh_time)
|
||||||
|
async def refresh_member_context_monthly_job():
|
||||||
|
if self.service:
|
||||||
|
self.LOG.info("开始刷新成员交互摘要(月任务)")
|
||||||
|
self.service.refresh_all_chatrooms(enable_weekly_digest=False, enable_monthly_digest=True)
|
||||||
|
self.LOG.info("成员交互摘要刷新完成(月任务)")
|
||||||
self._job_registered = True
|
self._job_registered = True
|
||||||
self.LOG.debug(f"{self.name} 插件初始化完成")
|
self.LOG.debug(f"{self.name} 插件初始化完成")
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
app:
|
app:
|
||||||
description: 按群和日期提取群成员日画像,输出严格 JSON,供 member_context 插件直接消费
|
description: 支持日/周/月成员摘要生成的统一 workflow,按 digest_type 分支输出严格 JSON
|
||||||
icon: 🤖
|
icon: 🤖
|
||||||
icon_background: '#E0F2FE'
|
icon_background: '#E0F2FE'
|
||||||
mode: workflow
|
mode: workflow
|
||||||
@@ -12,7 +12,7 @@ dependencies:
|
|||||||
marketplace_plugin_unique_identifier: langgenius/openai_api_compatible:0.0.27@f9ce3ff5e28f09931a3a7fca59add2d09590408f7e9a3d701b10c77a60249719
|
marketplace_plugin_unique_identifier: langgenius/openai_api_compatible:0.0.27@f9ce3ff5e28f09931a3a7fca59add2d09590408f7e9a3d701b10c77a60249719
|
||||||
version: null
|
version: null
|
||||||
kind: app
|
kind: app
|
||||||
version: 0.5.0
|
version: 0.6.0
|
||||||
workflow:
|
workflow:
|
||||||
conversation_variables: []
|
conversation_variables: []
|
||||||
environment_variables: []
|
environment_variables: []
|
||||||
@@ -62,48 +62,116 @@ workflow:
|
|||||||
isInIteration: false
|
isInIteration: false
|
||||||
isInLoop: false
|
isInLoop: false
|
||||||
sourceType: start
|
sourceType: start
|
||||||
targetType: llm
|
targetType: if-else
|
||||||
id: start-source-llm-target
|
id: start-source-router-target
|
||||||
selected: false
|
selected: false
|
||||||
source: start_node
|
source: start_node
|
||||||
sourceHandle: source
|
sourceHandle: source
|
||||||
target: llm_node
|
target: digest_router
|
||||||
targetHandle: target
|
targetHandle: target
|
||||||
type: custom
|
type: custom
|
||||||
zIndex: 0
|
zIndex: 0
|
||||||
- data:
|
- data:
|
||||||
isInIteration: false
|
isInIteration: false
|
||||||
isInLoop: false
|
isInLoop: false
|
||||||
sourceType: llm
|
sourceType: if-else
|
||||||
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
|
targetType: llm
|
||||||
id: llm_node-fail-branch-1775115372864-target
|
id: digest_router-daily-daily_llm-target
|
||||||
source: llm_node
|
selected: false
|
||||||
sourceHandle: fail-branch
|
source: digest_router
|
||||||
target: '1775115372864'
|
sourceHandle: daily_case
|
||||||
|
target: daily_llm
|
||||||
targetHandle: target
|
targetHandle: target
|
||||||
type: custom
|
type: custom
|
||||||
zIndex: 0
|
zIndex: 0
|
||||||
- data:
|
- data:
|
||||||
|
isInIteration: false
|
||||||
|
isInLoop: false
|
||||||
|
sourceType: if-else
|
||||||
|
targetType: llm
|
||||||
|
id: digest_router-weekly-weekly_llm-target
|
||||||
|
selected: false
|
||||||
|
source: digest_router
|
||||||
|
sourceHandle: weekly_case
|
||||||
|
target: weekly_llm
|
||||||
|
targetHandle: target
|
||||||
|
type: custom
|
||||||
|
zIndex: 0
|
||||||
|
- data:
|
||||||
|
isInIteration: false
|
||||||
|
isInLoop: false
|
||||||
|
sourceType: if-else
|
||||||
|
targetType: llm
|
||||||
|
id: digest_router-monthly-monthly_llm-target
|
||||||
|
selected: false
|
||||||
|
source: digest_router
|
||||||
|
sourceHandle: monthly_case
|
||||||
|
target: monthly_llm
|
||||||
|
targetHandle: target
|
||||||
|
type: custom
|
||||||
|
zIndex: 0
|
||||||
|
- data:
|
||||||
|
isInIteration: false
|
||||||
|
isInLoop: false
|
||||||
|
sourceType: if-else
|
||||||
|
targetType: llm
|
||||||
|
id: digest_router-false-invalid_llm-target
|
||||||
|
selected: false
|
||||||
|
source: digest_router
|
||||||
|
sourceHandle: 'false'
|
||||||
|
target: invalid_llm
|
||||||
|
targetHandle: target
|
||||||
|
type: custom
|
||||||
|
zIndex: 0
|
||||||
|
- data:
|
||||||
|
isInIteration: false
|
||||||
isInLoop: false
|
isInLoop: false
|
||||||
sourceType: llm
|
sourceType: llm
|
||||||
targetType: end
|
targetType: end
|
||||||
id: 1775115372864-source-end_node-target
|
id: daily_llm-source-end_daily-target
|
||||||
source: '1775115372864'
|
selected: false
|
||||||
|
source: daily_llm
|
||||||
sourceHandle: source
|
sourceHandle: source
|
||||||
target: end_node
|
target: end_daily
|
||||||
|
targetHandle: target
|
||||||
|
type: custom
|
||||||
|
zIndex: 0
|
||||||
|
- data:
|
||||||
|
isInIteration: false
|
||||||
|
isInLoop: false
|
||||||
|
sourceType: llm
|
||||||
|
targetType: end
|
||||||
|
id: weekly_llm-source-end_weekly-target
|
||||||
|
selected: false
|
||||||
|
source: weekly_llm
|
||||||
|
sourceHandle: source
|
||||||
|
target: end_weekly
|
||||||
|
targetHandle: target
|
||||||
|
type: custom
|
||||||
|
zIndex: 0
|
||||||
|
- data:
|
||||||
|
isInIteration: false
|
||||||
|
isInLoop: false
|
||||||
|
sourceType: llm
|
||||||
|
targetType: end
|
||||||
|
id: monthly_llm-source-end_monthly-target
|
||||||
|
selected: false
|
||||||
|
source: monthly_llm
|
||||||
|
sourceHandle: source
|
||||||
|
target: end_monthly
|
||||||
|
targetHandle: target
|
||||||
|
type: custom
|
||||||
|
zIndex: 0
|
||||||
|
- data:
|
||||||
|
isInIteration: false
|
||||||
|
isInLoop: false
|
||||||
|
sourceType: llm
|
||||||
|
targetType: end
|
||||||
|
id: invalid_llm-source-end_invalid-target
|
||||||
|
selected: false
|
||||||
|
source: invalid_llm
|
||||||
|
sourceHandle: source
|
||||||
|
target: end_invalid
|
||||||
targetHandle: target
|
targetHandle: target
|
||||||
type: custom
|
type: custom
|
||||||
zIndex: 0
|
zIndex: 0
|
||||||
@@ -114,6 +182,12 @@ workflow:
|
|||||||
title: 开始
|
title: 开始
|
||||||
type: start
|
type: start
|
||||||
variables:
|
variables:
|
||||||
|
- label: digest_type
|
||||||
|
max_length: 32
|
||||||
|
options: []
|
||||||
|
required: true
|
||||||
|
type: text-input
|
||||||
|
variable: digest_type
|
||||||
- label: query
|
- label: query
|
||||||
max_length: 120000
|
max_length: 120000
|
||||||
options: []
|
options: []
|
||||||
@@ -126,32 +200,114 @@ workflow:
|
|||||||
required: true
|
required: true
|
||||||
type: text-input
|
type: text-input
|
||||||
variable: chatroom_id
|
variable: chatroom_id
|
||||||
|
- label: wxid
|
||||||
|
max_length: 128
|
||||||
|
options: []
|
||||||
|
required: false
|
||||||
|
type: text-input
|
||||||
|
variable: wxid
|
||||||
|
- label: display_name
|
||||||
|
max_length: 128
|
||||||
|
options: []
|
||||||
|
required: false
|
||||||
|
type: text-input
|
||||||
|
variable: display_name
|
||||||
- label: digest_date
|
- label: digest_date
|
||||||
max_length: 32
|
max_length: 32
|
||||||
options: []
|
options: []
|
||||||
required: true
|
required: false
|
||||||
type: text-input
|
type: text-input
|
||||||
variable: digest_date
|
variable: digest_date
|
||||||
|
- label: period_key
|
||||||
|
max_length: 64
|
||||||
|
options: []
|
||||||
|
required: false
|
||||||
|
type: text-input
|
||||||
|
variable: period_key
|
||||||
- label: member_labels
|
- label: member_labels
|
||||||
max_length: 50000
|
max_length: 50000
|
||||||
options: []
|
options: []
|
||||||
required: true
|
required: false
|
||||||
type: paragraph
|
type: paragraph
|
||||||
variable: member_labels
|
variable: member_labels
|
||||||
- label: compressed_chat
|
- label: compressed_chat
|
||||||
max_length: 200000
|
max_length: 200000
|
||||||
options: []
|
options: []
|
||||||
required: true
|
required: false
|
||||||
type: paragraph
|
type: paragraph
|
||||||
variable: compressed_chat
|
variable: compressed_chat
|
||||||
height: 213
|
- label: source_items_json
|
||||||
|
max_length: 200000
|
||||||
|
options: []
|
||||||
|
required: false
|
||||||
|
type: paragraph
|
||||||
|
variable: source_items_json
|
||||||
|
- label: source_item_count
|
||||||
|
max_length: 32
|
||||||
|
options: []
|
||||||
|
required: false
|
||||||
|
type: text-input
|
||||||
|
variable: source_item_count
|
||||||
|
height: 388
|
||||||
id: start_node
|
id: start_node
|
||||||
position:
|
position:
|
||||||
x: 0
|
x: 0
|
||||||
y: 0
|
y: 110
|
||||||
positionAbsolute:
|
positionAbsolute:
|
||||||
x: 0
|
x: 0
|
||||||
y: 0
|
y: 110
|
||||||
|
selected: false
|
||||||
|
sourcePosition: right
|
||||||
|
targetPosition: left
|
||||||
|
type: custom
|
||||||
|
width: 242
|
||||||
|
- data:
|
||||||
|
cases:
|
||||||
|
- case_id: daily_case
|
||||||
|
conditions:
|
||||||
|
- comparison_operator: contains
|
||||||
|
id: daily_case_cond
|
||||||
|
value: daily
|
||||||
|
varType: string
|
||||||
|
variable_selector:
|
||||||
|
- start_node
|
||||||
|
- digest_type
|
||||||
|
id: daily_case
|
||||||
|
logical_operator: and
|
||||||
|
- case_id: weekly_case
|
||||||
|
conditions:
|
||||||
|
- comparison_operator: contains
|
||||||
|
id: weekly_case_cond
|
||||||
|
value: weekly
|
||||||
|
varType: string
|
||||||
|
variable_selector:
|
||||||
|
- start_node
|
||||||
|
- digest_type
|
||||||
|
id: weekly_case
|
||||||
|
logical_operator: and
|
||||||
|
- case_id: monthly_case
|
||||||
|
conditions:
|
||||||
|
- comparison_operator: contains
|
||||||
|
id: monthly_case_cond
|
||||||
|
value: monthly
|
||||||
|
varType: string
|
||||||
|
variable_selector:
|
||||||
|
- start_node
|
||||||
|
- digest_type
|
||||||
|
id: monthly_case
|
||||||
|
logical_operator: and
|
||||||
|
desc: ''
|
||||||
|
selected: false
|
||||||
|
title: 摘要类型分支
|
||||||
|
type: if-else
|
||||||
|
height: 222
|
||||||
|
id: digest_router
|
||||||
|
position:
|
||||||
|
x: 312
|
||||||
|
y: 162
|
||||||
|
positionAbsolute:
|
||||||
|
x: 312
|
||||||
|
y: 162
|
||||||
selected: false
|
selected: false
|
||||||
sourcePosition: right
|
sourcePosition: right
|
||||||
targetPosition: left
|
targetPosition: left
|
||||||
@@ -162,7 +318,7 @@ workflow:
|
|||||||
enabled: false
|
enabled: false
|
||||||
variable_selector: []
|
variable_selector: []
|
||||||
desc: ''
|
desc: ''
|
||||||
error_strategy: fail-branch
|
error_strategy: default-value
|
||||||
model:
|
model:
|
||||||
completion_params:
|
completion_params:
|
||||||
temperature: 0.2
|
temperature: 0.2
|
||||||
@@ -170,94 +326,289 @@ workflow:
|
|||||||
name: gpt-5.4-mini
|
name: gpt-5.4-mini
|
||||||
provider: langgenius/openai_api_compatible/openai_api_compatible
|
provider: langgenius/openai_api_compatible/openai_api_compatible
|
||||||
prompt_template:
|
prompt_template:
|
||||||
- id: system_prompt_member_context
|
- id: daily_system_prompt
|
||||||
role: system
|
role: system
|
||||||
text: "你是微信群后台的成员日行为证据提取器。\n\n任务:\n根据给定的一天群聊记录,只按 wxid 识别成员,输出每个成员当天的结构化行为观察。\n\
|
text: |
|
||||||
\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#}}
|
|
||||||
|
|
||||||
|
任务:
|
||||||
|
根据给定的一天群聊记录,只按 wxid 识别成员,输出每个成员当天的结构化行为观察。
|
||||||
|
|
||||||
|
关键规则:
|
||||||
|
1. wxid 是唯一标识。display_name 仅用于展示,不用于身份判定。
|
||||||
|
2. 每个 wxid 最终只能输出一条记录,严禁重复输出同一个 wxid。
|
||||||
|
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
|
||||||
|
5. identity_clues、family_signals、life_stage_signals 没有明确公开证据时允许为空。
|
||||||
|
6. 不允许因为“短句较多”就统一输出空数组和通用摘要。
|
||||||
|
7. 不做心理诊断、不做隐私猜测、不把玩笑当事实。
|
||||||
|
8. 只能输出候选成员列表中的 wxid。
|
||||||
|
9. topics 更偏向反复出现的关注方向;discussion_scenarios 更偏向什么情境下会发言;skill_signals 更偏向能力表现;problem_solving_signals 更偏向怎么处理问题;value_preferences 更偏向判断偏好;social_role 更偏向当天在群里的实际作用。
|
||||||
|
10. expression_markers 记录可观察的表达标记,如常用句式、口头禅、是否爱列步骤/贴日志;reply_entry_points 记录什么样的接话方式最容易接住他。
|
||||||
|
|
||||||
|
输出要求:
|
||||||
|
- 只输出严格 JSON,不要 markdown,不要解释,不要前后缀。
|
||||||
|
- 输出格式:
|
||||||
|
{
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"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"],
|
||||||
|
"interaction_style": "一句中文",
|
||||||
|
"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"],
|
||||||
|
"temperament_signal": "一句中文",
|
||||||
|
"summary_text": "不超过100字",
|
||||||
|
"representative_messages": ["原话1", "原话2"],
|
||||||
|
"confidence": 0.95
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
字段约束:
|
||||||
|
- 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 条
|
||||||
|
- 如果某成员样本明显不足,可以不输出该成员
|
||||||
|
- id: daily_user_prompt
|
||||||
|
role: user
|
||||||
|
text: |
|
||||||
|
摘要类型: daily
|
||||||
|
群ID: {{#start_node.chatroom_id#}}
|
||||||
日期: {{#start_node.digest_date#}}
|
日期: {{#start_node.digest_date#}}
|
||||||
|
|
||||||
|
|
||||||
候选成员:
|
候选成员:
|
||||||
|
|
||||||
{{#start_node.member_labels#}}
|
{{#start_node.member_labels#}}
|
||||||
|
|
||||||
|
|
||||||
压缩后的群聊记录:
|
压缩后的群聊记录:
|
||||||
|
|
||||||
{{#start_node.compressed_chat#}}
|
{{#start_node.compressed_chat#}}
|
||||||
|
|
||||||
'
|
补充上下文:
|
||||||
|
{{#start_node.query#}}
|
||||||
retry_config:
|
retry_config:
|
||||||
max_retries: 3
|
max_retries: 3
|
||||||
retry_enabled: true
|
retry_enabled: true
|
||||||
retry_interval: 1000
|
retry_interval: 1000
|
||||||
selected: false
|
selected: false
|
||||||
title: 成员画像提取
|
title: 日摘要生成
|
||||||
type: llm
|
type: llm
|
||||||
variables: []
|
variables: []
|
||||||
vision:
|
vision:
|
||||||
enabled: false
|
enabled: false
|
||||||
height: 154
|
height: 154
|
||||||
id: llm_node
|
id: daily_llm
|
||||||
position:
|
position:
|
||||||
x: 342
|
x: 652
|
||||||
y: 30
|
y: 18
|
||||||
positionAbsolute:
|
positionAbsolute:
|
||||||
x: 342
|
x: 652
|
||||||
y: 30
|
y: 18
|
||||||
selected: false
|
selected: false
|
||||||
sourcePosition: right
|
sourcePosition: right
|
||||||
targetPosition: left
|
targetPosition: left
|
||||||
type: custom
|
type: custom
|
||||||
width: 242
|
width: 242
|
||||||
- data:
|
- data:
|
||||||
|
context:
|
||||||
|
enabled: false
|
||||||
|
variable_selector: []
|
||||||
desc: ''
|
desc: ''
|
||||||
outputs:
|
error_strategy: default-value
|
||||||
- value_selector:
|
model:
|
||||||
- llm_node
|
completion_params:
|
||||||
- text
|
temperature: 0.2
|
||||||
variable: text
|
mode: chat
|
||||||
|
name: gpt-5.4-mini
|
||||||
|
provider: langgenius/openai_api_compatible/openai_api_compatible
|
||||||
|
prompt_template:
|
||||||
|
- id: weekly_system_prompt
|
||||||
|
role: system
|
||||||
|
text: |
|
||||||
|
你是微信群后台的成员周摘要生成器。
|
||||||
|
|
||||||
|
任务:
|
||||||
|
根据同一成员的多个日摘要,提炼本周重复出现的模式,过滤单日噪音,输出严格 JSON。
|
||||||
|
|
||||||
|
关键规则:
|
||||||
|
1. 只根据输入中的下级摘要生成,不可编造新增事实。
|
||||||
|
2. 只有多个日摘要反复出现的特征,才允许进入 stable_topics、stable_traits、habit_patterns、reply_preferences。
|
||||||
|
3. recent_state 只描述当前阶段状态,不要把短期现象写成长期人格。
|
||||||
|
4. identity_traits、family_profile、life_stage_profile 只能保留反复出现的公开线索。
|
||||||
|
5. common_scenarios、problem_solving_profile、expression_profile、reply_entry_profile 要尽量写成具体行为模式。
|
||||||
|
|
||||||
|
输出要求:
|
||||||
|
- 只输出严格 JSON,不要 markdown,不要解释,不要前后缀。
|
||||||
|
- 输出格式:
|
||||||
|
{
|
||||||
|
"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": "一句中文",
|
||||||
|
"recent_state": ["状态1"],
|
||||||
|
"temperament_tendency": "一句中文",
|
||||||
|
"summary_text": "一段不超过120字的周摘要",
|
||||||
|
"confidence": 0.0
|
||||||
|
}
|
||||||
|
- id: weekly_user_prompt
|
||||||
|
role: user
|
||||||
|
text: |
|
||||||
|
摘要类型: weekly
|
||||||
|
群ID: {{#start_node.chatroom_id#}}
|
||||||
|
成员: {{#start_node.display_name#}} ({{#start_node.wxid#}})
|
||||||
|
周期: {{#start_node.period_key#}}
|
||||||
|
下级摘要数量: {{#start_node.source_item_count#}}
|
||||||
|
|
||||||
|
下级摘要 JSON:
|
||||||
|
{{#start_node.source_items_json#}}
|
||||||
|
|
||||||
|
补充上下文:
|
||||||
|
{{#start_node.query#}}
|
||||||
|
retry_config:
|
||||||
|
max_retries: 3
|
||||||
|
retry_enabled: true
|
||||||
|
retry_interval: 1000
|
||||||
selected: false
|
selected: false
|
||||||
title: 结束
|
title: 周摘要生成
|
||||||
type: end
|
type: llm
|
||||||
height: 88
|
variables: []
|
||||||
id: end_node
|
vision:
|
||||||
|
enabled: false
|
||||||
|
height: 154
|
||||||
|
id: weekly_llm
|
||||||
position:
|
position:
|
||||||
x: 1066
|
x: 652
|
||||||
y: 52
|
y: 196
|
||||||
positionAbsolute:
|
positionAbsolute:
|
||||||
x: 1066
|
x: 652
|
||||||
y: 52
|
y: 196
|
||||||
|
selected: false
|
||||||
|
sourcePosition: right
|
||||||
|
targetPosition: left
|
||||||
|
type: custom
|
||||||
|
width: 242
|
||||||
|
- data:
|
||||||
|
context:
|
||||||
|
enabled: false
|
||||||
|
variable_selector: []
|
||||||
|
desc: ''
|
||||||
|
error_strategy: default-value
|
||||||
|
model:
|
||||||
|
completion_params:
|
||||||
|
temperature: 0.2
|
||||||
|
mode: chat
|
||||||
|
name: gpt-5.4-mini
|
||||||
|
provider: langgenius/openai_api_compatible/openai_api_compatible
|
||||||
|
prompt_template:
|
||||||
|
- id: monthly_system_prompt
|
||||||
|
role: system
|
||||||
|
text: |
|
||||||
|
你是微信群后台的成员月摘要生成器。
|
||||||
|
|
||||||
|
任务:
|
||||||
|
根据同一成员的多个周摘要,提炼阶段性稳定特征,只有反复出现的模式才能进入长期层,输出严格 JSON。
|
||||||
|
|
||||||
|
关键规则:
|
||||||
|
1. 只根据输入中的下级摘要生成,不可编造新增事实。
|
||||||
|
2. 只有多个周摘要反复出现的特征,才允许进入 long_term_topics、stable_traits、habit_patterns、long_term_reply_preferences。
|
||||||
|
3. phase_state 只描述当前阶段状态,不要冒充长期人格。
|
||||||
|
4. identity_traits、family_profile、life_stage_profile 只能保留反复出现的公开线索。
|
||||||
|
5. group_role、decision_profile、problem_solving_profile、expression_profile 要总结阶段性稳定模式。
|
||||||
|
|
||||||
|
输出要求:
|
||||||
|
- 只输出严格 JSON,不要 markdown,不要解释,不要前后缀。
|
||||||
|
- 输出格式:
|
||||||
|
{
|
||||||
|
"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": "一句中文",
|
||||||
|
"phase_state": ["状态1"],
|
||||||
|
"temperament_tendency": "一句中文",
|
||||||
|
"summary_text": "一段不超过140字的月摘要",
|
||||||
|
"confidence": 0.0
|
||||||
|
}
|
||||||
|
- id: monthly_user_prompt
|
||||||
|
role: user
|
||||||
|
text: |
|
||||||
|
摘要类型: monthly
|
||||||
|
群ID: {{#start_node.chatroom_id#}}
|
||||||
|
成员: {{#start_node.display_name#}} ({{#start_node.wxid#}})
|
||||||
|
周期: {{#start_node.period_key#}}
|
||||||
|
下级摘要数量: {{#start_node.source_item_count#}}
|
||||||
|
|
||||||
|
下级摘要 JSON:
|
||||||
|
{{#start_node.source_items_json#}}
|
||||||
|
|
||||||
|
补充上下文:
|
||||||
|
{{#start_node.query#}}
|
||||||
|
retry_config:
|
||||||
|
max_retries: 3
|
||||||
|
retry_enabled: true
|
||||||
|
retry_interval: 1000
|
||||||
|
selected: false
|
||||||
|
title: 月摘要生成
|
||||||
|
type: llm
|
||||||
|
variables: []
|
||||||
|
vision:
|
||||||
|
enabled: false
|
||||||
|
height: 154
|
||||||
|
id: monthly_llm
|
||||||
|
position:
|
||||||
|
x: 652
|
||||||
|
y: 374
|
||||||
|
positionAbsolute:
|
||||||
|
x: 652
|
||||||
|
y: 374
|
||||||
selected: false
|
selected: false
|
||||||
sourcePosition: right
|
sourcePosition: right
|
||||||
targetPosition: left
|
targetPosition: left
|
||||||
@@ -270,82 +621,139 @@ workflow:
|
|||||||
default_value:
|
default_value:
|
||||||
- key: text
|
- key: text
|
||||||
type: string
|
type: string
|
||||||
value: '{"members": []}'
|
value: '{"error":"unsupported_digest_type"}'
|
||||||
|
desc: ''
|
||||||
error_strategy: default-value
|
error_strategy: default-value
|
||||||
model:
|
model:
|
||||||
completion_params:
|
completion_params:
|
||||||
temperature: 0.7
|
temperature: 0.1
|
||||||
mode: chat
|
mode: chat
|
||||||
name: grok-4-fast
|
name: gpt-5.4-mini
|
||||||
provider: langgenius/openai_api_compatible/openai_api_compatible
|
provider: langgenius/openai_api_compatible/openai_api_compatible
|
||||||
prompt_template:
|
prompt_template:
|
||||||
- id: c5ee983e-d6e0-4790-ac3b-f4e097013b70
|
- id: invalid_system_prompt
|
||||||
role: system
|
role: system
|
||||||
text: "你是微信群后台的成员日行为证据提取器。\n\n任务:\n根据给定的一天群聊记录,只按 wxid 识别成员,输出每个成员当天的结构化行为观察。\n\
|
text: |
|
||||||
\n关键规则:\n1. wxid 是唯一标识。display_name 仅用于展示,不用于身份判定。\n2. 每个 wxid 最终只能输出一条记录,严禁重复输出同一个\
|
你是一个严格返回 JSON 的错误响应器。
|
||||||
\ wxid。\n3. 请先按 wxid 汇总该成员全天发言,再提取结果。\n4. 即使成员发言以短句为主,只要样本量足够,也必须尽量提炼:\n\
|
当摘要类型不受支持时,只返回一个紧凑 JSON。
|
||||||
\ - topics\n - discussion_scenarios\n - skill_signals\n - problem_solving_signals\n\
|
- id: invalid_user_prompt
|
||||||
\ - 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
|
role: user
|
||||||
text: '群ID: {{#start_node.chatroom_id#}}
|
text: |
|
||||||
|
输入的 digest_type 是: {{#start_node.digest_type#}}
|
||||||
日期: {{#start_node.digest_date#}}
|
请只返回:
|
||||||
|
{"error":"unsupported_digest_type","digest_type":"原值"}
|
||||||
|
selected: false
|
||||||
候选成员:
|
title: 非法类型处理
|
||||||
|
|
||||||
{{#start_node.member_labels#}}
|
|
||||||
|
|
||||||
|
|
||||||
压缩后的群聊记录:
|
|
||||||
|
|
||||||
{{#start_node.compressed_chat#}}
|
|
||||||
|
|
||||||
'
|
|
||||||
selected: true
|
|
||||||
title: 异常分支
|
|
||||||
type: llm
|
type: llm
|
||||||
vision:
|
vision:
|
||||||
enabled: false
|
enabled: false
|
||||||
height: 124
|
height: 124
|
||||||
id: '1775115372864'
|
id: invalid_llm
|
||||||
position:
|
position:
|
||||||
x: 704
|
x: 652
|
||||||
y: 132
|
y: 552
|
||||||
positionAbsolute:
|
positionAbsolute:
|
||||||
x: 704
|
x: 652
|
||||||
y: 132
|
y: 552
|
||||||
selected: true
|
selected: false
|
||||||
|
sourcePosition: right
|
||||||
|
targetPosition: left
|
||||||
|
type: custom
|
||||||
|
width: 242
|
||||||
|
- data:
|
||||||
|
desc: ''
|
||||||
|
outputs:
|
||||||
|
- value_selector:
|
||||||
|
- daily_llm
|
||||||
|
- text
|
||||||
|
variable: text
|
||||||
|
selected: false
|
||||||
|
title: 结束-日
|
||||||
|
type: end
|
||||||
|
height: 88
|
||||||
|
id: end_daily
|
||||||
|
position:
|
||||||
|
x: 1016
|
||||||
|
y: 54
|
||||||
|
positionAbsolute:
|
||||||
|
x: 1016
|
||||||
|
y: 54
|
||||||
|
selected: false
|
||||||
|
sourcePosition: right
|
||||||
|
targetPosition: left
|
||||||
|
type: custom
|
||||||
|
width: 242
|
||||||
|
- data:
|
||||||
|
desc: ''
|
||||||
|
outputs:
|
||||||
|
- value_selector:
|
||||||
|
- weekly_llm
|
||||||
|
- text
|
||||||
|
variable: text
|
||||||
|
selected: false
|
||||||
|
title: 结束-周
|
||||||
|
type: end
|
||||||
|
height: 88
|
||||||
|
id: end_weekly
|
||||||
|
position:
|
||||||
|
x: 1016
|
||||||
|
y: 232
|
||||||
|
positionAbsolute:
|
||||||
|
x: 1016
|
||||||
|
y: 232
|
||||||
|
selected: false
|
||||||
|
sourcePosition: right
|
||||||
|
targetPosition: left
|
||||||
|
type: custom
|
||||||
|
width: 242
|
||||||
|
- data:
|
||||||
|
desc: ''
|
||||||
|
outputs:
|
||||||
|
- value_selector:
|
||||||
|
- monthly_llm
|
||||||
|
- text
|
||||||
|
variable: text
|
||||||
|
selected: false
|
||||||
|
title: 结束-月
|
||||||
|
type: end
|
||||||
|
height: 88
|
||||||
|
id: end_monthly
|
||||||
|
position:
|
||||||
|
x: 1016
|
||||||
|
y: 410
|
||||||
|
positionAbsolute:
|
||||||
|
x: 1016
|
||||||
|
y: 410
|
||||||
|
selected: false
|
||||||
|
sourcePosition: right
|
||||||
|
targetPosition: left
|
||||||
|
type: custom
|
||||||
|
width: 242
|
||||||
|
- data:
|
||||||
|
desc: ''
|
||||||
|
outputs:
|
||||||
|
- value_selector:
|
||||||
|
- invalid_llm
|
||||||
|
- text
|
||||||
|
variable: text
|
||||||
|
selected: false
|
||||||
|
title: 结束-异常
|
||||||
|
type: end
|
||||||
|
height: 88
|
||||||
|
id: end_invalid
|
||||||
|
position:
|
||||||
|
x: 1016
|
||||||
|
y: 588
|
||||||
|
positionAbsolute:
|
||||||
|
x: 1016
|
||||||
|
y: 588
|
||||||
|
selected: false
|
||||||
sourcePosition: right
|
sourcePosition: right
|
||||||
targetPosition: left
|
targetPosition: left
|
||||||
type: custom
|
type: custom
|
||||||
width: 242
|
width: 242
|
||||||
viewport:
|
viewport:
|
||||||
x: 210
|
x: 60
|
||||||
y: 340
|
y: 120
|
||||||
zoom: 0.7
|
zoom: 0.7
|
||||||
rag_pipeline_variables: []
|
rag_pipeline_variables: []
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
app:
|
app:
|
||||||
description: 按群和日期提取群成员日画像,输出严格 JSON,供 member_context 插件直接消费
|
description: 支持日/周/月成员摘要生成的统一 workflow,按 digest_type 分支输出严格 JSON
|
||||||
icon: 🧠
|
icon: 🤖
|
||||||
icon_background: '#E0F2FE'
|
icon_background: '#E0F2FE'
|
||||||
mode: workflow
|
mode: workflow
|
||||||
name: member_context
|
name: member_context
|
||||||
@@ -9,9 +9,10 @@ dependencies:
|
|||||||
- current_identifier: null
|
- current_identifier: null
|
||||||
type: marketplace
|
type: marketplace
|
||||||
value:
|
value:
|
||||||
marketplace_plugin_unique_identifier: langgenius/volcengine_maas:0.0.13@d402dc32a505b1b4f27588f10e729209bf413ec263467635774d96c4345bd197
|
marketplace_plugin_unique_identifier: langgenius/openai_api_compatible:0.0.27@f9ce3ff5e28f09931a3a7fca59add2d09590408f7e9a3d701b10c77a60249719
|
||||||
|
version: null
|
||||||
kind: app
|
kind: app
|
||||||
version: 0.3.0
|
version: 0.6.0
|
||||||
workflow:
|
workflow:
|
||||||
conversation_variables: []
|
conversation_variables: []
|
||||||
environment_variables: []
|
environment_variables: []
|
||||||
@@ -29,7 +30,9 @@ workflow:
|
|||||||
audio_file_size_limit: 50
|
audio_file_size_limit: 50
|
||||||
batch_count_limit: 5
|
batch_count_limit: 5
|
||||||
file_size_limit: 15
|
file_size_limit: 15
|
||||||
|
image_file_batch_limit: 10
|
||||||
image_file_size_limit: 10
|
image_file_size_limit: 10
|
||||||
|
single_chunk_attachment_limit: 10
|
||||||
video_file_size_limit: 100
|
video_file_size_limit: 100
|
||||||
workflow_file_upload_limit: 10
|
workflow_file_upload_limit: 10
|
||||||
image:
|
image:
|
||||||
@@ -59,12 +62,64 @@ workflow:
|
|||||||
isInIteration: false
|
isInIteration: false
|
||||||
isInLoop: false
|
isInLoop: false
|
||||||
sourceType: start
|
sourceType: start
|
||||||
targetType: llm
|
targetType: if-else
|
||||||
id: start-source-llm-target
|
id: start-source-router-target
|
||||||
selected: false
|
selected: false
|
||||||
source: 'start_node'
|
source: start_node
|
||||||
sourceHandle: source
|
sourceHandle: source
|
||||||
target: 'llm_node'
|
target: digest_router
|
||||||
|
targetHandle: target
|
||||||
|
type: custom
|
||||||
|
zIndex: 0
|
||||||
|
- data:
|
||||||
|
isInIteration: false
|
||||||
|
isInLoop: false
|
||||||
|
sourceType: if-else
|
||||||
|
targetType: llm
|
||||||
|
id: digest_router-daily-daily_llm-target
|
||||||
|
selected: false
|
||||||
|
source: digest_router
|
||||||
|
sourceHandle: daily_case
|
||||||
|
target: daily_llm
|
||||||
|
targetHandle: target
|
||||||
|
type: custom
|
||||||
|
zIndex: 0
|
||||||
|
- data:
|
||||||
|
isInIteration: false
|
||||||
|
isInLoop: false
|
||||||
|
sourceType: if-else
|
||||||
|
targetType: llm
|
||||||
|
id: digest_router-weekly-weekly_llm-target
|
||||||
|
selected: false
|
||||||
|
source: digest_router
|
||||||
|
sourceHandle: weekly_case
|
||||||
|
target: weekly_llm
|
||||||
|
targetHandle: target
|
||||||
|
type: custom
|
||||||
|
zIndex: 0
|
||||||
|
- data:
|
||||||
|
isInIteration: false
|
||||||
|
isInLoop: false
|
||||||
|
sourceType: if-else
|
||||||
|
targetType: llm
|
||||||
|
id: digest_router-monthly-monthly_llm-target
|
||||||
|
selected: false
|
||||||
|
source: digest_router
|
||||||
|
sourceHandle: monthly_case
|
||||||
|
target: monthly_llm
|
||||||
|
targetHandle: target
|
||||||
|
type: custom
|
||||||
|
zIndex: 0
|
||||||
|
- data:
|
||||||
|
isInIteration: false
|
||||||
|
isInLoop: false
|
||||||
|
sourceType: if-else
|
||||||
|
targetType: llm
|
||||||
|
id: digest_router-false-invalid_llm-target
|
||||||
|
selected: false
|
||||||
|
source: digest_router
|
||||||
|
sourceHandle: 'false'
|
||||||
|
target: invalid_llm
|
||||||
targetHandle: target
|
targetHandle: target
|
||||||
type: custom
|
type: custom
|
||||||
zIndex: 0
|
zIndex: 0
|
||||||
@@ -73,11 +128,50 @@ workflow:
|
|||||||
isInLoop: false
|
isInLoop: false
|
||||||
sourceType: llm
|
sourceType: llm
|
||||||
targetType: end
|
targetType: end
|
||||||
id: llm-source-end-target
|
id: daily_llm-source-end_daily-target
|
||||||
selected: false
|
selected: false
|
||||||
source: 'llm_node'
|
source: daily_llm
|
||||||
sourceHandle: source
|
sourceHandle: source
|
||||||
target: 'end_node'
|
target: end_daily
|
||||||
|
targetHandle: target
|
||||||
|
type: custom
|
||||||
|
zIndex: 0
|
||||||
|
- data:
|
||||||
|
isInIteration: false
|
||||||
|
isInLoop: false
|
||||||
|
sourceType: llm
|
||||||
|
targetType: end
|
||||||
|
id: weekly_llm-source-end_weekly-target
|
||||||
|
selected: false
|
||||||
|
source: weekly_llm
|
||||||
|
sourceHandle: source
|
||||||
|
target: end_weekly
|
||||||
|
targetHandle: target
|
||||||
|
type: custom
|
||||||
|
zIndex: 0
|
||||||
|
- data:
|
||||||
|
isInIteration: false
|
||||||
|
isInLoop: false
|
||||||
|
sourceType: llm
|
||||||
|
targetType: end
|
||||||
|
id: monthly_llm-source-end_monthly-target
|
||||||
|
selected: false
|
||||||
|
source: monthly_llm
|
||||||
|
sourceHandle: source
|
||||||
|
target: end_monthly
|
||||||
|
targetHandle: target
|
||||||
|
type: custom
|
||||||
|
zIndex: 0
|
||||||
|
- data:
|
||||||
|
isInIteration: false
|
||||||
|
isInLoop: false
|
||||||
|
sourceType: llm
|
||||||
|
targetType: end
|
||||||
|
id: invalid_llm-source-end_invalid-target
|
||||||
|
selected: false
|
||||||
|
source: invalid_llm
|
||||||
|
sourceHandle: source
|
||||||
|
target: end_invalid
|
||||||
targetHandle: target
|
targetHandle: target
|
||||||
type: custom
|
type: custom
|
||||||
zIndex: 0
|
zIndex: 0
|
||||||
@@ -88,6 +182,12 @@ workflow:
|
|||||||
title: 开始
|
title: 开始
|
||||||
type: start
|
type: start
|
||||||
variables:
|
variables:
|
||||||
|
- label: digest_type
|
||||||
|
max_length: 32
|
||||||
|
options: []
|
||||||
|
required: true
|
||||||
|
type: text-input
|
||||||
|
variable: digest_type
|
||||||
- label: query
|
- label: query
|
||||||
max_length: 120000
|
max_length: 120000
|
||||||
options: []
|
options: []
|
||||||
@@ -100,55 +200,133 @@ workflow:
|
|||||||
required: true
|
required: true
|
||||||
type: text-input
|
type: text-input
|
||||||
variable: chatroom_id
|
variable: chatroom_id
|
||||||
|
- label: wxid
|
||||||
|
max_length: 128
|
||||||
|
options: []
|
||||||
|
required: false
|
||||||
|
type: text-input
|
||||||
|
variable: wxid
|
||||||
|
- label: display_name
|
||||||
|
max_length: 128
|
||||||
|
options: []
|
||||||
|
required: false
|
||||||
|
type: text-input
|
||||||
|
variable: display_name
|
||||||
- label: digest_date
|
- label: digest_date
|
||||||
max_length: 32
|
max_length: 32
|
||||||
options: []
|
options: []
|
||||||
required: true
|
required: false
|
||||||
type: text-input
|
type: text-input
|
||||||
variable: digest_date
|
variable: digest_date
|
||||||
|
- label: period_key
|
||||||
|
max_length: 64
|
||||||
|
options: []
|
||||||
|
required: false
|
||||||
|
type: text-input
|
||||||
|
variable: period_key
|
||||||
- label: member_labels
|
- label: member_labels
|
||||||
max_length: 50000
|
max_length: 50000
|
||||||
options: []
|
options: []
|
||||||
required: true
|
required: false
|
||||||
type: paragraph
|
type: paragraph
|
||||||
variable: member_labels
|
variable: member_labels
|
||||||
- label: compressed_chat
|
- label: compressed_chat
|
||||||
max_length: 200000
|
max_length: 200000
|
||||||
options: []
|
options: []
|
||||||
required: true
|
required: false
|
||||||
type: paragraph
|
type: paragraph
|
||||||
variable: compressed_chat
|
variable: compressed_chat
|
||||||
height: 194
|
- label: source_items_json
|
||||||
id: 'start_node'
|
max_length: 200000
|
||||||
|
options: []
|
||||||
|
required: false
|
||||||
|
type: paragraph
|
||||||
|
variable: source_items_json
|
||||||
|
- label: source_item_count
|
||||||
|
max_length: 32
|
||||||
|
options: []
|
||||||
|
required: false
|
||||||
|
type: text-input
|
||||||
|
variable: source_item_count
|
||||||
|
height: 388
|
||||||
|
id: start_node
|
||||||
position:
|
position:
|
||||||
x: -420
|
x: 0
|
||||||
y: 120
|
y: 110
|
||||||
positionAbsolute:
|
positionAbsolute:
|
||||||
x: -420
|
x: 0
|
||||||
y: 120
|
y: 110
|
||||||
selected: false
|
selected: false
|
||||||
sourcePosition: right
|
sourcePosition: right
|
||||||
targetPosition: left
|
targetPosition: left
|
||||||
type: custom
|
type: custom
|
||||||
width: 244
|
width: 242
|
||||||
|
- data:
|
||||||
|
cases:
|
||||||
|
- case_id: daily_case
|
||||||
|
conditions:
|
||||||
|
- comparison_operator: contains
|
||||||
|
id: daily_case_cond
|
||||||
|
value: daily
|
||||||
|
varType: string
|
||||||
|
variable_selector:
|
||||||
|
- start_node
|
||||||
|
- digest_type
|
||||||
|
id: daily_case
|
||||||
|
logical_operator: and
|
||||||
|
- case_id: weekly_case
|
||||||
|
conditions:
|
||||||
|
- comparison_operator: contains
|
||||||
|
id: weekly_case_cond
|
||||||
|
value: weekly
|
||||||
|
varType: string
|
||||||
|
variable_selector:
|
||||||
|
- start_node
|
||||||
|
- digest_type
|
||||||
|
id: weekly_case
|
||||||
|
logical_operator: and
|
||||||
|
- case_id: monthly_case
|
||||||
|
conditions:
|
||||||
|
- comparison_operator: contains
|
||||||
|
id: monthly_case_cond
|
||||||
|
value: monthly
|
||||||
|
varType: string
|
||||||
|
variable_selector:
|
||||||
|
- start_node
|
||||||
|
- digest_type
|
||||||
|
id: monthly_case
|
||||||
|
logical_operator: and
|
||||||
|
desc: ''
|
||||||
|
selected: false
|
||||||
|
title: 摘要类型分支
|
||||||
|
type: if-else
|
||||||
|
height: 222
|
||||||
|
id: digest_router
|
||||||
|
position:
|
||||||
|
x: 312
|
||||||
|
y: 162
|
||||||
|
positionAbsolute:
|
||||||
|
x: 312
|
||||||
|
y: 162
|
||||||
|
selected: false
|
||||||
|
sourcePosition: right
|
||||||
|
targetPosition: left
|
||||||
|
type: custom
|
||||||
|
width: 242
|
||||||
- data:
|
- data:
|
||||||
context:
|
context:
|
||||||
enabled: false
|
enabled: false
|
||||||
variable_selector: []
|
variable_selector: []
|
||||||
default_value:
|
|
||||||
- key: text
|
|
||||||
type: string
|
|
||||||
value: '{"members":[]}'
|
|
||||||
desc: ''
|
desc: ''
|
||||||
error_strategy: default-value
|
error_strategy: default-value
|
||||||
model:
|
model:
|
||||||
completion_params:
|
completion_params:
|
||||||
temperature: 0.2
|
temperature: 0.2
|
||||||
mode: chat
|
mode: chat
|
||||||
name: Doubao-1.5-pro-256k
|
name: gpt-5.4-mini
|
||||||
provider: langgenius/volcengine_maas/volcengine_maas
|
provider: langgenius/openai_api_compatible/openai_api_compatible
|
||||||
prompt_template:
|
prompt_template:
|
||||||
- id: system_prompt_member_context
|
- id: daily_system_prompt
|
||||||
role: system
|
role: system
|
||||||
text: |
|
text: |
|
||||||
你是微信群后台的成员日行为证据提取器。
|
你是微信群后台的成员日行为证据提取器。
|
||||||
@@ -179,7 +357,6 @@ workflow:
|
|||||||
8. 只能输出候选成员列表中的 wxid。
|
8. 只能输出候选成员列表中的 wxid。
|
||||||
9. topics 更偏向反复出现的关注方向;discussion_scenarios 更偏向什么情境下会发言;skill_signals 更偏向能力表现;problem_solving_signals 更偏向怎么处理问题;value_preferences 更偏向判断偏好;social_role 更偏向当天在群里的实际作用。
|
9. topics 更偏向反复出现的关注方向;discussion_scenarios 更偏向什么情境下会发言;skill_signals 更偏向能力表现;problem_solving_signals 更偏向怎么处理问题;value_preferences 更偏向判断偏好;social_role 更偏向当天在群里的实际作用。
|
||||||
10. expression_markers 记录可观察的表达标记,如常用句式、口头禅、是否爱列步骤/贴日志;reply_entry_points 记录什么样的接话方式最容易接住他。
|
10. expression_markers 记录可观察的表达标记,如常用句式、口头禅、是否爱列步骤/贴日志;reply_entry_points 记录什么样的接话方式最容易接住他。
|
||||||
11. 输出前自行去重,同一个 wxid 只保留一条最完整结果。
|
|
||||||
|
|
||||||
输出要求:
|
输出要求:
|
||||||
- 只输出严格 JSON,不要 markdown,不要解释,不要前后缀。
|
- 只输出严格 JSON,不要 markdown,不要解释,不要前后缀。
|
||||||
@@ -221,9 +398,10 @@ workflow:
|
|||||||
- reply_taboos 最多 3 个
|
- reply_taboos 最多 3 个
|
||||||
- representative_messages 最多 3 条
|
- representative_messages 最多 3 条
|
||||||
- 如果某成员样本明显不足,可以不输出该成员
|
- 如果某成员样本明显不足,可以不输出该成员
|
||||||
- id: user_prompt_member_context
|
- id: daily_user_prompt
|
||||||
role: user
|
role: user
|
||||||
text: |
|
text: |
|
||||||
|
摘要类型: daily
|
||||||
群ID: {{#start_node.chatroom_id#}}
|
群ID: {{#start_node.chatroom_id#}}
|
||||||
日期: {{#start_node.digest_date#}}
|
日期: {{#start_node.digest_date#}}
|
||||||
|
|
||||||
@@ -232,49 +410,350 @@ workflow:
|
|||||||
|
|
||||||
压缩后的群聊记录:
|
压缩后的群聊记录:
|
||||||
{{#start_node.compressed_chat#}}
|
{{#start_node.compressed_chat#}}
|
||||||
|
|
||||||
|
补充上下文:
|
||||||
|
{{#start_node.query#}}
|
||||||
|
retry_config:
|
||||||
|
max_retries: 3
|
||||||
|
retry_enabled: true
|
||||||
|
retry_interval: 1000
|
||||||
selected: false
|
selected: false
|
||||||
title: 成员画像提取
|
title: 日摘要生成
|
||||||
type: llm
|
type: llm
|
||||||
variables: []
|
variables: []
|
||||||
vision:
|
vision:
|
||||||
enabled: false
|
enabled: false
|
||||||
height: 98
|
height: 154
|
||||||
id: 'llm_node'
|
id: daily_llm
|
||||||
position:
|
position:
|
||||||
x: 10
|
x: 652
|
||||||
y: 140
|
y: 18
|
||||||
positionAbsolute:
|
positionAbsolute:
|
||||||
x: 10
|
x: 652
|
||||||
y: 140
|
y: 18
|
||||||
selected: false
|
selected: false
|
||||||
sourcePosition: right
|
sourcePosition: right
|
||||||
targetPosition: left
|
targetPosition: left
|
||||||
type: custom
|
type: custom
|
||||||
width: 244
|
width: 242
|
||||||
|
- data:
|
||||||
|
context:
|
||||||
|
enabled: false
|
||||||
|
variable_selector: []
|
||||||
|
desc: ''
|
||||||
|
error_strategy: default-value
|
||||||
|
model:
|
||||||
|
completion_params:
|
||||||
|
temperature: 0.2
|
||||||
|
mode: chat
|
||||||
|
name: gpt-5.4-mini
|
||||||
|
provider: langgenius/openai_api_compatible/openai_api_compatible
|
||||||
|
prompt_template:
|
||||||
|
- id: weekly_system_prompt
|
||||||
|
role: system
|
||||||
|
text: |
|
||||||
|
你是微信群后台的成员周摘要生成器。
|
||||||
|
|
||||||
|
任务:
|
||||||
|
根据同一成员的多个日摘要,提炼本周重复出现的模式,过滤单日噪音,输出严格 JSON。
|
||||||
|
|
||||||
|
关键规则:
|
||||||
|
1. 只根据输入中的下级摘要生成,不可编造新增事实。
|
||||||
|
2. 只有多个日摘要反复出现的特征,才允许进入 stable_topics、stable_traits、habit_patterns、reply_preferences。
|
||||||
|
3. recent_state 只描述当前阶段状态,不要把短期现象写成长期人格。
|
||||||
|
4. identity_traits、family_profile、life_stage_profile 只能保留反复出现的公开线索。
|
||||||
|
5. common_scenarios、problem_solving_profile、expression_profile、reply_entry_profile 要尽量写成具体行为模式。
|
||||||
|
|
||||||
|
输出要求:
|
||||||
|
- 只输出严格 JSON,不要 markdown,不要解释,不要前后缀。
|
||||||
|
- 输出格式:
|
||||||
|
{
|
||||||
|
"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": "一句中文",
|
||||||
|
"recent_state": ["状态1"],
|
||||||
|
"temperament_tendency": "一句中文",
|
||||||
|
"summary_text": "一段不超过120字的周摘要",
|
||||||
|
"confidence": 0.0
|
||||||
|
}
|
||||||
|
- id: weekly_user_prompt
|
||||||
|
role: user
|
||||||
|
text: |
|
||||||
|
摘要类型: weekly
|
||||||
|
群ID: {{#start_node.chatroom_id#}}
|
||||||
|
成员: {{#start_node.display_name#}} ({{#start_node.wxid#}})
|
||||||
|
周期: {{#start_node.period_key#}}
|
||||||
|
下级摘要数量: {{#start_node.source_item_count#}}
|
||||||
|
|
||||||
|
下级摘要 JSON:
|
||||||
|
{{#start_node.source_items_json#}}
|
||||||
|
|
||||||
|
补充上下文:
|
||||||
|
{{#start_node.query#}}
|
||||||
|
retry_config:
|
||||||
|
max_retries: 3
|
||||||
|
retry_enabled: true
|
||||||
|
retry_interval: 1000
|
||||||
|
selected: false
|
||||||
|
title: 周摘要生成
|
||||||
|
type: llm
|
||||||
|
variables: []
|
||||||
|
vision:
|
||||||
|
enabled: false
|
||||||
|
height: 154
|
||||||
|
id: weekly_llm
|
||||||
|
position:
|
||||||
|
x: 652
|
||||||
|
y: 196
|
||||||
|
positionAbsolute:
|
||||||
|
x: 652
|
||||||
|
y: 196
|
||||||
|
selected: false
|
||||||
|
sourcePosition: right
|
||||||
|
targetPosition: left
|
||||||
|
type: custom
|
||||||
|
width: 242
|
||||||
|
- data:
|
||||||
|
context:
|
||||||
|
enabled: false
|
||||||
|
variable_selector: []
|
||||||
|
desc: ''
|
||||||
|
error_strategy: default-value
|
||||||
|
model:
|
||||||
|
completion_params:
|
||||||
|
temperature: 0.2
|
||||||
|
mode: chat
|
||||||
|
name: gpt-5.4-mini
|
||||||
|
provider: langgenius/openai_api_compatible/openai_api_compatible
|
||||||
|
prompt_template:
|
||||||
|
- id: monthly_system_prompt
|
||||||
|
role: system
|
||||||
|
text: |
|
||||||
|
你是微信群后台的成员月摘要生成器。
|
||||||
|
|
||||||
|
任务:
|
||||||
|
根据同一成员的多个周摘要,提炼阶段性稳定特征,只有反复出现的模式才能进入长期层,输出严格 JSON。
|
||||||
|
|
||||||
|
关键规则:
|
||||||
|
1. 只根据输入中的下级摘要生成,不可编造新增事实。
|
||||||
|
2. 只有多个周摘要反复出现的特征,才允许进入 long_term_topics、stable_traits、habit_patterns、long_term_reply_preferences。
|
||||||
|
3. phase_state 只描述当前阶段状态,不要冒充长期人格。
|
||||||
|
4. identity_traits、family_profile、life_stage_profile 只能保留反复出现的公开线索。
|
||||||
|
5. group_role、decision_profile、problem_solving_profile、expression_profile 要总结阶段性稳定模式。
|
||||||
|
|
||||||
|
输出要求:
|
||||||
|
- 只输出严格 JSON,不要 markdown,不要解释,不要前后缀。
|
||||||
|
- 输出格式:
|
||||||
|
{
|
||||||
|
"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": "一句中文",
|
||||||
|
"phase_state": ["状态1"],
|
||||||
|
"temperament_tendency": "一句中文",
|
||||||
|
"summary_text": "一段不超过140字的月摘要",
|
||||||
|
"confidence": 0.0
|
||||||
|
}
|
||||||
|
- id: monthly_user_prompt
|
||||||
|
role: user
|
||||||
|
text: |
|
||||||
|
摘要类型: monthly
|
||||||
|
群ID: {{#start_node.chatroom_id#}}
|
||||||
|
成员: {{#start_node.display_name#}} ({{#start_node.wxid#}})
|
||||||
|
周期: {{#start_node.period_key#}}
|
||||||
|
下级摘要数量: {{#start_node.source_item_count#}}
|
||||||
|
|
||||||
|
下级摘要 JSON:
|
||||||
|
{{#start_node.source_items_json#}}
|
||||||
|
|
||||||
|
补充上下文:
|
||||||
|
{{#start_node.query#}}
|
||||||
|
retry_config:
|
||||||
|
max_retries: 3
|
||||||
|
retry_enabled: true
|
||||||
|
retry_interval: 1000
|
||||||
|
selected: false
|
||||||
|
title: 月摘要生成
|
||||||
|
type: llm
|
||||||
|
variables: []
|
||||||
|
vision:
|
||||||
|
enabled: false
|
||||||
|
height: 154
|
||||||
|
id: monthly_llm
|
||||||
|
position:
|
||||||
|
x: 652
|
||||||
|
y: 374
|
||||||
|
positionAbsolute:
|
||||||
|
x: 652
|
||||||
|
y: 374
|
||||||
|
selected: false
|
||||||
|
sourcePosition: right
|
||||||
|
targetPosition: left
|
||||||
|
type: custom
|
||||||
|
width: 242
|
||||||
|
- data:
|
||||||
|
context:
|
||||||
|
enabled: false
|
||||||
|
variable_selector: []
|
||||||
|
default_value:
|
||||||
|
- key: text
|
||||||
|
type: string
|
||||||
|
value: '{"error":"unsupported_digest_type"}'
|
||||||
|
desc: ''
|
||||||
|
error_strategy: default-value
|
||||||
|
model:
|
||||||
|
completion_params:
|
||||||
|
temperature: 0.1
|
||||||
|
mode: chat
|
||||||
|
name: gpt-5.4-mini
|
||||||
|
provider: langgenius/openai_api_compatible/openai_api_compatible
|
||||||
|
prompt_template:
|
||||||
|
- id: invalid_system_prompt
|
||||||
|
role: system
|
||||||
|
text: |
|
||||||
|
你是一个严格返回 JSON 的错误响应器。
|
||||||
|
当摘要类型不受支持时,只返回一个紧凑 JSON。
|
||||||
|
- id: invalid_user_prompt
|
||||||
|
role: user
|
||||||
|
text: |
|
||||||
|
输入的 digest_type 是: {{#start_node.digest_type#}}
|
||||||
|
请只返回:
|
||||||
|
{"error":"unsupported_digest_type","digest_type":"原值"}
|
||||||
|
selected: false
|
||||||
|
title: 非法类型处理
|
||||||
|
type: llm
|
||||||
|
vision:
|
||||||
|
enabled: false
|
||||||
|
height: 124
|
||||||
|
id: invalid_llm
|
||||||
|
position:
|
||||||
|
x: 652
|
||||||
|
y: 552
|
||||||
|
positionAbsolute:
|
||||||
|
x: 652
|
||||||
|
y: 552
|
||||||
|
selected: false
|
||||||
|
sourcePosition: right
|
||||||
|
targetPosition: left
|
||||||
|
type: custom
|
||||||
|
width: 242
|
||||||
- data:
|
- data:
|
||||||
desc: ''
|
desc: ''
|
||||||
outputs:
|
outputs:
|
||||||
- value_selector:
|
- value_selector:
|
||||||
- 'llm_node'
|
- daily_llm
|
||||||
- text
|
- text
|
||||||
variable: text
|
variable: text
|
||||||
selected: false
|
selected: false
|
||||||
title: 结束
|
title: 结束-日
|
||||||
type: end
|
type: end
|
||||||
height: 90
|
height: 88
|
||||||
id: 'end_node'
|
id: end_daily
|
||||||
position:
|
position:
|
||||||
x: 430
|
x: 1016
|
||||||
y: 140
|
y: 54
|
||||||
positionAbsolute:
|
positionAbsolute:
|
||||||
x: 430
|
x: 1016
|
||||||
y: 140
|
y: 54
|
||||||
selected: false
|
selected: false
|
||||||
sourcePosition: right
|
sourcePosition: right
|
||||||
targetPosition: left
|
targetPosition: left
|
||||||
type: custom
|
type: custom
|
||||||
width: 244
|
width: 242
|
||||||
|
- data:
|
||||||
|
desc: ''
|
||||||
|
outputs:
|
||||||
|
- value_selector:
|
||||||
|
- weekly_llm
|
||||||
|
- text
|
||||||
|
variable: text
|
||||||
|
selected: false
|
||||||
|
title: 结束-周
|
||||||
|
type: end
|
||||||
|
height: 88
|
||||||
|
id: end_weekly
|
||||||
|
position:
|
||||||
|
x: 1016
|
||||||
|
y: 232
|
||||||
|
positionAbsolute:
|
||||||
|
x: 1016
|
||||||
|
y: 232
|
||||||
|
selected: false
|
||||||
|
sourcePosition: right
|
||||||
|
targetPosition: left
|
||||||
|
type: custom
|
||||||
|
width: 242
|
||||||
|
- data:
|
||||||
|
desc: ''
|
||||||
|
outputs:
|
||||||
|
- value_selector:
|
||||||
|
- monthly_llm
|
||||||
|
- text
|
||||||
|
variable: text
|
||||||
|
selected: false
|
||||||
|
title: 结束-月
|
||||||
|
type: end
|
||||||
|
height: 88
|
||||||
|
id: end_monthly
|
||||||
|
position:
|
||||||
|
x: 1016
|
||||||
|
y: 410
|
||||||
|
positionAbsolute:
|
||||||
|
x: 1016
|
||||||
|
y: 410
|
||||||
|
selected: false
|
||||||
|
sourcePosition: right
|
||||||
|
targetPosition: left
|
||||||
|
type: custom
|
||||||
|
width: 242
|
||||||
|
- data:
|
||||||
|
desc: ''
|
||||||
|
outputs:
|
||||||
|
- value_selector:
|
||||||
|
- invalid_llm
|
||||||
|
- text
|
||||||
|
variable: text
|
||||||
|
selected: false
|
||||||
|
title: 结束-异常
|
||||||
|
type: end
|
||||||
|
height: 88
|
||||||
|
id: end_invalid
|
||||||
|
position:
|
||||||
|
x: 1016
|
||||||
|
y: 588
|
||||||
|
positionAbsolute:
|
||||||
|
x: 1016
|
||||||
|
y: 588
|
||||||
|
selected: false
|
||||||
|
sourcePosition: right
|
||||||
|
targetPosition: left
|
||||||
|
type: custom
|
||||||
|
width: 242
|
||||||
viewport:
|
viewport:
|
||||||
x: 120
|
x: 60
|
||||||
y: 180
|
y: 120
|
||||||
zoom: 0.9
|
zoom: 0.7
|
||||||
|
rag_pipeline_variables: []
|
||||||
|
|||||||
@@ -69,7 +69,8 @@ class MemberContextService:
|
|||||||
|
|
||||||
def build_member_context(self, chatroom_id: str, wxid: str, days: Optional[int] = None,
|
def build_member_context(self, chatroom_id: str, wxid: str, days: Optional[int] = None,
|
||||||
limit: Optional[int] = None, force_digest_rebuild: bool = False,
|
limit: Optional[int] = None, force_digest_rebuild: bool = False,
|
||||||
ensure_group_daily: bool = True) -> Dict:
|
ensure_group_daily: bool = True, enable_weekly_digest: bool = True,
|
||||||
|
enable_monthly_digest: bool = True) -> Dict:
|
||||||
days = days or self.sample_days
|
days = days or self.sample_days
|
||||||
limit = limit or self.refresh_limit_per_member
|
limit = limit or self.refresh_limit_per_member
|
||||||
|
|
||||||
@@ -83,11 +84,15 @@ class MemberContextService:
|
|||||||
chatroom_id, force=force_digest_rebuild
|
chatroom_id, force=force_digest_rebuild
|
||||||
)
|
)
|
||||||
digest_snapshot = self.digest_service.ensure_member_digest_pipeline(
|
digest_snapshot = self.digest_service.ensure_member_digest_pipeline(
|
||||||
chatroom_id, wxid, force=force_digest_rebuild
|
chatroom_id, wxid, force=force_digest_rebuild,
|
||||||
|
enable_weekly=enable_weekly_digest, enable_monthly=enable_monthly_digest
|
||||||
)
|
)
|
||||||
daily_digests = digest_snapshot.get("daily_digests", [])
|
daily_digests = digest_snapshot.get("daily_digests", [])
|
||||||
weekly_digests = digest_snapshot.get("weekly_digests", [])
|
weekly_digests = digest_snapshot.get("weekly_digests", [])
|
||||||
monthly_digests = digest_snapshot.get("monthly_digests", [])
|
monthly_digests = digest_snapshot.get("monthly_digests", [])
|
||||||
|
all_daily_digests = digest_snapshot.get("all_daily_digests", daily_digests)
|
||||||
|
all_weekly_digests = digest_snapshot.get("all_weekly_digests", weekly_digests)
|
||||||
|
all_monthly_digests = digest_snapshot.get("all_monthly_digests", monthly_digests)
|
||||||
|
|
||||||
recent_messages = self.message_db.get_member_recent_messages(
|
recent_messages = self.message_db.get_member_recent_messages(
|
||||||
chatroom_id,
|
chatroom_id,
|
||||||
@@ -100,7 +105,7 @@ class MemberContextService:
|
|||||||
weekly_structured = [item.get("structured", {}) or {} for item in weekly_digests]
|
weekly_structured = [item.get("structured", {}) or {} for item in weekly_digests]
|
||||||
daily_structured = [item.get("structured", {}) or {} for item in daily_digests]
|
daily_structured = [item.get("structured", {}) or {} for item in daily_digests]
|
||||||
|
|
||||||
observation_days = self._calc_observation_days(daily_digests)
|
observation_days = self._calc_observation_days(all_daily_digests)
|
||||||
activity_level = self._calc_activity_level(len(recent_messages), max(min(days, 7), 1))
|
activity_level = self._calc_activity_level(len(recent_messages), max(min(days, 7), 1))
|
||||||
context = {
|
context = {
|
||||||
"chatroom_id": chatroom_id,
|
"chatroom_id": chatroom_id,
|
||||||
@@ -121,7 +126,7 @@ class MemberContextService:
|
|||||||
),
|
),
|
||||||
"recent_focus": self._extract_scored_items(daily_structured, ["topics"], limit=4),
|
"recent_focus": self._extract_scored_items(daily_structured, ["topics"], limit=4),
|
||||||
"summary_text": "",
|
"summary_text": "",
|
||||||
"confidence": self._calc_digest_confidence(monthly_digests, weekly_digests, daily_digests),
|
"confidence": self._calc_digest_confidence(all_monthly_digests, all_weekly_digests, all_daily_digests),
|
||||||
"source_message_count": len(recent_messages),
|
"source_message_count": len(recent_messages),
|
||||||
"source_days": days,
|
"source_days": days,
|
||||||
"last_profiled_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
"last_profiled_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
@@ -194,10 +199,10 @@ class MemberContextService:
|
|||||||
"observation_days": observation_days,
|
"observation_days": observation_days,
|
||||||
"stable_ready": observation_days >= self.stable_ready_days,
|
"stable_ready": observation_days >= self.stable_ready_days,
|
||||||
"profile_iterations": int(((existing_context or {}).get("meta", {}) or {}).get("profile_iterations", 0)) + 1,
|
"profile_iterations": int(((existing_context or {}).get("meta", {}) or {}).get("profile_iterations", 0)) + 1,
|
||||||
"history_message_count": self._sum_digest_source_count(daily_digests),
|
"history_message_count": self._sum_digest_source_count(all_daily_digests),
|
||||||
"digest_daily_count": len(daily_digests),
|
"digest_daily_count": len(all_daily_digests),
|
||||||
"digest_weekly_count": len(weekly_digests),
|
"digest_weekly_count": len(all_weekly_digests),
|
||||||
"digest_monthly_count": len(monthly_digests),
|
"digest_monthly_count": len(all_monthly_digests),
|
||||||
"last_daily_digest_at": daily_digests[0].get("last_generated_at") if daily_digests else "",
|
"last_daily_digest_at": daily_digests[0].get("last_generated_at") if daily_digests else "",
|
||||||
"last_weekly_digest_at": weekly_digests[0].get("last_generated_at") if weekly_digests else "",
|
"last_weekly_digest_at": weekly_digests[0].get("last_generated_at") if weekly_digests else "",
|
||||||
"last_monthly_digest_at": monthly_digests[0].get("last_generated_at") if monthly_digests else "",
|
"last_monthly_digest_at": monthly_digests[0].get("last_generated_at") if monthly_digests else "",
|
||||||
@@ -226,11 +231,15 @@ class MemberContextService:
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
def refresh_member_context(self, chatroom_id: str, wxid: str, days: Optional[int] = None,
|
def refresh_member_context(self, chatroom_id: str, wxid: str, days: Optional[int] = None,
|
||||||
limit: Optional[int] = None) -> Dict:
|
limit: Optional[int] = None, enable_weekly_digest: bool = True,
|
||||||
|
enable_monthly_digest: bool = True) -> Dict:
|
||||||
if not self.is_group_enabled(chatroom_id):
|
if not self.is_group_enabled(chatroom_id):
|
||||||
raise ValueError(f"群 {chatroom_id} 未启用成员交互摘要功能")
|
raise ValueError(f"群 {chatroom_id} 未启用成员交互摘要功能")
|
||||||
self.LOG.info(f"[成员交互摘要] 开始刷新单个成员: group={chatroom_id}, wxid={wxid}")
|
self.LOG.info(f"[成员交互摘要] 开始刷新单个成员: group={chatroom_id}, wxid={wxid}")
|
||||||
context = self.build_member_context(chatroom_id, wxid, days=days, limit=limit)
|
context = self.build_member_context(
|
||||||
|
chatroom_id, wxid, days=days, limit=limit,
|
||||||
|
enable_weekly_digest=enable_weekly_digest, enable_monthly_digest=enable_monthly_digest
|
||||||
|
)
|
||||||
self.member_context_db.save_member_context(context)
|
self.member_context_db.save_member_context(context)
|
||||||
self.LOG.info(
|
self.LOG.info(
|
||||||
f"[成员交互摘要] 单个成员刷新完成: group={chatroom_id}, wxid={wxid}, "
|
f"[成员交互摘要] 单个成员刷新完成: group={chatroom_id}, wxid={wxid}, "
|
||||||
@@ -244,7 +253,8 @@ class MemberContextService:
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
def refresh_group_contexts(self, chatroom_id: str, days: Optional[int] = None,
|
def refresh_group_contexts(self, chatroom_id: str, days: Optional[int] = None,
|
||||||
limit_per_member: Optional[int] = None) -> Dict:
|
limit_per_member: Optional[int] = None, enable_weekly_digest: bool = True,
|
||||||
|
enable_monthly_digest: bool = True) -> Dict:
|
||||||
days = days or self.sample_days
|
days = days or self.sample_days
|
||||||
limit_per_member = limit_per_member or self.refresh_limit_per_member
|
limit_per_member = limit_per_member or self.refresh_limit_per_member
|
||||||
|
|
||||||
@@ -321,7 +331,8 @@ class MemberContextService:
|
|||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
context = self.build_member_context(
|
context = self.build_member_context(
|
||||||
chatroom_id, wxid, days=days, limit=limit_per_member, ensure_group_daily=False
|
chatroom_id, wxid, days=days, limit=limit_per_member, ensure_group_daily=False,
|
||||||
|
enable_weekly_digest=enable_weekly_digest, enable_monthly_digest=enable_monthly_digest
|
||||||
)
|
)
|
||||||
if context["source_message_count"] <= 0 and context.get("meta", {}).get("digest_daily_count", 0) <= 0:
|
if context["source_message_count"] <= 0 and context.get("meta", {}).get("digest_daily_count", 0) <= 0:
|
||||||
skipped += 1
|
skipped += 1
|
||||||
@@ -350,7 +361,8 @@ class MemberContextService:
|
|||||||
)
|
)
|
||||||
return {"refreshed": refreshed, "skipped": skipped, "active_candidates": len(active_members)}
|
return {"refreshed": refreshed, "skipped": skipped, "active_candidates": len(active_members)}
|
||||||
|
|
||||||
def refresh_all_chatrooms(self, days: Optional[int] = None, limit_per_member: Optional[int] = None) -> Dict:
|
def refresh_all_chatrooms(self, days: Optional[int] = None, limit_per_member: Optional[int] = None,
|
||||||
|
enable_weekly_digest: bool = True, enable_monthly_digest: bool = True) -> Dict:
|
||||||
days = days or self.sample_days
|
days = days or self.sample_days
|
||||||
limit_per_member = limit_per_member or self.refresh_limit_per_member
|
limit_per_member = limit_per_member or self.refresh_limit_per_member
|
||||||
|
|
||||||
@@ -386,7 +398,13 @@ class MemberContextService:
|
|||||||
self.LOG.info(
|
self.LOG.info(
|
||||||
f"[成员交互摘要] 批量刷新进度: group_index={processed_groups}/{total_groups}, group={chatroom_id}"
|
f"[成员交互摘要] 批量刷新进度: group_index={processed_groups}/{total_groups}, group={chatroom_id}"
|
||||||
)
|
)
|
||||||
result = self.refresh_group_contexts(chatroom_id, days=days, limit_per_member=limit_per_member)
|
result = self.refresh_group_contexts(
|
||||||
|
chatroom_id,
|
||||||
|
days=days,
|
||||||
|
limit_per_member=limit_per_member,
|
||||||
|
enable_weekly_digest=enable_weekly_digest,
|
||||||
|
enable_monthly_digest=enable_monthly_digest,
|
||||||
|
)
|
||||||
if result.get("disabled"):
|
if result.get("disabled"):
|
||||||
disabled += 1
|
disabled += 1
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -71,6 +71,70 @@ class AsyncJob:
|
|||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
def every_week_time(self, weekday: int, time_str: str):
|
||||||
|
"""
|
||||||
|
每周 weekday(0=周一,6=周日) 的 time_str 时间执行
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(func: Callable):
|
||||||
|
async def wrapper():
|
||||||
|
while True:
|
||||||
|
now = datetime.now()
|
||||||
|
target_time = datetime.strptime(time_str, "%H:%M").time()
|
||||||
|
|
||||||
|
days_ahead = (weekday - now.weekday() + 7) % 7
|
||||||
|
target_date = now.date() + timedelta(days=days_ahead)
|
||||||
|
target_dt = datetime.combine(target_date, target_time)
|
||||||
|
|
||||||
|
if target_dt <= now:
|
||||||
|
target_dt += timedelta(days=7)
|
||||||
|
|
||||||
|
sleep_secs = (target_dt - now).total_seconds()
|
||||||
|
await asyncio.sleep(sleep_secs)
|
||||||
|
await func()
|
||||||
|
|
||||||
|
self.tasks.append(wrapper)
|
||||||
|
return func
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
def every_month_last_day_time(self, time_str: str):
|
||||||
|
"""
|
||||||
|
每月最后一天的 time_str 时间执行
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(func: Callable):
|
||||||
|
async def wrapper():
|
||||||
|
while True:
|
||||||
|
now = datetime.now()
|
||||||
|
target_time = datetime.strptime(time_str, "%H:%M").time()
|
||||||
|
|
||||||
|
if now.month == 12:
|
||||||
|
next_month = datetime(now.year + 1, 1, 1)
|
||||||
|
else:
|
||||||
|
next_month = datetime(now.year, now.month + 1, 1)
|
||||||
|
last_day = next_month - timedelta(days=1)
|
||||||
|
target_dt = datetime.combine(last_day.date(), target_time)
|
||||||
|
|
||||||
|
if target_dt <= now:
|
||||||
|
if now.month == 12:
|
||||||
|
next_month = datetime(now.year + 1, 2, 1)
|
||||||
|
elif now.month == 11:
|
||||||
|
next_month = datetime(now.year + 1, 1, 1)
|
||||||
|
else:
|
||||||
|
next_month = datetime(now.year, now.month + 2, 1)
|
||||||
|
last_day = next_month - timedelta(days=1)
|
||||||
|
target_dt = datetime.combine(last_day.date(), target_time)
|
||||||
|
|
||||||
|
sleep_secs = (target_dt - now).total_seconds()
|
||||||
|
await asyncio.sleep(sleep_secs)
|
||||||
|
await func()
|
||||||
|
|
||||||
|
self.tasks.append(wrapper)
|
||||||
|
return func
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
async def run_all(self):
|
async def run_all(self):
|
||||||
await asyncio.gather(*(task() for task in self.tasks))
|
await asyncio.gather(*(task() for task in self.tasks))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user