Guides

@track decorator

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 parameter inheritance capabilities that are especially useful for sequences of related GenAI calls that share the same business context.

Purpose of the @track Decorator

The primary purpose of this decorator is to annotate your functions with metadata such as:

When used with Pay-i's instrumentation system (via payi_instrument()), 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, limit and user IDs
  • Complex applications with many GenAI calls

For request-specific attributes that vary with each call, consider combining decorators with Custom Headers.

Setup & Installation

Prerequisites

  • Pay-i Python SDK installed (pip install payi)
  • A valid Pay-i API key
  • One or more supported GenAI providers (OpenAI, Azure OpenAI, Anthropic, AWS Bedrock)

Initializing Pay-i Instrumentation

Before you can use the @track decorator, you must initialize payi_instrument():

import os
from payi.lib.instrument import payi_instrument

# Method 1: Initialize with config dictionary (simplest approach)
# This automatically creates Pay-i clients internally using environment variables
payi_instrument(config={"proxy": False})  # False for Direct Provider Call with Telemetry

You can also explicitly create and pass a Pay-i client:

import os
from payi import Payi  # [Payi](/docs/payi-clients) client class
from payi.lib.instrument import payi_instrument

# Read API key from environment variables (best practice)
payi_key = os.getenv("PAYI_API_KEY", "YOUR_PAYI_API_KEY")

# Method 2: Create and provide a Pay-i client
payi = Payi(api_key=payi_key)
payi_instrument(payi)

Once you've initialized the instrumentation, import the @track decorator:

from payi.lib.instrument import track

GenAI Provider Client Configuration

For Direct Provider Call with Telemetry

When using Direct Provider Call with Telemetry, configure your GenAI provider client normally (direct to provider):

import os
from openai import OpenAI

# Configure a standard provider client with direct access
openai_key = os.getenv("OPENAI_API_KEY", "YOUR_OPENAI_API_KEY")
client = OpenAI(api_key=openai_key)

For Pay-i as a Proxy

When using Pay-i as a Proxy, configure your GenAI provider client to use Pay-i as a proxy:

import os
from openai import OpenAI  # Can also be AzureOpenAI or other providers
from payi.lib.helpers import payi_openai_url

# Read API keys from environment variables
payi_key = os.getenv("PAYI_API_KEY", "YOUR_PAYI_API_KEY")
openai_key = os.getenv("OPENAI_API_KEY", "YOUR_OPENAI_API_KEY")

# Configure provider client to use Pay-i as a proxy
client = OpenAI(
    api_key=openai_key,
    base_url=payi_openai_url(),  # Use Pay-i's URL as the base
    default_headers={"xProxy-api-key": payi_key}  # Authenticate with Pay-i
)

Note:

  • For detailed configuration examples with other providers (Azure OpenAI, Anthropic, AWS Bedrock), refer to the Provider Configuration guide.
  • With proxy setup alone, Pay-i will track all API calls but won't have function annotations. To add annotations, you must also initialize payi_instrument() and use the @track decorator in your code.

Using the @track Decorator

After initializing payi_instrument() and configuring your provider client, you can use the @track decorator to annotate your functions.

How @track works: The decorator first executes your decorated function and then handles the GenAI calls based on your Pay-i configuration (Direct Provider Call with Telemetry or Proxy Routing).

from payi.lib.instrument import track

# Apply @track to a function to add business context
@track(
    use_case_name='document_summary',
    limit_ids=['daily_budget'],
    user_id='user_123'
)
def summarize_document(client, document_text):
    # Every API call inside this function will automatically
    # include the use_case_name, limit_ids and user_id
    response = client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=[{"role": "user", "content": f"Summarize this: {document_text}"}]
    )
    
    return response.choices[0].message.content

When using the @track decorator:

  1. The decorator's metadata (use_case_name, limit_ids, user_id, etc.) is added to a context stack
  2. All API calls inside the function automatically inherit these parameters
  3. Pay-i captures usage data with all the annotations you've provided
  4. When specifying use_case_name (without use_case_id), the decorator will create an ID automatically

Examples

The @track decorator works for different types of GenAI applications:

# Example: Document summarization with use case tracking
@track(use_case_name='document_summary')
def summarize_document(client, document_text):
    # Simple annotation with just use case name
    response = client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=[{"role": "user", "content": f"Summarize this: {document_text}"}]
    )
    return response.choices[0].message.content

# Example: Customer support function with multiple limits
@track(
    use_case_name='customer_support',
    limit_ids=['support_budget', 'quality_limit']
)
def get_support_response(client, question):
    # Automatically tracks against both limits
    response = client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=[{"role": "user", "content": question}]
    )
    return response.choices[0].message.content

# Example: Using application context for user ID
from flask import g  # Application context example

@track(
    use_case_name='user_dashboard',
    user_id=g.user.id  # User ID from application context
)
def generate_dashboard_summary(client):
    # Automatically attributes API usage to the current user
    response = client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=[{"role": "user", "content": "Summarize dashboard data"}]
    )
    return response.choices[0].message.content

Supported Parameters

The @track decorator supports these parameters:

ParameterDescription
use_case_nameName of the use case for tracking purposes
use_case_idID of the use case (generated automatically if not provided)
use_case_versionVersion number of the use case
limit_idsList of limit IDs to apply to the wrapped function
user_idID of the user making the request (from application context)
proxyControl whether to use proxy mode (True) or ingest mode (False)

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