Files
SillyTavern_replica/backend/services/chat_summary_service.py

214 lines
6.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
聊天总结服务
负责调用LLM对历史消息进行总结
"""
import json
from typing import List, Dict, Any, Optional
from datetime import datetime
from models.internal import ChatMessage, SummaryConfig
from core.config import settings
class ChatSummaryService:
"""聊天总结服务类"""
@staticmethod
async def summarize_messages(
messages: List[ChatMessage],
start_floor: int,
end_floor: int,
summary_config: SummaryConfig,
api_config: Dict[str, str]
) -> str:
"""
对指定范围的消息进行总结
Args:
messages: 完整的消息列表
start_floor: 总结起始楼层1-based
end_floor: 总结结束楼层1-based
summary_config: 总结配置
api_config: API配置 {api_url, api_key, model}
Returns:
总结文本
"""
# 1. 提取需要总结的消息
messages_to_summarize = ChatSummaryService._extract_messages(
messages, start_floor, end_floor, summary_config.includeUserInput
)
if not messages_to_summarize:
return ""
# 2. 构建总结提示词
prompt = ChatSummaryService._build_summary_prompt(
messages_to_summarize, summary_config
)
# 3. 调用LLM生成总结
summary_text = await ChatSummaryService._call_llm_for_summary(
prompt, api_config, summary_config.maxSummaryLength
)
return summary_text
@staticmethod
def _extract_messages(
messages: List[ChatMessage],
start_floor: int,
end_floor: int,
include_user_input: bool
) -> List[ChatMessage]:
"""
提取需要总结的消息
✅ 根据用户需求后端不筛选全部输入给LLM
Args:
messages: 完整消息列表
start_floor: 起始楼层
end_floor: 结束楼层
include_user_input: 是否包含用户输入(此参数目前不使用,但保留以保持接口兼容)
Returns:
需要总结的消息列表(全部消息,不做筛选)
"""
# 转换为0-based索引
start_idx = start_floor - 1
end_idx = end_floor - 1
# 提取范围内的所有消息(不筛选)
return messages[start_idx:end_idx + 1]
@staticmethod
def _build_summary_prompt(
messages: List[ChatMessage],
summary_config: SummaryConfig
) -> str:
"""
构建总结提示词
Args:
messages: 需要总结的消息列表
summary_config: 总结配置
Returns:
完整的提示词
"""
# 使用用户自定义的总结提示词,或默认提示词
base_prompt = summary_config.summaryPrompt or (
"请总结以下对话内容,保留关键信息和上下文。"
"用简洁的语言概括主要事件、人物状态和重要细节。"
)
# 构建对话内容
conversation_text = "\n\n".join([
f"{'用户' if msg.is_user else msg.name}: {msg.mes}"
for msg in messages
])
# 组合完整提示词
full_prompt = f"""{base_prompt}
对话内容:
{conversation_text}
总结要求:
1. 保持简洁明了
2. 保留关键情节和设定
3. 不超过{summary_config.maxSummaryLength}
4. 使用客观叙述语气
总结:"""
return full_prompt
@staticmethod
async def _call_llm_for_summary(
prompt: str,
api_config: Dict[str, str],
max_length: int
) -> str:
"""
调用LLM生成总结
Args:
prompt: 总结提示词
api_config: API配置
max_length: 最大长度限制
Returns:
总结文本
"""
try:
# 导入LLM客户端
from utils.llm_client import llm_client
# 构建消息
messages = [
{
"role": "system",
"content": "你是一个专业的对话总结助手,擅长提取关键信息并用简洁的语言概括。"
},
{
"role": "user",
"content": prompt
}
]
# 调用LLM
response = await llm_client.chat_completion(
messages=messages,
api_url=api_config.get("api_url", ""),
api_key=api_config.get("api_key", ""),
model=api_config.get("model", "gpt-3.5-turbo"),
temperature=0.3, # 总结需要较低的随机性
max_tokens=max_length,
request_timeout=30
)
# 提取总结文本
if isinstance(response, dict):
summary = response.get("choices", [{}])[0].get("message", {}).get("content", "")
else:
summary = str(response)
# 清理和截断
summary = summary.strip()
if len(summary) > max_length:
summary = summary[:max_length] + "..."
return summary
except Exception as e:
print(f"[ChatSummary] ❌ LLM总结失败: {e}")
# 返回降级总结(基于规则的简单摘要)
return ChatSummaryService._fallback_summary(prompt)
@staticmethod
def _fallback_summary(prompt: str) -> str:
"""
降级总结当LLM调用失败时使用
Args:
prompt: 原始提示词
Returns:
简单的降级总结
"""
# 提取对话中的关键信息
lines = prompt.split("\n")
user_lines = [l for l in lines if l.startswith("用户:")]
ai_lines = [l for l in lines if l.startswith("AI:")]
fallback = f"[自动总结] 对话包含 {len(user_lines)} 条用户消息和 {len(ai_lines)} 条AI回复。"
return fallback
# 全局实例
chat_summary_service = ChatSummaryService()