use latest group image for xiaoniu image followups
This commit is contained in:
@@ -43,6 +43,24 @@ class MessageStorageDB(BaseDBOperator):
|
||||
params = (hours_ago, group_id, min_content_length)
|
||||
return self.execute_query(sql, params) or []
|
||||
|
||||
def get_latest_image_message(self, group_id: str, before_timestamp: str = "", hours_ago: int = 8) -> Optional[Dict]:
|
||||
"""获取指定群最近一条已落盘图片消息"""
|
||||
sql = """
|
||||
SELECT timestamp, sender, content, message_type, image_path
|
||||
FROM messages
|
||||
WHERE timestamp >= DATE_SUB(NOW(), INTERVAL %s HOUR)
|
||||
AND group_id = %s
|
||||
AND message_type = 3
|
||||
AND image_path IS NOT NULL
|
||||
AND image_path <> ''
|
||||
"""
|
||||
params: List = [hours_ago, group_id]
|
||||
if before_timestamp:
|
||||
sql += " AND timestamp <= %s"
|
||||
params.append(before_timestamp)
|
||||
sql += " ORDER BY timestamp DESC LIMIT 1"
|
||||
return self.execute_query(sql, tuple(params), fetch_one=True)
|
||||
|
||||
def get_member_recent_messages(self, group_id: str, wxid: str, days: int = 30,
|
||||
limit: int = 200, include_today: bool = True) -> List[Dict]:
|
||||
"""获取指定群成员近期消息"""
|
||||
|
||||
@@ -22,6 +22,7 @@ class ContextBuilder:
|
||||
reply_mode: str,
|
||||
vector_memories: List[Dict],
|
||||
quote_context: Dict | None = None,
|
||||
image_context: Dict | None = None,
|
||||
) -> Dict:
|
||||
recent_lines = []
|
||||
for item in recent_messages[-self.recent_context_size:]:
|
||||
@@ -45,6 +46,7 @@ class ContextBuilder:
|
||||
"vector_memory_prompt": self._build_vector_memory_prompt(vector_memories),
|
||||
"group_profile_prompt": self._build_group_profile_prompt(group_profile or {}),
|
||||
"quote_prompt": self._build_quote_prompt(quote_context or {}),
|
||||
"image_prompt": self._build_image_prompt(image_context or {}),
|
||||
"current_message": f"{sender_name}: {content}",
|
||||
}
|
||||
|
||||
@@ -136,3 +138,14 @@ class ContextBuilder:
|
||||
f"被引用内容:{quote_body}" if quote_body else "",
|
||||
]
|
||||
return "\n".join([line for line in lines if line])
|
||||
|
||||
@staticmethod
|
||||
def _build_image_prompt(image_context: Dict) -> str:
|
||||
if not image_context:
|
||||
return ""
|
||||
lines = [
|
||||
"已附带最近一张群图片作为上下文。",
|
||||
f"图片发送者:{image_context.get('sender_name', '未知成员')}",
|
||||
f"图片说明:{image_context.get('hint', '')}" if image_context.get("hint") else "",
|
||||
]
|
||||
return "\n".join([line for line in lines if line])
|
||||
|
||||
@@ -241,7 +241,12 @@ class AIAutoResponsePlugin(MessagePluginInterface):
|
||||
vector_memories = []
|
||||
if self.vector_memory.should_search(reply_mode, trigger.trigger_type, memory_hints.get("returning_member_state", "")):
|
||||
vector_memories = self.vector_memory.search(content, room_id, sender)
|
||||
image_context = self._build_recent_image_context(message, room_id, content, quote_context)
|
||||
image_urls = await self._prepare_quote_image_inputs(bot, quote_context)
|
||||
if not image_urls and image_context:
|
||||
recent_image_url = self._build_local_image_data_url(str(image_context.get("image_path", "") or ""))
|
||||
if recent_image_url:
|
||||
image_urls = [recent_image_url]
|
||||
self._log_event(
|
||||
"context",
|
||||
room_id=room_id,
|
||||
@@ -268,6 +273,7 @@ class AIAutoResponsePlugin(MessagePluginInterface):
|
||||
reply_mode=reply_mode,
|
||||
vector_memories=vector_memories,
|
||||
quote_context=quote_context | {"has_image_attachment": bool(image_urls)},
|
||||
image_context=image_context,
|
||||
)
|
||||
|
||||
system_prompt = self.persona_engine.build_system_prompt(group_profile)
|
||||
@@ -377,6 +383,7 @@ class AIAutoResponsePlugin(MessagePluginInterface):
|
||||
f"当前群聊消息:\n{recent_text}\n\n"
|
||||
f"当前发言:{context.get('current_message', '')}\n"
|
||||
f"引用补充:\n{context.get('quote_prompt', '') or '无'}\n"
|
||||
f"图片补充:\n{context.get('image_prompt', '') or '无'}\n"
|
||||
f"触发类型:{context.get('trigger_type', 'none')}\n"
|
||||
f"回复模式:{context.get('reply_mode', 'social_short')}\n"
|
||||
f"当前心流状态:{context.get('flow_state', 'idle')}\n"
|
||||
@@ -725,6 +732,40 @@ class AIAutoResponsePlugin(MessagePluginInterface):
|
||||
return title[:220].strip()
|
||||
return ref_content[:220].strip()
|
||||
|
||||
def _build_recent_image_context(
|
||||
self,
|
||||
message: Dict[str, Any],
|
||||
room_id: str,
|
||||
content: str,
|
||||
quote_context: Dict[str, str],
|
||||
) -> Dict[str, str]:
|
||||
if quote_context:
|
||||
return {}
|
||||
if not self._is_recent_image_followup(content):
|
||||
return {}
|
||||
latest_image = self.memory_store.get_latest_image_message(
|
||||
room_id,
|
||||
before_timestamp=str(message.get("timestamp") or ""),
|
||||
)
|
||||
if not latest_image:
|
||||
return {}
|
||||
sender = str(latest_image.get("sender", "") or "")
|
||||
sender_name = self._get_sender_name(room_id, sender) if sender else "未知成员"
|
||||
return {
|
||||
"sender_name": sender_name,
|
||||
"image_path": str(latest_image.get("image_path", "") or ""),
|
||||
"hint": "用户当前这句大概率是在追问这张最近图片",
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _is_recent_image_followup(content: str) -> bool:
|
||||
text = str(content or "").strip().lower()
|
||||
if not text:
|
||||
return False
|
||||
image_words = ["图", "图片", "照片", "截图"]
|
||||
ask_words = ["看看", "看下", "帮我看", "帮看看", "这个", "咋样", "什么", "识别", "分析"]
|
||||
return any(word in text for word in image_words) and any(word in text for word in ask_words)
|
||||
|
||||
async def _prepare_quote_image_inputs(self, bot: WechatAPIClient, quote_context: Dict[str, str]) -> List[str]:
|
||||
if not quote_context or quote_context.get("quote_type_label") != "引用图片":
|
||||
return []
|
||||
@@ -746,6 +787,21 @@ class AIAutoResponsePlugin(MessagePluginInterface):
|
||||
return []
|
||||
return [data_url]
|
||||
|
||||
def _build_local_image_data_url(self, image_path: str) -> str:
|
||||
if not image_path:
|
||||
return ""
|
||||
relative_path = image_path.lstrip("/\\").replace("/", "\\")
|
||||
full_path = self.get_main_path() / relative_path
|
||||
if not full_path.exists():
|
||||
return ""
|
||||
try:
|
||||
image_bytes = full_path.read_bytes()
|
||||
except Exception:
|
||||
return ""
|
||||
image_type = imghdr.what(None, h=image_bytes) or "jpeg"
|
||||
raw_base64 = base64.b64encode(image_bytes).decode("utf-8")
|
||||
return f"data:image/{image_type};base64,{raw_base64}"
|
||||
|
||||
@staticmethod
|
||||
def _extract_quote_image_info(ref_content: str) -> Dict[str, str]:
|
||||
if not ref_content:
|
||||
|
||||
@@ -20,6 +20,10 @@ class MemoryStore:
|
||||
size = int(self.config.get("recent_context_size", 30))
|
||||
return recent[-size:]
|
||||
|
||||
def get_latest_image_message(self, room_id: str, before_timestamp: str = "") -> Optional[Dict]:
|
||||
hours = int(self.config.get("active_context_hours", 8))
|
||||
return self.message_db.get_latest_image_message(room_id, before_timestamp=before_timestamp, hours_ago=hours)
|
||||
|
||||
def get_member_context(self, room_id: str, wxid: str) -> Optional[Dict]:
|
||||
if not self.config.get("enable_member_context", True):
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user