貉 🦝
轻松构建随着时间推移成本和速度越来越低的 LLM 驱动应用程序。
加入我们的 Discord
目录
介绍
貉是一种在 Python 中轻松调用 LLM 以取代函数体的方法,具备与手工实现的函数相同的参数和输出。
这些 LLM 驱动的函数是类型良好的、可靠的、无状态的,并且可无缝地集成到应用程序中。与其面对无休止的提示纠缠和意想不到的惊喜,使用这些 LLM 驱动的函数和应用程序的行为像传统函数一样,具有适当的错误处理。
最后,使用貉的函数越多,它们通过自动模型蒸馏变得越便宜和更快(最多 9-10 倍!)。
@tanuki.patch
def some_function(input: TypedInput) -> TypedOutput:
"""(可选)包含函数将如何使用的描述。"""
@tanuki.align
def test_some_function(example_typed_input: TypedInput,
example_typed_output: TypedOutput):
assert some_function(example_typed_input) == example_typed_output
功能
- 简单无缝集成 - 在几秒钟内将 LLM 增强功能添加到任何工作流程中。使用
@tanuki.patch
装饰一个函数存根,添加类型提示和文档字符串来指导执行。就是这样。 - 类型感知 - 确保 LLM 的输出符合函数的类型约束(Python 基本类型,Pydantic 类,Literals,Generics 等)以防止使用 LLM 时出现错误或意外的副作用。
- 对齐输出 - LLM 不可靠,这使得它们很难替代传统编程函数。使用装饰有
@tanuki.align
的函数中的简单assert
语句,可以将补丁函数的行为与预期对齐。 - 降低成本和延迟 - 随着使用量的增加,实现成本最多降低 90%,延迟减少 80%。该包将通过蒸馏处理模型训练、MLOps 和 DataOps 工作,改进 LLM 功能。
- 支持流行模型 - 貉支持广泛的流行模型(OpenAI、Amazon Bedrock、Together AI)来执行函数。
- 支持 RAG - 无缝获取嵌入输出以实现下游 RAG(检索增强生成)实现。然后可以轻松地存储输出嵌入并用于相关文档检索,以降低成本和延迟,提高长篇内容的性能。
- 内置电池 - 除了 OpenAI 外没有远程依赖项。
安装和入门
安装
pip install tanuki.py
或者使用 Poetry
poetry add tanuki.py
设置您的 OpenAI 密钥:
export OPENAI_API_KEY=sk-...
上手
入门:
- 创建一个装饰有
@tanuki.patch
的函数存根,包括类型提示和文档字符串。 - (可选)创建另一个装饰有
@tanuki.align
的函数,包含声明补丁函数的预期行为的常规assert
语句。 - (可选)配置您希望用于该函数的模型。默认情况下使用 GPT-4,但如果您想使用我们支持的其他模型,请在
@tanuki.patch
操作符中配置。您可以在我们的文档中找到有关如何配置 Amazon Bedrock 模型和 Together AI 模型的详细信息。
现在可以像正常一样在您的代码中调用补丁函数。
要添加功能对齐,如果以下情况之一成立,则必须调用带有 align
注释的函数:
- 这是首次调用补丁函数(包括对函数签名的任何更新,即文档字符串、输入参数、输入类型提示、命名或输出类型提示)。
- 您对
assert
语句进行了更改。
以下是一个简单的分类函数示例:
@tanuki.patch
def classify_sentiment(msg: str) -> Optional[Literal['Good', 'Bad']]:
"""将用户的消息分类为 Good、Bad 或 None。"""
@tanuki.align
def align_classify_sentiment():
assert classify_sentiment("I love you") == 'Good'
assert classify_sentiment("I hate you") == 'Bad'
assert not classify_sentiment("People from Phoenix are called Phoenicians")
if __name__ == "__main__":
align_classify_sentiment()
print(classify_sentiment("I like you")) # Good
print(classify_sentiment("Apples might be red")) # None
有关貉函数的配置选项,请参阅 这里
工作原理
在开发过程中调用貉补丁函数时,会以 n-shot 配置调用一个 LLM 来生成类型化响应。
使用的例子数量取决于使用 align
装饰器注释的函数中提供的对齐语句的数量。
响应将进行后处理,并将程序化地实例化提供的输出类型,以确保返回正确的类型。
此响应可以传递到应用程序的其他部分 / 存储在数据库中 / 显示给用户。
确保在运行补丁函数之前至少执行一次所有对齐函数,以确保预期行为已注册。这些将被缓存到磁盘上供将来参考。
函数执行期间将存储函数的输入和输出,作为未来的训练数据。 随着数据量的增加,将使用较大模型的输出进行蒸馏较小的模型。
较小的模型将以较低的计算成本、较低的延迟捕捉所需的行为和性能,并且不需要任何 MLOps 工作。
类型输出
LLM API 输出通常为自然语言。在许多情况下,最好对输出的格式施加约束,以便更好地将它们集成到工作流程中。
貉的核心概念之一是对参数和输出进行类型化。支持补丁函数的类型输出使您能够声明补丁函数允许传递的回传数据的规则,以便在程序的其余部分中使用。这将防止 LLMs 的冗长或不一致的输出,该模型的训练目的是尽可能地“提供帮助”。
您可以使用 Literals 或在 Pydantic 中创建自定义类型来表达补丁函数可以返回的复杂规则。它们充当模型的护栏,防止补丁函数破坏代码或下游工作流程,并且可以避免在应用程序中编写自定义验证逻辑。
@dataclass
class ActionItem:
goal: str = Field(description="需要完成的任务")
deadline: datetime = Field(description="需要完成任务的日期")
@tanuki.patch
def action_items(input: str) -> List[ActionItem]:
"""生成一个行动项目列表"""
@tanuki.align
def align_action_items():
goal = "Can you please get the presentation to me by Tuesday?"
next_tuesday = (datetime.now() + timedelta((1 - datetime.now().weekday() + 7) % 7)).replace(hour=0, minute=0, second=0, microsecond=0)
assert action_items(goal) == ActionItem(goal="Prepare the presentation", deadline=next_tuesday)
通过限制补丁函数可以传递的数据类型,您声明了模型可以返回的潜在输出,并指定了程序存在的世界。
您可以为 Pydantic 字段值的输出添加整数约束,如果您愿意,可以添加泛型。
@tanuki.patch
def score_sentiment(input: str) -> Optional[Annotated[int, Field(gt=0, lt=10)]]:
"""给输入评分,分数在 0-10 之间"""
@tanuki.align
def align_score_sentiment():
"""注册几个例子以对齐您的函数"""
assert score_sentiment("I love you") == 10
assert score_sentiment("I hate you") == 0
assert score_sentiment("You're okay I guess") == 5
# 可以使用 pytest 或 unittest 调用的正常测试
def test_score_sentiment():
"""我们可以使用 Pytest 或 Unittest 正常测试函数"""
score = score_sentiment("I like you")
assert score >= 7
if __name__ == "__main__":
align_score_sentiment()
print(score_sentiment("I like you")) # 7
print(score_sentiment("Apples might be red")) # None
要查看更多使用貉不同用例的例子(包括如何与 FastAPI 集成),请查看 examples.
对于 RAG 支持的嵌入输出,请参阅 这里
测试驱动对齐
在经典的 测试驱动开发 (TDD) 中,标准做法是在编写使测试通过的代码之前编写一个失败的测试。
测试驱动对齐(TDA)采用了这一概念,将其用于对补丁函数的行为进行对齐,并通过测试定义期望。
要对补丁函数的行为进行对齐以符合您的需求,使用 @align
装饰函数并使用“assert”语句断言函数输出,与标准测试一样。
@tanuki.align
def align_classify_sentiment():
assert classify_sentiment("I love this!") == 'Good'
assert classify_sentiment("I hate this.") == 'Bad'
@tanuki.align
def align_score_sentiment():
assert score_sentiment("I like you") == 7
通过编写一个包含 tanuki 补丁函数预期行为的测试,您声明了函数必须履行的合同。这使您能够:
- 验证期望值: 确认函数遵循所需的输出。
- 捕捉行为细微差别: 确保 LLM 尊重您的测试规定的边缘情况和细微差别。
- 迭代开发: 通过将所需行为声明为测试,改进和更新 tanuki 补丁函数的行为。
与传统 TDD 的目标是编写通过测试的代码不同,TDA 翻转了脚本:测试不会失败。它们的存在及其所采用的形式足以让 LLM 与预期行为对齐。
TDA 提供了一种精简但稳健的方法,将机器学习嫁接到现有或新的 Python 代码库中。它结合了 TDD 的预防美德,同时解决了 LLM 动态性带来的特定挑战。
(对齐函数链正在进行中)
def test_score_sentiment():
"""我们可以使用 Pytest 或 Unittest 正常测试函数"""
assert multiply_by_two(score_sentiment("I like you")) == 14
assert 2*score_sentiment("I like you") == 14
扩展和微调
在工作流程中使用貉的一个好处是,随着数据点数量的增加,成本和延迟的降低。
适合作为微调的成功执行的补丁函数将保存在训练数据集上,该数据集将用于蒸馏每个补丁函数的较小模型。模型蒸馏和伪标签是一种经过验证的方法,可以降低模型大小并在延迟和内存占用方面获得改善,同时对性能造成的影响微不足道(https://arxiv.org/pdf/2305.02301.pdf, https://arxiv.org/pdf/2306.13649.pdf, https://arxiv.org/pdf/2311.00430.pdf 等)。
貉库处理训练较小的特定函数的模型并将其部署,因此用户无需额外的 MLOps 或 DataOps 工作即可获得好处。请注意:微调当前仅适用于从 GPT-4(教师)到 GPT-3.5(学生),尚未实现对 AWS Bedrock 和 Together AI 模型的微调
我们使用 SQuAD2、Spider 和 IMDB 影评数据集对 OpenAI 模型进行了貉的模型蒸馏测试。我们使用 GPT-4(教师)的少量示例响应对 GPT-3.5-turbo 模型(学生)进行了微调,我们的初步测试显示,使用不到 600 个数据点的训练数据,我们能够让 GPT-3.5 turbo 的性能基本相当(在保持集上性能差异不到 1.5%)于 GPT-4,同时成本降低至 12 倍,延迟降低至 6 倍以上(成本和延迟的减少取决于任务的特定特征,如输入输出令牌大小和对齐语句令牌大小)。这些测试显示了这种形式的模型蒸馏在智能降本和降低延迟方面的潜力,而不会牺牲性能。
常见问题
简介
用简单的话来说,貉是什么?
貉是一种在 Python 中创建 LLM 增强函数的简单无缝方法,可确保 LLM 的输出遵循特定结构。此外,调用补丁函数的次数越多,执行的成本和速度越低。
这与LangChain等其他框架相比如何?
- Langchain: Tanuki的范围比Langchain更窄。我们的使命是通过微调来确保可预测和一致的LLM执行,并自动减少成本和延迟。
- Magentic / Marvin: Tanuki相比Magentic/Marvin提供了两个主要优势,即通过自动蒸馏来降低成本和延迟,以及通过测试驱动对齐来实现更可预测的行为。目前,有两个案例你应该使用Magentic,一是需要工具(函数)支持的情况下,这是我们规划中的一项特性;二是需要异步函数支持的情况下。
有哪些示例用例?
我们创建了一些示例,展示了如何使用Tanuki解决不同问题。你可以在这里找到它们。一些想法如下:
- 为客户请求添加重要性分类器
- 创建一个攻击性语言分类功能
- 创建一个食品评论应用
- 生成符合你的数据库架构的数据,可以立即使用
为什么我需要类型化的响应?
在调用LLM时,输出是自由格式的。这意味着在软件产品中使用时,它们的可预测性较低。使用类型可以确保输出遵守特定的约束或规则,以便你的程序可以处理这些输出。
你们提供其他语言的支持吗(如Typescript)?
目前没有,但如果你希望支持其他语言,请加入我们的Discord服务器或在Github上提出问题。
入门
如何开始?
如何对齐我的函数?
我需要自己的OpenAI key吗?
是的。
它只能与OpenAI一起使用吗?
目前是的,但我们计划支持Anthropic和流行的开源模型。如果你有具体请求,请加入我们的Discord服务器或创建Github问题。
工作原理
LLM如何随着时间推移变得更便宜和更快?能节省多少成本和时间?
简单来说,我们使用LLM模型的蒸馏方法。
详细来说,使用更大(教师)模型的输出,小(学生)模型将被训练以模拟教师模型的行为,并由于体积更小而更加快速和便宜。在某些情况下,通过少量执行你修补后的函数,可以实现最多90%的成本降低和80%的延迟减少。
需要多少次调用才能获得改进?
默认最少需要200次调用,不过可以通过在修补装饰器中添加标志来更改这个值。
我可以将函数链接在一起吗?
可以!可以将一个修补函数的输出用作另一个修补函数的输入,像使用普通Python函数一样操作即可。
微调会降低LLM的性能吗?
不一定。目前,唯一改善LLM性能的方法是拥有更好的对齐语句。由于学生模型同时接受对齐语句和输入输出调用的训练,所以微调后的学生模型在推理过程中可能会超过N-shot教师模型的性能。
准确性与可靠性
你如何保证修补函数输出的稳定性?
每个LLM的输出将以编程方式实例化为输出类,确保输出为正确类型,就像你的Python函数一样。如果输出不正确且实例化正确的输出对象失败,则会启动一个自动反馈修复循环来修正错误。
类型化输出有多可靠?
对于简单到中等复杂性类,GPT-4使用对齐语句在输出正确类型方面具有很高的可靠性。此外,我们实现了一个错误反馈修复循环,以“修复”错误的输出并将正确的输出添加到训练数据集中。
你如何处理幻觉问题?
幻觉在目前甚至将来可能都无法完全从LLM中去除。然而,通过创建带有@tanuki.align
装饰的测试函数,你可以使用普通的assert
语句来对齐模型,使其行为符合你的预期。此外,你可以使用Pydantic创建类型,这些类型起到了护栏作用,可以防止意外情况并提供正确的错误处理。
你如何处理偏见问题?
通过增加涵盖更广泛输入范围的对齐语句,你可以确保模型的偏见减少。
蒸馏会影响性能吗?
视情况而定。对于即使是最佳模型(如GPT-4)也具挑战性的任务,蒸馏会降低性能。然而,在这些情况下,蒸馏可以手动关闭。此外,如果蒸馏模型频繁未能生成正确输出,蒸馏模型将自动关闭。
这不适合做什么?
- 时间序列数据
- 需要大量上下文才能正确完成的任务
- 对于直接输出复杂自然语言的任务,你将从Tanuki中获得较少的价值,可能需要直接考虑OpenAI API。