Skip to content

Commit f5e1d09

Browse files
committed
feat: add group message flow context mode
1 parent dceacd5 commit f5e1d09

14 files changed

Lines changed: 1090 additions & 51 deletions

File tree

astrbot/builtin_stars/astrbot/long_term_memory.py

Lines changed: 312 additions & 45 deletions
Large diffs are not rendered by default.

astrbot/builtin_stars/astrbot/main.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,5 +238,7 @@ async def after_message_sent(self, event: AstrMessageEvent) -> None:
238238
clean_session = event.get_extra("_clean_ltm_session", False)
239239
if clean_session:
240240
await self.ltm.remove_session(event)
241+
else:
242+
await self.ltm.record_bot_message(event)
241243
except Exception as e:
242244
logger.error(f"ltm: {e}")

astrbot/core/config/default.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,12 @@
217217
},
218218
"provider_ltm_settings": {
219219
"group_icl_enable": False,
220+
"group_context_mode": "sliding_window",
220221
"group_message_max_cnt": 300,
222+
"group_flow_max_records": 5000,
223+
"group_flow_max_delta_messages": 200,
224+
"group_flow_max_message_chars": 1000,
225+
"group_flow_record_bot_messages": False,
221226
"image_caption": False,
222227
"image_caption_provider_id": "",
223228
"active_reply": {
@@ -2884,9 +2889,25 @@
28842889
"group_icl_enable": {
28852890
"type": "bool",
28862891
},
2892+
"group_context_mode": {
2893+
"type": "string",
2894+
"options": ["sliding_window", "flow"],
2895+
},
28872896
"group_message_max_cnt": {
28882897
"type": "int",
28892898
},
2899+
"group_flow_max_records": {
2900+
"type": "int",
2901+
},
2902+
"group_flow_max_delta_messages": {
2903+
"type": "int",
2904+
},
2905+
"group_flow_max_message_chars": {
2906+
"type": "int",
2907+
},
2908+
"group_flow_record_bot_messages": {
2909+
"type": "bool",
2910+
},
28902911
"image_caption": {
28912912
"type": "bool",
28922913
},
@@ -4100,9 +4121,60 @@
41004121
"description": "启用群聊上下文感知",
41014122
"type": "bool",
41024123
},
4124+
"provider_ltm_settings.group_context_mode": {
4125+
"description": "群聊上下文模式",
4126+
"type": "string",
4127+
"options": ["sliding_window", "flow"],
4128+
"labels": ["滑动窗口", "消息流"],
4129+
"hint": "sliding_window 保持旧的滑动窗口行为;flow 使用持久化群聊消息流和对话游标。",
4130+
"condition": {
4131+
"provider_ltm_settings.group_icl_enable": True,
4132+
},
4133+
},
41034134
"provider_ltm_settings.group_message_max_cnt": {
41044135
"description": "最大消息数量",
41054136
"type": "int",
4137+
"hint": "仅用于 sliding_window 模式。",
4138+
"condition": {
4139+
"provider_ltm_settings.group_icl_enable": True,
4140+
"provider_ltm_settings.group_context_mode": "sliding_window",
4141+
},
4142+
},
4143+
"provider_ltm_settings.group_flow_max_records": {
4144+
"description": "群聊消息流保留数量",
4145+
"type": "int",
4146+
"hint": "仅用于 flow 模式。每个群聊消息流最多保留的历史消息数,0 表示不清理。",
4147+
"condition": {
4148+
"provider_ltm_settings.group_icl_enable": True,
4149+
"provider_ltm_settings.group_context_mode": "flow",
4150+
},
4151+
},
4152+
"provider_ltm_settings.group_flow_max_delta_messages": {
4153+
"description": "单次注入消息数量上限",
4154+
"type": "int",
4155+
"hint": "仅用于 flow 模式。每次只注入游标之后、当前触发消息之前的最近 N 条群聊消息;0 表示不限制。",
4156+
"condition": {
4157+
"provider_ltm_settings.group_icl_enable": True,
4158+
"provider_ltm_settings.group_context_mode": "flow",
4159+
},
4160+
},
4161+
"provider_ltm_settings.group_flow_max_message_chars": {
4162+
"description": "单条消息字符上限",
4163+
"type": "int",
4164+
"hint": "仅用于 flow 模式。每条注入的群聊消息最多保留前 N 个字符;0 表示不限制。",
4165+
"condition": {
4166+
"provider_ltm_settings.group_icl_enable": True,
4167+
"provider_ltm_settings.group_context_mode": "flow",
4168+
},
4169+
},
4170+
"provider_ltm_settings.group_flow_record_bot_messages": {
4171+
"description": "记录普通机器人消息",
4172+
"type": "bool",
4173+
"hint": "仅用于 flow 模式。LLM 本次回复始终不会写入群聊消息流;此项只影响命令或插件产生的普通机器人消息。",
4174+
"condition": {
4175+
"provider_ltm_settings.group_icl_enable": True,
4176+
"provider_ltm_settings.group_context_mode": "flow",
4177+
},
41064178
},
41074179
"provider_ltm_settings.image_caption": {
41084180
"description": "自动理解图片",

astrbot/core/core_lifecycle.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from astrbot.core.conversation_mgr import ConversationManager
2424
from astrbot.core.cron import CronJobManager
2525
from astrbot.core.db import BaseDatabase
26+
from astrbot.core.group_message_flow_mgr import GroupMessageFlowManager
2627
from astrbot.core.knowledge_base.kb_mgr import KnowledgeBaseManager
2728
from astrbot.core.persona_mgr import PersonaManager
2829
from astrbot.core.pipeline.scheduler import PipelineContext, PipelineScheduler
@@ -211,6 +212,9 @@ async def initialize(self) -> None:
211212
# 初始化平台消息历史管理器
212213
self.platform_message_history_manager = PlatformMessageHistoryManager(self.db)
213214

215+
# 初始化群聊消息流管理器
216+
self.group_message_flow_manager = GroupMessageFlowManager(self.db)
217+
214218
# 初始化知识库管理器
215219
self.kb_manager = KnowledgeBaseManager(self.provider_manager)
216220

@@ -233,7 +237,8 @@ async def initialize(self) -> None:
233237
self.astrbot_config_mgr,
234238
self.kb_manager,
235239
self.cron_manager,
236-
self.subagent_orchestrator,
240+
subagent_orchestrator=self.subagent_orchestrator,
241+
group_message_flow_manager=self.group_message_flow_manager,
237242
)
238243

239244
# 初始化插件管理器

astrbot/core/db/__init__.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
CommandConflict,
1616
ConversationV2,
1717
CronJob,
18+
GroupMessageFlowCursor,
19+
GroupMessageFlowRecord,
1820
Persona,
1921
PersonaFolder,
2022
PlatformMessageHistory,
@@ -254,6 +256,69 @@ async def get_platform_message_history_by_id(
254256
"""Get a platform message history record by its ID."""
255257
...
256258

259+
@abc.abstractmethod
260+
async def insert_group_message_flow_record(
261+
self,
262+
platform_id: str,
263+
flow_session_id: str,
264+
content: list,
265+
rendered_text: str,
266+
group_id: str | None = None,
267+
sender_id: str | None = None,
268+
sender_name: str | None = None,
269+
role: str = "user",
270+
) -> GroupMessageFlowRecord:
271+
"""Insert a persisted group message flow record."""
272+
...
273+
274+
@abc.abstractmethod
275+
async def get_group_message_flow_records_after(
276+
self,
277+
flow_session_id: str,
278+
after_id: int,
279+
before_id: int | None = None,
280+
limit: int = 0,
281+
) -> list[GroupMessageFlowRecord]:
282+
"""Get recent group message flow records after a cursor, ordered oldest first."""
283+
...
284+
285+
@abc.abstractmethod
286+
async def get_latest_group_message_flow_record_id(
287+
self,
288+
flow_session_id: str,
289+
) -> int:
290+
"""Get the latest record ID for a group message flow."""
291+
...
292+
293+
@abc.abstractmethod
294+
async def get_group_message_flow_cursor(
295+
self,
296+
flow_session_id: str,
297+
conversation_id: str,
298+
) -> GroupMessageFlowCursor | None:
299+
"""Get a conversation cursor for a group message flow."""
300+
...
301+
302+
@abc.abstractmethod
303+
async def upsert_group_message_flow_cursor(
304+
self,
305+
platform_id: str,
306+
flow_session_id: str,
307+
conversation_id: str,
308+
last_record_id: int,
309+
) -> GroupMessageFlowCursor:
310+
"""Create or update a conversation cursor for a group message flow."""
311+
...
312+
313+
@abc.abstractmethod
314+
async def prune_group_message_flow_records(
315+
self,
316+
flow_session_id: str,
317+
max_records: int,
318+
) -> None:
319+
"""Keep at most max_records records for a group message flow."""
320+
...
321+
257322
@abc.abstractmethod
258323
async def create_webchat_thread(
259324
self,

astrbot/core/db/po.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,50 @@ class PlatformMessageHistory(TimestampMixin, SQLModel, table=True):
247247
llm_checkpoint_id: str | None = Field(default=None, index=True)
248248

249249

250+
class GroupMessageFlowRecord(TimestampMixin, SQLModel, table=True):
251+
"""Persisted group chat messages for long-context group flow."""
252+
253+
__tablename__: str = "group_message_flow_records"
254+
255+
id: int | None = Field(
256+
primary_key=True,
257+
sa_column_kwargs={"autoincrement": True},
258+
default=None,
259+
)
260+
platform_id: str = Field(nullable=False, index=True)
261+
flow_session_id: str = Field(nullable=False, index=True)
262+
group_id: str | None = Field(default=None, index=True)
263+
sender_id: str | None = Field(default=None, index=True)
264+
sender_name: str | None = Field(default=None)
265+
role: str = Field(default="user", nullable=False, index=True)
266+
content: list = Field(default_factory=list, sa_type=JSON, nullable=False)
267+
rendered_text: str = Field(default="", sa_type=Text, nullable=False)
268+
269+
270+
class GroupMessageFlowCursor(TimestampMixin, SQLModel, table=True):
271+
"""Per-conversation cursor into a group message flow."""
272+
273+
__tablename__: str = "group_message_flow_cursors"
274+
275+
id: int | None = Field(
276+
primary_key=True,
277+
sa_column_kwargs={"autoincrement": True},
278+
default=None,
279+
)
280+
platform_id: str = Field(nullable=False, index=True)
281+
flow_session_id: str = Field(nullable=False, index=True)
282+
conversation_id: str = Field(nullable=False, index=True)
283+
last_record_id: int = Field(default=0, nullable=False)
284+
285+
__table_args__ = (
286+
UniqueConstraint(
287+
"flow_session_id",
288+
"conversation_id",
289+
name="uix_group_message_flow_cursor",
290+
),
291+
)
292+
293+
250294
class WebChatThread(TimestampMixin, SQLModel, table=True):
251295
"""A side thread created from a selected WebChat assistant response."""
252296

0 commit comments

Comments
 (0)