From 483d7e01f2f25421b557635a65a2312584c84217 Mon Sep 17 00:00:00 2001 From: CooperWang0912 Date: Wed, 8 Jul 2026 13:54:31 +0800 Subject: [PATCH 1/5] Added New Word Count Method --- .../core/pipeline/result_decorate/stage.py | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/astrbot/core/pipeline/result_decorate/stage.py b/astrbot/core/pipeline/result_decorate/stage.py index 5956c8b5ef..22160505df 100644 --- a/astrbot/core/pipeline/result_decorate/stage.py +++ b/astrbot/core/pipeline/result_decorate/stage.py @@ -18,6 +18,26 @@ from ..stage import Stage, register_stage, registered_stages +async def _word_cnt(text: str) -> int: + """ + 将不同语言分开计算 + - 中文/日语: 按字数计算 + - 其他语言: 按空格分割计算 + """ + # \u4e00-\u9fff : CJK Unified Ideographs (Chinese Hanzi & Japanese Kanji) + # \u3040-\u309f : Japanese Hiragana + # \u30a0-\u30ff : Japanese Katakana + no_space_pattern = r"[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff]" + + char_count = len(re.findall(no_space_pattern, text)) + + text_remaining = re.sub(no_space_pattern, " ", text) + + spaced_words = len(text_remaining.split()) + + return char_count + spaced_words + + @register_stage class ResultDecorateStage(Stage): async def initialize(self, ctx: PipelineContext) -> None: @@ -213,7 +233,8 @@ async def process( new_chain = [] for comp in result.chain: if isinstance(comp, Plain): - if len(comp.text) > self.words_count_threshold: + word_count = await _word_cnt(comp.text) + if word_count > self.words_count_threshold: # 不分段回复 new_chain.append(comp) continue @@ -296,7 +317,8 @@ async def process( if should_tts and tts_provider: new_chain = [] for comp in result.chain: - if isinstance(comp, Plain) and len(comp.text) > 1: + word_count = await _word_cnt(comp.text) + if isinstance(comp, Plain) and word_count > 1: try: logger.info(f"TTS 请求: {comp.text}") audio_path = await tts_provider.get_audio(comp.text) @@ -356,7 +378,8 @@ async def process( else: break plain_str = "".join(parts) - if plain_str and len(plain_str) > self.t2i_word_threshold: + word_count = await _word_cnt(plain_str) + if plain_str and word_count > self.t2i_word_threshold: render_start = time.time() try: url = await html_renderer.render_t2i( @@ -391,7 +414,7 @@ async def process( word_cnt = 0 for comp in result.chain: if isinstance(comp, Plain): - word_cnt += len(comp.text) + word_cnt += await _word_cnt(comp.text) if word_cnt > self.forward_threshold: node = Node( uin=event.get_self_id(), From efbdf572fd22c5e769af30798c21cd152d041a6b Mon Sep 17 00:00:00 2001 From: CooperWang0912 Date: Wed, 8 Jul 2026 14:13:45 +0800 Subject: [PATCH 2/5] Changed Method Signature and Logic --- astrbot/core/pipeline/result_decorate/stage.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/astrbot/core/pipeline/result_decorate/stage.py b/astrbot/core/pipeline/result_decorate/stage.py index 22160505df..d45deadf91 100644 --- a/astrbot/core/pipeline/result_decorate/stage.py +++ b/astrbot/core/pipeline/result_decorate/stage.py @@ -18,7 +18,7 @@ from ..stage import Stage, register_stage, registered_stages -async def _word_cnt(text: str) -> int: +def _word_cnt(text: str) -> int: """ 将不同语言分开计算 - 中文/日语: 按字数计算 @@ -233,7 +233,7 @@ async def process( new_chain = [] for comp in result.chain: if isinstance(comp, Plain): - word_count = await _word_cnt(comp.text) + word_count = _word_cnt(comp.text) if word_count > self.words_count_threshold: # 不分段回复 new_chain.append(comp) @@ -317,8 +317,7 @@ async def process( if should_tts and tts_provider: new_chain = [] for comp in result.chain: - word_count = await _word_cnt(comp.text) - if isinstance(comp, Plain) and word_count > 1: + if isinstance(comp, Plain) and _word_cnt(comp.text) > 1: try: logger.info(f"TTS 请求: {comp.text}") audio_path = await tts_provider.get_audio(comp.text) @@ -378,7 +377,7 @@ async def process( else: break plain_str = "".join(parts) - word_count = await _word_cnt(plain_str) + word_count = _word_cnt(plain_str) if plain_str and word_count > self.t2i_word_threshold: render_start = time.time() try: @@ -414,7 +413,7 @@ async def process( word_cnt = 0 for comp in result.chain: if isinstance(comp, Plain): - word_cnt += await _word_cnt(comp.text) + word_cnt += _word_cnt(comp.text) if word_cnt > self.forward_threshold: node = Node( uin=event.get_self_id(), From 45d96ebccadf6d7f5a7b0d2da1fb699843c60951 Mon Sep 17 00:00:00 2001 From: CooperWang0912 Date: Fri, 10 Jul 2026 18:12:02 +0800 Subject: [PATCH 3/5] Fixed Word Counter in Interval Calculation --- astrbot/core/pipeline/respond/stage.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/astrbot/core/pipeline/respond/stage.py b/astrbot/core/pipeline/respond/stage.py index 888fb65a4b..bacaa6ee54 100644 --- a/astrbot/core/pipeline/respond/stage.py +++ b/astrbot/core/pipeline/respond/stage.py @@ -1,6 +1,7 @@ import asyncio import math import random +import re from collections.abc import AsyncGenerator import astrbot.core.message.components as Comp @@ -88,12 +89,23 @@ async def initialize(self, ctx: PipelineContext) -> None: logger.info(f"分段回复间隔时间:{self.interval}") async def _word_cnt(self, text: str) -> int: - """分段回复 统计字数""" - if all(ord(c) < 128 for c in text): - word_count = len(text.split()) - else: - word_count = len([c for c in text if c.isalnum()]) - return word_count + """ + 将不同语言分开计算 + - 中文/日语: 按字数计算 + - 其他语言: 按空格分割计算 + """ + # \u4e00-\u9fff : CJK Unified Ideographs (Chinese Hanzi & Japanese Kanji) + # \u3040-\u309f : Japanese Hiragana + # \u30a0-\u30ff : Japanese Katakana + no_space_pattern = r"[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff]" + + char_count = len(re.findall(no_space_pattern, text)) + + text_remaining = re.sub(no_space_pattern, " ", text) + + spaced_words = len(text_remaining.split()) + + return char_count + spaced_words async def _calc_comp_interval(self, comp: BaseMessageComponent) -> float: """分段回复 计算间隔时间""" From f26c3abb8fe858a49ca4d91d72bfbba317ca36aa Mon Sep 17 00:00:00 2001 From: CooperWang0912 Date: Fri, 10 Jul 2026 20:28:56 +0800 Subject: [PATCH 4/5] Create Separate Util File for Method --- astrbot/core/pipeline/respond/stage.py | 19 ++---------- .../core/pipeline/result_decorate/stage.py | 29 ++++--------------- astrbot/core/utils/text_utils.py | 21 ++++++++++++++ 3 files changed, 28 insertions(+), 41 deletions(-) create mode 100644 astrbot/core/utils/text_utils.py diff --git a/astrbot/core/pipeline/respond/stage.py b/astrbot/core/pipeline/respond/stage.py index bacaa6ee54..2bf7dcaf75 100644 --- a/astrbot/core/pipeline/respond/stage.py +++ b/astrbot/core/pipeline/respond/stage.py @@ -11,6 +11,7 @@ from astrbot.core.platform.astr_message_event import AstrMessageEvent from astrbot.core.star.star_handler import EventType from astrbot.core.utils.path_util import path_Mapping +from astrbot.core.utils.text_utils import calculate_word_count from ..context import PipelineContext, call_event_hook from ..stage import Stage, register_stage @@ -89,23 +90,7 @@ async def initialize(self, ctx: PipelineContext) -> None: logger.info(f"分段回复间隔时间:{self.interval}") async def _word_cnt(self, text: str) -> int: - """ - 将不同语言分开计算 - - 中文/日语: 按字数计算 - - 其他语言: 按空格分割计算 - """ - # \u4e00-\u9fff : CJK Unified Ideographs (Chinese Hanzi & Japanese Kanji) - # \u3040-\u309f : Japanese Hiragana - # \u30a0-\u30ff : Japanese Katakana - no_space_pattern = r"[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff]" - - char_count = len(re.findall(no_space_pattern, text)) - - text_remaining = re.sub(no_space_pattern, " ", text) - - spaced_words = len(text_remaining.split()) - - return char_count + spaced_words + return calculate_word_count(text) async def _calc_comp_interval(self, comp: BaseMessageComponent) -> float: """分段回复 计算间隔时间""" diff --git a/astrbot/core/pipeline/result_decorate/stage.py b/astrbot/core/pipeline/result_decorate/stage.py index d45deadf91..527eee84e3 100644 --- a/astrbot/core/pipeline/result_decorate/stage.py +++ b/astrbot/core/pipeline/result_decorate/stage.py @@ -13,31 +13,12 @@ from astrbot.core.star.session_llm_manager import SessionServiceManager from astrbot.core.star.star import star_map from astrbot.core.star.star_handler import EventType, star_handlers_registry +from astrbot.core.utils.text_utils import calculate_word_count from ..context import PipelineContext from ..stage import Stage, register_stage, registered_stages -def _word_cnt(text: str) -> int: - """ - 将不同语言分开计算 - - 中文/日语: 按字数计算 - - 其他语言: 按空格分割计算 - """ - # \u4e00-\u9fff : CJK Unified Ideographs (Chinese Hanzi & Japanese Kanji) - # \u3040-\u309f : Japanese Hiragana - # \u30a0-\u30ff : Japanese Katakana - no_space_pattern = r"[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff]" - - char_count = len(re.findall(no_space_pattern, text)) - - text_remaining = re.sub(no_space_pattern, " ", text) - - spaced_words = len(text_remaining.split()) - - return char_count + spaced_words - - @register_stage class ResultDecorateStage(Stage): async def initialize(self, ctx: PipelineContext) -> None: @@ -233,7 +214,7 @@ async def process( new_chain = [] for comp in result.chain: if isinstance(comp, Plain): - word_count = _word_cnt(comp.text) + word_count = calculate_word_count(comp.text) if word_count > self.words_count_threshold: # 不分段回复 new_chain.append(comp) @@ -317,7 +298,7 @@ async def process( if should_tts and tts_provider: new_chain = [] for comp in result.chain: - if isinstance(comp, Plain) and _word_cnt(comp.text) > 1: + if isinstance(comp, Plain) and calculate_word_count(comp.text) > 1: try: logger.info(f"TTS 请求: {comp.text}") audio_path = await tts_provider.get_audio(comp.text) @@ -377,7 +358,7 @@ async def process( else: break plain_str = "".join(parts) - word_count = _word_cnt(plain_str) + word_count = calculate_word_count(plain_str) if plain_str and word_count > self.t2i_word_threshold: render_start = time.time() try: @@ -413,7 +394,7 @@ async def process( word_cnt = 0 for comp in result.chain: if isinstance(comp, Plain): - word_cnt += _word_cnt(comp.text) + word_cnt += calculate_word_count(comp.text) if word_cnt > self.forward_threshold: node = Node( uin=event.get_self_id(), diff --git a/astrbot/core/utils/text_utils.py b/astrbot/core/utils/text_utils.py new file mode 100644 index 0000000000..8920a59232 --- /dev/null +++ b/astrbot/core/utils/text_utils.py @@ -0,0 +1,21 @@ +import re + + +def calculate_word_count(text: str) -> int: + """ + 将不同语言分开计算 + - 中文/日语: 按字数计算 + - 其他语言: 按空格分割计算 + """ + # \u4e00-\u9fff : CJK Unified Ideographs (Chinese Hanzi & Japanese Kanji) + # \u3040-\u309f : Japanese Hiragana + # \u30a0-\u30ff : Japanese Katakana + no_space_pattern = r"[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff]" + + char_count = len(re.findall(no_space_pattern, text)) + + text_remaining = re.sub(no_space_pattern, " ", text) + + spaced_words = len(text_remaining.split()) + + return char_count + spaced_words From ea61da5af54b9d16b865ec9dd9d77df2bba18a50 Mon Sep 17 00:00:00 2001 From: CooperWang0912 Date: Fri, 10 Jul 2026 20:29:55 +0800 Subject: [PATCH 5/5] Removed Unused Import --- astrbot/core/pipeline/respond/stage.py | 1 - 1 file changed, 1 deletion(-) diff --git a/astrbot/core/pipeline/respond/stage.py b/astrbot/core/pipeline/respond/stage.py index 2bf7dcaf75..adb7dde0cc 100644 --- a/astrbot/core/pipeline/respond/stage.py +++ b/astrbot/core/pipeline/respond/stage.py @@ -1,7 +1,6 @@ import asyncio import math import random -import re from collections.abc import AsyncGenerator import astrbot.core.message.components as Comp