19.3 loading源码逐行剖析
为什么要关注加载(loading)部分的代码呢?因为它涉及一些初始化操作,你可以将其视为一种实用的封装工具。在代码中的第一行注释已经很明确了,它进行加载提示。当进行加载操作时,需要一个基于模板的单元,这些代码都是基础的Python操作。大家可能会关注到示例(examples)部分,因为涉及到FewShotPromptTemplate,对于一些同学来说,甚至包括一些做过LangChain开发的同学,对FewShotPromptTemplate可能不太熟悉,但它确实非常有用。特别是我们之前从原理的角度讨论过一个问题,对于大型模型来说,如果你只给它一些少量的示例,例如1到3个示例,它的性能就会显著提升。我们在之前分享的一篇论文中,很清楚地提到了这一点。加载操作可能涉及本地或远程文件的基本操作,其他没有太特别的地方,大家应该不会有太多问题。Gavin大咖微信:NLP_Matrix_Space
loading.py的代码实现:
- """加载提示."""
- import importlib
- import json
- import logging
- from pathlib import Path
- from typing import Union
- import yaml
- from langchain.output_parsers.regex import RegexParser
- from langchain.prompts.few_shot import FewShotPromptTemplate
- from langchain.prompts.prompt import PromptTemplate
- from langchain.schema import BaseLLMOutputParser, BasePromptTemplate, NoOpOutputParser
- from langchain.utilities.loading import try_load_from_hub
- URL_BASE = "https://raw.githubusercontent.com/hwchase17/langchain-hub/master/prompts/"
- logger = logging.getLogger(__name__)
- ...
- def _load_examples(config: dict) -> dict:
- """必要时加载示例."""
- if isinstance(config["examples"], list):
- pass
- elif isinstance(config["examples"], str):
- with open(config["examples"]) as f:
- if config["examples"].endswith(".json"):
- examples = json.load(f)
- elif config["examples"].endswith((".yaml", ".yml")):
- examples = yaml.safe_load(f)
- else:
- raise ValueError(
- "Invalid file format. Only json or yaml formats are supported."
- )
- config["examples"] = examples
- else:
- raise ValueError("Invalid examples format. Only list or string are supported.")
- return config
- ...
以上代码中,定义了一个_load_examples函数,用于加载示例数据。该函数接收一个配置字典作为参数,并根据配置中的examples字段的类型进行处理。如果examples是一个列表,表示示例已经加载,不需要进行任何操作。如果examples是一个字符串,则根据文件的扩展名来确定文件格式,然后读取文件内容,并将其转换为对应的数据类型。支持的文件格式包括.json和.yaml(或.yml)。如果文件格式无效,则抛出ValueError异常,然后,将加载的示例数据更新到配置字典中,并返回更新后的配置。Gavin大咖微信:NLP_Matrix_Space
19.4 few_shot_with_templates.py源码逐行剖析
接下来,我们看few_shot_with_templates.py的内容。
few_shot_with_templates.py的代码实现:
- """包含少量快照示例的提示模板."""
- from typing import Any, Dict, List, Optional
- from pydantic import Extra, root_validator
- from langchain.prompts.base import DEFAULT_FORMATTER_MAPPING, StringPromptTemplate
- from langchain.prompts.example_selector.base import BaseExampleSelector
- from langchain.prompts.prompt import PromptTemplate
- class FewShotPromptWithTemplates(StringPromptTemplate):
- """包含少量快照示例的提示模板."""
- examples: Optional[List[dict]] = None
- """格式化为提示的示例。应提供this或example_selector."""
- example_selector: Optional[BaseExampleSelector] = None
- """ExampleSelector选择要设置为提示格式的示例。应提供这一点或示例。"""
- example_prompt: PromptTemplate
- """用于格式化单个示例的PromptTemplate."""
- suffix: StringPromptTemplate
- """放在示例后面的PromptTemplate."""
- input_variables: List[str]
- """提示模板所需的变量名称列表."""
- example_separator: str = "\n\n"
- """用于连接前缀、示例和后缀的字符串分隔符"""
- prefix: Optional[StringPromptTemplate] = None
- """示例前的PromptTemplate."""
- template_format: str = "f-string"
- """提示模板的格式。选项为:“f-string”、“jinja2”."""
- validate_template: bool = True
- """是否尝试验证模板."""
- @root_validator(pre=True)
- def check_examples_and_selector(cls, values: Dict) -> Dict:
- """检查是否仅提供了examples/example_selector中的一个."""
- examples = values.get("examples", None)
- example_selector = values.get("example_selector", None)
- if examples and example_selector:
- raise ValueError(
- "Only one of 'examples' and 'example_selector' should be provided"
- )
- if examples is None and example_selector is None:
- raise ValueError(
- "One of 'examples' and 'example_selector' should be provided"
- )
- return values
- @root_validator()
- def template_is_valid(cls, values: Dict) -> Dict:
- """检查前缀、后缀和输入变量是否一致."""
- if values["validate_template"]:
- input_variables = values["input_variables"]
- expected_input_variables = set(values["suffix"].input_variables)
- expected_input_variables |= set(values["partial_variables"])
- if values["prefix"] is not None:
- expected_input_variables |= set(values["prefix"].input_variables)
- missing_vars = expected_input_variables.difference(input_variables)
- if missing_vars:
- raise ValueError(
- f"Got input_variables={input_variables}, but based on "
- f"prefix/suffix expected {expected_input_variables}"
- )
- return values
- class Config:
- """pydantic对象的配置"""
- extra = Extra.forbid
- arbitrary_types_allowed = True
- def _get_examples(self, **kwargs: Any) -> List[dict]:
- if self.examples is not None:
- return self.examples
- elif self.example_selector is not None:
- return self.example_selector.select_examples(kwargs)
- else:
- raise ValueError
- def format(self, **kwargs: Any) -> str:
- """使用输入设置提示的格式
- 参数:
- kwargs: 要传递给提示模板的任何参数。
- 返回:
- 格式化的字符串
- 示例:
- .. code-block:: python
- prompt.format(variable1="foo")
- """
- kwargs = self._merge_partial_and_user_variables(**kwargs)
- # 获取要使用的示例
- examples = self._get_examples(**kwargs)
- # 格式化示例
- example_strings = [
- self.example_prompt.format(**example) for example in examples
- ]
- # 创建整体前缀。
- if self.prefix is None:
- prefix = ""
- else:
- prefix_kwargs = {
- k: v for k, v in kwargs.items() if k in self.prefix.input_variables
- }
- for k in prefix_kwargs.keys():
- kwargs.pop(k)
- prefix = self.prefix.format(**prefix_kwargs)
- # 创建整体后缀
- suffix_kwargs = {
- k: v for k, v in kwargs.items() if k in self.suffix.input_variables
- }
- for k in suffix_kwargs.keys():
- kwargs.pop(k)
- suffix = self.suffix.format(
- **suffix_kwargs,
- )
- pieces = [prefix, *example_strings, suffix]
- template = self.example_separator.join([piece for piece in pieces if piece])
- # 使用输入变量设置模板的格式
- return DEFAULT_FORMATTER_MAPPING[self.template_format](template, **kwargs)
- @property
- def _prompt_type(self) -> str:
- """返回提示类型键"""
- return "few_shot_with_templates"
- def dict(self, **kwargs: Any) -> Dict:
- """返回提示的字典."""
- if self.example_selector:
- raise ValueError("Saving an example selector is not currently supported")
- return super().dict(**kwargs)
以上代码定义了一个FewShotPromptWithTemplates类,它是一个提示模板,用于包含少量快照示例。FewShotPromptWithTemplates类继承自StringPromptTemplate类,该类包含了一些属性和方法来处理提示的格式化和验证。Gavin大咖微信:NLP_Matrix_Space
FewShotPromptWithTemplates类的属性包括:
- examples:一个可选的包含示例的列表。示例格式化为提示。它是一个可选属性,可以是一个空值。它接收一个示例列表(List)作为输入,并将这些示例格式化为提示。
- example_selector:一个可选的示例选择器,用于选择要设置为提示格式的示例。它与具体的示例相关联,在每次与模型进行交互时,我们可以通过具体的Example Selector来选择要设置为提示格式的示例。需要注意的是,examples和example_selector这两者只能选择其中之一来提供。
- example_prompt:用于格式化单个示例的PromptTemplate对象。
- suffix:放在示例后面的PromptTemplate对象。
- input_variables:提示模板所需的变量名称列表。
- example_separator:用于连接前缀、示例和后缀的字符串分隔符。会有一些具体的格式要求。例如,对于一个示例,可以使用回车换行符作为标记来分隔,当然,也可以使用其他标记,在这里,默认统一使用回车换行符。
- prefix:示例前的PromptTemplate对象。
- template_format:提示模板的格式,可以是"f-string"或"jinja2"。
- validate_template:一个布尔值,指示是否尝试验证模板。
FewShotPromptWithTemplates类中定义了一些验证器方法,用于检查提供的示例和示例选择器是否符合要求,以及检查前缀、后缀和输入变量是否一致。类中的方法包括:
- check_examples_and_selector:相当于一个工具方法。用于验证输入的字典中是否仅提供了examples或example_selector其中一个的键。如果两者都提供了或两者都没有提供,则会引发ValueError。如果验证通过,该方法将返回输入的字典values。
- template_is_valid:用于验证输入的提示模板中的前缀、后缀和输入变量是否一致,这样可以避免在格式化提示时出现错误或不完整的情况。该方法接收一个values的字典作为输入,并从中获取各种属性的值,然后进行以下验证:获取输入变量的列表input_variables。通过组合suffix的输入变量、partial_variables变量,如果prefix存在,则预期的输入变量集合还包括prefix的输入变量。然后,计算缺失的变量集合missing_vars,即预期的输入变量集合与实际输入变量集合之间的差异。如果存在缺失的变量,抛出ValueError,提示实际输入变量与预期输入变量不一致。如果验证通过,该方法将返回输入的字典values。
- _get_examples:根据配置获取要使用的示例。它前面有下划线,是一个内部方法,用于获取示例列表。根据给定的参数,它有三种可能的行为:如果examples属性不为None,则直接返回self.examples;如果example_selector属性不为None,则通过调用example_selector.select_examples(kwargs)来选择示例,并返回选择的示例列表;如果既没有examples也没有example_selector,则抛出ValueError。上述代码的第84行,其中example_selector是一个BaseExampleSelector类实例。BaseExampleSelector是一个抽象基类,用于选择要包含在提示中的示例。该类包含了两个抽象方法:add_example方法用于向选择器中添加新示例的方法;select_examples方法根据输入选择要在提示中使用的示例的方法。子类需要实现这些方法。
example_selector的base.py的BaseExampleSelector代码实现:
- class BaseExampleSelector(ABC):
- """用于选择要包含在提示中的示例的接口"""
- @abstractmethod
- def add_example(self, example: Dict[str, str]) -> Any:
- """为密钥添加要存储的新示例."""
- @abstractmethod
- def select_examples(self, input_variables: Dict[str, str]) -> List[dict]:
- """根据输入选择要使用的示例"""
- format:几乎所有的应用程序中,你都会看见这个format,从控制台的角度来看,它会生成最终的字符串,也就是你要输入到大型语言模型中的提示内容,即所谓的Prompt。format方法使用提供的输入参数设置提示的格式,并返回格式化后的字符串。该方法接受任意数量的关键字参数作为输入,并将这些参数应用于提示模板,生成格式化后的字符串。首先,合并部分变量和用户变量,以确保所有变量都被正确处理;获取要使用的示例列表;格式化示例列表,将示例应用于示例模板,生成示例字符串列表;创建前缀,根据前缀模板和相应的输入变量,格式化前缀字符串;创建后缀,根据后缀模板和相应的输入变量,格式化后缀字符串;组合前缀、示例字符串列表和后缀,生成最终的提示模板。使用输入变量来格式化最终的提示模板,生成最终的格式化字符串。然后,返回格式化后的字符串。以上代码第135行是一个DEFAULT_FORMATTER_MAPPING,它会有两种不同的方式。
- DEFAULT_FORMATTER_MAPPING: Dict[str, Callable] = {
- "f-string": formatter.format,
- "jinja2": jinja2_formatter,
- }
- _prompt_type:返回提示类型键。Gavin大咖微信:NLP_Matrix_Space
- dict:返回提示的字典表示形式。