[LangChain语言模型组件的设计与实现]基于Chat模型的提示词模板
我们知道提示词 是调用语言模型最为核心的输入,提示词的质量直接影响答案的质量,所以我们不论抬高提示词的重要性都不过分。提示词在LangChain中通过PromptValue类型表示,我们一般利用预定义的提示词模板来生成它。提示词模板也以一个Runnable对象的形式存在,由于提示词模板生成的PromptValue正好使模型的输入,所有两者正好组合成一个LCEL链。这一篇将会介绍Chat模型的提示词
我们知道提示词 是调用语言模型最为核心的输入,提示词的质量直接影响答案的质量,所以我们不论抬高提示词的重要性都不过分。提示词在LangChain中通过PromptValue类型表示,我们一般利用预定义的提示词模板来生成它。提示词模板也以一个Runnable对象的形式存在,由于提示词模板生成的PromptValue正好使模型的输入,所有两者正好组合成一个LCEL链。LangChain中的提示词模板设计很多的类型,它们之间的关系基本提下在如下的UML类图中,上篇文章主要介绍基于Completion模型的提示词模板,这一篇将会介绍Chat模型的提示词模板,涉及的类型很多,对应框出来的部分。
1. BaseChatPromptTemplate
BaseChatPromptTemplate作为生成ChatPromptValue的提示词模板类型的基类。由于ChatPromptValue本质上就是对一组消息的封装,所以生成ChatPromptValue本质上是生成它封装的消息列表,所以它将消息列表的生成定义成抽象方法format_messages下放给具体的子类,aformat_messages方法的默认实现会调用此方法。它实现的format_prompt方法和重写的aformat_prompt返回的ChatPromptValue正是通过调用format_messages/aformat_messages方法返回的消息列表创建而成。它实现的format方法和重写aformat方法返回的是字符串则是由生成ChatPromptValue的to_string方法返回的结果。
class BaseChatPromptTemplate(BasePromptTemplate, ABC):
def format(self, **kwargs: Any) -> str:
return self.format_prompt(**kwargs).to_string()
async def aformat(self, **kwargs: Any) -> str:
return (await self.aformat_prompt(**kwargs)).to_string()
def format_prompt(self, **kwargs: Any) -> ChatPromptValue:
messages = self.format_messages(**kwargs)
return ChatPromptValue(messages=messages)
async def aformat_prompt(self, **kwargs: Any) -> ChatPromptValue:
messages = await self.aformat_messages(**kwargs)
return ChatPromptValue(messages=messages)
@abstractmethod
def format_messages(self, **kwargs: Any) -> list[BaseMessage]:
async def aformat_messages(self, **kwargs: Any) -> list[BaseMessage]:
return self.format_messages(**kwargs)
def pretty_repr(
self,
html: bool = False
) -> str:
raise NotImplementedError
def pretty_print(self) -> None:
print(self.pretty_repr(html=is_interactive_env()))
虽然BaseChatPromptTemplate定义了pretty_repr方法,但是默认实现直接抛出NotImplementedError异常,另一个pretty_print方法输出的内容则是pretty_repr方法返回的文本,所以子类如果需要一个可读性的表示,需要重写pretty_repr方法。
2. ChatPromptTemplate
作为一个具体的派生于BaseChatPromptTemplate的提示词模板类型,它必需实现format_messages方法生成构建ChatPromptValue的消息列表。 对于ChatPromptTemplate来说,消息列表是通过messages字段表示的一组MessageLike对象生成的。
MessageLike = BaseMessagePromptTemplate | BaseMessage | BaseChatPromptTemplate
class ChatPromptTemplate(BaseChatPromptTemplate):
messages: Annotated[list[MessageLike], SkipValidation()]
MessageLike是针对BaseMessagePromptTemplate 、 BaseMessage 和 BaseChatPromptTemplate三个类型的联合。与其说MessageLike表示“长得像消息” 的类型,不如说它是能够转换成消息的类型。对于三个联合成员来说,BaseMessage本身就是消息的基类,BaseChatPromptTemplate定义了生成消息的format_messages和aformat_messages方法。我们现在着重介绍BaseMessagePromptTemplate类型,虽然以 “PromptTemplate” 后缀冠名,单它并非继承BasePromptTemplate基类的用于生成PromptValue的提示词模板,格式化 BaseMessagePromptTemplate提供的模板是为了生成消息,这里我们姑且按照字面意义称其为 “消息提示词模板” 。
2.1 消息提示词模板
BaseMessagePromptTemplate不但没有继承BasePromptTemplate,自身也不是具有构建LCEL链的Runnable。这个抽象类旨在利用方法format_messages/aformat_messages格式化模板生成消息列表,其中format_messages被定义成抽象方法。它还定义了抽象属性input_variables返回变量名称列表。
class BaseMessagePromptTemplate(Serializable, ABC):
@abstractmethod
def format_messages(self, **kwargs: Any) -> list[BaseMessage]:
async def aformat_messages(self, **kwargs: Any) -> list[BaseMessage]:
@property
@abstractmethod
def input_variables(self) -> list[str]:
def pretty_repr(
self,
html: bool = False,
) -> str:
raise NotImplementedError
def pretty_print(self) -> None:
print(self.pretty_repr(html=is_interactive_env()))
def __add__(self, other: Any) -> ChatPromptTemplate:
prompt = ChatPromptTemplate(messages=[self])
return prompt.__add__(other)
BaseMessagePromptTemplate同样定义了pretty_repr和pretty_print方法,但是前者的默认实现抛出NotImplementedError,所以需要可读性表示输出的子类需要重写此方法。它还重写了__add__方法,使它和其他兼容类型的对象进行拼接生成一个ChatPromptTemplate对象,这提供很好的编程体验。
2.2 BaseStringMessagePromptTemplate
BaseStringMessagePromptTemplate继承自BaseMessagePromptTemplate,它借助于StringPromptTemplate实现针对消息格式化。它的prompt字段返回这个StringPromptTemplate对象,另一个字段additional_kwargs用于存储预填充的变量。
class BaseStringMessagePromptTemplate(BaseMessagePromptTemplate, ABC):
prompt: StringPromptTemplate
additional_kwargs: dict = Field(default_factory=dict)
@classmethod
def from_template(
cls,
template: str,
template_format: PromptTemplateFormat = "f-string",
partial_variables: dict[str, Any] | None = None,
**kwargs: Any,
) -> Self:
prompt = PromptTemplate.from_template(
template,
template_format=template_format,
partial_variables=partial_variables,
)
return cls(prompt=prompt, **kwargs)
@classmethod
def from_template_file(
cls,
template_file: str | Path,
**kwargs: Any,
) -> Self:
prompt = PromptTemplate.from_file(template_file)
return cls(prompt=prompt, **kwargs)
@abstractmethod
def format(self, **kwargs: Any) -> BaseMessage
async def aformat(self, **kwargs: Any) -> BaseMessage:
return self.format(**kwargs)
def format_messages(self, **kwargs: Any) -> list[BaseMessage]:
return [self.format(**kwargs)]
async def aformat_messages(self, **kwargs: Any) -> list[BaseMessage]:
return [await self.aformat(**kwargs)]
@property
def input_variables(self) -> list[str]:
return self.prompt.input_variables
@override
def pretty_repr(self, html: bool = False) -> str
抽象类BaseStringMessagePromptTemplate并没有直接使用StringPromptTemplate来完成消息的格式化,而是利用format抽象方法将实现下放给具体的子类,aformat方法会默认调用此方法。由于格式化StringPromptTemplate总是生成单一的字符串文本,所以format/aformat方法返回的是单一消息,而不是消息列表。实现的format_messages/aformat_messages方法会调用这两个方法,最终返回包含单一消息的列表。
它实现的另一个input_variables属性直接返回StringPromptTemplate对象的同名属性。pretty_repr也进行相应的重写以返回一个具有可读性的表示。由于它定义了类方法from_template和from_template_file作为工厂方法,所以我们一般调用其子类继承下来的这两个类方法来创建具体的消息提示词模板。
2.3 ChatMessagePromptTemplate
继承自BaseStringMessagePromptTemplate的ChatMessagePromptTemplate用来为Chat模型生成绑定为某一角色的消息,所以它额外定义了表示角色的role字段。它实现的format方法会利用StringPromptTemplate对象得到格式化的文本,并借此生成返回的ChatMessage对象。它的另一个aformat方法也进行了类似的重写。
class ChatMessagePromptTemplate(BaseStringMessagePromptTemplate):
role: str
def format(self, **kwargs: Any) -> BaseMessage:
text = self.prompt.format(**kwargs)
return ChatMessage(
content=text, role=self.role, additional_kwargs=self.additional_kwargs
)
async def aformat(self, **kwargs: Any) -> BaseMessage:
text = await self.prompt.aformat(**kwargs)
return ChatMessage(
content=text, role=self.role, additional_kwargs=self.additional_kwargs
)
2.4 _StringImageMessagePromptTemplate
由于ChatMessagePromptTemplate派生于BaseStringMessagePromptTemplate,它只能生成具有单一文本内容块的消息,_StringImageMessagePromptTemplate对此进行了扩展,不仅可以生成具有多内容块的消息,其内容形式还可以是图片。它的prompt字段不仅可以是一个StringPromptTemplate对象,还可以是一组StringPromptTemplate、ImagePromptTemplate或者DictPromptTemplate(我们会在后面的内容介绍此类型)对象的列表。它的另一个字段_msg_class返回的是format方法格式化生成的消息类型,它最终作为创建消息对象的构造函数。additional_kwargs字段则为创建的消息提供额外的数据成员。
class _StringImageMessagePromptTemplate(BaseMessagePromptTemplate):
prompt: (
StringPromptTemplate
| list[StringPromptTemplate | ImagePromptTemplate | DictPromptTemplate]
)
additional_kwargs: dict = Field(default_factory=dict)
_msg_class: type[BaseMessage]
def format(self, **kwargs: Any) -> BaseMessage:
if isinstance(self.prompt, StringPromptTemplate):
text = self.prompt.format(**kwargs)
return self._msg_class(
content=text, additional_kwargs=self.additional_kwargs
)
content: list = []
for prompt in self.prompt:
inputs = {var: kwargs[var] for var in prompt.input_variables}
if isinstance(prompt, StringPromptTemplate):
formatted_text: str = prompt.format(**inputs)
if formatted_text != "":
content.append({"type": "text", "text": formatted_text})
elif isinstance(prompt, ImagePromptTemplate):
formatted_image: ImageURL = prompt.format(**inputs)
content.append({"type": "image_url", "image_url": formatted_image})
elif isinstance(prompt, DictPromptTemplate):
formatted_dict: dict[str, Any] = prompt.format(**inputs)
content.append(formatted_dict)
return self._msg_class(
content=content, additional_kwargs=self.additional_kwargs
)
实现的format方法会利用prompt字段创建消息的主体内容(content)。针对该字段提供的不同类型,会采用不同的内容构建方式,具体逻辑如下:
- StringPromptTemplate:content为
StringPromptTemplate格式化生成的文本。 - StringPromptTemplate列表:利用每个
StringPromptTemplate生成一个格式化文本(formatted_text),并创建一个 结构为 {“type”: “text”, “text”: formatted_text} 的字典,content为该字典的列表。 - ImagePromptTemplate列表:格式化每个
ImagePromptTemplate对象生成一个ImageURL对象(formatted_image),并创建一个结构为 {“type”: “image_url”, “image_url”: formatted_image} 的字典,content为该字典的列表; - DictPromptTemplate列表:格式化每个DictPromptTemplate生成一个字典,content为该字典的列表。
format方法最终将_msg_class字段返回的消息类型作为构造函数,传入上面创建的content和additional_kwargs字段作为参数来创建消息对象。aformat方法也采用类似的形式进行了重写,实现的format_message方法和重写的aformat_messages方法返回的消息由format和aformat方法格式化生成。重写的input_variables属性返回的变量名称列表来源于prompt字段提供的所有StringPromptTemplate、ImagePromptTemplate或者DictPromptTemplate对象。重写的pretty_repr方法会返回针对当前模板的可读性表示。
class _StringImageMessagePromptTemplate(BaseMessagePromptTemplate):
@property
def input_variables(self) -> list[str]
def format_messages(self, **kwargs: Any) -> list[BaseMessage]
async def aformat_messages(self, **kwargs: Any) -> list[BaseMessage]
async def aformat(self, **kwargs: Any) -> BaseMessage
@override
def pretty_repr(self, html: bool = False) -> str
2.5 三种具体的消息提示词模板
如下所示的HumanMessagePromptTemplate、AIMessagePromptTemplate和SystemMessagePromptTemplate类型都是_StringImageMessagePromptTemplate的子类,它们通过提供对应的消息类型(HumanMessage、AIMessage、SystemMessage)生成对应的消息对象。虽然这三种类型均没有提供构造函数,但是从_StringImageMessagePromptTemplate继承下来的类方法from_template和from_template_file可以作为它们的工厂方法。
class HumanMessagePromptTemplate(_StringImageMessagePromptTemplate):
_msg_class: type[BaseMessage] = HumanMessage
class AIMessagePromptTemplate(_StringImageMessagePromptTemplate):
_msg_class: type[BaseMessage] = AIMessage
class SystemMessagePromptTemplate(_StringImageMessagePromptTemplate):
_msg_class: type[BaseMessage] = SystemMessage
2.6 MessagesPlaceholder
MessagesPlaceholder是LangChain 中一个非常实用的 “插槽” 工具。在构建聊天应用时,我们往往无法预知用户会对话多少轮。普通变量通常只对应一段字符串,而MessagesPlaceholder则是专门为消息列表预留的占位符。它的variable_name字段表示变量名称,optional字段决定格式化时对应的值是否必需提供。如果该该字段设置为True并且在格式化时没有提供对应的值,占位符所在的为止会填充一个空的消息列表。n_messages字段对填充的消息数量作了限制。
class MessagesPlaceholder(BaseMessagePromptTemplate):
variable_name: str
optional: bool = False
n_messages: PositiveInt | None = None
def __init__(
self, variable_name: str, *, optional: bool = False, **kwargs: Any
) -> None
def format_messages(self, **kwargs: Any) -> list[BaseMessage]:
value = (
kwargs.get(self.variable_name, [])
if self.optional
else kwargs[self.variable_name]
)
if not isinstance(value, list):
msg = (
f"variable {self.variable_name} should be a list of base messages, "
f"got {value} of type {type(value)}"
)
raise ValueError(msg) # noqa: TRY004
value = convert_to_messages(value)
if self.n_messages:
value = value[-self.n_messages :]
return value
@property
def input_variables(self) -> list[str]:
return [self.variable_name] if not self.optional else []
@override
def pretty_repr(self, html: bool = False) -> str
实现的format_messages方法逻辑很简单,它根据variable_name从提供的变量字典中查找对应的值(要求是一个列表),如果找到则将他们转换成消息列表填充占位符。如果没有,占位符就用一个空的列表来替换。由于input_variables属性返回的“必需”的变量名称,所以当optional字段为True是,它返回一个空的列表,否则返回variable_name作为单一元素的列表。
3. DictPromptTemplate
DictPromptTemplate是 LangChain 中一种特殊的模板类,它的核心目标不是生成 “字符串” 或 “消息列表” ,而是生成一个结构化的字典。它继承自RunnableSerializable[dict, dict],意味着作为LCEL链中的一环,其输出和输出都是字典。从template_format的定义可以看出,它支持 “f-string” 和 “mustache” 两种模板格式。我们将模板字符串保存在template字段返回的字典中。针对模板的格式化同时体现在invoke和format/aformat方法上。
class DictPromptTemplate(RunnableSerializable[dict, dict]):
template: dict[str, Any]
template_format: Literal["f-string", "mustache"]
@property
def input_variables(self) -> list[str]
def format(self, **kwargs: Any) -> dict[str, Any]
async def aformat(self, **kwargs: Any) -> dict[str, Any]
@override
def invoke(
self, input: dict, config: RunnableConfig | None = None, **kwargs: Any
) -> dict
def pretty_repr(self, *, html: bool = False) -> str:
raise NotImplementedError
DictPromptTemplate的强大之处在于它支持嵌套和递归。其template字段返回的字典的值类型并未限制为字符串,它可以是一个列表或者元组,甚至另一个字典。格式化会根据具体的类型递归地进行下去。如下的演示程序体现了这一特性。
from langchain_core.prompts import DictPromptTemplate
import json
template = "The weather in {location} is {weather}"
templates = {
"single": template,
"list": [template, template],
"dict": {"foo": template, "bar": template},
}
prompt = DictPromptTemplate(template=templates, template_format="f-string")
result = prompt.format(location="New York", weather="sunny")
print(json.dumps(result, indent=2))
# output:
# {
# "single": "The weather in New York is sunny",
# "list": [
# "The weather in New York is sunny",
# "The weather in New York is sunny"
# ],
# "dict": {
# "foo": "The weather in New York is sunny",
# "bar": "The weather in New York is sunny"
# }
# }
4. 从MessageLikeRepresentation到MessageLike的转换
我们再把目光转移到ChatPromptTemplate上,看看它如何格式化模板并最终生成ChatPromptValue对象。我们已经知道一个ChatPromptTemplate的核心成员是通过messages字段表示的MessageLike列表,但是构造函数提供的是一个MessageLikeRepresentation对象的序列,所以它需要将MessageLikeRepresentation对象转换成MessageLike对象。
MessageLike = BaseMessagePromptTemplate | BaseMessage | BaseChatPromptTemplate
MessageLikeRepresentation = (
MessageLike
| tuple[str | type, str | Sequence[dict] | Sequence[object]]
| str
| dict[str, Any]
)
class ChatPromptTemplate(BaseChatPromptTemplate):
messages: Annotated[list[MessageLike], SkipValidation()]
def __init__(
self,
messages: Sequence[MessageLikeRepresentation],
*,
template_format: PromptTemplateFormat = "f-string",
**kwargs: Any,
) -> None
def format_messages(self, **kwargs: Any) -> list[BaseMessage]:
kwargs = self._merge_partial_and_user_variables(**kwargs)
result = []
for message_template in self.messages:
if isinstance(message_template, BaseMessage):
result.extend([message_template])
elif isinstance(
message_template, (BaseMessagePromptTemplate, BaseChatPromptTemplate)
):
message = message_template.format_messages(**kwargs)
result.extend(message)
else:
msg = f"Unexpected input: {message_template}"
raise ValueError(msg)
return result
从MessageLikeRepresentation到MessageLike的转换实现在一个名为_convert_to_message_template的私有函数中,我们接下来将利用它举例说明底层的类型转换规则。联合类型MessageLikeRepresentation在MessageLike基础上额外添加了三种形式的类型成员。如果对象自身就是一个MessageLike对象(BaseMessagePromptTemplate 、BaseMessage 或者 BaseChatPromptTemplate),自然不在需要额外的类型转换。我们现在来聊聊针对其他三种类型的转换规则。如果是单纯的字符串,最终创建的是将其作为模板的HumanMessagePromptTemplate对象,如下的演示程序证实了这一点。
from langchain_core.prompts import HumanMessagePromptTemplate,ChatPromptTemplate
from langchain_core.prompts.chat import _convert_to_message_template
template = "What is the weather like in {location}?"
result = HumanMessagePromptTemplate.from_template(template)
assert result == _convert_to_message_template(template)
如果被转换的是一个dict[str, Any]对象,_convert_to_message_template函数会使用“role”和“content”作为Key从中提取角色和模板,并根据角色创建对应类型的“消息提示词模板”,角色与消息模板类型的映射关系如下。下面的代码演示了在这种情况下针对四种“消息提示词模板”类型的转换。
- HumanMessagePromptTemplate:human或者user
- AIMessagePromptTemplate:ai或者assistant
- SystemMessagePromptTemplate:system
- MessagePlaceholder :placeholder
from langchain_core.prompts import HumanMessagePromptTemplate,AIMessagePromptTemplate
from langchain_core.prompts import SystemMessagePromptTemplate,MessagesPlaceholder
from langchain_core.prompts.chat import _convert_to_message_template
template = "What is the weather like in {location}?"
human = HumanMessagePromptTemplate.from_template(template)
ai= AIMessagePromptTemplate.from_template(template)
system = SystemMessagePromptTemplate.from_template(template)
placeholder = MessagesPlaceholder(variable_name="messages", optional=True)
input = {"role": "human", "content": template}
assert human == _convert_to_message_template(human)
input["role"] = "user"
assert human == _convert_to_message_template(input)
input["role"] = "ai"
assert ai == _convert_to_message_template(input)
input["role"] = "assistant"
assert ai == _convert_to_message_template(input)
input["role"] = "system"
assert system == _convert_to_message_template(input)
input["role"] = "placeholder"
input["content"] = "{messages}"
assert placeholder == _convert_to_message_template(input)
如果待转换对象是一个二元组,两个成员将分别被视为“角色”和“模板”。如果角色部分是字符串或者能够转换成字符的对象,会直接得到字符串形式的角色。如果指定的是一个Pydantic类型,并且定义了数据成员type,该成员的默认值将被作为角色(比如AIMessage、HumanMessage和SystemMessage就是符合这样的规则)。解析出来的角色将按照上面的规则映射并创建具体类型的“消息提示词模板”,如下的演示程序证实了这样的转换规则。
from langchain_core.prompts import HumanMessagePromptTemplate,AIMessagePromptTemplate
from langchain_core.prompts import SystemMessagePromptTemplate,MessagesPlaceholder
from langchain_core.prompts.chat import _convert_to_message_template
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
template = "What is the weather like in {location}?"
human = HumanMessagePromptTemplate.from_template(template)
ai= AIMessagePromptTemplate.from_template(template)
system = SystemMessagePromptTemplate.from_template(template)
placeholder = MessagesPlaceholder(variable_name="messages", optional=True)
assert human == _convert_to_message_template(("human", template))
assert human == _convert_to_message_template(("user", template))
assert human == _convert_to_message_template((HumanMessage, template))
assert ai == _convert_to_message_template(("ai", template))
assert ai == _convert_to_message_template(("assistant", template))
assert ai == _convert_to_message_template((AIMessage, template))
assert system == _convert_to_message_template(("system", template))
assert system == _convert_to_message_template((SystemMessage, template))
ChatPromptTemplate定义了from_template和from_messages两个简单且意图清晰得类方法来创建该对象,它们分别根据模板字符串和MessageLikeRepresentation序列来创建ChatPromptTemplate对象。
class ChatPromptTemplate(BaseChatPromptTemplate):
@classmethod
def from_template(cls, template: str, **kwargs: Any) -> ChatPromptTemplate:
prompt_template = PromptTemplate.from_template(template, **kwargs)
message = HumanMessagePromptTemplate(prompt=prompt_template)
return cls.from_messages([message])
@classmethod
def from_messages(
cls,
messages: Sequence[MessageLikeRepresentation],
template_format: PromptTemplateFormat = "f-string",
) -> ChatPromptTemplate:
return cls(messages, template_format=template_format)
如下的程序演示了传入各种形式的参数调用from_messages创建同一个等效ChatPromptTemplate对象的例子。
from langchain_core.prompts import HumanMessagePromptTemplate,ChatPromptTemplate
from langchain_core.messages import HumanMessage
templateString = "What is the weather like in {location}?"
result = [HumanMessage(content = templateString.format(location="Suzhou"))]
# [BaseMessage]
template = ChatPromptTemplate.from_messages(result)
assert template.format_messages() == result
# [BaseChatPromptTemplate]
template = ChatPromptTemplate.from_messages([template])
assert template.format_messages() == result
# [ChatMessagePromptTemplate]
template = ChatPromptTemplate.from_messages([HumanMessagePromptTemplate.from_template(templateString)])
assert template.format_messages(location="Suzhou") == result
# [dict]
template = ChatPromptTemplate.from_messages([{"role": "user", "content": templateString}])
assert template.format_messages(location="Suzhou") == result
# [tuple]
template = ChatPromptTemplate.from_messages([("user", templateString)])
assert template.format_messages(location="Suzhou") == result
# [str]
template = ChatPromptTemplate.from_messages([templateString])
assert template.format_messages(location="Suzhou") == result
5. 针对消息的格式化
作为BaseChatPromptTemplate的继承类型,ChatPromptTemplate的使命是利用实现的format_messages方法传入指定的变量,格式化模板生成对应的消息列表。具体的实现很简单,如下面的代码片段所示,它会遍历messages字段返回的序列,并在每次迭代中生成一个消息列表。具体来说,如果迭代元素自身是一个BaseMessage对象,它不需要做任何处理。如果是BaseMessagePromptTemplate或者BaseChatPromptTemplate,则直接调用它们的format_messages生成消息列表。所有的消息组成的列表就是该方法的返回值。aformat_messages方法也做了类似的重写。
class ChatPromptTemplate(BaseChatPromptTemplate):
def format_messages(self, **kwargs: Any) -> list[BaseMessage]:
kwargs = self._merge_partial_and_user_variables(**kwargs)
result = []
for message_template in self.messages:
if isinstance(message_template, BaseMessage):
result.extend([message_template])
elif isinstance(
message_template, (BaseMessagePromptTemplate, BaseChatPromptTemplate)
):
message = message_template.format_messages(**kwargs)
result.extend(message)
else:
msg = f"Unexpected input: {message_template}"
raise ValueError(msg) # noqa: TRY004
return result
async def aformat_messages(self, **kwargs: Any) -> list[BaseMessage]:
6. “可加性”的实现
在介绍BaseMessagePromptTemplate的时候,我们提到它重写了__add__方法实现与ChatPromptTemplate对象的“加法”运算,该方法的实现如下所示。可以看出最终调用的是ChatPromptTemplate的__add__方法。
class BaseMessagePromptTemplate(Serializable, ABC):
def __add__(self, other: Any) -> ChatPromptTemplate:
prompt = ChatPromptTemplate(messages=[self])
return prompt.__add__(other)\
ChatPromptTemplate类型采用如下的方式重写了__add__方法,实现与ChatPromptTemplate、BaseMessagePromptTemplate、BaseMessage、BaseChatPromptTemplate、字符串、列表与元组的 “拼接”。从具体实现可以看出,该方法会同时对消息、变量名和预填充变量进行合并。
class ChatPromptTemplate(BaseChatPromptTemplate):
def __add__(self, other: Any) -> ChatPromptTemplate:
partials = {**self.partial_variables}
if hasattr(other, "partial_variables") and other.partial_variables:
partials.update(other.partial_variables)
# Allow for easy combining
if isinstance(other, ChatPromptTemplate):
return ChatPromptTemplate(messages=self.messages + other.messages).partial(
**partials
)
if isinstance(
other, (BaseMessagePromptTemplate, BaseMessage, BaseChatPromptTemplate)):
return ChatPromptTemplate(messages=[*self.messages, other]).partial(
**partials
)
if isinstance(other, (list, tuple)):
other_ = ChatPromptTemplate.from_messages(other)
return ChatPromptTemplate(messages=self.messages + other_.messages).partial(
**partials
)
if isinstance(other, str):
prompt = HumanMessagePromptTemplate.from_template(other)
return ChatPromptTemplate(messages=[*self.messages, prompt]).partial(
**partials
)
msg = f"Unsupported operand type for +: {type(other)}"
raise NotImplementedError(msg)
def partial(self, **kwargs: Any) -> ChatPromptTemplate:
prompt_dict = self.__dict__.copy()
prompt_dict["input_variables"] = list(
set(self.input_variables).difference(kwargs)
)
prompt_dict["partial_variables"] = {**self.partial_variables, **kwargs}
return type(self)(**prompt_dict)
下面的程序演示了ChatPromptTemplate分别与ChatPromptTemplate、BaseMessagePromptTemplate、字典序列、元组序列和字符串进行拼接得到一个新的ChatPromptTemplate对象。
from langchain_core.prompts import HumanMessagePromptTemplate,ChatPromptTemplate
from langchain_core.prompts import SystemMessagePromptTemplate
system = SystemMessagePromptTemplate.from_template("You are a helpful assistant.")
human = HumanMessagePromptTemplate.from_template("What is the weather like in {city}?")
prompt = ChatPromptTemplate.from_messages([system])
result = ChatPromptTemplate.from_messages([ system,human,])
assert prompt + human == result
assert prompt + ChatPromptTemplate.from_messages([human]) == result
assert prompt + [{"role": "user", "content": "What is the weather like in {city}?"}] == result
assert prompt + [("user", "What is the weather like in {city}?")] == result
assert prompt + "What is the weather like in {city}?" == result
更多推荐



所有评论(0)