From e2b7987c7e602865f00f97b3c1f32417fcfe5d27 Mon Sep 17 00:00:00 2001 From: jesxion Date: Mon, 13 Apr 2026 12:38:58 +0800 Subject: [PATCH] =?UTF-8?q?Phase=201:=20=E8=81=94=E7=B3=BB=E4=BA=BA?= =?UTF-8?q?=E5=88=86=E7=B1=BB=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增文件: - config.contacts.yaml: 联系人配置示例 - src/core/contact_manager.py: 联系人管理器 功能: - priority: 重点用户,自动回复 - ignore: 忽略列表,不回复 - normal: 普通用户,默认不回复 engine.py 改动: - 集成 ContactManager - 消息处理流程增加联系人分类决策 - 日志显示 contact_type (priority/ignore/normal) --- config.contacts.yaml | 26 +++++ config.example.yaml | 28 +++++- .../__pycache__/settings.cpython-311.pyc | Bin 8908 -> 10753 bytes src/config/settings.py | 27 ++++++ src/core/__pycache__/engine.cpython-311.pyc | Bin 21179 -> 21835 bytes src/core/contact_manager.py | 90 ++++++++++++++++++ src/core/engine.py | 53 +++++++---- 7 files changed, 204 insertions(+), 20 deletions(-) create mode 100644 config.contacts.yaml create mode 100644 src/core/contact_manager.py diff --git a/config.contacts.yaml b/config.contacts.yaml new file mode 100644 index 0000000..8e35a4c --- /dev/null +++ b/config.contacts.yaml @@ -0,0 +1,26 @@ +# 联系人分类配置 +# 用于控制哪些联系人需要自动回复,哪些需要忽略 + +# 重点用户:开启自动回复(关键词 + AI) +priority: + enabled: true + users: + - "尾巴~" + # - "张三" + # - "李四" + # 支持模糊匹配(TODO):如 "张*" 匹配所有姓张的 + +# 忽略列表:不自动回复,手动处理 +ignore: + enabled: true + users: + - "相亲相爱一家人" + - "工作通知群" + - "文件传输助手" + # 公众号前缀匹配 + - "公众号:" + # 也可以用正则匹配(TODO) + +# 普通用户:默认不自动回复 +normal: + auto_reply: false # true = AI 回复,false = 不回复 diff --git a/config.example.yaml b/config.example.yaml index ae56d68..31ade59 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -50,7 +50,7 @@ rules: reply_type: keyword reply_content: "您好,有什么可以帮您的?" enabled: true - + # AI 回复模式(无匹配关键词时,使用 LLM 生成回复) # 这是默认模式,会根据对话上下文生成自然回复 - keywords: [] @@ -58,6 +58,32 @@ rules: reply_content: "" enabled: true +# ============================================ +# 联系人分类配置(Phase 1) +# ============================================ +# 用于控制哪些联系人需要自动回复,哪些需要忽略 +contacts: + # 重点用户:开启自动回复(关键词 + AI) + priority: + enabled: true + users: + - "尾巴~" + # - "张三" + # 支持模糊匹配(TODO) + + # 忽略列表:不自动回复,手动处理 + ignore: + enabled: true + users: + - "相亲相爱一家人" + - "工作通知群" + - "文件传输助手" + - "公众号:" # 所有公众号 + + # 普通用户:默认不自动回复 + normal: + auto_reply: false # true = AI 回复,false = 不回复 + # 知识库(可选,后续接入 OpenViking) knowledge_base: url: http://192.168.5.5:1933 diff --git a/src/config/__pycache__/settings.cpython-311.pyc b/src/config/__pycache__/settings.cpython-311.pyc index 9a4e19b8776dede6b4646ba6352dd218f6159d10..f3a434a92e998b2529bd50a201ee895d87b66489 100644 GIT binary patch delta 2665 zcmb7GU2Gdg5Wc;ff8w9`Cy9U3{5T4SX-I<%2^S1}j4^~~b+btY4gYsk2x9R;VA9?%E7BAr?fp!reZ6^-9QxbUY5GT4X zaAGwLkcMU#o7cd+_8QM|H{sFSkXz&jxS%I{nhcvsKYc@J&hVUMlFYY+YXl_WS(U7m zERxkG@R5~$|9+1-MWNJ*AsfD#)&tJZuz5Da0pZ>D&_We6| ze|ziQ`=5V2C~t=)xgDTjm{BBI=?&Us6qh}0!HeiaV1hf5szVSEb|CCQFd+EpptYGi zOH(G#&KlqRp@l(J@IlWBzPz{o(#(xP)w?a{-KKfB%^zIYeyQT(9#z-|J;x9T+HUh& zDnJ*uKS${ol~2(MvpZv!43bF{n2m-ZwB95d*~|nMn+sK|7Ar80CdXzRu)S=sZmMpi zd0I|R$;tG&f-yOknv$hh!3D!)Vj`ZHNl(RPX?o&ZZ_v8V2jBwwgV~yqcY{(H57j-; ziBuN?^GX9!jR;K$drN?XG6FEa?)Q`6R|S7Q@c323?I|@7%LQUuAU1z^VM-NZ&~rj8 z-`KJ!-R@Hxqq)YY));+qKDN-O3Q_1eAquOiPP0WSnA-7a5)vOCAlUT`CEJL+Y4aiw_IdHX&UbgJ2ESN|12%}$H zT8W2#WAr}CYVQWQpdcTIK^~wTuI*$fE4qS&jM4Ypety=F{mAXF z*ixJ$qJ7PMZwU%|S|85(IsS|l^s{!xR-)qe67{H#Lyj@- zKk29{=@6ae`#AsCeK3z$d+0tJbVUCBK5IJ4_n9-HFI=wV20M`3=@zU1*}Ay1bR)55 zj)0E?;QMub0X_St}kfdd1_(u;4ueBEf1FuLVOgElnx~(|AHjq|-7>l!8f- z(rmVN7|V%McmU=}!I+qymQv!n!&x0J-#{-wEOYS4pji)A2vD!LwWnZT%~K^VCejJT z3?hNmrk5!RIJ28JWWsbpR-`y=g;F{N0?Jo>_MG<$$rO#8&9hjeS{tC}>jPKowEC?J z$MO#U;wjbf1oV8Q`^!2la&$S;zZB_Lj}NIY59cBYEt0royzJ1Nt(r5G4|jbstA%@( z!@W!4-g{@&6GOT1NiBS`EYhBLco#cWM=Nxceq9S6UJf74EQOEWdr3X=%I$qQ|UM!1j&4)UdL;IFO`@U?ww?7x^(?WgA zp@F5)fI2v=zA}~zjccLth1kVD&DQplb<<*6wYEUdw{BJ2Id!E8Ow#L#$>|A69;LyCz2sU}YIv1oj=@sU{qQGi z^{d?rOESAME3Dl(W#t)5V8J>vkx-OLDLpO~g~gL9K3X&FO9o<+ETOnW@eq(`KQ ziFh20e-5|`0dRHevDAaI2z_+4X(8BV@La7aaxmQ3%!V~sso;#R3nz;_EEZ2wZ(!Pu z`yf}?UyYNfXoP+c7__plLpRQQ=pMW=nTnUsdYZdPrh-I@+@r99yaxzy7H_@a zFY+j41G>lyR0|9)-jTG9Mt6H=&g0AW!;9k^ARiMPGaH JFCy$~{x@y^Zpi=u delta 1006 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 F{~vi%!-@a^ diff --git a/src/core/contact_manager.py b/src/core/contact_manager.py new file mode 100644 index 0000000..8d5cc1b --- /dev/null +++ b/src/core/contact_manager.py @@ -0,0 +1,90 @@ +""" +联系人管理器 +管理联系人分类:priority / ignore / normal +""" + +import fnmatch +import logging +from typing import List, Optional + +from src.config.settings import ContactsConfig + +logger = logging.getLogger(__name__) + + +class ContactManager: + """联系人管理器""" + + def __init__(self, config: ContactsConfig): + self.config = config + + def is_priority(self, sender: str) -> bool: + """检查是否是重点用户""" + if not self.config.priority.enabled: + return False + return self._match_user(sender, self.config.priority.users) + + def is_ignored(self, sender: str) -> bool: + """检查是否在忽略列表""" + if not self.config.ignore.enabled: + return False + return self._match_user(sender, self.config.ignore.users) + + def should_auto_reply(self, sender: str) -> bool: + """判断是否应该自动回复""" + # 忽略列表中的不回复 + if self.is_ignored(sender): + return False + + # 重点用户自动回复 + if self.is_priority(sender): + return True + + # 普通用户根据配置决定 + return self.config.normal_auto_reply + + def _match_user(self, sender: str, patterns: List[str]) -> bool: + """匹配用户 + 支持: + - 精确匹配: "尾巴~" + - 通配符匹配: "张*" 匹配所有姓张的 + - 前缀匹配: "公众号:" 匹配所有公众号 + """ + if not sender or not patterns: + return False + + sender_lower = sender.lower() + + for pattern in patterns: + if not pattern: + continue + + # 精确匹配 + if sender == pattern: + return True + + # 忽略大小写匹配 + if sender_lower == pattern.lower(): + return True + + # 通配符匹配 (fnmatch) + if fnmatch.fnmatch(sender, pattern): + return True + + # 前缀匹配(如 "公众号:" 匹配 "公众号:xxx") + if pattern.endswith(":*") and sender.startswith(pattern[:-1]): + return True + + # 包含匹配 + if pattern in sender: + return True + + return False + + def get_contact_type(self, sender: str) -> str: + """获取联系人类型""" + if self.is_priority(sender): + return "priority" + if self.is_ignored(sender): + return "ignore" + return "normal" diff --git a/src/core/engine.py b/src/core/engine.py index f4538b1..c9c0653 100644 --- a/src/core/engine.py +++ b/src/core/engine.py @@ -11,6 +11,8 @@ from typing import List, Optional, Callable, Dict, Any from enum import Enum from queue import Queue +from src.core.contact_manager import ContactManager + logger = logging.getLogger(__name__) @@ -174,6 +176,7 @@ class WeChatAgent: self.llm = llm_client self.config = config self.processor = MessageProcessor(vlm_client, llm_client, config) + self.contact_manager = ContactManager(config.contacts) self._state = AgentState.IDLE self._thread: Optional[threading.Thread] = None @@ -269,16 +272,16 @@ class WeChatAgent: 流程: 1. 截图 - 2. VLM 分析(不依赖 has_new_message) - 3. 去重检查(20秒窗口) - 4. 回复决策(sender == "我" 则跳过) + 2. VLM 分析 + 3. 联系人分类(priority / ignore / normal) + 4. 去重检查 + 5. 发送回复 """ try: # 阶段1: 截图 screenshot_path = self.wechat.screenshot() # 阶段2: VLM 分析 - # 注意:不依赖 has_new_message(VLM 判断不可靠) chat_info = self.vlm.analyze_chat_screenshot(screenshot_path) chat_name = chat_info.get("current_chat", "") messages = chat_info.get("messages", []) @@ -288,50 +291,62 @@ class WeChatAgent: logger.debug("无消息,跳过") return - # 阶段3: 获取最新消息 + # 阶段3: 获取最新消息并分类 latest = messages[0] latest_sender = latest.get("sender", "").strip() latest_content = latest.get("content", "").strip()[:60] - # 调试日志 - logger.debug(f"[轮询] chat={chat_name}, sender={latest_sender}, content={latest_content[:30]}") - - # 阶段4: 回复决策 - # 规则:只有 sender 是"我"时才跳过(这是最可靠的判断) - skip_senders = {"我", "自己"} - if latest_sender in skip_senders: - logger.debug(f"己方消息,跳过: sender={latest_sender}") + # 跳过己方消息 + if latest_sender in ("我", "自己"): + logger.debug(f"己方消息,跳过") return - # 阶段5: 去重检查 + # 联系人分类决策 + contact_type = self.contact_manager.get_contact_type(latest_sender) + logger.debug(f"[消息] sender={latest_sender}, type={contact_type}, content={latest_content[:30]}") + + # 忽略列表:不处理 + if contact_type == "ignore": + logger.debug(f"忽略列表用户,不回复: {latest_sender}") + return + + # 普通用户:不自动回复(除非配置了 normal_auto_reply) + if contact_type == "normal": + should_reply = self.contact_manager.should_auto_reply(latest_sender) + if not should_reply: + logger.debug(f"普通用户,无需自动回复: {latest_sender}") + return + + # 阶段4: 去重检查 dedup_key = f"{latest_sender}|{latest_content}" if dedup_key in self._last_processed_time: elapsed = current_time - self._last_processed_time[dedup_key] if elapsed < 20: - logger.debug(f"消息已处理过({elapsed:.1f}s前),跳过: {latest_content[:30]}") + logger.debug(f"消息已处理过({elapsed:.1f}s前),跳过") return # 记录已处理 self._last_processed_time[dedup_key] = current_time - # 阶段6: 创建快照并发送回复 + # 阶段5: 创建快照并发送回复 snapshot = ChatSnapshot( timestamp=current_time, chat_name=chat_name, messages=messages, screenshot_path=screenshot_path, - has_new=True # 强制设为 True,因为我们相信有新消息 + has_new=True ) # 触发消息回调 self._emit("on_message", { "chat_name": chat_name, "latest_message": latest, - "all_messages": messages + "all_messages": messages, + "contact_type": contact_type }) # 生成并发送回复 - logger.info(f"检测到新消息: [{latest_sender}] {latest_content[:40]}") + logger.info(f"自动回复 [{contact_type}]: [{latest_sender}] {latest_content[:40]}") reply = self.processor.generate_reply(snapshot) if reply: