跳过内容

钩子

Hooks 提供了一种强大的机制,用于在 Instructor 库的完成和解析过程中拦截和处理事件。它们允许您在 API 交互的各个阶段添加自定义行为、日志记录或错误处理。

概述

Instructor 中的 Hooks 系统基于 Hooks 类,该类管理事件的注册和触发。它支持多个预定义的事件,这些事件对应于完成和解析过程的不同阶段。

支持的钩子事件

completion:kwargs

当提供完成参数时,会触发此钩子。它接收传递给完成函数的所有参数。这些参数将包含modelmessagestools,在任何response_modelvalidation_context参数转换为各自的值之后。

def handler(*args, **kwargs) -> None: ...

completion:response

当接收到完成响应时,会触发此钩子。它从完成API接收原始响应对象。

def handler(response) -> None: ...

completion:error

当在完成过程中发生错误时,此钩子会在任何重试尝试之前发出,并且响应被解析为pydantic模型。

def handler(error) -> None: ...

parse:error

当在将响应解析为pydantic模型时发生错误时,会触发此钩子。如果响应无效或pydantic模型与响应不兼容,可能会发生这种情况。

def handler(error) -> None: ...

completion:last_attempt

当进行最后一次重试尝试时,会触发此钩子。

def handler(error) -> None: ...

实现细节

Hooks系统在instructor/hooks.py文件中实现。Hooks类处理钩子事件的注册和触发。您可以参考此文件以了解钩子是如何在底层工作的。使用Hooks的重试逻辑在instructor/retry.py文件中实现。这展示了在完成过程中出现错误后重试时如何使用Hooks。

注册钩子

你可以使用Instructor客户端的on方法或Hooks实例来注册钩子。以下是一个示例:

import instructor
import openai
import pprint

client = instructor.from_openai(openai.OpenAI())


def log_completion_kwargs(*args, **kwargs):
    pprint.pprint({"args": args, "kwargs": kwargs})


client.on("completion:kwargs", log_completion_kwargs)

resp = client.chat.completions.create(
    model="gpt-3.5-turbo",
    messages=[{"role": "user", "content": "Hello, world!"}],
    response_model=str,
)
print(resp)
#> Hello, world!

发射事件

事件会在适当的时候由Instructor库自动发出。在大多数情况下,您不需要手动发出事件。

移除钩子

你可以使用off方法来移除特定的钩子:

client.off("completion:kwargs", log_completion_kwargs)

清除钩子

要删除特定事件或所有事件的所有钩子:

# Clear hooks for a specific event
client.clear("completion:kwargs")

# Clear all hooks
client.clear()

示例:日志记录和调试

这里有一个全面的示例,展示了如何使用钩子进行日志记录和调试:

import instructor
import openai
import pydantic


def log_completion_kwargs(kwargs) -> None:
    print("## Completion kwargs:")
    print(kwargs)
    """
    {
        "messages": [
            {
                "role": "user",
                "content": "Extract the user name and age from the following text: 'John is 20 years old'",
            }
        ],
        "model": "gpt-4o-mini",
        "tools": [
            {
                "type": "function",
                "function": {
                    "name": "User",
                    "description": "Correctly extracted `User` with all the required parameters with correct types",
                    "parameters": {
                        "properties": {
                            "name": {"title": "Name", "type": "string"},
                            "age": {"title": "Age", "type": "integer"},
                        },
                        "required": ["age", "name"],
                        "type": "object",
                    },
                },
            }
        ],
        "tool_choice": {"type": "function", "function": {"name": "User"}},
    }
    """


def log_completion_response(response) -> None:
    print("## Completion response:")
    #> ## Completion response:
    """
    {
        'id': 'chatcmpl-AWl4Mj5Jrv7m7JkOTIiHXSldQIOFm',
        'choices': [
            {
                'finish_reason': 'stop',
                'index': 0,
                'logprobs': None,
                'message': {
                    'content': None,
                    'refusal': None,
                    'role': 'assistant',
                    'audio': None,
                    'function_call': None,
                    'tool_calls': [
                        {
                            'id': 'call_6oQ9WXxeSiVEV71B9IYtsbIE',
                            'function': {
                                'arguments': '{"name":"John","age":-1}',
                                'name': 'User',
                            },
                            'type': 'function',
                        }
                    ],
                },
            }
        ],
        'created': 1732370794,
        'model': 'gpt-4o-mini-2024-07-18',
        'object': 'chat.completion',
        'service_tier': None,
        'system_fingerprint': 'fp_0705bf87c0',
        'usage': {
            'completion_tokens': 10,
            'prompt_tokens': 87,
            'total_tokens': 97,
            'completion_tokens_details': {
                'audio_tokens': 0,
                'reasoning_tokens': 0,
                'accepted_prediction_tokens': 0,
                'rejected_prediction_tokens': 0,
            },
            'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0},
        },
    }
    """
    print(response.model_dump())
    """
    {
        'id': 'chatcmpl-AWl4Mxdq0BUGRlVCA61z8YOIVga7F',
        'choices': [
            {
                'finish_reason': 'stop',
                'index': 0,
                'logprobs': None,
                'message': {
                    'content': None,
                    'refusal': None,
                    'role': 'assistant',
                    'audio': None,
                    'function_call': None,
                    'tool_calls': [
                        {
                            'id': 'call_EJIEr27Mb6sdbplnYw4iBWlm',
                            'function': {
                                'arguments': '{"name":"John","age":10}',
                                'name': 'User',
                            },
                            'type': 'function',
                        }
                    ],
                },
            }
        ],
        'created': 1732370794,
        'model': 'gpt-4o-mini-2024-07-18',
        'object': 'chat.completion',
        'service_tier': None,
        'system_fingerprint': 'fp_0705bf87c0',
        'usage': {
            'completion_tokens': 9,
            'prompt_tokens': 87,
            'total_tokens': 96,
            'completion_tokens_details': {
                'audio_tokens': 0,
                'reasoning_tokens': 0,
                'accepted_prediction_tokens': 0,
                'rejected_prediction_tokens': 0,
            },
            'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0},
        },
    }
    """


def log_completion_error(error) -> None:
    print("## Completion error:")
    print({"error": error})


def log_parse_error(error) -> None:
    print("## Parse error:")
    #> ## Parse error:
    print(error)
    """
    1 validation error for User
    age
    Value error, Age cannot be negative [type=value_error, input_value=-10, input_type=int]
        For further information visit https://errors.pydantic.dev/2.8/v/value_error
    """


# Create an Instructor client
client = instructor.from_openai(openai.OpenAI())

client.on("completion:kwargs", log_completion_kwargs)
client.on("completion:response", log_completion_response)

client.on("completion:error", log_completion_error)
client.on("parse:error", log_parse_error)


# Define a model with a validator
class User(pydantic.BaseModel):
    name: str
    age: int

    @pydantic.field_validator("age")
    def check_age(cls, v: int) -> int:
        if v < 0:
            raise ValueError("Age cannot be negative")
        return v


try:
    # Use the client to create a completion
    user = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {
    #> ## Parse error:
                "role": "user",
                "content": "Extract the user name and age from the following text: 'John is -1 years old'",
    """
    1 validation error for User
    age
      Value error, Age cannot be negative [type=value_error, input_value=-1, input_type=int]
        For further information visit https://errors.pydantic.dev/2.9/v/value_error
    """
            }
        ],
        response_model=User,
        max_retries=1,
    )
except Exception as e:
    print(f"Error: {e}")


user = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[
        {
            "role": "user",
            "content": "Extract the user name and age from the following text: 'John is 10 years old'",
        }
    ],
    response_model=User,
    max_retries=1,
)
print(user)
#> name='John' age=10
    """
    Error: 1 validation error for User
    age
      Value error, Age cannot be negative [type=value_error, input_value=-1, input_type=int]
        For further information visit https://errors.pydantic.dev/2.9/v/value_error
    """

这个示例演示:

  1. 为不同事件定义钩子处理程序。
  2. 与Instructor客户端注册钩子。
  3. 使用带验证器的 Pydantic 模型。
  4. 发起一个完成请求,将触发各种钩子。

钩子将在过程的不同阶段记录信息,帮助调试和理解数据流。

最佳实践

  1. 错误处理:始终在您的钩子处理程序中包含错误处理,以防止异常中断主执行流程。如果在钩子处理程序中引发异常,我们将自动发出警告。

  2. 性能:保持钩子处理程序轻量级,以避免影响主应用程序的性能。

  3. 模块化:使用钩子来分离关注点。例如,使用钩子进行日志记录、监控或自定义业务逻辑,而不会使主代码变得混乱。

  4. 一致性:在所有钩子中使用相同的命名约定和模式,以提高可维护性。

  5. 文档: 记录每个钩子处理程序的目的和预期的输入/输出,以便于协作和维护。

通过有效利用钩子,您可以在使用Instructor库和语言模型时创建更灵活、可调试和可维护的应用程序。