优秀的编程知识分享平台

网站首页 > 技术文章 正文

第19章 LangChain源码逐行解密之prompts

nanyue 2024-08-09 07:03:38 技术文章 9 ℃

19.3 loading源码逐行剖析

为什么要关注加载(loading)部分的代码呢?因为它涉及一些初始化操作,你可以将其视为一种实用的封装工具。在代码中的第一行注释已经很明确了,它进行加载提示。当进行加载操作时,需要一个基于模板的单元,这些代码都是基础的Python操作。大家可能会关注到示例(examples)部分,因为涉及到FewShotPromptTemplate,对于一些同学来说,甚至包括一些做过LangChain开发的同学,对FewShotPromptTemplate可能不太熟悉,但它确实非常有用。特别是我们之前从原理的角度讨论过一个问题,对于大型模型来说,如果你只给它一些少量的示例,例如1到3个示例,它的性能就会显著提升。我们在之前分享的一篇论文中,很清楚地提到了这一点。加载操作可能涉及本地或远程文件的基本操作,其他没有太特别的地方,大家应该不会有太多问题。Gavin大咖微信:NLP_Matrix_Space

loading.py的代码实现:

  1. """加载提示."""
  2. import importlib
  3. import json
  4. import logging
  5. from pathlib import Path
  6. from typing import Union
  7. import yaml
  8. from langchain.output_parsers.regex import RegexParser
  9. from langchain.prompts.few_shot import FewShotPromptTemplate
  10. from langchain.prompts.prompt import PromptTemplate
  11. from langchain.schema import BaseLLMOutputParser, BasePromptTemplate, NoOpOutputParser
  12. from langchain.utilities.loading import try_load_from_hub
  13. URL_BASE = "https://raw.githubusercontent.com/hwchase17/langchain-hub/master/prompts/"
  14. logger = logging.getLogger(__name__)
  15. ...
  16. def _load_examples(config: dict) -> dict:
  17. """必要时加载示例."""
  18. if isinstance(config["examples"], list):
  19. pass
  20. elif isinstance(config["examples"], str):
  21. with open(config["examples"]) as f:
  22. if config["examples"].endswith(".json"):
  23. examples = json.load(f)
  24. elif config["examples"].endswith((".yaml", ".yml")):
  25. examples = yaml.safe_load(f)
  26. else:
  27. raise ValueError(
  28. "Invalid file format. Only json or yaml formats are supported."
  29. )
  30. config["examples"] = examples
  31. else:
  32. raise ValueError("Invalid examples format. Only list or string are supported.")
  33. return config
  34. ...

以上代码中,定义了一个_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的代码实现:

  1. """包含少量快照示例的提示模板."""
  2. from typing import Any, Dict, List, Optional
  3. from pydantic import Extra, root_validator
  4. from langchain.prompts.base import DEFAULT_FORMATTER_MAPPING, StringPromptTemplate
  5. from langchain.prompts.example_selector.base import BaseExampleSelector
  6. from langchain.prompts.prompt import PromptTemplate
  7. class FewShotPromptWithTemplates(StringPromptTemplate):
  8. """包含少量快照示例的提示模板."""
  9. examples: Optional[List[dict]] = None
  10. """格式化为提示的示例。应提供this或example_selector."""
  11. example_selector: Optional[BaseExampleSelector] = None
  12. """ExampleSelector选择要设置为提示格式的示例。应提供这一点或示例。"""
  13. example_prompt: PromptTemplate
  14. """用于格式化单个示例的PromptTemplate."""
  15. suffix: StringPromptTemplate
  16. """放在示例后面的PromptTemplate."""
  17. input_variables: List[str]
  18. """提示模板所需的变量名称列表."""
  19. example_separator: str = "\n\n"
  20. """用于连接前缀、示例和后缀的字符串分隔符"""
  21. prefix: Optional[StringPromptTemplate] = None
  22. """示例前的PromptTemplate."""
  23. template_format: str = "f-string"
  24. """提示模板的格式。选项为:“f-string”、“jinja2”."""
  25. validate_template: bool = True
  26. """是否尝试验证模板."""
  27. @root_validator(pre=True)
  28. def check_examples_and_selector(cls, values: Dict) -> Dict:
  29. """检查是否仅提供了examples/example_selector中的一个."""
  30. examples = values.get("examples", None)
  31. example_selector = values.get("example_selector", None)
  32. if examples and example_selector:
  33. raise ValueError(
  34. "Only one of 'examples' and 'example_selector' should be provided"
  35. )
  36. if examples is None and example_selector is None:
  37. raise ValueError(
  38. "One of 'examples' and 'example_selector' should be provided"
  39. )
  40. return values
  41. @root_validator()
  42. def template_is_valid(cls, values: Dict) -> Dict:
  43. """检查前缀、后缀和输入变量是否一致."""
  44. if values["validate_template"]:
  45. input_variables = values["input_variables"]
  46. expected_input_variables = set(values["suffix"].input_variables)
  47. expected_input_variables |= set(values["partial_variables"])
  48. if values["prefix"] is not None:
  49. expected_input_variables |= set(values["prefix"].input_variables)
  50. missing_vars = expected_input_variables.difference(input_variables)
  51. if missing_vars:
  52. raise ValueError(
  53. f"Got input_variables={input_variables}, but based on "
  54. f"prefix/suffix expected {expected_input_variables}"
  55. )
  56. return values
  57. class Config:
  58. """pydantic对象的配置"""
  59. extra = Extra.forbid
  60. arbitrary_types_allowed = True
  61. def _get_examples(self, **kwargs: Any) -> List[dict]:
  62. if self.examples is not None:
  63. return self.examples
  64. elif self.example_selector is not None:
  65. return self.example_selector.select_examples(kwargs)
  66. else:
  67. raise ValueError
  68. def format(self, **kwargs: Any) -> str:
  69. """使用输入设置提示的格式
  70. 参数:
  71. kwargs: 要传递给提示模板的任何参数。
  72. 返回:
  73. 格式化的字符串
  74. 示例:
  75. .. code-block:: python
  76. prompt.format(variable1="foo")
  77. """
  78. kwargs = self._merge_partial_and_user_variables(**kwargs)
  79. # 获取要使用的示例
  80. examples = self._get_examples(**kwargs)
  81. # 格式化示例
  82. example_strings = [
  83. self.example_prompt.format(**example) for example in examples
  84. ]
  85. # 创建整体前缀。
  86. if self.prefix is None:
  87. prefix = ""
  88. else:
  89. prefix_kwargs = {
  90. k: v for k, v in kwargs.items() if k in self.prefix.input_variables
  91. }
  92. for k in prefix_kwargs.keys():
  93. kwargs.pop(k)
  94. prefix = self.prefix.format(**prefix_kwargs)
  95. # 创建整体后缀
  96. suffix_kwargs = {
  97. k: v for k, v in kwargs.items() if k in self.suffix.input_variables
  98. }
  99. for k in suffix_kwargs.keys():
  100. kwargs.pop(k)
  101. suffix = self.suffix.format(
  102. **suffix_kwargs,
  103. )
  104. pieces = [prefix, *example_strings, suffix]
  105. template = self.example_separator.join([piece for piece in pieces if piece])
  106. # 使用输入变量设置模板的格式
  107. return DEFAULT_FORMATTER_MAPPING[self.template_format](template, **kwargs)
  108. @property
  109. def _prompt_type(self) -> str:
  110. """返回提示类型键"""
  111. return "few_shot_with_templates"
  112. def dict(self, **kwargs: Any) -> Dict:
  113. """返回提示的字典."""
  114. if self.example_selector:
  115. raise ValueError("Saving an example selector is not currently supported")
  116. 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代码实现:

  1. class BaseExampleSelector(ABC):
  2. """用于选择要包含在提示中的示例的接口"""
  3. @abstractmethod
  4. def add_example(self, example: Dict[str, str]) -> Any:
  5. """为密钥添加要存储的新示例."""
  6. @abstractmethod
  7. def select_examples(self, input_variables: Dict[str, str]) -> List[dict]:
  8. """根据输入选择要使用的示例"""
  • format:几乎所有的应用程序中,你都会看见这个format,从控制台的角度来看,它会生成最终的字符串,也就是你要输入到大型语言模型中的提示内容,即所谓的Prompt。format方法使用提供的输入参数设置提示的格式,并返回格式化后的字符串。该方法接受任意数量的关键字参数作为输入,并将这些参数应用于提示模板,生成格式化后的字符串。首先,合并部分变量和用户变量,以确保所有变量都被正确处理;获取要使用的示例列表;格式化示例列表,将示例应用于示例模板,生成示例字符串列表;创建前缀,根据前缀模板和相应的输入变量,格式化前缀字符串;创建后缀,根据后缀模板和相应的输入变量,格式化后缀字符串;组合前缀、示例字符串列表和后缀,生成最终的提示模板。使用输入变量来格式化最终的提示模板,生成最终的格式化字符串。然后,返回格式化后的字符串。以上代码第135行是一个DEFAULT_FORMATTER_MAPPING,它会有两种不同的方式。
  1. DEFAULT_FORMATTER_MAPPING: Dict[str, Callable] = {
  2. "f-string": formatter.format,
  3. "jinja2": jinja2_formatter,
  4. }
  • _prompt_type:返回提示类型键。Gavin大咖微信:NLP_Matrix_Space
  • dict:返回提示的字典表示形式。
最近发表
标签列表