Guides

Day 2: Tracking Use Cases with @track

📋Overview

The Pay-i Python SDK offers a powerful function decorator, @track, that makes it easy to organize and track your GenAI consumption at the function level. This decorator provides inheritance capabilities that are especially useful for sequences of related GenAI calls that share the same business context.

Purpose of the @track Decorator

This decorator is used to annotate your functions with metadata such as:

The @track decorator helps maintain consistent tracking across multiple API calls while reducing boilerplate code.

Best Suited For

The @track decorator is particularly well-suited for:

  • Function-level annotations that remain consistent across multiple API calls
  • Hierarchical tracking structures where nested functions inherit parameters
  • Consistent metadata like use case names or organizational limit IDs
  • Complex applications with many GenAI calls

Basic Example: Tracking Use Cases

In this example, we decorate the summarize_document() function with @track(). In @track, we define a use_case_name of "document_summary". This means that each time the summarize_document() function is called, an Instance of the document_summary use case will automatically be created by Pay-i and given a randomly generated Instance ID.

All GenAI calls that occur within summarize_document() or any methods it invokes (i.e., the entire call stack) will be treated as requests within the newly generated Instance. In a later example, we will demonstrate how to specify custom Instance IDs or re-use ones generated by Pay-i so that multiple executions can be treated as part of the same Instance, if needed.

Note that before you can use the @track decorator, you must initialize payi_instrument().

import os
from dotenv import load_dotenv
from openai import OpenAI
from payi.lib.instrument import payi_instrument
from payi.lib.instrument import track # Newly added import.

# Load environment variables from .env file
load_dotenv()

# Initialize Pay-i instrumentation
payi_instrument()

# Initialize OpenAI
client = OpenAI()

# Decorate a function with @track to add business context
@track(use_case_name='document_summary')
def summarize_document(client, document_text):
    # Every GenAI call inside this function will automatically
    # include the use_case_name, limit_ids and user_id defined in the decorator
    response = client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=[{"role": "user", "content": f"Summarize this: {document_text}"}]
    )
		# Other GenAI calls here...

    return response.choices[0].message.content

When using the @track decorator:

  1. All GenAI calls inside the function automatically inherit the parameters of the decorator
  2. When specifying use_case_name without use_case_id, the decorator will create an ID automatically
  3. If you have multiple methods decorated with @track, then there are two types of behaviors depending on whether the parameter is a singleton (e.g., use_case_name) or a list (e.g., limit_ids).
    1. For singletons, the annotation method that executes latest in the code flow takes precedence.
    2. For lists, the fields are unioned together.

Supported Parameters

The @track decorator supports the below parameters. All parameters are optional. However, not all parameters are recommended for use at the decorator scope, since they likely require details that are typically only available at runtime (such as the user_id), and these fields are not easily parameterized in a decorator. Though they are provided in @track for completeness, Pay-i provides two lower-level instrumentation approaches which are more suited to these types of parameters: track_context() and custom headers. These are both described in their own sections to follow.

ParameterDescriptionTypeRecommended
use_case_nameName of the use case for tracking purposesstr
use_case_idA custom ID for the use case Instance.str
use_case_versionVersion number of the use caseint
limit_idsList of limit IDs to apply to the wrapped functionlist[str]
user_idID of the user initiating the use casestr
account_nameThe account to which the user belongs, for grouping purposesstr
request_propertiesAny custom key:value properties to be added to all requests made in the decorator call stack.dict[str,str]
use_case_propertiesAny custom key:value properties to be added to all use case Instances made in the decorator call stack.dict[str,str]

For request-specific parameters not directly supported by @track, see the track_context() function and Custom Headers documentation.

Note: For combining @track with request-specific parameters, see Combined Annotations.

Understanding Parameter Inheritance

When nesting decorated functions or using them with other annotation methods, Pay-i follows a "latest wins" strategy based on execution order.

Here's a simple example showing how parameter inheritance works when calling a decorated function from another decorated function:

from payi.lib.instrument import track

@track(use_case_name='outer_function')
def outer_function():
    # All API calls here use use_case_name='outer_function'
    
    # Call another decorated function
    inner_function()  # Inherits use_case_name='outer_function'
    
    # Call a function with its own annotation
    specific_function()  # Does NOT inherit - uses its own use_case_name

@track()  # Empty decorator - will inherit from caller
def inner_function():
    # When called from outer_function():
    # - Inherits use_case_name='outer_function'
    client.chat.completions.create(...)

@track(use_case_name='specific_function')
def specific_function():
    # Always uses its own annotation regardless of caller
    # - use_case_name='specific_function' (overrides any inherited value)
    client.chat.completions.create(...)

This shows the basic pattern of inheritance. For comprehensive details on parameter precedence, including how different parameter types behave and how they combine with other annotation methods, see:

Related Resources