diff --git a/ai-service/app/services/mid/__init__.py b/ai-service/app/services/mid/__init__.py index 87976d6..1bd5a11 100644 --- a/ai-service/app/services/mid/__init__.py +++ b/ai-service/app/services/mid/__init__.py @@ -14,6 +14,7 @@ from .metrics_collector import MetricsCollector, SessionMetrics, AggregatedMetri from .tool_registry import ToolRegistry, ToolDefinition, ToolExecutionResult, get_tool_registry, init_tool_registry from .tool_call_recorder import ToolCallRecorder, ToolCallStatistics, get_tool_call_recorder from .memory_adapter import MemoryAdapter, UserMemory +from .memory_summary_generator import MemorySummaryGenerator from .default_kb_tool_runner import DefaultKbToolRunner, KbToolResult, KbToolConfig, get_default_kb_tool_runner from .segment_humanizer import SegmentHumanizer, HumanizeConfig, LengthBucket, get_segment_humanizer from .runtime_observer import RuntimeObserver, RuntimeContext, get_runtime_observer @@ -55,6 +56,7 @@ __all__ = [ "get_tool_call_recorder", "MemoryAdapter", "UserMemory", + "MemorySummaryGenerator", "DefaultKbToolRunner", "KbToolResult", "KbToolConfig", diff --git a/ai-service/app/services/mid/memory_summary_generator.py b/ai-service/app/services/mid/memory_summary_generator.py new file mode 100644 index 0000000..2a34c2d --- /dev/null +++ b/ai-service/app/services/mid/memory_summary_generator.py @@ -0,0 +1,58 @@ +""" +Memory summary generator using LLM. +[AC-IDMP-14] Generate rolling summary for memory update. +""" + +from __future__ import annotations + +import logging +from typing import Any + +from app.services.llm.base import LLMConfig +from app.services.llm.factory import get_llm_config_manager +from app.services.mid.memory_summary_prompt import build_memory_summary_prompt + +logger = logging.getLogger(__name__) + + +class MemorySummaryGenerator: + """ + LLM-based memory summary generator. + + Output expected to be a JSON object or structured text. + """ + + def __init__(self, max_tokens: int = 512, temperature: float = 0.2): + self._max_tokens = max_tokens + self._temperature = temperature + + async def __call__( + self, + messages: list[dict[str, Any]], + old_summary: str | None = None, + ) -> str | None: + try: + llm_manager = get_llm_config_manager() + llm_client = llm_manager.get_client() + except Exception as e: + logger.warning(f"[AC-IDMP-14] Failed to get LLM client: {e}") + return None + + prompt = build_memory_summary_prompt(messages, old_summary) + + try: + response = await llm_client.generate( + messages=[ + {"role": "system", "content": "你是一个严格遵循 JSON 格式的摘要器。仅输出 JSON。"}, + {"role": "user", "content": prompt}, + ], + config=LLMConfig( + max_tokens=self._max_tokens, + temperature=self._temperature, + ), + ) + except Exception as e: + logger.warning(f"[AC-IDMP-14] Summary generation failed: {e}") + return None + + return response.content diff --git a/ai-service/app/services/mid/memory_summary_prompt.py b/ai-service/app/services/mid/memory_summary_prompt.py new file mode 100644 index 0000000..6580d8b --- /dev/null +++ b/ai-service/app/services/mid/memory_summary_prompt.py @@ -0,0 +1,46 @@ +""" +Memory summary prompt builder. +[AC-IDMP-14] Rolling summary prompt for memory update. +""" + +from __future__ import annotations + +from typing import Any + +SUMMARY_PROMPT_TEMPLATE = """ +你是一个记忆摘要生成器。你的目标是把对话中稳定、有长期价值的信息归纳为“可用于记忆召回”的摘要。 +要求: +1) 必须保留:稳定事实、用户偏好、未解决问题; +2) 不要写闲聊/情绪; +3) 输出必须为严格 JSON 对象,只允许以下字段:summary, facts, preferences, open_issues; +4) summary 为一段话(150-300字以内);facts/preferences/open_issues 为列表; +5) 如果新内容没有变化,保留旧摘要并可轻微精简; +6) 所有内容必须基于对话,不允许编造。 + +【旧摘要】 +{old_summary} + +【最近对话】 +{recent_messages} +""".strip() + + +def build_recent_messages_text(messages: list[dict[str, Any]]) -> str: + lines: list[str] = [] + for msg in messages: + role = msg.get("role", "unknown") + content = msg.get("content", "") + if not content: + continue + lines.append(f"{role}: {content}") + return "\n".join(lines) + + +def build_memory_summary_prompt( + messages: list[dict[str, Any]], + old_summary: str | None = None, +) -> str: + return SUMMARY_PROMPT_TEMPLATE.format( + old_summary=old_summary or "无", + recent_messages=build_recent_messages_text(messages), + )