跳过内容

2024

Instructor 提案:集成 Jinja 模板引擎

作为 Instructor 的创建者,我一直致力于保持我们的产品开发流程精简,避免不必要的复杂性。然而,我现在确信是时候在我们的数据结构中融入更好的模板机制了,特别是通过集成 Jinja。

这个决定有多种目的

  1. 它解决了我的提示格式化需求日益增长的复杂性
  2. 它使我们能够在添加已证明的实用功能的同时,与标准库区分开来。
  3. 它与我在生产代码和客户端代码中一贯采用的实践相一致。
  4. 它提供了一个机会来引入已经在 Instructor 私有版本中测试过的 API 更改。

为什么 Jinja 是正确的选择

  1. 格式化能力
  2. 提示格式化复杂性增加了。
  3. 列表迭代和条件实现对于格式化是必要的。
  4. 这改进了块生成、少样本学习和动态规则。

  5. 验证

  6. Jinja 模板变量用于渲染和验证。
  7. Pydantic 的验证上下文允许在验证函数中访问模板变量。

  8. 版本控制和日志记录

  9. 渲染变量分离增强了提示的版本控制和日志记录。
  10. 模板变量的差异比较简化了提示更改的对比。

通过将 Jinja 集成到 Instructor 中,我们不仅仅是添加了一个功能;我们正在增强处理复杂格式、改进验证流程以及简化版本控制和日志记录的能力。这一添加将显著提升 Instructor 的能力和灵活性,使其成为一个对用户而言更强大的工具。

增强格式化能力

在 Instructor 中,我们建议在我们的创建方法中实现一个新的 context 关键字。这个添加将允许用户使用提供的上下文来渲染提示,利用 Jinja 的模板能力。工作原理如下

  1. 用户将一个 context 字典传递给创建方法。
  2. 使用 Jinja 语法编写的提示模板定义在消息的 content 字段中。
  3. Instructor 使用提供的上下文渲染提示,填充模板变量。

这种方法提供了以下优势

  • 提示结构和动态内容的分离
  • 管理带有条件和循环的复杂提示
  • 在不同上下文中复用提示模板

让我们看一个例子来说明这个特性

client.create(
    model="gpt-4o",
    messages=[
        {
            "role": "user",
            "content": """
                You are a {{ role }} tasks with the following question 

                <question>
                {{ question }}
                </question>

                Use the following context to answer the question, make sure to return [id] for every citation:

                <context>
                {% for chunk in context %}
                  <context_chunk>
                    <id>{{ chunk.id }}</id>
                    <text>{{ chunk.text }}</text>
                  </context_chunk>
                {% endfor %}
                </context>

                {% if rules %}
                Make sure to follow these rules:

                {% for rule in rules %}
                  * {{ rule }}
                {% endfor %}
                {% endif %}
            """,
        },
    ],
    context={
        "role": "professional educator",
        "question": "What is the capital of France?",
        "context": [
            {"id": 1, "text": "Paris is the capital of France."},
            {"id": 2, "text": "France is a country in Europe."},
        ],
        "rules": ["Use markdown."],
    },
)

验证

让我们考虑一个从文本中删除敏感词的场景。通过使用 ValidationInfo 访问上下文并将其传递给验证器和模板,我们可以实现一个处理敏感信息的系统。这种方法使我们能够

  1. 验证输入以确保不包含禁用词。
  2. 使用正则表达式删除模式。
  3. 向语言模型提供关于词语使用限制的指令。

以下是使用 Pydantic 验证器演示此概念的示例

from pydantic import BaseModel, ValidationInfo, field_validator

class Response(BaseModel):
    text: str

    @field_validator('text')
    @classmethod
    def no_banned_words(cls, v: str, info: ValidationInfo):
        context = info.context
        if context:
            banned_words = context.get('banned_words', set())
            banned_words_found = [word for word in banned_words if word.lower() in v.lower()]
            if banned_words_found:
                raise ValueError(f"Banned words found in text: {', '.join(banned_words_found)}, rewrite it but just without the banned words")
        return v

    @field_validator('text')
    @classmethod
    def redact_regex(cls, v: str, info: ValidationInfo):
        context = info.context
        if context:
            redact_patterns = context.get('redact_patterns', [])
            for pattern in redact_patterns:
                v = re.sub(pattern, '****', v)
        return v

response = client.create(
    model="gpt-4o",
    response_model=Response,
    messages=[
        {
            "role": "user", 
            "content": """
                Write about a {{ topic }}

                {% if banned_words %}
                You must not use the following banned words:

                <banned_words>
                {% for word in banned_words %}
                * {{ word }}
                {% endfor %}
                </banned_words>
                {% endif %}
              """
        },
    ],
    context={
        "topic": "jason and now his phone number is 123-456-7890"
        "banned_words": ["jason"],
        "redact_patterns": [
            r"\b\d{3}[-.]?\d{3}[-.]?\d{4}\b",  # Phone number pattern
            r"\b\d{3}-\d{2}-\d{4}\b",          # SSN pattern
        ],
    },
    max_retries=3,
)

print(response.text)
# > While i can't say his name anymore, his phone number is ****

更好的版本控制和日志记录

通过分离提示模板和变量,我们获得了几个优势

  1. 版本控制:我们现在可以对模板进行版本控制,并为给定的提示检索合适的模板。这有助于更好地管理模板历史、进行差异比较。

  2. 增强的日志记录:分离有助于结构化日志记录,使得调试更容易,并能与各种日志接收器、数据库以及 OpenTelemetry 等可观测性工具集成。

  3. 安全性:变量中的敏感信息可以与模板分开处理,从而实现更好的访问控制和数据保护。

这种关注点分离遵循了软件设计的最佳实践,从而构建了一个更易维护、可扩展且健壮的系统,用于管理提示及其关联数据。

上下文也是 Pydantic 模型的一个附带好处

由于它们只是 Python 对象,我们可以使用 Pydantic 模型来验证上下文,并控制它们的渲染方式,这样即使是秘密信息也可以动态渲染!考虑使用秘密字符串将敏感信息传递给 LLM。

from pydantic import BaseModel, SecretStr


class UserContext(BaseModel):
    name: str
    address: SecretStr


class Address(BaseModel):
    street: SecretStr
    city: str
    state: str
    zipcode: str


def normalize_address(address: Address):
    context = UserContext(username="scolvin", address=address)
    address = client.create(
        model="gpt-4o",
        messages=[
            {
                "role": "user",
                "content": "{{ user.name }} is `{{ user.address.get_secret_value() }}`, normalize it to an address object",
            },
        ],
        context={"user": context},
    )
    print(context)
    #> UserContext(username='jliu', address="******")
    print(address)
    #> Address(street='******', city="Toronto", state="Ontario", zipcode="M5A 0J3")
    logger.info(
        f"Normalized address: {address}",
        extra={"user_context": context, "address": address},
    )
    return address

这种方法提供了几个优势

  1. 安全日志记录:您可以放心地记录模板变量,而不会有敏感信息暴露的风险。
  2. 类型安全:Pydantic 模型提供类型检查和验证,降低出错风险。
  3. 灵活性:您可以轻松控制不同类型的数据如何在模板中显示或使用。

我为什么要使用提示缓存?

开发者在使用大上下文时经常面临两个关键挑战——响应时间慢和成本高。当我们随着时间进行多次此类调用时,情况尤其如此,这会严重影响我们应用的成本和延迟。借助 Anthropic 新推出的提示缓存功能,我们可以轻松解决这两个问题。

由于这项新功能仍处于 Beta 阶段,我们将在其正式可用后才将其集成到 instructor 中。在此期间,我们整理了一份快速入门指南,介绍如何在您自己的应用程序中使用该功能。

现在支持 Gemini 的结构化输出

我们很高兴地宣布,instructor 现在支持使用工具调用(tool calling)为 Gemini SDK 和 VertexAI SDK 提供结构化输出。

特别感谢 Sonal 对 Gemini 工具调用支持的贡献。

让我们来看一个简单的例子,了解如何使用这些新功能

安装

要开始使用,请安装最新版本的 instructor。根据您使用的是 Gemini 还是 VertexAI,您应该安装以下内容

pip install "instructor[google-generativeai]"
pip install "instructor[vertexai]"

这确保您拥有使用 Gemini 或 VertexAI SDK 与 instructor 所需的依赖项。

我们建议使用 Gemini SDK 而非 VertexAI SDK,主要有两个原因。

  1. 与 VertexAI SDK 相比,Gemini SDK 为开发者提供了每日 15 亿 tokens 的免费配额。
  2. Gemini SDK 的设置要简单得多,您只需要一个在 GCP 控制台中生成的 GOOGLE_API_KEY。而 VertexAI SDK 则需要 credentials.json 文件或 OAuth 集成才能使用。

入门

通过我们与提供商无关的 API,您可以使用相同的接口与两个 SDK 进行交互,这里唯一改变的是我们如何初始化客户端本身。

在运行以下代码之前,您需要确保已在 shell 中将 Gemini API 密钥设置在别名 GOOGLE_API_KEY 下。

import instructor
import google.generativeai as genai
from pydantic import BaseModel


class User(BaseModel):
    name: str
    age: int


client = instructor.from_gemini(
    client=genai.GenerativeModel(
        model_name="models/gemini-1.5-flash-latest",  # (1)!
    )
)

resp = client.chat.completions.create(
    messages=[
        {
            "role": "user",
            "content": "Extract Jason is 25 years old.",
        }
    ],
    response_model=User,
)

print(resp)
#> name='Jason' age=25
  1. 当前支持工具调用的 Gemini 模型是 gemini-1.5-flash-latestgemini-1.5-pro-latest

我们可以使用 VertexAI SDK 实现类似的功能。为此,您需要向 VertexAI 进行身份验证。

这里有一些说明 here,但我发现最简单的方法是直接下载 GCloud CLI 并运行 gcloud auth application-default login

import instructor
import vertexai  # type: ignore
from vertexai.generative_models import GenerativeModel  # type: ignore
from pydantic import BaseModel

vertexai.init()


class User(BaseModel):
    name: str
    age: int


client = instructor.from_vertexai(
    client=GenerativeModel("gemini-1.5-pro-preview-0409"),  # (1)!
)


resp = client.chat.completions.create(
    messages=[
        {
            "role": "user",
            "content": "Extract Jason is 25 years old.",
        }
    ],
    response_model=User,
)

print(resp)
#> name='Jason' age=25
  1. 当前支持工具调用的 Gemini 模型是 gemini-1.5-flash-latestgemini-1.5-pro-latest

我应该使用结构化输出吗?

OpenAI 最近宣布了结构化输出(Structured Outputs),它可以确保生成的响应匹配任何任意提供的 JSON Schema。在他们的公告文章中,他们承认该功能受到了 instructor 等库的启发。

主要挑战

如果您正在构建复杂的 LLM 工作流,您很可能已经考虑过将 OpenAI 的结构化输出作为 instructor 的潜在替代方案。

但在您这样做之前,仍存在三个关键挑战

  1. 有限的验证和重试逻辑:结构化输出确保遵循 schema,但不能保证内容有用。您可能会得到格式完美但无用的响应
  2. 流式处理挑战:使用 SDK 从流式响应中解析原始 JSON 对象容易出错且效率低下
  3. 不可预测的延迟问题:结构化输出存在随机延迟峰值,这可能导致响应时间增加近 20 倍

此外,采用结构化输出将您锁定在 OpenAI 的生态系统中,限制了您尝试可能更适合特定用例的多种模型或提供商的能力。

这种供应商锁定增加了对提供商中断的脆弱性,可能导致应用程序停机和 SLA 违约,从而损害用户信任并影响您的业务声誉。

在本文中,我们将展示 instructor 如何通过验证失败时自动重新提问、对经验证的流式数据提供自动支持等功能来解决其中许多挑战。

使用 Parea 监控、测试 & 微调 Instructor

Parea 是一个平台,使团队能够监控、协作、测试 & 标注 LLM 应用程序。在这篇博客中,我们将探讨如何使用 Parea 来增强 OpenAI 客户端与 instructor 的结合,并调试 + 改进 instructor 调用。Parea 具有一些特性,使其对 instructor 特别有用

  • 它会自动将因重试导致的任何 LLM 调用分组到单个跟踪下
  • 它会自动跟踪使用 instructor 时发生的任何验证错误计数 & 字段
  • 它提供了一个用户界面,通过填写表单而不是编辑 JSON 对象来标注 JSON 响应
配置 Parea

在开始本教程之前,请确保您已注册 Parea 账户。您还需要创建一个 API 密钥

示例:使用 Instructor 文档中的 URL 撰写电子邮件

我们将通过使用 instructor 撰写仅包含 instructor 文档中 URL 的电子邮件来演示 Parea。在继续之前,我们需要安装依赖项,因此只需运行下面的命令即可。

使用 Instructor 分析 YouTube 视频文本

提取章节信息

代码片段

一如既往,代码已在我们的仓库中的 examples/youtube 文件夹中提供,您可以在 run.py 文件中参考。

在这篇文章中,我们将向您展示如何使用 instructor 将 YouTube 视频文本总结为不同的章节,然后探讨一些将代码应用于不同应用程序的方法。

阅读完本文后,您将能够根据下面的视频构建一个应用程序。

为什么 Instructor 是从 LLMs 获取 JSON 的最佳方式

大型语言模型 (LLMs) 如 GPT 非常强大,但让它们返回格式良好的 JSON 可能具有挑战性。这就是 Instructor 库的优势所在。Instructor 允许您使用 Python 类型注解和 Pydantic 模型轻松地将 LLM 输出映射到 JSON 数据。

Instructor 使从 LLMs(如 GPT-3.5、GPT-4、GPT-4-Vision)以及包括 Mistral/MixtralOllamallama-cpp-python 在内的开源模型获取结构化数据(如 JSON)变得容易。

它的特点是简洁性、透明性和以用户为中心的设计,构建在 Pydantic 之上。Instructor 帮助您管理验证上下文、使用 Tenacity 进行重试,以及流式处理列表(Lists)部分(Partial)响应。

使用 Instructor 通过时间过滤器增强 RAG

检索增强生成 (RAG) 系统通常需要处理带有时间限制的查询,例如“上个季度发布了哪些新功能?”或“显示过去一周的支持工单。”有效的时间过滤对于提供准确、相关的响应至关重要。

Instructor 是一个 Python 库,它简化了大型语言模型 (LLMs) 与数据源和 API 的集成。它允许使用 Pydantic 定义结构化输出模型,这些模型可以用作提示或解析 LLM 输出。

为什么 Logfire 与 FastAPI + Instructor 是绝配

Logfire 是一个新工具,通过 Open Telemetry 为您的应用程序提供关键洞察。Logfire 不是使用临时的 print 语句,而是帮助分析您应用程序的每个部分,并直接集成到 Pydantic 和 FastAPI 中,这是 Instructor 用户中流行的两个库。

简而言之,这是帮助您的应用程序达到终点并超越的秘诀。我们将通过两个例子向您展示如何轻松地将 Logfire 集成到 FastAPI 中,这是 Instructor 用户中最流行的选择之一

  1. 从单个用户查询中提取数据
  2. 使用 asyncio 并行处理多个用户
  3. 使用 Iterable 流式传输多个对象,使其按需可用