From c75cc88fb5175b742af73a546d4192a79ef2f1b0 Mon Sep 17 00:00:00 2001 From: Windpicker-owo <3431391539@qq.com> Date: Thu, 11 Dec 2025 13:57:17 +0800 Subject: [PATCH] =?UTF-8?q?feat(expression=5Fselector):=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E6=B8=A9=E5=BA=A6=E9=87=87=E6=A0=B7=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E4=BB=A5=E4=BC=98=E5=8C=96=E8=A1=A8=E8=BE=BE=E9=80=89=E6=8B=A9?= =?UTF-8?q?=20feat(official=5Fconfigs):=20=E6=96=B0=E5=A2=9E=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E6=B8=A9=E5=BA=A6=E9=85=8D=E7=BD=AE=E9=A1=B9=E4=BB=A5?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E8=A1=A8=E8=BE=BE=E6=A8=A1=E5=9E=8B=E9=87=87?= =?UTF-8?q?=E6=A0=B7=20chore(bot=5Fconfig=5Ftemplate):=20=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E7=89=88=E6=9C=AC=E5=8F=B7=E5=B9=B6=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E6=B8=A9=E5=BA=A6=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/express/expression_selector.py | 55 +++++++++++++++++++++++-- src/common/core_sink_manager.py | 5 --- src/config/official_configs.py | 7 +++- template/bot_config_template.toml | 4 +- 4 files changed, 61 insertions(+), 10 deletions(-) diff --git a/src/chat/express/expression_selector.py b/src/chat/express/expression_selector.py index 59ab4329e..3359e7c05 100644 --- a/src/chat/express/expression_selector.py +++ b/src/chat/express/expression_selector.py @@ -1,5 +1,6 @@ import asyncio import hashlib +import math import random import time from typing import Any @@ -76,6 +77,45 @@ def weighted_sample(population: list[dict], weights: list[float], k: int) -> lis class ExpressionSelector: + @staticmethod + def _sample_with_temperature( + candidates: list[tuple[Any, float, float, str]], + max_num: int, + temperature: float, + ) -> list[tuple[Any, float, float, str]]: + """ + 对候选表达按温度采样,温度越高越均匀。 + + Args: + candidates: (expr, similarity, count, best_predicted) 列表 + max_num: 需要返回的数量 + temperature: 温度参数,0 表示贪婪选择 + """ + if max_num <= 0 or not candidates: + return [] + + if temperature <= 0: + return candidates[:max_num] + + adjusted_temp = max(temperature, 1e-6) + # 使用与排序相同的打分,但通过 softmax/temperature 放大尾部概率 + scores = [max(c[1] * (c[2] ** 0.5), 1e-8) for c in candidates] + max_score = max(scores) + weights = [math.exp((s - max_score) / adjusted_temp) for s in scores] + + # 始终保留最高分一个,剩余的按温度采样,避免过度集中 + best_idx = scores.index(max_score) + selected = [candidates[best_idx]] + remaining_indices = [i for i in range(len(candidates)) if i != best_idx] + + while remaining_indices and len(selected) < max_num: + current_weights = [weights[i] for i in remaining_indices] + picked_pos = random.choices(range(len(remaining_indices)), weights=current_weights, k=1)[0] + picked_idx = remaining_indices.pop(picked_pos) + selected.append(candidates[picked_idx]) + + return selected + def __init__(self, chat_id: str = ""): self.chat_id = chat_id if model_config is None: @@ -517,12 +557,21 @@ class ExpressionSelector: ) return [] - # 按照相似度*count排序,选择最佳匹配 + # 按照相似度*count排序,并根据温度采样,避免过度集中 matched_expressions.sort(key=lambda x: x[1] * (x[2] ** 0.5), reverse=True) - expressions_objs = [e[0] for e in matched_expressions[:max_num]] + temperature = getattr(global_config.expression, "model_temperature", 0.0) + sampled_matches = self._sample_with_temperature( + candidates=matched_expressions, + max_num=max_num, + temperature=temperature, + ) + expressions_objs = [e[0] for e in sampled_matches] # 显示最佳匹配的详细信息 - logger.debug(f"模糊匹配成功: 找到 {len(expressions_objs)} 个表达方式") + logger.debug( + f"模糊匹配成功: 找到 {len(expressions_objs)} 个表达方式 " + f"(候选 {len(matched_expressions)},temperature={temperature})" + ) # 转换为字典格式 expressions = [ diff --git a/src/common/core_sink_manager.py b/src/common/core_sink_manager.py index a92af07a0..390c60cab 100644 --- a/src/common/core_sink_manager.py +++ b/src/common/core_sink_manager.py @@ -10,11 +10,6 @@ CoreSink 统一管理器 3. 使用 MessageRuntime 进行消息路由和处理 4. 提供统一的消息发送接口 -架构说明(2025-11 重构): -- 集成 mofox_wire.MessageRuntime 作为消息路由中心 -- 使用 @runtime.on_message() 装饰器注册消息处理器 -- 利用 before_hook/after_hook/error_hook 处理前置/后置/错误逻辑 -- 简化消息处理链条,提高可扩展性 """ from __future__ import annotations diff --git a/src/config/official_configs.py b/src/config/official_configs.py index 9fa1da378..d8b8d48df 100644 --- a/src/config/official_configs.py +++ b/src/config/official_configs.py @@ -213,6 +213,12 @@ class ExpressionConfig(ValidatedConfigBase): default="classic", description="表达方式选择模式: classic=经典LLM评估, exp_model=机器学习模型预测" ) + model_temperature: float = Field( + default=1.0, + ge=0.0, + le=5.0, + description="表达模型采样温度,0为贪婪,值越大越容易采样到低分表达" + ) expiration_days: int = Field( default=90, description="表达方式过期天数,超过此天数未激活的表达方式将被清理" @@ -1009,4 +1015,3 @@ class KokoroFlowChatterConfig(ValidatedConfigBase): default_factory=KokoroFlowChatterProactiveConfig, description="私聊专属主动思考配置" ) - diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index 91d478d64..073b3db0d 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -1,5 +1,5 @@ [inner] -version = "7.9.8" +version = "7.9.9" #----以下是给开发人员阅读的,如果你只是部署了MoFox-Bot,不需要阅读---- #如果你想要修改配置文件,请递增version的值 @@ -134,6 +134,8 @@ compress_identity = false # 是否压缩身份,压缩后会精简身份信息 # - "classic": 经典模式,随机抽样 + LLM选择 # - "exp_model": 表达模型模式,使用机器学习模型预测最合适的表达 mode = "classic" +# model_temperature: 机器预测模式下的“温度”,0 为贪婪,越大越爱探索(更容易选到低分表达) +model_temperature = 1.0 # expiration_days: 表达方式过期天数,超过此天数未激活的表达方式将被清理 expiration_days = 1