![[LangChain语言模型组件-06]基于Completion模型的提示词模板](http://pic.xiahunao.cn/yaotu/[LangChain语言模型组件-06]基于Completion模型的提示词模板)
我们知道提示词是调用语言模型最为核心的输入提示词的质量直接影响答案的质量所以我们不论怎么抬高提示词的重要性都不过分。提示词在LangChain中通过PromptValue类型表示我们一般利用预定义的提示词模板来生成它。提示词模板也以一个Runnable对象的形式存在由于提示词模板生成的PromptValue正好使模型的输入所有两者正好组合成一个LCEL链。LangChain中的提示词模板设计很多的类型它们之间的关系基本提下在如下的UML类图中本篇文章主要介绍基于Completion模型的提示词模板下一篇将会介绍Chat模型的提示词模板。1. BasePromptTemplate抽象类BasePromptTemplate继承自RunnableSerializable。作为输入的字典用于提供模板渲染时替换占位符的变量值而输出就是表示提示词的PromptValue对象。BasePromptTemplate通过定义抽象方法format_prompt将格式化实现下放给它的子类重写的invoke/ainvoke方法会调用此方法。BasePromptTemplate是一个泛型类型作为泛型参数的FormatOutputType是抽象方法format格式化生成的目标类型。FormatOutputTypeTypeVar(FormatOutputType)classBasePromptTemplate(RunnableSerializable[dict,PromptValue],ABC,Generic[FormatOutputType])overridedefinvoke(self,input:dict,config:RunnableConfig|NoneNone,**kwargs:Any)-PromptValueoverrideasyncdefainvoke(self,input:dict,config:RunnableConfig|NoneNone,**kwargs:Any)-PromptValueabstractmethoddefformat_prompt(self,**kwargs:Any)-PromptValueabstractmethoddefformat(self,**kwargs:Any)-FormatOutputTypeasyncdefaformat(self,**kwargs:Any)-FormatOutputType我们接下来看看BasePromptTemplate定义了哪些字段/属性成员。提示模板中利用占位符定义了一系列变量有的变量在格式化时必需提供对应的值input_variables字段返回它们的名称列表。有的变量在格式化时可以忽略比如针对MessagesPlaceholder的变量它们的名称存储在optional_variables字段中。input_types字段定义了变量的类型默认为字符串。除了在格式化的时候提供变量值我们也可以利用partial_variables对变量进行预先填充。metadata/tags提供的元数据和标签会自动附加到跟踪数据中。classBasePromptTemplate(RunnableSerializable[dict,PromptValue],ABC,Generic[FormatOutputType]):input_variables:list[str]optional_variables:list[str]Field(default[])input_types:builtins.dict[str,Any]Field(default_factorydict,excludeTrue)partial_variables:Mapping[str,Any]Field(default_factorydict)metadata:builtins.dict[str,Any]|NoneNonetags:list[str]|NoneNoneoutput_parser:BaseOutputParser|NoneNone字段说明如下input_variables在格式化时必须提供对应值的变量名称列表optional_variables在格式化时可以忽略的变量名称列表input_types变量的类型没有在此定义的变量默认视为字符串partial_variables预先填充的变量值metadata/tags提供的元数据和标签会自动附加到跟踪数据中output_parser解析语言模型输出的BaseOutputParser对象。有人可以会觉得奇怪BasePromptTemplate用于生成模型的输入为何需要定义output_parser这个用于处理模型输出的字段。在LangChain的设计哲学中提示词模板是输入数据与预期结构之间的桥梁。在BasePromptTemplate中定义output_parser主要基于以下几个深层次原因实现端到端的类型安全将OutputParser包含在提示词模板中能够使整个LCEL链实现闭环的类型检查。如果没有这个成员提示词模板只能声明它产生的字符串格式而无法声明我期望LLM返回什么样的数据结构自动化指令注入许多OutputParser拥有自动生成格式化指令的能力。当我们将它绑定到提示词模板时可以调用它的get_format_instructions方法。这会生成类似请返回JSON格式必须包含 name 和 age 字段...的文本。通过将两者定义在一起可以自动把解析器要求的咒语塞进生成提示词中支持with_types和自动文档在LangServe等部署工具中系统需要知道API的返回值。携带OutputParser能推断出该链条的输出类型逻辑一致性提示词模板是输出的源头OutputParser的解析逻辑通常是高度依赖提示词内容的。如果在提示词里要求模型返回CSV我们必须配一个CSVOutputParser。将它们绑定在一起符合高内聚的原则当你更换提示词模板时往往也需要更换或调整配套的解析逻辑。总之OutputParser绑定在提示词模板中是为了让后者具备自解释能力。它不仅告诉LLM该做什么还规定了结果应该具有怎样的结构。BasePromptTemplate还定义了如下三个有用的方法:partial用于向partial_variables字段中填充/覆盖预定义变量dict自身连同指定的关键词参数转换成一个字典。它会调用Pydantic的model_dump方法并手动插入_type字段。这是为了确保从文件重新加载时LangChain知道该实例化哪个子类save以JSON或YAML形式将模板配置保存到本地磁盘具体的格式取决于指定的文件扩展名.json、.yaml或者.yml。值得一提的是如果partial_variables字段存储了预定义变量该方法会报错。classBasePromptTemplate(RunnableSerializable[dict,PromptValue],ABC,Generic[FormatOutputType]):defpartial(self,**kwargs:str|Callable[[],str])-BasePromptTemplatedefdict(self,**kwargs:Any)-dictdefsave(self,file_path:Path|str)-None2. StringPromptTemplate我们可以简单地将StringPromptValue和ChatPromptValue视为Completion模型和Chat模型的提示词StringPromptTemplate就是生成StringPromptValue的提示词模板类型的基类它是继承BasePromptTemplate的抽象类。如下面的代码片段所示它定义抽象方法format旨在生成格式化的字符串。重写的format_prompt/aformat_prompt方法会调用此方法并利用生成的格式化文本创建返回的StringPromptValue对象。StringPromptTemplate还定义了pretty_repr和pretty_print方法以可读性的形式输出模板内容。classStringPromptTemplate(BasePromptTemplate,ABC):defformat_prompt(self,**kwargs:Any)-PromptValue:returnStringPromptValue(textself.format(**kwargs))asyncdefaformat_prompt(self,**kwargs:Any)-PromptValue:returnStringPromptValue(textawaitself.aformat(**kwargs))overrideabstractmethoddefformat(self,**kwargs:Any)-strdefpretty_repr(self,html:boolFalse,)-strdefpretty_print(self)-None3. PromptTemplatePromptTemplate继承自StringPromptTemplate。template_format字段表示的模板格式同时决定了format方法采用的模板引擎template字段表示模板字符串应该根据它来定义。布尔类型的validate_template字段决定了在实例化时需不需要检查模板中的变量是否与input_variables匹配。在大部分情况下我们都是调用类方法from_template根据指定的模板、模板格式和预填充变量来创建PromptTemplate对象。如果模板存储在文本文件中我们还可以调用另一个类方法from_file。PromptTemplateFormatLiteral[f-string,mustache,jinja2]classPromptTemplate(StringPromptTemplate):template:strtemplate_format:PromptTemplateFormatf-stringvalidate_template:boolFalsedefformat(self,**kwargs:Any)-strclassmethoddeffrom_file(cls,template_file:str|Path,encoding:str|NoneNone,**kwargs:Any,)-PromptTemplateclassmethoddeffrom_template(cls,template:str,*,template_format:PromptTemplateFormatf-string,partial_variables:dict[str,Any]|NoneNone,**kwargs:Any,)-PromptTemplatetemplate_format字段表示的木模板格式具有三种选项分别对应所用的三种模板引擎。默认值选项f-string模拟了Python原生的f-string语法使用单花括号{variable}作为占位符。这是最常用、直观的格式它简单且高效适合绝大多数静态结构的提示词如下是一个使用此模板格式的例子。fromlangchain_core.promptsimportPromptTemplatefromlangchain_core.prompt_valuesimportStringPromptValue promptPromptTemplate.from_template(template请用{adjective}的语气写一段关于{topic}的介绍。,template_formatf-string,partial_variables{adjective:幽默})assertprompt.format(topic量子力学)请用幽默的语气写一段关于量子力学的介绍。promptValue:StringPromptValueprompt.invoke(input{topic:量子力学})assertpromptValue.text请用幽默的语气写一段关于量子力学的介绍。Jinja2是Python中最强大的模板引擎。它不仅能填充变量还支持if/else条件判断、for循环和复杂的逻辑运算它适合逻辑复杂的提示词格式化。这种模板格式的变量定义在双层花括号中{{variable}}控制逻辑则定义{% raw %}{% %}{% endraw %}中。使用这种模板格式需要确保执行环境安装了Jinja2如果希望深入了解这种模板引擎可以访问其官方站点。fromlangchain_core.promptsimportPromptTemplate template 你是一个向导。 我们要去的地方是{{ location }}。 {% if weather %}今天的具体天气是{{ weather }}请给出针对性建议。 {% else %}天气情况未知请提醒用户注意查天气。 {% endif %} promptPromptTemplate.from_template(templatetemplate,template_formatjinja2)print(prompt.format(location北京,weather大雨))print(prompt.format(location北京,weather))prompt.pretty_print()在上面这个例子中我们定义了一个包含location和weather两个变量的模板对于是否利用变量weather指定了具体的天气格式化会输出如下所示的不同内容。你是一个向导。 我们要去的地方是北京。 今天的具体天气是大雨请给出针对性建议。你是一个向导。 我们要去的地方是北京。 天气情况未知请提醒用户注意查天气。你是一个向导。 我们要去的地方是{location}。 今天的具体天气是{weather}请给出针对性建议。Mustache是一种无逻辑的模板语法在前端开发中非常流行。它通过简单的标签来处理变量和列表。在跨语言通用性方面表象良好如果你的提示词需要在Python、JavaScript等多端共享Mustache是个好选择。在语法层面Mustache也使用双层花括号定义变量如果相对这种模板引擎做进一步了解可以访问其GitHub站点。fromlangchain_core.promptsimportPromptTemplate template 你好{{name}}你的待办事项有 {{#items}} - {{.}} {{/items}} promptPromptTemplate.from_template(templatetemplate,template_formatmustache)print(prompt.format(name老王,items[开会,写代码,喂猫]))prompt.pretty_print()在上面的演示程序中我们采用Mustache语法定义了一个绑定到列表的模板格式化后会生成如下的输出。你好老王你的待办事项有 - 开会 - 写代码 - 喂猫你好{name}你的待办事项有 - {items}4. ImagePromptTemplate从命名可以看出ImagePromptTemplate代表生成ImagePromptValue的提示词模板。从如下的代码片段可以看出它继承BasePromptTemplate[ImageURL]类型意味着它实现的format方法会生成一个ImageURL对象ImagePromptValue对象正式根据此对象创建而成。classImagePromptTemplate(BasePromptTemplate[ImageURL]):template:dictField(default_factorydict)template_format:PromptTemplateFormatf-stringdefformat_prompt(self,**kwargs:Any)-PromptValue:returnImagePromptValue(image_urlself.format(**kwargs))asyncdefaformat_prompt(self,**kwargs:Any)-PromptValue:returnImagePromptValue(image_urlawaitself.aformat(**kwargs))defformat(self,**kwargs:Any,)-ImageURLasyncdefaformat(self,**kwargs:Any)-ImageURL由于ImageURL具有url和detail两个属性所以它的template字段返回的字典应该包含这两个Keydetail可选对应的Value则是用于格式化生成这两个字段的模板定义模板采用的语法应该与template_format字段相匹配。format会提取对应的模板并采用对应的模板引擎生成url和detail并据此创建返回的ImageURL对象。下面的程序演示了ImagePromptTemplate的使用方式。fromlangchain_core.prompts.imageimportImagePromptTemplate template{url:http://img.resources.com/avatar/{{user_id}}.jpg,detail:{% if vip %}high{% else %}auto{% endif %}}promptImagePromptTemplate(templatetemplate,template_formatjinja2)input{user_id:jayden,vip:True}imageUrlprompt.format(**input)promptValueprompt.invoke(input)assertimageUrlpromptValue.image_url# type: ignoreformessageinpromptValue.to_messages():message.pretty_print()# type: ignore输出 Human Message [{detail: high, url: http://img.resources.com/avatar/jayden.jpg}]