feat(affinity_flow_chatter): 重构计划器以支持多动作并优化思考逻辑

本次提交对亲和流聊天器(AFC)的计划与决策核心进行了重大重构和功能增强,旨在提升其响应的灵活性、鲁棒性和可观测性。

主要变更包括:

1.  **多动作支持与解析重构**:
    -   `PlanFilter` 现在能够正确解析并处理 LLM 返回的动作列表(`"actions": [...]`),而不仅限于单个动作,这使得机器人能够执行更复杂的组合行为。
    -   增强了动作解析的鲁棒性,当找不到 `target_message_id` 时会优雅降级(如 `reply` 变为 `no_action`),并会根据当前实际可用的动作列表对 LLM 的选择进行验证。

2.  **提示词工程与思考模式优化**:
    -   重新设计了核心 Planner 提示词,将 `thinking` 字段定义为“思绪流”,引导 LLM 生成更自然、更符合角色的内心独白,而非简单的决策理由,从而提升决策质量和角色扮演的沉浸感。
    -   强制要求 LLM 为需要目标消息的动作提供 `target_message_id`,提高了动作执行的准确性。

3.  **上下文构建与鲁棒性增强**:
    -   在 `PlanFilter` 中增加了上下文回退机制,当内存中缺少历史消息时(如冷启动),会自动从数据库加载最近的消息记录,确保决策所需上下文的完整性。
    -   简化了提供给 LLM 的未读消息格式,移除了兴趣度分数等内部信息,并加入了用户昵称,使其更易于理解和处理。

4.  **可观测性与日志改进**:
    -   在 AFC 的多个关键节点(消息接收、决策、动作执行)增加了彩色的详细日志,使其决策流程像 HFC 一样清晰可见,极大地方便了调试。
    -   将系统中多个模块(视频分析、兴趣度匹配、情绪管理)的常规日志级别从 `INFO` 调整为 `DEBUG`,以减少在生产环境中的日志噪音。

5.  **动作描述优化**:
    -   优化了 `set_emoji_like` 和 `emoji` 等动作的描述,使其意图更清晰,帮助 LLM 做出更准确的动作选择。
This commit is contained in:
tt-P607
2025-09-24 01:41:04 +08:00
committed by Windpicker-owo
parent df5e68abe9
commit b5342376eb
13 changed files with 278 additions and 103 deletions

View File

@@ -464,7 +464,7 @@ class BotInterestManager:
low_similarity_count += 1
result.add_match(tag.tag_name, enhanced_score, [tag.tag_name])
logger.info(
logger.debug(
f"匹配统计: {match_count}/{len(active_tags)} 个标签命中 | "
f"高(>{high_threshold}): {high_similarity_count}, "
f"中(>{medium_threshold}): {medium_similarity_count}, "
@@ -492,9 +492,9 @@ class BotInterestManager:
if result.matched_tags:
top_tag_name = max(result.match_scores.items(), key=lambda x: x[1])[0]
result.top_tag = top_tag_name
logger.info(f"最佳匹配: '{top_tag_name}' (分数: {result.match_scores[top_tag_name]:.3f})")
logger.debug(f"最佳匹配: '{top_tag_name}' (分数: {result.match_scores[top_tag_name]:.3f})")
logger.info(
logger.debug(
f"最终结果: 总分={result.overall_score:.3f}, 置信度={result.confidence:.3f}, 匹配标签数={len(result.matched_tags)}"
)
return result

View File

@@ -59,10 +59,59 @@ class VideoAnalyzer:
# 人格与提示模板
try:
persona = global_config.personality
self.personality_core = getattr(persona, "personality_core", "是一个积极向上的女大学生")
self.personality_side = getattr(persona, "personality_side", "用一句话或几句话描述人格的侧面特点")
except Exception: # pragma: no cover
import cv2
opencv_available = True
except ImportError:
pass
if not RUST_VIDEO_AVAILABLE and not opencv_available:
logger.error("❌ 没有可用的视频处理实现,视频分析器将被禁用")
self.disabled = True
return
elif not RUST_VIDEO_AVAILABLE:
logger.warning("⚠️ Rust视频处理模块不可用将使用Python降级实现")
elif not opencv_available:
logger.warning("⚠️ OpenCV不可用仅支持Rust关键帧模式")
self.disabled = False
# 使用专用的视频分析配置
try:
self.video_llm = LLMRequest(
model_set=model_config.model_task_config.video_analysis, request_type="video_analysis"
)
logger.debug("✅ 使用video_analysis模型配置")
except (AttributeError, KeyError) as e:
# 如果video_analysis不存在使用vlm配置
self.video_llm = LLMRequest(model_set=model_config.model_task_config.vlm, request_type="vlm")
logger.warning(f"video_analysis配置不可用({e})回退使用vlm配置")
# 从配置文件读取参数,如果配置不存在则使用默认值
config = global_config.video_analysis
# 使用 getattr 统一获取配置参数,如果配置不存在则使用默认值
self.max_frames = getattr(config, "max_frames", 6)
self.frame_quality = getattr(config, "frame_quality", 85)
self.max_image_size = getattr(config, "max_image_size", 600)
self.enable_frame_timing = getattr(config, "enable_frame_timing", True)
# Rust模块相关配置
self.rust_keyframe_threshold = getattr(config, "rust_keyframe_threshold", 2.0)
self.rust_use_simd = getattr(config, "rust_use_simd", True)
self.rust_block_size = getattr(config, "rust_block_size", 8192)
self.rust_threads = getattr(config, "rust_threads", 0)
self.ffmpeg_path = getattr(config, "ffmpeg_path", "ffmpeg")
# 从personality配置中获取人格信息
try:
personality_config = global_config.personality
self.personality_core = getattr(personality_config, "personality_core", "是一个积极向上的女大学生")
self.personality_side = getattr(
personality_config, "personality_side", "用一句话或几句话描述人格的侧面特点"
)
except AttributeError:
# 如果没有personality配置使用默认值
self.personality_core = "是一个积极向上的女大学生"
self.personality_side = "用一句话或几句话描述人格的侧面特点"
@@ -72,12 +121,76 @@ class VideoAnalyzer:
"""请以第一人称视角阅读这些按时间顺序提取的关键帧。\n核心:{personality_core}\n人格:{personality_side}\n请详细描述视频(主题/人物与场景/动作与时间线/视觉风格/情绪氛围/特殊元素)。""",
)
# 新增的线程池配置
self.use_multiprocessing = getattr(config, "use_multiprocessing", True)
self.max_workers = getattr(config, "max_workers", 2)
self.frame_extraction_mode = getattr(config, "frame_extraction_mode", "fixed_number")
self.frame_interval_seconds = getattr(config, "frame_interval_seconds", 2.0)
# 将配置文件中的模式映射到内部使用的模式名称
config_mode = getattr(config, "analysis_mode", "auto")
if config_mode == "batch_frames":
self.analysis_mode = "batch"
elif config_mode == "frame_by_frame":
self.analysis_mode = "sequential"
elif config_mode == "auto":
self.analysis_mode = "auto"
else:
logger.warning(f"无效的分析模式: {config_mode}使用默认的auto模式")
self.analysis_mode = "auto"
self.frame_analysis_delay = 0.3 # API调用间隔
self.frame_interval = 1.0 # 抽帧时间间隔(秒)
self.batch_size = 3 # 批处理时每批处理的帧数
self.timeout = 60.0 # 分析超时时间(秒)
if config:
logger.debug("✅ 从配置文件读取视频分析参数")
else:
logger.warning("配置文件中缺少video_analysis配置使用默认值")
# 系统提示词
self.system_prompt = "你是一个专业的视频内容分析助手。请仔细观察用户提供的视频关键帧,详细描述视频内容。"
logger.debug(f"✅ 视频分析器初始化完成,分析模式: {self.analysis_mode}, 线程池: {self.use_multiprocessing}")
# 获取Rust模块系统信息
self._log_system_info()
def _log_system_info(self):
"""记录系统信息"""
if not RUST_VIDEO_AVAILABLE:
logger.info("⚠️ Rust模块不可用跳过系统信息获取")
return
try:
self.video_llm = LLMRequest(
model_set=model_config.model_task_config.video_analysis, request_type="video_analysis"
)
except Exception:
self.video_llm = LLMRequest(model_set=model_config.model_task_config.vlm, request_type="vlm")
system_info = rust_video.get_system_info()
logger.debug(f"🔧 系统信息: 线程数={system_info.get('threads', '未知')}")
# 记录CPU特性
features = []
if system_info.get("avx2_supported"):
features.append("AVX2")
if system_info.get("sse2_supported"):
features.append("SSE2")
if system_info.get("simd_supported"):
features.append("SIMD")
if features:
logger.debug(f"🚀 CPU特性: {', '.join(features)}")
else:
logger.debug("⚠️ 未检测到SIMD支持")
logger.debug(f"📦 Rust模块版本: {system_info.get('version', '未知')}")
except Exception as e:
logger.warning(f"获取系统信息失败: {e}")
def _calculate_video_hash(self, video_data: bytes) -> str:
"""计算视频文件的hash值"""
hash_obj = hashlib.sha256()
hash_obj.update(video_data)
return hash_obj.hexdigest()
self._log_system()