mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-15 17:30:13 +08:00
* refactor(ltm): redesign long-term memory with context compaction - Add raw_records / contexts / summaries data model per group - Add LLM summary compaction strategy alongside truncation - Add turn-based (_split_into_rounds) granularity - Add image caption integration into LTM history - Add tool_call / tool_result persistence into raw_records - Add active reply support driven by LTM state - Improve summary injection prefix with system note and delimiters - Add info-level logging for summary compaction lifecycle - Clarify default summary prompt with explicit preserve/drop rules - Add context_guard for history overflow protection in agent runner - Add internal agent history compaction in agent_sub_stages - Add comprehensive LTM unit tests and compaction test suites * fix(ltm): handle malformed JSON in tool args and clean up lock on session removal * fix(ltm): guard against duplicate system prompt note injection * fix(ltm): fall back to user message when internal marker parsing fails - Treat lines starting with <T:CALL>, <T:RES, or <BOT/ as regular user messages when their respective parsers return None, instead of silently dropping them. Defensive guard against malformed internal markers. * fix(ltm): release session lock during LLM summary generation * fix(ltm): trim raw_records in handle_message to prevent unbounded growth * perf(ltm): use len(s) instead of len(s.encode()) in trim loop Avoid allocating a new bytes object for every string when calculating buffer size in _trim_raw_records. Character count is sufficient for the approximate memory cap. * feat(ltm): make user segment truncation limits configurable * feat(ltm): pre-fill default LTM summary prompt in config and i18n * refactor(ltm): hardcode internal segment/trim constants * refactor(ltm): unify compaction strategy with main agent runner * feat(ltm): add @mention weight marker for group chat messages * test: fix test failures from LTM compaction unification * chore(dashboard): remove obsolete LTM compaction i18n metadata * chore: shrink codebase * feat(group-chat): implement group chat context management and related functionality --------- Co-authored-by: Tsukumi <112180165+Tsukumi233@users.noreply.github.com> Co-authored-by: zenfun <zenfun510@gmail.com> Co-authored-by: Soulter <905617992@qq.com>
This commit is contained in:
@@ -260,6 +260,33 @@ class SingleToolThenFinalProvider(MockProvider):
|
||||
)
|
||||
|
||||
|
||||
class CapturingToolLoopProvider(MockProvider):
|
||||
def __init__(self, tool_name: str):
|
||||
super().__init__()
|
||||
self.tool_name = tool_name
|
||||
self.received_contexts = []
|
||||
|
||||
async def text_chat(self, **kwargs) -> LLMResponse:
|
||||
self.call_count += 1
|
||||
self.received_contexts.append(list(kwargs.get("contexts") or []))
|
||||
func_tool = kwargs.get("func_tool")
|
||||
if func_tool is None or self.call_count > 1:
|
||||
return LLMResponse(
|
||||
role="assistant",
|
||||
completion_text="最终回复",
|
||||
usage=TokenUsage(input_other=10, output=5),
|
||||
)
|
||||
|
||||
return LLMResponse(
|
||||
role="assistant",
|
||||
completion_text="",
|
||||
tools_call_name=[self.tool_name],
|
||||
tools_call_args=[{"query": "test"}],
|
||||
tools_call_ids=["call_context_refresh"],
|
||||
usage=TokenUsage(input_other=10, output=5),
|
||||
)
|
||||
|
||||
|
||||
class SequentialToolProvider(MockProvider):
|
||||
def __init__(self, tool_sequence: list[str]):
|
||||
super().__init__()
|
||||
@@ -450,6 +477,68 @@ async def test_max_step_limit_functionality(
|
||||
assert last_message.role == "assistant", "最后一条消息应该是assistant的最终回答"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_max_step_final_request_includes_limit_prompt(
|
||||
runner, provider_request, mock_tool_executor, mock_hooks
|
||||
):
|
||||
"""The forced final step must use contexts recomputed after max-step prompt."""
|
||||
provider = CapturingToolLoopProvider("test_tool")
|
||||
|
||||
await runner.reset(
|
||||
provider=provider,
|
||||
request=provider_request,
|
||||
run_context=ContextWrapper(context=None),
|
||||
tool_executor=mock_tool_executor,
|
||||
agent_hooks=mock_hooks,
|
||||
streaming=False,
|
||||
)
|
||||
|
||||
async def snapshot_context_manager(messages, trusted_token_usage=0):
|
||||
return list(messages)
|
||||
|
||||
runner.request_context_manager.process = snapshot_context_manager
|
||||
|
||||
async for _ in runner.step_until_done(1):
|
||||
pass
|
||||
|
||||
assert provider.call_count == 2
|
||||
final_contexts = provider.received_contexts[-1]
|
||||
assert final_contexts[-1].role == "user"
|
||||
assert final_contexts[-1].content == runner.MAX_STEPS_REACHED_PROMPT
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_tool_loop_next_request_includes_tool_result(
|
||||
runner, provider_request, mock_tool_executor, mock_hooks
|
||||
):
|
||||
"""Tool-loop provider contexts must be recomputed after tool results append."""
|
||||
provider = CapturingToolLoopProvider("test_tool")
|
||||
|
||||
await runner.reset(
|
||||
provider=provider,
|
||||
request=provider_request,
|
||||
run_context=ContextWrapper(context=None),
|
||||
tool_executor=mock_tool_executor,
|
||||
agent_hooks=mock_hooks,
|
||||
streaming=False,
|
||||
)
|
||||
|
||||
async def snapshot_context_manager(messages, trusted_token_usage=0):
|
||||
return list(messages)
|
||||
|
||||
runner.request_context_manager.process = snapshot_context_manager
|
||||
|
||||
async for _ in runner.step_until_done(3):
|
||||
pass
|
||||
|
||||
assert provider.call_count == 2
|
||||
second_contexts = provider.received_contexts[1]
|
||||
tool_messages = [msg for msg in second_contexts if msg.role == "tool"]
|
||||
assert len(tool_messages) == 1
|
||||
assert tool_messages[0].tool_call_id == "call_context_refresh"
|
||||
assert "工具执行结果" in tool_messages[0].content
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_normal_completion_without_max_step(
|
||||
runner, mock_provider, provider_request, mock_tool_executor, mock_hooks
|
||||
|
||||
Reference in New Issue
Block a user