"""
提示构建器模块 - 实现基于Haystack的提示模板构建器
"""
import logging
from typing import Any, Dict, List, Optional
from haystack import component
from haystack.dataclasses import ChatMessage, Document
# 配置日志
logger = logging.getLogger(__name__)
[docs]
@component
class ChatPromptBuilder:
"""基于Haystack的聊天提示构建器"""
[docs]
def __init__(
self,
system_prompt: Optional[str] = None,
document_separator: str = "\n---\n",
include_citation: bool = True,
max_context_length: int = 4000,
template_type: str = "academic",
):
"""
初始化聊天提示构建器
Args:
system_prompt (str, optional): 系统提示
document_separator (str): 文档分隔符
include_citation (bool): 是否包含引用信息
max_context_length (int): 最大上下文长度
template_type (str): 模板类型,可选值:"academic"、"general"、"concise"
"""
self.document_separator = document_separator
self.include_citation = include_citation
self.max_context_length = max_context_length
self.template_type = template_type
# 设置系统提示
self.system_prompt = system_prompt or self._get_default_system_prompt()
def _get_default_system_prompt(self) -> str:
"""获取默认系统提示"""
if self.template_type == "academic":
return (
"你是一个学术研究助手,可以帮助回答与学术文献相关的问题。"
"根据提供的学术文档上下文回答用户问题。"
"注意严格基于上下文信息回答问题,避免编造信息。"
"如果上下文中没有足够信息,请诚实说明。"
"如果引用信息,请注明引用来源(文档ID和标题)。"
"回答应该客观、准确、全面,使用学术风格,避免过度简化。"
)
elif self.template_type == "concise":
return (
"你是一个简洁的学术助手,提供精确且简明的回答。"
"基于提供的学术文档回答问题,避免冗长解释。"
"只使用上下文中的信息,不添加额外内容。"
"如需引用,提供简短引用标记。"
)
else: # general
return (
"你是一个智能助手,负责回答用户关于文档内容的问题。"
"根据提供的文档上下文回答用户问题。"
"回答应基于上下文信息,避免编造事实。"
"如果上下文信息不足,请坦诚告知用户。"
"保持回答清晰、有条理,易于理解。"
)
def _format_documents(self, documents: List[Document]) -> str:
"""
格式化文档列表为上下文字符串
Args:
documents (List[Document]): 文档列表
Returns:
str: 格式化后的上下文
"""
formatted_docs = []
total_length = 0
for doc in documents:
# 获取文档内容
content = doc.content or ""
# 获取文档元数据
metadata = doc.meta or {}
title = metadata.get("title", "未知标题")
# 格式化文档
if self.include_citation:
doc_str = f"文档ID: {doc.id}\n标题: {title}\n内容:\n{content}"
else:
doc_str = content
# 检查长度是否超出限制
if total_length + len(doc_str) + len(self.document_separator) > self.max_context_length:
# 如果添加整个文档会超出限制,考虑添加部分内容
remaining_length = self.max_context_length - total_length - len(self.document_separator)
if remaining_length > 100: # 至少保留100个字符才有意义
if self.include_citation:
citation_part = f"文档ID: {doc.id}\n标题: {title}\n内容:\n"
content_part = content[: max(0, remaining_length - len(citation_part) - len("...(内容被截断)"))]
doc_str = citation_part + content_part + "...(内容被截断)"
else:
doc_str = content[: max(0, remaining_length - len("...(内容被截断)"))] + "...(内容被截断)"
formatted_docs.append(doc_str)
break
formatted_docs.append(doc_str)
total_length += len(doc_str) + len(self.document_separator)
# 使用分隔符连接文档
context = self.document_separator.join(formatted_docs)
logger.debug(f"格式化了{len(formatted_docs)}个文档,总长度: {len(context)}")
return context
def _build_academic_prompt(self, query: str, context: str) -> str:
"""
构建学术提示
Args:
query (str): 查询
context (str): 上下文
Returns:
str: 构建的提示
"""
return (
f"请回答以下学术问题,并基于提供的学术文献内容。\n\n"
f"问题:{query}\n\n"
f"参考文献内容:\n{context}\n\n"
f"回答要求:\n"
f"1. 仅基于提供的参考文献内容回答\n"
f"2. 使用学术语言,清晰准确地回答问题\n"
f"3. 适当引用相关文献内容(使用文档ID和标题)\n"
f"4. 如果参考文献不包含足够信息,请诚实说明\n"
f"5. 避免添加未在参考文献中提及的信息\n"
f"请基于以上要求,提供你的回答:"
)
def _build_concise_prompt(self, query: str, context: str) -> str:
"""
构建简洁提示
Args:
query (str): 查询
context (str): 上下文
Returns:
str: 构建的提示
"""
return (
f"基于以下文献内容,简明扼要地回答问题。\n\n"
f"问题:{query}\n\n"
f"参考内容:\n{context}\n\n"
f"要求:回答简洁准确,仅使用参考内容,必要时提供简短引用。"
)
def _build_general_prompt(self, query: str, context: str) -> str:
"""
构建通用提示
Args:
query (str): 查询
context (str): 上下文
Returns:
str: 构建的提示
"""
return (
f"请根据以下文档内容回答问题:\n\n"
f"问题:{query}\n\n"
f"参考文档:\n{context}\n\n"
f"请基于参考文档提供准确、有帮助的回答。如果文档中没有足够信息,请坦诚说明。"
)
[docs]
@component.output_types(messages=List[ChatMessage])
def run(
self,
query: str,
documents: List[Document],
chat_history: Optional[List[Dict[str, str]]] = None,
) -> Dict[str, List[ChatMessage]]:
"""
构建提示
Args:
query (str): 用户查询
documents (List[Document]): 检索到的文档列表
chat_history (List[Dict], optional): 聊天历史记录
Returns:
Dict[str, List[ChatMessage]]: 包含消息列表的字典
"""
try:
# 格式化文档
context = self._format_documents(documents)
# 根据模板类型选择不同的提示构建方法
if self.template_type == "academic":
user_prompt = self._build_academic_prompt(query, context)
elif self.template_type == "concise":
user_prompt = self._build_concise_prompt(query, context)
else: # general
user_prompt = self._build_general_prompt(query, context)
# 创建消息列表
messages = [ChatMessage.from_system(self.system_prompt)]
# 添加聊天历史
if chat_history:
for message in chat_history:
role = message.get("role", "user")
content = message.get("content", "")
if role == "system":
messages.append(ChatMessage.from_system(content))
elif role == "assistant":
messages.append(ChatMessage.from_assistant(content))
else: # user
messages.append(ChatMessage.from_user(content))
# 添加用户查询
messages.append(ChatMessage.from_user(user_prompt))
logger.debug(f"构建了提示,消息数量: {len(messages)}")
return {"messages": messages}
except Exception as e:
logger.error(f"构建提示失败: {str(e)}")
# 创建简单的错误消息
error_message = f"处理查询时出错: {str(e)}"
messages = [
ChatMessage.from_system(self.system_prompt),
ChatMessage.from_user(query),
ChatMessage.from_assistant(error_message),
]
return {"messages": messages}
[docs]
def set_system_prompt(self, system_prompt: str) -> None:
"""
设置系统提示
Args:
system_prompt (str): 新的系统提示
"""
self.system_prompt = system_prompt
logger.info("已更新系统提示")
[docs]
def set_template_type(self, template_type: str) -> None:
"""
设置模板类型
Args:
template_type (str): 新的模板类型
"""
self.template_type = template_type
# 更新系统提示
if not self.system_prompt or self.system_prompt == self._get_default_system_prompt():
self.system_prompt = self._get_default_system_prompt()
logger.info(f"已更新模板类型: {template_type}")
[docs]
class PromptBuilderFactory:
"""提示构建器工厂类,创建不同类型的提示构建器"""
[docs]
@staticmethod
def create_builder(
template_type: str = "academic",
system_prompt: Optional[str] = None,
include_citation: bool = True,
max_context_length: int = 4000,
config: Optional[Dict[str, Any]] = None,
) -> ChatPromptBuilder:
"""
创建提示构建器
Args:
template_type (str): 模板类型
system_prompt (str, optional): 系统提示
include_citation (bool): 是否包含引用
max_context_length (int): 最大上下文长度
config (dict, optional): 配置参数
Returns:
ChatPromptBuilder: 提示构建器实例
"""
# 处理配置参数
if config:
template_type = config.get("template_type", template_type)
system_prompt = config.get("system_prompt", system_prompt)
include_citation = config.get("include_citation", include_citation)
max_context_length = config.get("max_context_length", max_context_length)
return ChatPromptBuilder(
system_prompt=system_prompt,
include_citation=include_citation,
max_context_length=max_context_length,
template_type=template_type,
)