Merge branch 'dev' of https://github.com/A0000Xz/MaiBot into dev

This commit is contained in:
A0000Xz
2025-07-08 02:32:05 +08:00
22 changed files with 454 additions and 453 deletions

View File

@@ -5,3 +5,5 @@ __pycache__
.DS_Store .DS_Store
mongodb mongodb
napcat napcat
docs/
.github/

View File

@@ -12,18 +12,20 @@ on:
- "*.*.*" - "*.*.*"
- "*.*.*-*" - "*.*.*-*"
# Workflow's jobs
jobs: jobs:
build-amd64: build-amd64:
name: Build AMD64 Image name: Build AMD64 Image
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: outputs:
DOCKERHUB_USER: ${{ secrets.DOCKERHUB_USERNAME }} digest: ${{ steps.build.outputs.digest }}
steps: steps:
- name: Checkout code - name: Check out git repository
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
# Clone required dependencies
- name: Clone maim_message - name: Clone maim_message
run: git clone https://github.com/MaiM-with-u/maim_message maim_message run: git clone https://github.com/MaiM-with-u/maim_message maim_message
@@ -35,106 +37,93 @@ jobs:
with: with:
buildkitd-flags: --debug buildkitd-flags: --debug
# Log in docker hub
- name: Log in to Docker Hub - name: Log in to Docker Hub
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
# Generate metadata for Docker images
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
with: with:
images: ${{ secrets.DOCKERHUB_USERNAME }}/maibot images: ${{ secrets.DOCKERHUB_USERNAME }}/maibot
tags: |
type=ref,event=branch
type=ref,event=tag
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
- name: Build and Push AMD64 Docker Image # Build and push AMD64 image by digest
- name: Build and push AMD64
id: build
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
with: with:
context: . context: .
file: ./Dockerfile
platforms: linux/amd64 platforms: linux/amd64
tags: ${{ secrets.DOCKERHUB_USERNAME }}/maibot:amd64-${{ github.sha }} labels: ${{ steps.meta.outputs.labels }}
push: true file: ./Dockerfile
cache-from: type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/maibot:amd64-buildcache cache-from: type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/maibot:amd64-buildcache
cache-to: type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/maibot:amd64-buildcache,mode=max cache-to: type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/maibot:amd64-buildcache,mode=max
labels: ${{ steps.meta.outputs.labels }} outputs: type=image,name=${{ secrets.DOCKERHUB_USERNAME }}/maibot,push-by-digest=true,name-canonical=true,push=true
provenance: true
sbom: true
build-args: | build-args: |
BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
VCS_REF=${{ github.sha }} VCS_REF=${{ github.sha }}
outputs: type=image,push=true
build-arm64: build-arm64:
name: Build ARM64 Image name: Build ARM64 Image
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: outputs:
DOCKERHUB_USER: ${{ secrets.DOCKERHUB_USERNAME }} digest: ${{ steps.build.outputs.digest }}
steps: steps:
- name: Checkout code - name: Check out git repository
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Set up QEMU # Clone required dependencies
uses: docker/setup-qemu-action@v3
- name: Clone maim_message - name: Clone maim_message
run: git clone https://github.com/MaiM-with-u/maim_message maim_message run: git clone https://github.com/MaiM-with-u/maim_message maim_message
- name: Clone lpmm - name: Clone lpmm
run: git clone https://github.com/MaiM-with-u/MaiMBot-LPMM.git MaiMBot-LPMM run: git clone https://github.com/MaiM-with-u/MaiMBot-LPMM.git MaiMBot-LPMM
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
with:
platforms: arm64
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
with: with:
buildkitd-flags: --debug buildkitd-flags: --debug
# Log in docker hub
- name: Log in to Docker Hub - name: Log in to Docker Hub
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
# Generate metadata for Docker images
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
with: with:
images: ${{ secrets.DOCKERHUB_USERNAME }}/maibot images: ${{ secrets.DOCKERHUB_USERNAME }}/maibot
tags: |
type=ref,event=branch
type=ref,event=tag
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
- name: Build and Push ARM64 Docker Image # Build and push ARM64 image by digest
- name: Build and push ARM64
id: build
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
with: with:
context: . context: .
platforms: linux/arm64/v8
labels: ${{ steps.meta.outputs.labels }}
file: ./Dockerfile file: ./Dockerfile
platforms: linux/arm64
tags: ${{ secrets.DOCKERHUB_USERNAME }}/maibot:arm64-${{ github.sha }}
push: true
cache-from: type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/maibot:arm64-buildcache cache-from: type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/maibot:arm64-buildcache
cache-to: type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/maibot:arm64-buildcache,mode=max cache-to: type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/maibot:arm64-buildcache,mode=max
labels: ${{ steps.meta.outputs.labels }} outputs: type=image,name=${{ secrets.DOCKERHUB_USERNAME }}/maibot,push-by-digest=true,name-canonical=true,push=true
provenance: true
sbom: true
build-args: | build-args: |
BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
VCS_REF=${{ github.sha }} VCS_REF=${{ github.sha }}
outputs: type=image,push=true
create-manifest: create-manifest:
name: Create Multi-Arch Manifest name: Create Multi-Arch Manifest
@@ -143,12 +132,17 @@ jobs:
- build-amd64 - build-amd64
- build-arm64 - build-arm64
steps: steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# Log in docker hub
- name: Log in to Docker Hub - name: Log in to Docker Hub
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
# Generate metadata for Docker images
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
@@ -161,7 +155,7 @@ jobs:
type=semver,pattern={{version}} type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}} type=semver,pattern={{major}}
type=sha type=sha,prefix=${{ github.ref_name }}-,enable=${{ github.ref_type == 'branch' }}
- name: Create and Push Manifest - name: Create and Push Manifest
run: | run: |
@@ -169,6 +163,6 @@ jobs:
for tag in $(echo "${{ steps.meta.outputs.tags }}" | tr '\n' ' '); do for tag in $(echo "${{ steps.meta.outputs.tags }}" | tr '\n' ' '); do
echo "Creating manifest for $tag" echo "Creating manifest for $tag"
docker buildx imagetools create -t $tag \ docker buildx imagetools create -t $tag \
${{ secrets.DOCKERHUB_USERNAME }}/maibot:amd64-${{ github.sha }} \ ${{ secrets.DOCKERHUB_USERNAME }}/maibot@${{ needs.build-amd64.outputs.digest }} \
${{ secrets.DOCKERHUB_USERNAME }}/maibot:arm64-${{ github.sha }} ${{ secrets.DOCKERHUB_USERNAME }}/maibot@${{ needs.build-arm64.outputs.digest }}
done done

View File

@@ -17,7 +17,6 @@ services:
restart: always restart: always
networks: networks:
- maim_bot - maim_bot
core: core:
container_name: maim-bot-core container_name: maim-bot-core
#### prod #### #### prod ####
@@ -40,7 +39,6 @@ services:
restart: always restart: always
networks: networks:
- maim_bot - maim_bot
napcat: napcat:
environment: environment:
- NAPCAT_UID=1000 - NAPCAT_UID=1000
@@ -57,8 +55,8 @@ services:
image: mlikiowa/napcat-docker:latest image: mlikiowa/napcat-docker:latest
networks: networks:
- maim_bot - maim_bot
sqlite-web: sqlite-web:
# 注意coleifer/sqlite-web 镜像不支持arm64
image: coleifer/sqlite-web image: coleifer/sqlite-web
container_name: sqlite-web container_name: sqlite-web
restart: always restart: always
@@ -71,6 +69,18 @@ services:
networks: networks:
- maim_bot - maim_bot
# chat2db占用相对较高但是功能强大
# 内存占用约600m内存充足推荐选此
# chat2db:
# image: chat2db/chat2db:latest
# container_name: maim-bot-chat2db
# restart: always
# ports:
# - "10824:10824"
# volumes:
# - ./data/MaiMBot:/data/MaiMBot
# networks:
# - maim_bot
networks: networks:
maim_bot: maim_bot:
driver: bridge driver: bridge

View File

@@ -324,8 +324,6 @@ async def clear_temp_emoji() -> None:
os.remove(file_path) os.remove(file_path)
logger.debug(f"[清理] 删除: {filename}") logger.debug(f"[清理] 删除: {filename}")
logger.info("[清理] 完成")
async def clean_unused_emojis(emoji_dir: str, emoji_objects: List["MaiEmoji"], removed_count: int) -> int: async def clean_unused_emojis(emoji_dir: str, emoji_objects: List["MaiEmoji"], removed_count: int) -> int:
"""清理指定目录中未被 emoji_objects 追踪的表情包文件""" """清理指定目录中未被 emoji_objects 追踪的表情包文件"""
@@ -590,7 +588,7 @@ class EmojiManager:
"""定期检查表情包完整性和数量""" """定期检查表情包完整性和数量"""
await self.get_all_emoji_from_db() await self.get_all_emoji_from_db()
while True: while True:
logger.info("[扫描] 开始检查表情包完整性...") # logger.info("[扫描] 开始检查表情包完整性...")
await self.check_emoji_file_integrity() await self.check_emoji_file_integrity()
await clear_temp_emoji() await clear_temp_emoji()
logger.info("[扫描] 开始扫描新表情包...") logger.info("[扫描] 开始扫描新表情包...")

View File

@@ -30,7 +30,7 @@ class Heartflow:
# 注册子心流 # 注册子心流
self.subheartflows[subheartflow_id] = new_subflow self.subheartflows[subheartflow_id] = new_subflow
heartflow_name = get_chat_manager().get_stream_name(subheartflow_id) or subheartflow_id heartflow_name = get_chat_manager().get_stream_name(subheartflow_id) or subheartflow_id
logger.info(f"[{heartflow_name}] 开始接收消息") logger.debug(f"[{heartflow_name}] 开始接收消息")
return new_subflow return new_subflow
except Exception as e: except Exception as e:

View File

@@ -119,15 +119,13 @@ class HeartFCMessageReceiver:
# current_time = time.strftime("%H:%M:%S", time.localtime(message.message_info.time)) # current_time = time.strftime("%H:%M:%S", time.localtime(message.message_info.time))
current_talk_frequency = global_config.chat.get_current_talk_frequency(chat.stream_id) current_talk_frequency = global_config.chat.get_current_talk_frequency(chat.stream_id)
# 如果消息中包含图片标识,则日志展示为图片 # 如果消息中包含图片标识,则将 [picid:...] 替换为 [图片]
picid_pattern = r"\[picid:([^\]]+)\]"
processed_plain_text = re.sub(picid_pattern, "[图片]", message.processed_plain_text)
picid_match = re.search(r"\[picid:([^\]]+)\]", message.processed_plain_text) logger.info(f"[{mes_name}]{userinfo.user_nickname}:{processed_plain_text}")
if picid_match:
logger.info(f"[{mes_name}]{userinfo.user_nickname}: [图片] [当前回复频率: {current_talk_frequency}]") logger.debug(f"[{mes_name}][当前时段回复频率: {current_talk_frequency}]")
else:
logger.info(
f"[{mes_name}]{userinfo.user_nickname}:{message.processed_plain_text}[当前回复频率: {current_talk_frequency}]"
)
# 8. 关系处理 # 8. 关系处理
if global_config.relationship.enable_relationship: if global_config.relationship.enable_relationship:

View File

@@ -119,7 +119,8 @@ class MemoryActivator:
valid_keywords=keywords, max_memory_num=3, max_memory_length=2, max_depth=3 valid_keywords=keywords, max_memory_num=3, max_memory_length=2, max_depth=3
) )
logger.info(f"当前记忆关键词: {self.cached_keywords} 。获取到的记忆: {related_memory}") logger.debug(f"当前记忆关键词: {self.cached_keywords} ")
logger.debug(f"获取到的记忆: {related_memory}")
# 激活时所有已有记忆的duration+1达到3则移除 # 激活时所有已有记忆的duration+1达到3则移除
for m in self.running_memory[:]: for m in self.running_memory[:]:

View File

@@ -166,6 +166,7 @@ class ChatBot:
message_data["message_info"]["group_info"]["group_id"] = str( message_data["message_info"]["group_info"]["group_id"] = str(
message_data["message_info"]["group_info"]["group_id"] message_data["message_info"]["group_info"]["group_id"]
) )
if message_data["message_info"].get("user_info") is not None:
message_data["message_info"]["user_info"]["user_id"] = str( message_data["message_info"]["user_info"]["user_id"] = str(
message_data["message_info"]["user_info"]["user_id"] message_data["message_info"]["user_info"]["user_id"]
) )

View File

@@ -107,7 +107,9 @@ class MessageRecv(Message):
self.processed_plain_text = message_dict.get("processed_plain_text", "") self.processed_plain_text = message_dict.get("processed_plain_text", "")
self.detailed_plain_text = message_dict.get("detailed_plain_text", "") self.detailed_plain_text = message_dict.get("detailed_plain_text", "")
self.is_emoji = False self.is_emoji = False
self.has_emoji = False
self.is_picid = False self.is_picid = False
self.has_picid = False
self.is_mentioned = None self.is_mentioned = None
self.priority_mode = "interest" self.priority_mode = "interest"
self.priority_info = None self.priority_info = None
@@ -134,25 +136,35 @@ class MessageRecv(Message):
""" """
try: try:
if segment.type == "text": if segment.type == "text":
self.is_picid = False
self.is_emoji = False
return segment.data return segment.data
elif segment.type == "image": elif segment.type == "image":
# 如果是base64图片数据 # 如果是base64图片数据
if isinstance(segment.data, str): if isinstance(segment.data, str):
self.has_picid = True
self.is_picid = True self.is_picid = True
self.is_emoji = False
image_manager = get_image_manager() image_manager = get_image_manager()
# print(f"segment.data: {segment.data}") # print(f"segment.data: {segment.data}")
_, processed_text = await image_manager.process_image(segment.data) _, processed_text = await image_manager.process_image(segment.data)
return processed_text return processed_text
return "[发了一张图片,网卡了加载不出来]" return "[发了一张图片,网卡了加载不出来]"
elif segment.type == "emoji": elif segment.type == "emoji":
self.has_emoji = True
self.is_emoji = True self.is_emoji = True
self.is_picid = False
if isinstance(segment.data, str): if isinstance(segment.data, str):
return await get_image_manager().get_emoji_description(segment.data) return await get_image_manager().get_emoji_description(segment.data)
return "[发了一个表情包,网卡了加载不出来]" return "[发了一个表情包,网卡了加载不出来]"
elif segment.type == "mention_bot": elif segment.type == "mention_bot":
self.is_picid = False
self.is_emoji = False
self.is_mentioned = float(segment.data) self.is_mentioned = float(segment.data)
return "" return ""
elif segment.type == "priority_info": elif segment.type == "priority_info":
self.is_picid = False
self.is_emoji = False
if isinstance(segment.data, dict): if isinstance(segment.data, dict):
# 处理优先级信息 # 处理优先级信息
self.priority_mode = "priority" self.priority_mode = "priority"

View File

@@ -136,7 +136,7 @@ class MessageStorage:
if matched_message: if matched_message:
# 更新找到的消息记录 # 更新找到的消息记录
Messages.update(message_id=qq_message_id).where(Messages.id == matched_message.id).execute() Messages.update(message_id=qq_message_id).where(Messages.id == matched_message.id).execute()
logger.info(f"更新消息ID成功: {matched_message.message_id} -> {qq_message_id}") logger.debug(f"更新消息ID成功: {matched_message.message_id} -> {qq_message_id}")
else: else:
logger.debug("未找到匹配的消息") logger.debug("未找到匹配的消息")

View File

@@ -302,13 +302,17 @@ class NormalChat:
logger.info(f"[{self.stream_name}] 在处理上下文中检测到停止信号,退出") logger.info(f"[{self.stream_name}] 在处理上下文中检测到停止信号,退出")
break break
# 并行处理兴趣消息 semaphore = asyncio.Semaphore(5)
async def process_single_message(msg_id, message, interest_value, is_mentioned):
"""处理单个兴趣消息""" async def process_and_acquire(msg_id, message, interest_value, is_mentioned):
"""处理单个兴趣消息并管理信号量"""
async with semaphore:
try: try:
# 在处理每个消息前检查停止状态 # 在处理每个消息前检查停止状态
if self._disabled: if self._disabled:
logger.debug(f"[{self.stream_name}] 处理消息时检测到停用,跳过消息 {msg_id}") logger.debug(
f"[{self.stream_name}] 处理消息时检测到停用,跳过消息 {msg_id}"
)
return return
# 处理消息 # 处理消息
@@ -329,23 +333,13 @@ class NormalChat:
# 无论如何都要清理消息 # 无论如何都要清理消息
self.interest_dict.pop(msg_id, None) self.interest_dict.pop(msg_id, None)
# 创建并行任务列表 tasks = [
coroutines = [] process_and_acquire(msg_id, message, interest_value, is_mentioned)
for msg_id, (message, interest_value, is_mentioned) in items_to_process: for msg_id, (message, interest_value, is_mentioned) in items_to_process
coroutine = process_single_message(msg_id, message, interest_value, is_mentioned) ]
coroutines.append(coroutine)
# 并行执行所有任务,限制并发数量避免资源过度消耗 if tasks:
if coroutines: await asyncio.gather(*tasks, return_exceptions=True)
# 使用信号量控制并发数最多同时处理5个消息
semaphore = asyncio.Semaphore(5)
async def limited_process(coroutine, sem):
async with sem:
await coroutine
limited_tasks = [limited_process(coroutine, semaphore) for coroutine in coroutines]
await asyncio.gather(*limited_tasks, return_exceptions=True)
except asyncio.CancelledError: except asyncio.CancelledError:
logger.info(f"[{self.stream_name}] 处理上下文时任务被取消") logger.info(f"[{self.stream_name}] 处理上下文时任务被取消")
@@ -469,9 +463,6 @@ class NormalChat:
) -> Optional[list]: ) -> Optional[list]:
"""生成普通回复""" """生成普通回复"""
try: try:
logger.info(
f"NormalChat思考:{message.processed_plain_text[:30] + '...' if len(message.processed_plain_text) > 30 else message.processed_plain_text}"
)
person_info_manager = get_person_info_manager() person_info_manager = get_person_info_manager()
person_id = person_info_manager.get_person_id( person_id = person_info_manager.get_person_id(
message.chat_stream.user_info.platform, message.chat_stream.user_info.user_id message.chat_stream.user_info.platform, message.chat_stream.user_info.user_id
@@ -491,10 +482,6 @@ class NormalChat:
logger.info(f"{message.processed_plain_text} 的回复生成失败") logger.info(f"{message.processed_plain_text} 的回复生成失败")
return None return None
content = " ".join([item[1] for item in reply_set if item[0] == "text"])
if content:
logger.info(f"{global_config.bot.nickname}的备选回复是:{content}")
return reply_set return reply_set
except Exception as e: except Exception as e:
@@ -532,7 +519,15 @@ class NormalChat:
reasoning = plan_result["action_result"]["reasoning"] reasoning = plan_result["action_result"]["reasoning"]
is_parallel = plan_result["action_result"].get("is_parallel", False) is_parallel = plan_result["action_result"].get("is_parallel", False)
logger.info(f"[{self.stream_name}] Planner决策: {action_type}, 理由: {reasoning}, 并行执行: {is_parallel}") if action_type == "no_action":
logger.info(f"[{self.stream_name}] {global_config.bot.nickname} 决定进行回复")
elif is_parallel:
logger.info(
f"[{self.stream_name}] {global_config.bot.nickname} 决定进行回复, 同时执行{action_type}动作"
)
else:
logger.info(f"[{self.stream_name}] {global_config.bot.nickname} 决定执行{action_type}动作")
self.action_type = action_type # 更新实例属性 self.action_type = action_type # 更新实例属性
self.is_parallel_action = is_parallel # 新增:保存并行执行标志 self.is_parallel_action = is_parallel # 新增:保存并行执行标志
@@ -590,10 +585,21 @@ class NormalChat:
) )
response_set, plan_result = results response_set, plan_result = results
except asyncio.TimeoutError: except asyncio.TimeoutError:
gen_timed_out = not gen_task.done()
plan_timed_out = not plan_task.done()
timeout_details = []
if gen_timed_out:
timeout_details.append("回复生成(gen)")
if plan_timed_out:
timeout_details.append("动作规划(plan)")
timeout_source = "".join(timeout_details)
logger.warning( logger.warning(
f"[{self.stream_name}] 并行执行回复生成和动作规划超时 ({gather_timeout}秒),正在取消相关任务..." f"[{self.stream_name}] {timeout_source} 任务超时 ({global_config.chat.thinking_timeout}秒),正在取消相关任务..."
) )
print(f"111{self.timeout_count}") # print(f"111{self.timeout_count}")
self.timeout_count += 1 self.timeout_count += 1
if self.timeout_count > 5: if self.timeout_count > 5:
logger.warning( logger.warning(
@@ -623,18 +629,23 @@ class NormalChat:
elif plan_result: elif plan_result:
logger.debug(f"[{self.stream_name}] 额外动作处理完成: {self.action_type}") logger.debug(f"[{self.stream_name}] 额外动作处理完成: {self.action_type}")
if response_set:
content = " ".join([item[1] for item in response_set if item[0] == "text"])
if not response_set or ( if not response_set or (
self.enable_planner and self.action_type not in ["no_action"] and not self.is_parallel_action self.enable_planner and self.action_type not in ["no_action"] and not self.is_parallel_action
): ):
if not response_set: if not response_set:
logger.info(f"[{self.stream_name}] 模型未生成回复内容") logger.warning(f"[{self.stream_name}] 模型未生成回复内容")
elif self.enable_planner and self.action_type not in ["no_action"] and not self.is_parallel_action: elif self.enable_planner and self.action_type not in ["no_action"] and not self.is_parallel_action:
logger.info(f"[{self.stream_name}] 模型选择其他动作(非并行动作)") logger.info(
f"[{self.stream_name}] {global_config.bot.nickname} 原本想要回复:{content},但选择执行{self.action_type},不发表回复"
)
# 如果模型未生成回复,移除思考消息 # 如果模型未生成回复,移除思考消息
await self._cleanup_thinking_message_by_id(thinking_id) await self._cleanup_thinking_message_by_id(thinking_id)
return False return False
# logger.info(f"[{self.stream_name}] 回复内容: {response_set}") logger.info(f"[{self.stream_name}] {global_config.bot.nickname} 决定的回复内容: {content}")
if self._disabled: if self._disabled:
logger.info(f"[{self.stream_name}] 已停用,忽略 normal_response。") logger.info(f"[{self.stream_name}] 已停用,忽略 normal_response。")

View File

@@ -96,7 +96,7 @@ class ActionManager:
f"从插件系统加载Action组件: {action_name} (插件: {getattr(action_info, 'plugin_name', 'unknown')})" f"从插件系统加载Action组件: {action_name} (插件: {getattr(action_info, 'plugin_name', 'unknown')})"
) )
logger.info(f"从插件系统加载了 {len(action_components)} 个Action组件") logger.info(f"加载了 {len(action_components)} 个Action动作")
except Exception as e: except Exception as e:
logger.error(f"从插件系统加载Action组件失败: {e}") logger.error(f"从插件系统加载Action组件失败: {e}")

View File

@@ -124,6 +124,11 @@ class ActionPlanner:
logger.info(f"{self.log_prefix}规划器原始响应: {llm_content}") logger.info(f"{self.log_prefix}规划器原始响应: {llm_content}")
if reasoning_content: if reasoning_content:
logger.info(f"{self.log_prefix}规划器推理: {reasoning_content}") logger.info(f"{self.log_prefix}规划器推理: {reasoning_content}")
else:
logger.debug(f"{self.log_prefix}规划器原始提示词: {prompt}")
logger.debug(f"{self.log_prefix}规划器原始响应: {llm_content}")
if reasoning_content:
logger.debug(f"{self.log_prefix}规划器推理: {reasoning_content}")
except Exception as req_e: except Exception as req_e:
logger.error(f"{self.log_prefix}LLM 请求执行失败: {req_e}") logger.error(f"{self.log_prefix}LLM 请求执行失败: {req_e}")

View File

@@ -188,7 +188,7 @@ class DefaultReplyer:
} }
for key, value in reply_data.items(): for key, value in reply_data.items():
if not value: if not value:
logger.info(f"{self.log_prefix} 回复数据跳过{key},生成回复时将忽略。") logger.debug(f"{self.log_prefix} 回复数据跳过{key},生成回复时将忽略。")
# 3. 构建 Prompt # 3. 构建 Prompt
with Timer("构建Prompt", {}): # 内部计时器,可选保留 with Timer("构建Prompt", {}): # 内部计时器,可选保留
@@ -218,11 +218,13 @@ class DefaultReplyer:
) )
if global_config.debug.show_prompt: if global_config.debug.show_prompt:
logger.info(f"{self.log_prefix}Prompt:\n{prompt}\n") logger.info(f"{self.log_prefix}\n{prompt}\n")
else:
logger.debug(f"{self.log_prefix}\n{prompt}\n")
content, (reasoning_content, model_name) = await express_model.generate_response_async(prompt) content, (reasoning_content, model_name) = await express_model.generate_response_async(prompt)
logger.info(f"最终回复: {content}") logger.debug(f"replyer生成内容: {content}")
except Exception as llm_e: except Exception as llm_e:
# 精简报错信息 # 精简报错信息
@@ -331,7 +333,7 @@ class DefaultReplyer:
) )
if selected_expressions: if selected_expressions:
logger.info(f"{self.log_prefix} 使用处理器选中的{len(selected_expressions)}个表达方式") logger.debug(f"{self.log_prefix} 使用处理器选中的{len(selected_expressions)}个表达方式")
for expr in selected_expressions: for expr in selected_expressions:
if isinstance(expr, dict) and "situation" in expr and "style" in expr: if isinstance(expr, dict) and "situation" in expr and "style" in expr:
expr_type = expr.get("type", "style") expr_type = expr.get("type", "style")

View File

@@ -551,6 +551,9 @@ def build_readable_messages(
show_actions: 是否显示动作记录 show_actions: 是否显示动作记录
""" """
# 创建messages的深拷贝避免修改原始列表 # 创建messages的深拷贝避免修改原始列表
if not messages:
return ""
copy_messages = [msg.copy() for msg in messages] copy_messages = [msg.copy() for msg in messages]
if show_actions and copy_messages: if show_actions and copy_messages:

View File

@@ -252,8 +252,7 @@ class PersonInfo(BaseModel):
know_times = FloatField(null=True) # 认识时间 (时间戳) know_times = FloatField(null=True) # 认识时间 (时间戳)
know_since = FloatField(null=True) # 首次印象总结时间 know_since = FloatField(null=True) # 首次印象总结时间
last_know = FloatField(null=True) # 最后一次印象总结时间 last_know = FloatField(null=True) # 最后一次印象总结时间
familiarity_value = IntegerField(null=True, default=0) # 熟悉0-100完全陌生到非常熟悉 attitude = IntegerField(null=True, default=50) # 0-100非常厌恶到十分喜欢
liking_value = IntegerField(null=True, default=50) # 好感度0-100从非常厌恶到十分喜欢
class Meta: class Meta:
# database = db # 继承自 BaseModel # database = db # 继承自 BaseModel

View File

@@ -321,14 +321,13 @@ MODULE_COLORS = {
# 核心模块 # 核心模块
"main": "\033[1;97m", # 亮白色+粗体 (主程序) "main": "\033[1;97m", # 亮白色+粗体 (主程序)
"api": "\033[92m", # 亮绿色 "api": "\033[92m", # 亮绿色
"emoji": "\033[92m", # 亮绿色 "emoji": "\033[33m", # 亮绿色
"chat": "\033[94m", # 亮蓝色 "chat": "\033[92m", # 亮蓝色
"config": "\033[93m", # 亮黄色 "config": "\033[93m", # 亮黄色
"common": "\033[95m", # 亮紫色 "common": "\033[95m", # 亮紫色
"tools": "\033[96m", # 亮青色 "tools": "\033[96m", # 亮青色
"lpmm": "\033[96m", "lpmm": "\033[96m",
"plugin_system": "\033[91m", # 亮红色 "plugin_system": "\033[91m", # 亮红色
"experimental": "\033[97m", # 亮白色
"person_info": "\033[32m", # 绿色 "person_info": "\033[32m", # 绿色
"individuality": "\033[34m", # 蓝色 "individuality": "\033[34m", # 蓝色
"manager": "\033[35m", # 紫色 "manager": "\033[35m", # 紫色
@@ -339,17 +338,13 @@ MODULE_COLORS = {
"planner": "\033[36m", "planner": "\033[36m",
"memory": "\033[34m", "memory": "\033[34m",
"hfc": "\033[96m", "hfc": "\033[96m",
"base_action": "\033[96m", "action_manager": "\033[38;5;166m",
"action_manager": "\033[32m",
# 关系系统 # 关系系统
"relation": "\033[38;5;201m", # 深粉色 "relation": "\033[38;5;201m", # 深粉色
# 聊天相关模块 # 聊天相关模块
"normal_chat": "\033[38;5;81m", # 亮蓝绿色 "normal_chat": "\033[38;5;81m", # 亮蓝绿色
"normal_chat_response": "\033[38;5;123m", # 青绿色 "normal_chat_response": "\033[38;5;123m", # 青绿色
"normal_chat_action_modifier": "\033[38;5;111m", # 蓝色
"normal_chat_planner": "\033[38;5;75m", # 浅蓝色
"heartflow": "\033[38;5;213m", # 粉色 "heartflow": "\033[38;5;213m", # 粉色
"heartflow_utils": "\033[38;5;219m", # 浅粉色
"sub_heartflow": "\033[38;5;207m", # 粉紫色 "sub_heartflow": "\033[38;5;207m", # 粉紫色
"subheartflow_manager": "\033[38;5;201m", # 深粉色 "subheartflow_manager": "\033[38;5;201m", # 深粉色
"background_tasks": "\033[38;5;240m", # 灰色 "background_tasks": "\033[38;5;240m", # 灰色
@@ -359,11 +354,9 @@ MODULE_COLORS = {
"message_storage": "\033[38;5;33m", # 深蓝色 "message_storage": "\033[38;5;33m", # 深蓝色
# 专注聊天模块 # 专注聊天模块
"replyer": "\033[38;5;166m", # 橙色 "replyer": "\033[38;5;166m", # 橙色
"expressor": "\033[38;5;172m", # 黄橙色
"processor": "\033[38;5;184m", # 黄绿色
"base_processor": "\033[38;5;190m", # 绿黄色 "base_processor": "\033[38;5;190m", # 绿黄色
"working_memory": "\033[38;5;22m", # 深绿色 "working_memory": "\033[38;5;22m", # 深绿色
"memory_activator": "\033[38;5;28m", # 绿色 "memory_activator": "\033[34m", # 绿色
# 插件系统 # 插件系统
"plugin_manager": "\033[38;5;208m", # 红色 "plugin_manager": "\033[38;5;208m", # 红色
"base_plugin": "\033[38;5;202m", # 橙红色 "base_plugin": "\033[38;5;202m", # 橙红色
@@ -389,11 +382,9 @@ MODULE_COLORS = {
"tool_executor": "\033[38;5;64m", # 深绿色 "tool_executor": "\033[38;5;64m", # 深绿色
"base_tool": "\033[38;5;70m", # 绿色 "base_tool": "\033[38;5;70m", # 绿色
# 工具和实用模块 # 工具和实用模块
"prompt": "\033[38;5;99m", # 紫色
"prompt_build": "\033[38;5;105m", # 紫色 "prompt_build": "\033[38;5;105m", # 紫色
"chat_utils": "\033[38;5;111m", # 蓝色 "chat_utils": "\033[38;5;111m", # 蓝色
"chat_image": "\033[38;5;117m", # 浅蓝色 "chat_image": "\033[38;5;117m", # 浅蓝色
"typo_gen": "\033[38;5;123m", # 青绿色
"maibot_statistic": "\033[38;5;129m", # 紫色 "maibot_statistic": "\033[38;5;129m", # 紫色
# 特殊功能插件 # 特殊功能插件
"mute_plugin": "\033[38;5;240m", # 灰色 "mute_plugin": "\033[38;5;240m", # 灰色
@@ -405,16 +396,13 @@ MODULE_COLORS = {
# 数据库和消息 # 数据库和消息
"database_model": "\033[38;5;94m", # 橙褐色 "database_model": "\033[38;5;94m", # 橙褐色
"maim_message": "\033[38;5;100m", # 绿褐色 "maim_message": "\033[38;5;100m", # 绿褐色
# 实验性模块
"pfc": "\033[38;5;252m", # 浅灰色
# 日志系统 # 日志系统
"logger": "\033[38;5;8m", # 深灰色 "logger": "\033[38;5;8m", # 深灰色
"demo": "\033[38;5;15m", # 白色
"confirm": "\033[1;93m", # 黄色+粗体 "confirm": "\033[1;93m", # 黄色+粗体
# 模型相关 # 模型相关
"model_utils": "\033[38;5;164m", # 紫红色 "model_utils": "\033[38;5;164m", # 紫红色
"relationship_fetcher": "\033[38;5;170m", # 浅紫色 "relationship_fetcher": "\033[38;5;170m", # 浅紫色
"relationship_builder": "\033[38;5;117m", # 浅蓝色 "relationship_builder": "\033[38;5;93m", # 浅蓝色
} }
RESET_COLOR = "\033[0m" RESET_COLOR = "\033[0m"

View File

@@ -48,6 +48,7 @@ person_info_default = {
"points": None, "points": None,
"forgotten_points": None, "forgotten_points": None,
"relation_value": None, "relation_value": None,
"attitude": 50,
} }

View File

@@ -140,7 +140,7 @@ class RelationshipBuilder:
segments.append(new_segment) segments.append(new_segment)
person_name = get_person_info_manager().get_value_sync(person_id, "person_name") or person_id person_name = get_person_info_manager().get_value_sync(person_id, "person_name") or person_id
logger.info( logger.debug(
f"{self.log_prefix} 眼熟用户 {person_name}{time.strftime('%H:%M:%S', time.localtime(potential_start_time))} - {time.strftime('%H:%M:%S', time.localtime(message_time))} 之间有 {new_segment['message_count']} 条消息" f"{self.log_prefix} 眼熟用户 {person_name}{time.strftime('%H:%M:%S', time.localtime(potential_start_time))} - {time.strftime('%H:%M:%S', time.localtime(message_time))} 之间有 {new_segment['message_count']} 条消息"
) )
self._save_cache() self._save_cache()
@@ -187,7 +187,9 @@ class RelationshipBuilder:
segments.append(new_segment) segments.append(new_segment)
person_info_manager = get_person_info_manager() person_info_manager = get_person_info_manager()
person_name = person_info_manager.get_value_sync(person_id, "person_name") or person_id person_name = person_info_manager.get_value_sync(person_id, "person_name") or person_id
logger.info(f"{self.log_prefix} 重新眼熟用户 {person_name} 创建新消息段超过10条消息间隔: {new_segment}") logger.debug(
f"{self.log_prefix} 重新眼熟用户 {person_name} 创建新消息段超过10条消息间隔: {new_segment}"
)
self._save_cache() self._save_cache()
@@ -384,7 +386,7 @@ class RelationshipBuilder:
total_message_count = self._get_total_message_count(person_id) total_message_count = self._get_total_message_count(person_id)
if total_message_count >= 45: if total_message_count >= 45:
users_to_build_relationship.append(person_id) users_to_build_relationship.append(person_id)
logger.info( logger.debug(
f"{self.log_prefix} 用户 {person_id} 满足关系构建条件,总消息数:{total_message_count},消息段数:{len(segments)}" f"{self.log_prefix} 用户 {person_id} 满足关系构建条件,总消息数:{total_message_count},消息段数:{len(segments)}"
) )
elif total_message_count > 0: elif total_message_count > 0:
@@ -422,7 +424,7 @@ class RelationshipBuilder:
# 获取该段的消息(包含边界) # 获取该段的消息(包含边界)
segment_messages = get_raw_msg_by_timestamp_with_chat_inclusive(self.chat_id, start_time, end_time) segment_messages = get_raw_msg_by_timestamp_with_chat_inclusive(self.chat_id, start_time, end_time)
logger.info( logger.debug(
f"消息段 {i + 1}: {start_date} - {time.strftime('%Y-%m-%d %H:%M', time.localtime(end_time))}, 消息数: {len(segment_messages)}" f"消息段 {i + 1}: {start_date} - {time.strftime('%Y-%m-%d %H:%M', time.localtime(end_time))}, 消息数: {len(segment_messages)}"
) )
@@ -450,7 +452,7 @@ class RelationshipBuilder:
# 按时间排序所有消息(包括间隔标识) # 按时间排序所有消息(包括间隔标识)
processed_messages.sort(key=lambda x: x["time"]) processed_messages.sort(key=lambda x: x["time"])
logger.info(f"{person_id} 获取到总共 {len(processed_messages)} 条消息(包含间隔标识)用于印象更新") logger.debug(f"{person_id} 获取到总共 {len(processed_messages)} 条消息(包含间隔标识)用于印象更新")
relationship_manager = get_relationship_manager() relationship_manager = get_relationship_manager()
# 调用原有的更新方法 # 调用原有的更新方法

View File

@@ -1,12 +1,10 @@
from src.common.logger import get_logger from src.common.logger import get_logger
import math
from src.person_info.person_info import PersonInfoManager, get_person_info_manager from src.person_info.person_info import PersonInfoManager, get_person_info_manager
import time import time
import random import random
from src.llm_models.utils_model import LLMRequest from src.llm_models.utils_model import LLMRequest
from src.config.config import global_config from src.config.config import global_config
from src.chat.utils.chat_message_builder import build_readable_messages from src.chat.utils.chat_message_builder import build_readable_messages
from src.manager.mood_manager import mood_manager
import json import json
from json_repair import repair_json from json_repair import repair_json
from datetime import datetime from datetime import datetime
@@ -21,67 +19,11 @@ logger = get_logger("relation")
class RelationshipManager: class RelationshipManager:
def __init__(self): def __init__(self):
self.positive_feedback_value = 0 # 正反馈系统
self.gain_coefficient = [1.0, 1.0, 1.1, 1.2, 1.4, 1.7, 1.9, 2.0]
self._mood_manager = None
self.relationship_llm = LLMRequest( self.relationship_llm = LLMRequest(
model=global_config.model.relation, model=global_config.model.relation,
request_type="relationship", # 用于动作规划 request_type="relationship", # 用于动作规划
) )
@property
def mood_manager(self):
if self._mood_manager is None:
self._mood_manager = mood_manager
return self._mood_manager
def positive_feedback_sys(self, label: str, stance: str):
"""正反馈系统,通过正反馈系数增益情绪变化,根据情绪再影响关系变更"""
positive_list = [
"开心",
"惊讶",
"害羞",
]
negative_list = [
"愤怒",
"悲伤",
"恐惧",
"厌恶",
]
if label in positive_list:
if 7 > self.positive_feedback_value >= 0:
self.positive_feedback_value += 1
elif self.positive_feedback_value < 0:
self.positive_feedback_value = 0
elif label in negative_list:
if -7 < self.positive_feedback_value <= 0:
self.positive_feedback_value -= 1
elif self.positive_feedback_value > 0:
self.positive_feedback_value = 0
if abs(self.positive_feedback_value) > 1:
logger.debug(f"触发mood变更增益当前增益系数{self.gain_coefficient[abs(self.positive_feedback_value)]}")
def mood_feedback(self, value):
"""情绪反馈"""
mood_manager = self.mood_manager
mood_gain = mood_manager.current_mood.valence**2 * math.copysign(1, value * mood_manager.current_mood.valence)
value += value * mood_gain
logger.debug(f"当前relationship增益系数{mood_gain:.3f}")
return value
def feedback_to_mood(self, mood_value):
"""对情绪的反馈"""
coefficient = self.gain_coefficient[abs(self.positive_feedback_value)]
if mood_value > 0 and self.positive_feedback_value > 0 or mood_value < 0 and self.positive_feedback_value < 0:
return mood_value * coefficient
else:
return mood_value / coefficient
@staticmethod @staticmethod
async def is_known_some_one(platform, user_id): async def is_known_some_one(platform, user_id):
"""判断是否认识某人""" """判断是否认识某人"""
@@ -168,19 +110,6 @@ class RelationshipManager:
return relation_prompt return relation_prompt
async def _update_list_field(self, person_id: str, field_name: str, new_items: list) -> None:
"""更新列表类型的字段,将新项目添加到现有列表中
Args:
person_id: 用户ID
field_name: 字段名称
new_items: 新的项目列表
"""
person_info_manager = get_person_info_manager()
old_items = await person_info_manager.get_value(person_id, field_name) or []
updated_items = list(set(old_items + [item for item in new_items if isinstance(item, str) and item]))
await person_info_manager.update_one_field(person_id, field_name, updated_items)
async def update_person_impression(self, person_id, timestamp, bot_engaged_messages=None): async def update_person_impression(self, person_id, timestamp, bot_engaged_messages=None):
"""更新用户印象 """更新用户印象
@@ -194,6 +123,7 @@ class RelationshipManager:
person_info_manager = get_person_info_manager() person_info_manager = get_person_info_manager()
person_name = await person_info_manager.get_value(person_id, "person_name") person_name = await person_info_manager.get_value(person_id, "person_name")
nickname = await person_info_manager.get_value(person_id, "nickname") nickname = await person_info_manager.get_value(person_id, "nickname")
know_times = await person_info_manager.get_value(person_id, "know_times") or 0
alias_str = ", ".join(global_config.bot.alias_names) alias_str = ", ".join(global_config.bot.alias_names)
# personality_block =get_individuality().get_personality_prompt(x_person=2, level=2) # personality_block =get_individuality().get_personality_prompt(x_person=2, level=2)
@@ -240,7 +170,9 @@ class RelationshipManager:
name_mapping[replace_person_name] = f"用户{current_user}{user_count if user_count > 1 else ''}" name_mapping[replace_person_name] = f"用户{current_user}{user_count if user_count > 1 else ''}"
current_user = chr(ord(current_user) + 1) current_user = chr(ord(current_user) + 1)
readable_messages = self.build_focus_readable_messages(messages=user_messages, target_person_id=person_id) readable_messages = build_readable_messages(
messages=user_messages, replace_bot_name=True, timestamp_mode="normal_no_YMD", truncate=True
)
if not readable_messages: if not readable_messages:
return return
@@ -385,7 +317,52 @@ class RelationshipManager:
# 如果points超过10条按权重随机选择多余的条目移动到forgotten_points # 如果points超过10条按权重随机选择多余的条目移动到forgotten_points
if len(current_points) > 10: if len(current_points) > 10:
current_points = await self._update_impression(person_id, current_points, timestamp)
# 更新数据库
await person_info_manager.update_one_field(
person_id, "points", json.dumps(current_points, ensure_ascii=False, indent=None)
)
await person_info_manager.update_one_field(person_id, "know_times", know_times + 1)
know_since = await person_info_manager.get_value(person_id, "know_since") or 0
if know_since == 0:
await person_info_manager.update_one_field(person_id, "know_since", timestamp)
await person_info_manager.update_one_field(person_id, "last_know", timestamp)
logger.debug(f"{person_name} 的印象更新完成")
async def _update_impression(self, person_id, current_points, timestamp):
# 获取现有forgotten_points # 获取现有forgotten_points
person_info_manager = get_person_info_manager()
person_name = await person_info_manager.get_value(person_id, "person_name")
nickname = await person_info_manager.get_value(person_id, "nickname")
know_times = await person_info_manager.get_value(person_id, "know_times") or 0
attitude = await person_info_manager.get_value(person_id, "attitude") or 50
# 根据熟悉度,调整印象和简短印象的最大长度
if know_times > 300:
max_impression_length = 2000
max_short_impression_length = 800
elif know_times > 100:
max_impression_length = 1000
max_short_impression_length = 500
elif know_times > 50:
max_impression_length = 500
max_short_impression_length = 300
elif know_times > 10:
max_impression_length = 200
max_short_impression_length = 100
else:
max_impression_length = 100
max_short_impression_length = 50
# 根据好感度,调整印象和简短印象的最大长度
attitude_multiplier = (abs(100 - attitude) / 100) + 1
max_impression_length = max_impression_length * attitude_multiplier
max_short_impression_length = max_short_impression_length * attitude_multiplier
forgotten_points = await person_info_manager.get_value(person_id, "forgotten_points") or [] forgotten_points = await person_info_manager.get_value(person_id, "forgotten_points") or []
if isinstance(forgotten_points, str): if isinstance(forgotten_points, str):
try: try:
@@ -436,7 +413,7 @@ class RelationshipManager:
current_points = remaining_points current_points = remaining_points
forgotten_points.extend(points_to_move) forgotten_points.extend(points_to_move)
# 检查forgotten_points是否达到5 # 检查forgotten_points是否达到10
if len(forgotten_points) >= 10: if len(forgotten_points) >= 10:
# 构建压缩总结提示词 # 构建压缩总结提示词
alias_str = ", ".join(global_config.bot.alias_names) alias_str = ", ".join(global_config.bot.alias_names)
@@ -466,7 +443,7 @@ class RelationshipManager:
你记得ta最近做的事 你记得ta最近做的事
{points_text} {points_text}
请输出一段平文本,以陈诉自白的语气,输出你对{person_name}的了解,不要输出任何其他内容。 请输出一段{max_impression_length}字左右的平文本,以陈诉自白的语气,输出你对{person_name}的了解,不要输出任何其他内容。
""" """
# 调用LLM生成压缩总结 # 调用LLM生成压缩总结
compressed_summary, _ = await self.relationship_llm.generate_response_async(prompt=compress_prompt) compressed_summary, _ = await self.relationship_llm.generate_response_async(prompt=compress_prompt)
@@ -487,7 +464,7 @@ class RelationshipManager:
1.对{person_name}的直观印象 1.对{person_name}的直观印象
2.{global_config.bot.nickname}{person_name}的关系 2.{global_config.bot.nickname}{person_name}的关系
3.{person_name}的关键信息 3.{person_name}的关键信息
请输出一段平文本,以陈诉自白的语气,输出你对{person_name}的概括,不要输出任何其他内容。 请输出一段{max_short_impression_length}字左右的平文本,以陈诉自白的语气,输出你对{person_name}的概括,不要输出任何其他内容。
""" """
compressed_short_summary, _ = await self.relationship_llm.generate_response_async( compressed_short_summary, _ = await self.relationship_llm.generate_response_async(
prompt=compress_short_prompt prompt=compress_short_prompt
@@ -503,15 +480,9 @@ class RelationshipManager:
你最近对{person_name}的了解如下: 你最近对{person_name}的了解如下:
{points_text} {points_text}
请根据以上信息,评估你和{person_name}的关系,给出两个维度的值:熟悉度和好感度。 请根据以上信息,评估你和{person_name}的关系,给出你对ta的态度。
1. 了解度 (familiarity_value): 0-100的整数表示这些信息让你对ta的了解增进程度。
- 0: 没有任何进一步了解
- 25: 有点进一步了解
- 50: 有进一步了解
- 75: 有更多了解
- 100: 有了更多重要的了解
2. **好感度 (liking_value)**: 0-100的整数表示这些信息让你对ta的 态度: 0-100的整数表示这些信息让你对ta的态度
- 0: 非常厌恶 - 0: 非常厌恶
- 25: 有点反感 - 25: 有点反感
- 50: 中立/无感(或者文本中无法明显看出) - 50: 中立/无感(或者文本中无法明显看出)
@@ -520,8 +491,7 @@ class RelationshipManager:
请严格按照json格式输出不要有其他多余内容 请严格按照json格式输出不要有其他多余内容
{{ {{
"familiarity_value": <0-100之间的整数>, "attitude": <0-100之间的整数>,
"liking_value": <0-100之间的整数>
}} }}
""" """
try: try:
@@ -531,28 +501,25 @@ class RelationshipManager:
relation_value_json = json.loads(repair_json(relation_value_response)) relation_value_json = json.loads(repair_json(relation_value_response))
# 从LLM获取新生成的值 # 从LLM获取新生成的值
new_familiarity_value = int(relation_value_json.get("familiarity_value", 0)) new_attitude = int(relation_value_json.get("attitude", 50))
new_liking_value = int(relation_value_json.get("liking_value", 50))
# 获取当前的关系值 # 获取当前的关系值
old_familiarity_value = await person_info_manager.get_value(person_id, "familiarity_value") or 0 old_attitude = await person_info_manager.get_value(person_id, "attitude") or 50
liking_value = await person_info_manager.get_value(person_id, "liking_value") or 50
# 更新熟悉度 # 更新熟悉度
if new_familiarity_value > 25: if new_attitude > 25:
familiarity_value = old_familiarity_value + (new_familiarity_value - 25) / 75 attitude = old_attitude + (new_attitude - 25) / 75
else: else:
familiarity_value = old_familiarity_value attitude = old_attitude
# 更新好感度 # 更新好感度
if new_liking_value > 50: if new_attitude > 50:
liking_value += (new_liking_value - 50) / 50 attitude += (new_attitude - 50) / 50
elif new_liking_value < 50: elif new_attitude < 50:
liking_value -= (50 - new_liking_value) / 50 * 1.5 attitude -= (50 - new_attitude) / 50 * 1.5
await person_info_manager.update_one_field(person_id, "familiarity_value", familiarity_value) await person_info_manager.update_one_field(person_id, "attitude", attitude)
await person_info_manager.update_one_field(person_id, "liking_value", liking_value) logger.info(f"更新了与 {person_name} 的态度: {attitude}")
logger.info(f"更新了与 {person_name} 的关系值: 熟悉度={familiarity_value}, 好感度={liking_value}")
except (json.JSONDecodeError, ValueError, TypeError) as e: except (json.JSONDecodeError, ValueError, TypeError) as e:
logger.error(f"解析relation_value JSON失败或值无效: {e}, 响应: {relation_value_response}") logger.error(f"解析relation_value JSON失败或值无效: {e}, 响应: {relation_value_response}")
@@ -566,28 +533,7 @@ class RelationshipManager:
person_id, "forgotten_points", json.dumps(forgotten_points, ensure_ascii=False, indent=None) person_id, "forgotten_points", json.dumps(forgotten_points, ensure_ascii=False, indent=None)
) )
# 更新数据库 return current_points
await person_info_manager.update_one_field(
person_id, "points", json.dumps(current_points, ensure_ascii=False, indent=None)
)
know_times = await person_info_manager.get_value(person_id, "know_times") or 0
await person_info_manager.update_one_field(person_id, "know_times", know_times + 1)
know_since = await person_info_manager.get_value(person_id, "know_since") or 0
if know_since == 0:
await person_info_manager.update_one_field(person_id, "know_since", timestamp)
await person_info_manager.update_one_field(person_id, "last_know", timestamp)
logger.info(f"{person_name} 的印象更新完成")
def build_focus_readable_messages(self, messages: list, target_person_id: str = None) -> str:
"""格式化消息,处理所有消息内容"""
if not messages:
return ""
# 直接处理所有消息,不进行过滤
return build_readable_messages(
messages=messages, replace_bot_name=True, timestamp_mode="normal_no_YMD", truncate=True
)
def calculate_time_weight(self, point_time: str, current_time: str) -> float: def calculate_time_weight(self, point_time: str, current_time: str) -> float:
"""计算基于时间的权重系数""" """计算基于时间的权重系数"""

View File

@@ -11,7 +11,7 @@ from src.plugin_system.apis import emoji_api
from src.plugins.built_in.core_actions.no_reply import NoReplyAction from src.plugins.built_in.core_actions.no_reply import NoReplyAction
logger = get_logger("core_actions") logger = get_logger("emoji")
class EmojiAction(BaseAction): class EmojiAction(BaseAction):
@@ -65,7 +65,7 @@ class EmojiAction(BaseAction):
return False, f"未找到匹配 '{description}' 的表情包" return False, f"未找到匹配 '{description}' 的表情包"
emoji_base64, emoji_description, matched_emotion = emoji_result emoji_base64, emoji_description, matched_emotion = emoji_result
logger.info(f"{self.log_prefix} 找到表情包: {emoji_description}, 匹配情感: {matched_emotion}") logger.info(f"{self.log_prefix} 找到表{matched_emotion}的表情包")
# 使用BaseAction的便捷方法发送表情包 # 使用BaseAction的便捷方法发送表情包
success = await self.send_emoji(emoji_base64) success = await self.send_emoji(emoji_base64)

View File

@@ -103,6 +103,7 @@ class NoReplyAction(BaseAction):
logger.info(f"{self.log_prefix} 选择不回复(第{count}次),开始摸鱼,原因: {reason}") logger.info(f"{self.log_prefix} 选择不回复(第{count}次),开始摸鱼,原因: {reason}")
# 进入等待状态
while True: while True:
current_time = time.time() current_time = time.time()
elapsed_time = current_time - start_time elapsed_time = current_time - start_time
@@ -110,7 +111,9 @@ class NoReplyAction(BaseAction):
if global_config.chat.chat_mode == "auto" and self.is_group: if global_config.chat.chat_mode == "auto" and self.is_group:
# 检查是否超时 # 检查是否超时
if elapsed_time >= self._max_timeout or self._check_no_activity_and_exit_focus(current_time): if elapsed_time >= self._max_timeout or self._check_no_activity_and_exit_focus(current_time):
logger.info(f"{self.log_prefix} 等待时间过久({self._max_timeout}或过去10分钟完全没有发言退出专注模式") logger.info(
f"{self.log_prefix} 等待时间过久({self._max_timeout}或过去10分钟完全没有发言退出专注模式"
)
# 标记退出专注模式 # 标记退出专注模式
self.action_data["_system_command"] = "stop_focus_chat" self.action_data["_system_command"] = "stop_focus_chat"
exit_reason = f"{global_config.bot.nickname}(你)等待了{self._max_timeout}秒,或完全没有说话,感觉群里没有新内容,决定退出专注模式,稍作休息" exit_reason = f"{global_config.bot.nickname}(你)等待了{self._max_timeout}秒,或完全没有说话,感觉群里没有新内容,决定退出专注模式,稍作休息"
@@ -139,19 +142,9 @@ class NoReplyAction(BaseAction):
# 判定条件累计3条消息或等待超过5秒且有新消息 # 判定条件累计3条消息或等待超过5秒且有新消息
time_since_last_judge = current_time - last_judge_time time_since_last_judge = current_time - last_judge_time
should_judge = ( should_judge, trigger_reason = self._should_trigger_judge(new_message_count, time_since_last_judge)
new_message_count >= 3 # 累计3条消息
or (new_message_count > 0 and time_since_last_judge >= 15.0) # 等待超过5秒且有新消息
)
if should_judge and time_since_last_judge >= min_judge_interval: if should_judge and time_since_last_judge >= min_judge_interval:
# 判断触发原因
trigger_reason = ""
if new_message_count >= 3:
trigger_reason = f"累计{new_message_count}条消息"
elif time_since_last_judge >= 10.0:
trigger_reason = f"等待{time_since_last_judge:.1f}秒且有新消息"
logger.info(f"{self.log_prefix} 触发判定({trigger_reason}),进行智能判断...") logger.info(f"{self.log_prefix} 触发判定({trigger_reason}),进行智能判断...")
# 获取最近的消息内容用于判断 # 获取最近的消息内容用于判断
@@ -164,7 +157,10 @@ class NoReplyAction(BaseAction):
if recent_messages: if recent_messages:
# 使用message_api构建可读的消息字符串 # 使用message_api构建可读的消息字符串
messages_text = message_api.build_readable_messages( messages_text = message_api.build_readable_messages(
messages=recent_messages, timestamp_mode="normal_no_YMD", truncate=False, show_actions=False messages=recent_messages,
timestamp_mode="normal_no_YMD",
truncate=False,
show_actions=False,
) )
# 获取身份信息 # 获取身份信息
@@ -187,81 +183,13 @@ class NoReplyAction(BaseAction):
history_block += "\n" history_block += "\n"
# 检查过去10分钟的发言频率 # 检查过去10分钟的发言频率
frequency_block = "" frequency_block, should_skip_llm_judge = self._get_fatigue_status(current_time)
should_skip_llm_judge = False # 是否跳过LLM判断
try:
# 获取过去10分钟的所有消息
past_10min_time = current_time - 600 # 10分钟前
all_messages_10min = message_api.get_messages_by_time_in_chat(
chat_id=self.chat_id,
start_time=past_10min_time,
end_time=current_time,
)
# 手动过滤bot自己的消息
bot_message_count = 0
if all_messages_10min:
user_id = global_config.bot.qq_account
for message in all_messages_10min:
# 检查消息发送者是否是bot
sender_id = message.get("user_id", "")
if sender_id == user_id:
bot_message_count += 1
talk_frequency_threshold = global_config.chat.get_current_talk_frequency(self.chat_id) * 10
if bot_message_count > talk_frequency_threshold:
over_count = bot_message_count - talk_frequency_threshold
# 根据超过的数量设置不同的提示词和跳过概率
skip_probability = 0
if over_count <= 3:
frequency_block = "你感觉稍微有些累,回复的有点多了。\n"
elif over_count <= 5:
frequency_block = "你今天说话比较多,感觉有点疲惫,想要稍微休息一下。\n"
elif over_count <= 8:
frequency_block = "你发现自己说话太多了,感觉很累,想要安静一会儿,除非有重要的事情否则不想回复。\n"
skip_probability = self._skip_probability
else:
frequency_block = "你感觉非常累,想要安静一会儿。\n"
skip_probability = 1
# 根据配置和概率决定是否跳过LLM判断
if self._skip_judge_when_tired and random.random() < skip_probability:
should_skip_llm_judge = True
logger.info(
f"{self.log_prefix} 发言过多(超过{over_count}条)随机决定跳过此次LLM判断(概率{skip_probability * 100:.0f}%)"
)
logger.info(
f"{self.log_prefix} 过去10分钟发言{bot_message_count}条,超过阈值{talk_frequency_threshold},添加疲惫提示"
)
else:
# 回复次数少时的正向提示
under_count = talk_frequency_threshold - bot_message_count
if under_count >= talk_frequency_threshold * 0.8: # 回复很少少于20%
frequency_block = "你感觉精力充沛,状态很好,积极参与聊天。\n"
elif under_count >= talk_frequency_threshold * 0.5: # 回复较少少于50%
frequency_block = "你感觉状态不错。\n"
else: # 刚好达到阈值
frequency_block = ""
logger.info(
f"{self.log_prefix} 过去10分钟发言{bot_message_count}条,未超过阈值{talk_frequency_threshold},添加正向提示"
)
except Exception as e:
logger.warning(f"{self.log_prefix} 检查发言频率时出错: {e}")
frequency_block = ""
# 如果决定跳过LLM判断直接更新时间并继续等待 # 如果决定跳过LLM判断直接更新时间并继续等待
if should_skip_llm_judge: if should_skip_llm_judge:
logger.info(f"{self.log_prefix} 疲劳,继续等待。")
last_judge_time = time.time() # 更新判断时间,避免立即重新判断 last_judge_time = time.time() # 更新判断时间,避免立即重新判断
start_time = current_time # 更新消息检查的起始时间,以避免重复判断
continue # 跳过本次LLM判断继续循环等待 continue # 跳过本次LLM判断继续循环等待
# 构建判断上下文 # 构建判断上下文
@@ -352,17 +280,14 @@ class NoReplyAction(BaseAction):
logger.error(f"{self.log_prefix} 模型判断异常: {e},继续等待") logger.error(f"{self.log_prefix} 模型判断异常: {e},继续等待")
last_judge_time = time.time() # 异常时也更新时间,避免频繁重试 last_judge_time = time.time() # 异常时也更新时间,避免频繁重试
# 每10秒输出一次等待状态 # 每10秒输出一次等待状态
logger.info(f"{self.log_prefix} 开始等待新消息...")
if elapsed_time < 60: if elapsed_time < 60:
if int(elapsed_time) % 10 == 0 and int(elapsed_time) > 0: if int(elapsed_time) % 10 == 0 and int(elapsed_time) > 0:
logger.debug(f"{self.log_prefix} 已等待{elapsed_time:.0f}秒,等待新消息...") logger.debug(f"{self.log_prefix} 已等待{elapsed_time:.0f}秒,等待新消息...")
await asyncio.sleep(1) await asyncio.sleep(1)
else: else:
if int(elapsed_time) % 60 == 0 and int(elapsed_time) > 0: if int(elapsed_time) % 180 == 0 and int(elapsed_time) > 0:
logger.debug(f"{self.log_prefix} 已等待{elapsed_time / 60:.0f}分钟,等待新消息...") logger.info(f"{self.log_prefix} 已等待{elapsed_time / 60:.0f}分钟,等待新消息...")
await asyncio.sleep(1) await asyncio.sleep(1)
# 短暂等待后继续检查 # 短暂等待后继续检查
@@ -380,6 +305,109 @@ class NoReplyAction(BaseAction):
) )
return False, f"不回复动作执行失败: {e}" return False, f"不回复动作执行失败: {e}"
def _should_trigger_judge(self, new_message_count: int, time_since_last_judge: float) -> Tuple[bool, str]:
"""判断是否应该触发智能判断,并返回触发原因。
Args:
new_message_count: 新消息的数量。
time_since_last_judge: 距离上次判断的时间。
Returns:
一个元组 (should_judge, reason)。
- should_judge: 一个布尔值,指示是否应该触发判断。
- reason: 触发判断的原因字符串。
"""
# 判定条件累计3条消息或等待超过15秒且有新消息
should_judge_flag = new_message_count >= 3 or (new_message_count > 0 and time_since_last_judge >= 15.0)
if not should_judge_flag:
return False, ""
# 判断触发原因
if new_message_count >= 3:
return True, f"累计{new_message_count}条消息"
elif new_message_count > 0 and time_since_last_judge >= 15.0:
return True, f"等待{time_since_last_judge:.1f}秒且有新消息"
return False, ""
def _get_fatigue_status(self, current_time: float) -> Tuple[str, bool]:
"""
根据最近的发言频率生成疲劳提示,并决定是否跳过判断。
Args:
current_time: 当前时间戳。
Returns:
一个元组 (frequency_block, should_skip_judge)。
- frequency_block: 疲劳度相关的提示字符串。
- should_skip_judge: 是否应该跳过LLM判断的布尔值。
"""
try:
# 获取过去10分钟的所有消息
past_10min_time = current_time - 600 # 10分钟前
all_messages_10min = message_api.get_messages_by_time_in_chat(
chat_id=self.chat_id,
start_time=past_10min_time,
end_time=current_time,
)
# 手动过滤bot自己的消息
bot_message_count = 0
if all_messages_10min:
user_id = global_config.bot.qq_account
for message in all_messages_10min:
sender_id = message.get("user_id", "")
if sender_id == user_id:
bot_message_count += 1
talk_frequency_threshold = global_config.chat.get_current_talk_frequency(self.chat_id) * 10
if bot_message_count > talk_frequency_threshold:
over_count = bot_message_count - talk_frequency_threshold
skip_probability = 0
frequency_block = ""
if over_count <= 3:
frequency_block = "你感觉稍微有些累,回复的有点多了。\n"
elif over_count <= 5:
frequency_block = "你今天说话比较多,感觉有点疲惫,想要稍微休息一下。\n"
elif over_count <= 8:
frequency_block = "你发现自己说话太多了,感觉很累,想要安静一会儿,除非有重要的事情否则不想回复。\n"
skip_probability = self._skip_probability
else:
frequency_block = "你感觉非常累,想要安静一会儿。\n"
skip_probability = 1
should_skip_judge = self._skip_judge_when_tired and random.random() < skip_probability
if should_skip_judge:
logger.info(
f"{self.log_prefix} 发言过多(超过{over_count}条)随机决定跳过此次LLM判断(概率{skip_probability * 100:.0f}%)"
)
logger.info(
f"{self.log_prefix} 过去10分钟发言{bot_message_count}条,超过阈值{talk_frequency_threshold},添加疲惫提示"
)
return frequency_block, should_skip_judge
else:
# 回复次数少时的正向提示
under_count = talk_frequency_threshold - bot_message_count
frequency_block = ""
if under_count >= talk_frequency_threshold * 0.8:
frequency_block = "你感觉精力充沛,状态很好,积极参与聊天。\n"
elif under_count >= talk_frequency_threshold * 0.5:
frequency_block = "你感觉状态不错。\n"
logger.info(
f"{self.log_prefix} 过去10分钟发言{bot_message_count}条,未超过阈值{talk_frequency_threshold},添加正向提示"
)
return frequency_block, False
except Exception as e:
logger.warning(f"{self.log_prefix} 检查发言频率时出错: {e}")
return "", False
def _check_no_activity_and_exit_focus(self, current_time: float) -> bool: def _check_no_activity_and_exit_focus(self, current_time: float) -> bool:
"""检查过去10分钟是否完全没有发言决定是否退出专注模式 """检查过去10分钟是否完全没有发言决定是否退出专注模式